From f3fa4dd1c2061d9d718ca58bb910b74b11a49e4c Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Sat, 12 Apr 2025 00:56:56 +0000 Subject: [PATCH 1/8] 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. --- web_src/css/base.css | 8 ++++++++ web_src/js/components/DashboardRepoList.vue | 2 +- web_src/js/webcomponents/overflow-menu.ts | 14 +++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/web_src/css/base.css b/web_src/css/base.css index 37ee7f5832f93..a4fc94ad2e2e3 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -747,6 +747,14 @@ overflow-menu .overflow-menu-button { padding: 0; } +overflow-menu .overflow-menu-button.active { + margin: 0 0 -2px; + border-bottom: 2px solid transparent; + background-color: transparent; + border-color: currentcolor; + font-weight: var(--font-weight-medium); +} + overflow-menu .overflow-menu-button:hover { color: var(--color-text-dark); } diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index fc6a7bd281f11..bbe226ee6edc2 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -391,7 +391,7 @@ export default defineComponent({ - +
{{ textAll }} diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index 4e729a268a02f..cfcd3e3a4fcc1 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -1,9 +1,11 @@ import {throttle} from 'throttle-debounce'; import {createTippy} from '../modules/tippy.ts'; -import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; +import {isDocumentFragmentOrElementNode, toggleClass} from '../utils/dom.ts'; import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; window.customElements.define('overflow-menu', class extends HTMLElement { + static observedAttributes = ['active']; + tippyContent: HTMLDivElement; tippyItems: Array; button: HTMLButtonElement; @@ -151,6 +153,10 @@ window.customElements.define('overflow-menu', class extends HTMLElement { }, 0); }, }); + + this.tippyContent.querySelector('.item').addEventListener('click', () => { + this.button._tippy.hide(); + }); }); init() { @@ -218,6 +224,12 @@ window.customElements.define('overflow-menu', class extends HTMLElement { } } + attributeChangedCallback() { + if (!this.button || !this.tippyContent) return; + const containActiveInTippy = this.tippyContent.querySelector('.item.active'); + toggleClass(this.button, 'active', Boolean(containActiveInTippy)); + } + disconnectedCallback() { this.mutationObserver?.disconnect(); this.resizeObserver?.disconnect(); From 8bab94aacb3a17e938a1e8c2bf5daa6a9bd84e9e Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Sat, 12 Apr 2025 04:57:07 +0000 Subject: [PATCH 2/8] fix --- web_src/css/base.css | 2 +- web_src/js/webcomponents/overflow-menu.ts | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/web_src/css/base.css b/web_src/css/base.css index a4fc94ad2e2e3..1757ff053819e 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -748,7 +748,7 @@ overflow-menu .overflow-menu-button { } overflow-menu .overflow-menu-button.active { - margin: 0 0 -2px; + padding: 2px 0 0; border-bottom: 2px solid transparent; background-color: transparent; border-color: currentcolor; diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index cfcd3e3a4fcc1..a3ebf7b3fc3db 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -14,6 +14,11 @@ window.customElements.define('overflow-menu', class extends HTMLElement { mutationObserver: MutationObserver; lastWidth: number; + updateButtonActivationState() { + if (!this.button || !this.tippyContent) return; + toggleClass(this.button, 'active', Boolean(this.tippyContent.querySelector('.item.active'))); + } + updateItems = throttle(100, () => { if (!this.tippyContent) { const div = document.createElement('div'); @@ -125,9 +130,18 @@ window.customElements.define('overflow-menu', class extends HTMLElement { this.tippyContent.append(item); } + // close tippy when clicking item of tippy + const items = this.tippyContent.querySelectorAll('.item'); + for (const item of items) { + item.addEventListener('click', () => { + this.button?._tippy.hide(); + }); + } + // update existing tippy if (this.button?._tippy) { this.button._tippy.setContent(this.tippyContent); + this.updateButtonActivationState(); return; } @@ -138,6 +152,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { btn.innerHTML = octiconKebabHorizontal; this.append(btn); this.button = btn; + this.updateButtonActivationState(); createTippy(btn, { trigger: 'click', @@ -153,10 +168,6 @@ window.customElements.define('overflow-menu', class extends HTMLElement { }, 0); }, }); - - this.tippyContent.querySelector('.item').addEventListener('click', () => { - this.button._tippy.hide(); - }); }); init() { @@ -225,9 +236,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { } attributeChangedCallback() { - if (!this.button || !this.tippyContent) return; - const containActiveInTippy = this.tippyContent.querySelector('.item.active'); - toggleClass(this.button, 'active', Boolean(containActiveInTippy)); + this.updateButtonActivationState(); } disconnectedCallback() { From be93e1597a6c768fc6c9e09e7280fd6a4030fdf9 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Sat, 12 Apr 2025 08:17:58 +0000 Subject: [PATCH 3/8] fix --- web_src/js/components/DashboardRepoList.vue | 2 +- web_src/js/webcomponents/overflow-menu.ts | 23 ++++++++------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index bbe226ee6edc2..fc6a7bd281f11 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -391,7 +391,7 @@ export default defineComponent({
- +
{{ textAll }} diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index a3ebf7b3fc3db..bc86c756d1ca7 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -4,8 +4,6 @@ import {isDocumentFragmentOrElementNode, toggleClass} from '../utils/dom.ts'; import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; window.customElements.define('overflow-menu', class extends HTMLElement { - static observedAttributes = ['active']; - tippyContent: HTMLDivElement; tippyItems: Array; button: HTMLButtonElement; @@ -104,7 +102,16 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const itemRight = item.offsetLeft + item.offsetWidth; if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button with some extra space this.tippyItems.push(item); + + // close tippy when clicking item of tippy + item.addEventListener('click', () => { + this.button?._tippy.hide(); + }); } + // refresh overflow-button active state + item.addEventListener('click', () => { + this.updateButtonActivationState(); + }); } itemFlexSpace?.style.removeProperty('display'); itemOverFlowMenuButton?.style.removeProperty('display'); @@ -130,14 +137,6 @@ window.customElements.define('overflow-menu', class extends HTMLElement { this.tippyContent.append(item); } - // close tippy when clicking item of tippy - const items = this.tippyContent.querySelectorAll('.item'); - for (const item of items) { - item.addEventListener('click', () => { - this.button?._tippy.hide(); - }); - } - // update existing tippy if (this.button?._tippy) { this.button._tippy.setContent(this.tippyContent); @@ -235,10 +234,6 @@ window.customElements.define('overflow-menu', class extends HTMLElement { } } - attributeChangedCallback() { - this.updateButtonActivationState(); - } - disconnectedCallback() { this.mutationObserver?.disconnect(); this.resizeObserver?.disconnect(); From 8cf1467c5c1811af806b527d9f431813bf8325e6 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Sat, 12 Apr 2025 09:41:30 +0000 Subject: [PATCH 4/8] fix --- web_src/js/webcomponents/overflow-menu.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index bc86c756d1ca7..efb374533bdb0 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -104,14 +104,20 @@ window.customElements.define('overflow-menu', class extends HTMLElement { this.tippyItems.push(item); // close tippy when clicking item of tippy + if (!item.hasAttribute('data-tippy-click-added')) { + item.addEventListener('click', () => { + this.button?._tippy.hide(); + }); + item.setAttribute('data-tippy-click-added', 'true'); + } + } + // refresh overflow-button active state + if (!item.hasAttribute('data-button-update-click-added')) { item.addEventListener('click', () => { - this.button?._tippy.hide(); + this.updateButtonActivationState(); }); + item.setAttribute('data-button-update-click-added', 'true'); } - // refresh overflow-button active state - item.addEventListener('click', () => { - this.updateButtonActivationState(); - }); } itemFlexSpace?.style.removeProperty('display'); itemOverFlowMenuButton?.style.removeProperty('display'); From 8315fb6f65c3cd65e9b7c399b9f43cf43d9ade63 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 12 Apr 2025 18:02:39 +0800 Subject: [PATCH 5/8] temp --- web_src/css/base.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/css/base.css b/web_src/css/base.css index 1757ff053819e..353ae851ad49c 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -747,11 +747,11 @@ overflow-menu .overflow-menu-button { padding: 0; } -overflow-menu .overflow-menu-button.active { +/* 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 transparent; + border-bottom: 2px solid currentcolor; background-color: transparent; - border-color: currentcolor; font-weight: var(--font-weight-medium); } From 24bcb4997e7796291e3553e1a567e0628ea8d564 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 12 Apr 2025 18:13:25 +0800 Subject: [PATCH 6/8] use addDelegatedEventListener --- web_src/js/utils/dom.ts | 2 +- web_src/js/webcomponents/overflow-menu.ts | 29 +++++++++-------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index b3debfde9eee6..98e5170a2bdb8 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -360,7 +360,7 @@ export function querySingleVisibleElem(parent: Element, s export function addDelegatedEventListener(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => Promisable, options?: boolean | AddEventListenerOptions) { parent.addEventListener(type, (e: Event) => { const elem = (e.target as HTMLElement).closest(selector); - if (!elem) return; + if (!elem || !parent.contains(elem)) return; listener(elem as T, e as E); }, options); } diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index efb374533bdb0..c87de5f2d3b47 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -1,6 +1,6 @@ import {throttle} from 'throttle-debounce'; import {createTippy} from '../modules/tippy.ts'; -import {isDocumentFragmentOrElementNode, toggleClass} from '../utils/dom.ts'; +import {addDelegatedEventListener, isDocumentFragmentOrElementNode, toggleClass} from '../utils/dom.ts'; import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; window.customElements.define('overflow-menu', class extends HTMLElement { @@ -20,7 +20,6 @@ window.customElements.define('overflow-menu', class extends HTMLElement { updateItems = throttle(100, () => { if (!this.tippyContent) { const div = document.createElement('div'); - div.classList.add('tippy-target'); div.tabIndex = -1; // for initial focus, programmatic focus only div.addEventListener('keydown', (e) => { if (e.key === 'Tab') { @@ -69,7 +68,8 @@ 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; } @@ -102,21 +102,6 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const itemRight = item.offsetLeft + item.offsetWidth; if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button with some extra space this.tippyItems.push(item); - - // close tippy when clicking item of tippy - if (!item.hasAttribute('data-tippy-click-added')) { - item.addEventListener('click', () => { - this.button?._tippy.hide(); - }); - item.setAttribute('data-tippy-click-added', 'true'); - } - } - // refresh overflow-button active state - if (!item.hasAttribute('data-button-update-click-added')) { - item.addEventListener('click', () => { - this.updateButtonActivationState(); - }); - item.setAttribute('data-button-update-click-added', 'true'); } } itemFlexSpace?.style.removeProperty('display'); @@ -209,6 +194,14 @@ window.customElements.define('overflow-menu', class extends HTMLElement { } }); 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() { From dfc139130e79566837e88b6683210f838e6d3837 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 12 Apr 2025 18:24:34 +0800 Subject: [PATCH 7/8] fix overflow bug --- web_src/js/webcomponents/overflow-menu.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index c87de5f2d3b47..ce8f5c5df0057 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -1,6 +1,6 @@ import {throttle} from 'throttle-debounce'; import {createTippy} from '../modules/tippy.ts'; -import {addDelegatedEventListener, isDocumentFragmentOrElementNode, toggleClass} from '../utils/dom.ts'; +import {addDelegatedEventListener, isDocumentFragmentOrElementNode} from '../utils/dom.ts'; import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; window.customElements.define('overflow-menu', class extends HTMLElement { @@ -14,7 +14,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { updateButtonActivationState() { if (!this.button || !this.tippyContent) return; - toggleClass(this.button, 'active', Boolean(this.tippyContent.querySelector('.item.active'))); + this.button.classList.toggle('active', Boolean(this.tippyContent.querySelector('.item.active'))); } updateItems = throttle(100, () => { @@ -71,7 +71,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { div.classList.add('tippy-target'); this.handleItemClick(div, '.tippy-target > .item'); this.tippyContent = div; - } + } // end if: no tippyContent and create a new one const itemFlexSpace = this.menuItemsEl.querySelector('.item-flex-space'); const itemOverFlowMenuButton = this.querySelector('.overflow-menu-button'); @@ -93,7 +93,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const menuRight = this.offsetLeft + this.offsetWidth; const menuItems = this.menuItemsEl.querySelectorAll('.item, .item-flex-space'); let afterFlexSpace = false; - for (const item of menuItems) { + for (const [idx, item] of menuItems.entries()) { if (item.classList.contains('item-flex-space')) { afterFlexSpace = true; continue; @@ -101,7 +101,10 @@ window.customElements.define('overflow-menu', class extends HTMLElement { if (afterFlexSpace) item.setAttribute('data-after-flex-space', 'true'); const itemRight = item.offsetLeft + item.offsetWidth; 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'); @@ -199,7 +202,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { handleItemClick(el: Element, selector: string) { addDelegatedEventListener(el, 'click', selector, () => { - this.button?._tippy.hide(); + this.button?._tippy?.hide(); this.updateButtonActivationState(); }); } From 2cf17b13478f810269ddc826d937c4d3e5226a08 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 12 Apr 2025 18:31:13 +0800 Subject: [PATCH 8/8] fix wrong call order --- web_src/js/webcomponents/overflow-menu.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index ce8f5c5df0057..ae93f2b758325 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -115,6 +115,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const btn = this.querySelector('.overflow-menu-button'); btn?._tippy?.destroy(); btn?.remove(); + this.button = null; return; } @@ -139,15 +140,12 @@ window.customElements.define('overflow-menu', class extends HTMLElement { } // create button initially - const btn = document.createElement('button'); - btn.classList.add('overflow-menu-button'); - btn.setAttribute('aria-label', window.config.i18n.more_items); - btn.innerHTML = octiconKebabHorizontal; - this.append(btn); - this.button = btn; - this.updateButtonActivationState(); - - createTippy(btn, { + this.button = document.createElement('button'); + this.button.classList.add('overflow-menu-button'); + this.button.setAttribute('aria-label', window.config.i18n.more_items); + this.button.innerHTML = octiconKebabHorizontal; + this.append(this.button); + createTippy(this.button, { trigger: 'click', hideOnClick: true, interactive: true, @@ -161,6 +159,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { }, 0); }, }); + this.updateButtonActivationState(); }); init() {