mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Optimize overflow-menu (#34183)
Optimized the overflow-menu: 1. Close the tippy when a menu item inside the tippy is clicked. 2. When a menu item inside the tippy is selected, move the active state of the menu to the tippy's button. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -747,6 +747,14 @@ overflow-menu .overflow-menu-button { | |||||||
|   padding: 0; |   padding: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* match the styles of ".ui.secondary.pointing.menu .active.item" */ | ||||||
|  | overflow-menu.ui.secondary.pointing.menu .overflow-menu-button.active { | ||||||
|  |   padding: 2px 0 0; | ||||||
|  |   border-bottom: 2px solid currentcolor; | ||||||
|  |   background-color: transparent; | ||||||
|  |   font-weight: var(--font-weight-medium); | ||||||
|  | } | ||||||
|  |  | ||||||
| overflow-menu .overflow-menu-button:hover { | overflow-menu .overflow-menu-button:hover { | ||||||
|   color: var(--color-text-dark); |   color: var(--color-text-dark); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -360,7 +360,7 @@ export function querySingleVisibleElem<T extends HTMLElement>(parent: Element, s | |||||||
| export function addDelegatedEventListener<T extends HTMLElement, E extends Event>(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => Promisable<void>, options?: boolean | AddEventListenerOptions) { | export function addDelegatedEventListener<T extends HTMLElement, E extends Event>(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => Promisable<void>, options?: boolean | AddEventListenerOptions) { | ||||||
|   parent.addEventListener(type, (e: Event) => { |   parent.addEventListener(type, (e: Event) => { | ||||||
|     const elem = (e.target as HTMLElement).closest(selector); |     const elem = (e.target as HTMLElement).closest(selector); | ||||||
|     if (!elem) return; |     if (!elem || !parent.contains(elem)) return; | ||||||
|     listener(elem as T, e as E); |     listener(elem as T, e as E); | ||||||
|   }, options); |   }, options); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import {throttle} from 'throttle-debounce'; | import {throttle} from 'throttle-debounce'; | ||||||
| import {createTippy} from '../modules/tippy.ts'; | import {createTippy} from '../modules/tippy.ts'; | ||||||
| import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; | import {addDelegatedEventListener, isDocumentFragmentOrElementNode} from '../utils/dom.ts'; | ||||||
| import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; | import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; | ||||||
|  |  | ||||||
| window.customElements.define('overflow-menu', class extends HTMLElement { | window.customElements.define('overflow-menu', class extends HTMLElement { | ||||||
| @@ -12,10 +12,14 @@ window.customElements.define('overflow-menu', class extends HTMLElement { | |||||||
|   mutationObserver: MutationObserver; |   mutationObserver: MutationObserver; | ||||||
|   lastWidth: number; |   lastWidth: number; | ||||||
|  |  | ||||||
|  |   updateButtonActivationState() { | ||||||
|  |     if (!this.button || !this.tippyContent) return; | ||||||
|  |     this.button.classList.toggle('active', Boolean(this.tippyContent.querySelector('.item.active'))); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   updateItems = throttle(100, () => { |   updateItems = throttle(100, () => { | ||||||
|     if (!this.tippyContent) { |     if (!this.tippyContent) { | ||||||
|       const div = document.createElement('div'); |       const div = document.createElement('div'); | ||||||
|       div.classList.add('tippy-target'); |  | ||||||
|       div.tabIndex = -1; // for initial focus, programmatic focus only |       div.tabIndex = -1; // for initial focus, programmatic focus only | ||||||
|       div.addEventListener('keydown', (e) => { |       div.addEventListener('keydown', (e) => { | ||||||
|         if (e.key === 'Tab') { |         if (e.key === 'Tab') { | ||||||
| @@ -64,9 +68,10 @@ window.customElements.define('overflow-menu', class extends HTMLElement { | |||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|       this.append(div); |       div.classList.add('tippy-target'); | ||||||
|  |       this.handleItemClick(div, '.tippy-target > .item'); | ||||||
|       this.tippyContent = div; |       this.tippyContent = div; | ||||||
|     } |     } // end if: no tippyContent and create a new one | ||||||
|  |  | ||||||
|     const itemFlexSpace = this.menuItemsEl.querySelector<HTMLSpanElement>('.item-flex-space'); |     const itemFlexSpace = this.menuItemsEl.querySelector<HTMLSpanElement>('.item-flex-space'); | ||||||
|     const itemOverFlowMenuButton = this.querySelector<HTMLButtonElement>('.overflow-menu-button'); |     const itemOverFlowMenuButton = this.querySelector<HTMLButtonElement>('.overflow-menu-button'); | ||||||
| @@ -88,7 +93,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { | |||||||
|     const menuRight = this.offsetLeft + this.offsetWidth; |     const menuRight = this.offsetLeft + this.offsetWidth; | ||||||
|     const menuItems = this.menuItemsEl.querySelectorAll<HTMLElement>('.item, .item-flex-space'); |     const menuItems = this.menuItemsEl.querySelectorAll<HTMLElement>('.item, .item-flex-space'); | ||||||
|     let afterFlexSpace = false; |     let afterFlexSpace = false; | ||||||
|     for (const item of menuItems) { |     for (const [idx, item] of menuItems.entries()) { | ||||||
|       if (item.classList.contains('item-flex-space')) { |       if (item.classList.contains('item-flex-space')) { | ||||||
|         afterFlexSpace = true; |         afterFlexSpace = true; | ||||||
|         continue; |         continue; | ||||||
| @@ -96,7 +101,10 @@ window.customElements.define('overflow-menu', class extends HTMLElement { | |||||||
|       if (afterFlexSpace) item.setAttribute('data-after-flex-space', 'true'); |       if (afterFlexSpace) item.setAttribute('data-after-flex-space', 'true'); | ||||||
|       const itemRight = item.offsetLeft + item.offsetWidth; |       const itemRight = item.offsetLeft + item.offsetWidth; | ||||||
|       if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button with some extra space |       if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button with some extra space | ||||||
|         this.tippyItems.push(item); |         const onlyLastItem = idx === menuItems.length - 1 && this.tippyItems.length === 0; | ||||||
|  |         const lastItemFit = onlyLastItem && menuRight - itemRight > 0; | ||||||
|  |         const moveToPopup = !onlyLastItem || !lastItemFit; | ||||||
|  |         if (moveToPopup) this.tippyItems.push(item); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     itemFlexSpace?.style.removeProperty('display'); |     itemFlexSpace?.style.removeProperty('display'); | ||||||
| @@ -107,6 +115,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { | |||||||
|       const btn = this.querySelector('.overflow-menu-button'); |       const btn = this.querySelector('.overflow-menu-button'); | ||||||
|       btn?._tippy?.destroy(); |       btn?._tippy?.destroy(); | ||||||
|       btn?.remove(); |       btn?.remove(); | ||||||
|  |       this.button = null; | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -126,18 +135,17 @@ window.customElements.define('overflow-menu', class extends HTMLElement { | |||||||
|     // update existing tippy |     // update existing tippy | ||||||
|     if (this.button?._tippy) { |     if (this.button?._tippy) { | ||||||
|       this.button._tippy.setContent(this.tippyContent); |       this.button._tippy.setContent(this.tippyContent); | ||||||
|  |       this.updateButtonActivationState(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // create button initially |     // create button initially | ||||||
|     const btn = document.createElement('button'); |     this.button = document.createElement('button'); | ||||||
|     btn.classList.add('overflow-menu-button'); |     this.button.classList.add('overflow-menu-button'); | ||||||
|     btn.setAttribute('aria-label', window.config.i18n.more_items); |     this.button.setAttribute('aria-label', window.config.i18n.more_items); | ||||||
|     btn.innerHTML = octiconKebabHorizontal; |     this.button.innerHTML = octiconKebabHorizontal; | ||||||
|     this.append(btn); |     this.append(this.button); | ||||||
|     this.button = btn; |     createTippy(this.button, { | ||||||
|  |  | ||||||
|     createTippy(btn, { |  | ||||||
|       trigger: 'click', |       trigger: 'click', | ||||||
|       hideOnClick: true, |       hideOnClick: true, | ||||||
|       interactive: true, |       interactive: true, | ||||||
| @@ -151,6 +159,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { | |||||||
|         }, 0); |         }, 0); | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|  |     this.updateButtonActivationState(); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   init() { |   init() { | ||||||
| @@ -187,6 +196,14 @@ window.customElements.define('overflow-menu', class extends HTMLElement { | |||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     this.resizeObserver.observe(this); |     this.resizeObserver.observe(this); | ||||||
|  |     this.handleItemClick(this, '.overflow-menu-items > .item'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   handleItemClick(el: Element, selector: string) { | ||||||
|  |     addDelegatedEventListener(el, 'click', selector, () => { | ||||||
|  |       this.button?._tippy?.hide(); | ||||||
|  |       this.updateButtonActivationState(); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   connectedCallback() { |   connectedCallback() { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user