From e5418607a3e2d1d3acfbc9b20b31a27e3e865710 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 12 May 2025 17:02:48 +0200 Subject: [PATCH 1/5] feat: add tab overflow menu (#1592) --- .../openscd/src/addons/menu-tabs/menu-tabs.ts | 172 ++++++++++++++---- 1 file changed, 139 insertions(+), 33 deletions(-) diff --git a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts index f06e3e1dbd..e1975a94be 100644 --- a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts +++ b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts @@ -5,14 +5,15 @@ import { property, query, state, - TemplateResult, css } from 'lit-element'; import '@material/mwc-list'; -import '@material/mwc-tab'; -import '@material/mwc-tab-bar'; +import '@material/mwc-menu'; +import type { Menu } from '@material/mwc-menu'; import '@material/mwc-button'; +import '@material/mwc-icon-button'; +import '@material/mwc-icon'; import { Plugin, @@ -33,47 +34,152 @@ export class OscdMenuTabs extends LitElement { }; @state() private activeTabIndex = 0; + @state() private visibleTabs: Plugin[] = []; + @state() private hiddenTabs: Plugin[] = []; + @query('.app-bar-container') private appBarContainer!: HTMLElement; + @query('mwc-menu') private overflowMenu!: Menu; - render(){ - if(this.editors.length === 0){ return html``; } + firstUpdated() { + this.#calculateVisibleTabs(); + window.addEventListener('resize', () => this.#calculateVisibleTabs()); + } + + disconnectedCallback() { + window.removeEventListener('resize', () => this.#calculateVisibleTabs()); + super.disconnectedCallback(); + } + + async #calculateVisibleTabs() { + await this.updateComplete; + + const visibleTabs: Plugin[] = []; + const hiddenTabs: Plugin[] = []; + let totalWidth = 0; + + const measurer = document.createElement('div'); + Object.assign(measurer.style, { + position: 'absolute', + visibility: 'hidden', + whiteSpace: 'nowrap', + fontSize: '14px', + padding: '0 20 px', + }); + document.body.appendChild(measurer); + + try { + for (let i = 0; i < this.editors.length; i++) { + measurer.textContent = this.editors[i].name; + const approximateWidthOtherThanText = 112; + const buttonWidth = + measurer.offsetWidth + approximateWidthOtherThanText; + + var availableWidth = this.appBarContainer.offsetWidth; + const isMenuButtonVisible = this.hiddenTabs.length > 0; + if (isMenuButtonVisible) { + availableWidth -= 48; + } + if (totalWidth + buttonWidth <= availableWidth) { + totalWidth += buttonWidth; + visibleTabs.push(this.editors[i]); + } else { + hiddenTabs.push(this.editors[i]); + } + } + + this.visibleTabs = visibleTabs; + this.hiddenTabs = hiddenTabs; + } finally { + document.body.removeChild(measurer); + } + } + + render() { + if (this.activeEditor === undefined && this.editors.length > 0) { + this.#activateTab(0); + } return html` - - ${ this.editors.map( EditorTab ) } - - ` +
+ ${this.hiddenTabs.length > 0 + ? html` + this.overflowMenu.show()} + > + + + ${this.hiddenTabs.map( + (editor, index) => html` + + this.#activateTab(this.visibleTabs.length + index)} + > + ${editor.name} + ${editor.icon} + + ` + )} + + ` + : ''} + ${this.visibleTabs.map( + (editor, index) => html` + this.#activateTab(index)} + > + ` + )} +
+ `; + } + + #activateTab(index: number) { + this.activeTabIndex = index; + this._activeEditor = this.editors[index]; + this.dispatchEvent( + new CustomEvent(TabActivatedEventKey, { + detail: { editor: this.editors[index] }, + composed: true, + bubbles: true, + }) + ); } static styles = css` - mwc-tab { - background-color: var(--primary); + .app-bar-container { + display: flex; + align-items: center; + height: 48px; + background-color: var(--mdc-theme-primary, #6200ee); + position: relative; + } + + mwc-button { + --mdc-theme-on-primary: #174b46; --mdc-theme-primary: var(--mdc-theme-on-primary); + --mdc-shape-small: 0px; + white-space: nowrap; + margin: 0 10px; } - ` - private handleActivatedEditorTab(e: CustomEvent): void { - const tabIndex = e.detail.index; - const editor = this.editors[tabIndex]; - this.activeTabIndex = tabIndex; - this.dispatchActivateEditor(editor); - } + mwc-button[active] { + background: #42a99f; + --mdc-theme-on-primary: white; + } - private dispatchActivateEditor( editor: Plugin ){ - const newEvent = new CustomEvent(TabActivatedEventKey, { - detail: { editor }, - composed: true, - bubbles: true - }) - this.dispatchEvent(newEvent) - } -} + mwc-icon-button { + color: #174b46; + } -function EditorTab({ name, icon }: Plugin): TemplateResult { - return html` - + mwc-menu { + position: absolute; + left: 0; + top: 100%; + } `; } From 7b8d54a2699fd7722dc83da16e5550d180b583c1 Mon Sep 17 00:00:00 2001 From: Jan <5037153+Tamriel@users.noreply.github.com> Date: Fri, 16 May 2025 15:03:26 +0200 Subject: [PATCH 2/5] style: correct formatting --- .../openscd/src/addons/menu-tabs/menu-tabs.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts index e1975a94be..1bc37f62ef 100644 --- a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts +++ b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts @@ -5,7 +5,7 @@ import { property, query, state, - css + css, } from 'lit-element'; import '@material/mwc-list'; @@ -15,23 +15,23 @@ import '@material/mwc-button'; import '@material/mwc-icon-button'; import '@material/mwc-icon'; -import { - Plugin, -} from "../../plugin.js" - +import { Plugin } from '../../plugin.js'; @customElement('oscd-menu-tabs') export class OscdMenuTabs extends LitElement { - @property({ type: Array }) editors: Plugin[] = []; _activeEditor: Plugin | undefined; - @property({ type: Object }) get activeEditor() { return this._activeEditor; } + @property({ type: Object }) get activeEditor() { + return this._activeEditor; + } set activeEditor(editor: Plugin | undefined) { this._activeEditor = editor; - const editorIndex = this.editors.findIndex(e => e.name === editor?.name && e.src === editor?.src); + const editorIndex = this.editors.findIndex( + e => e.name === editor?.name && e.src === editor?.src + ); this.activeTabIndex = editorIndex > -1 ? editorIndex : 0; this.requestUpdate(); - }; + } @state() private activeTabIndex = 0; @state() private visibleTabs: Plugin[] = []; @@ -183,6 +183,6 @@ export class OscdMenuTabs extends LitElement { `; } -export const TabActivatedEventKey = 'oscd-editor-tab-activated' +export const TabActivatedEventKey = 'oscd-editor-tab-activated'; export type TabActivatedEvent = CustomEvent; -export type TabActivatedEventDetail = { editor: Plugin } +export type TabActivatedEventDetail = { editor: Plugin }; From e1505a52a200bfc201ced27ff83c4712484e9c0d Mon Sep 17 00:00:00 2001 From: Jan <5037153+Tamriel@users.noreply.github.com> Date: Fri, 16 May 2025 15:06:05 +0200 Subject: [PATCH 3/5] fix: properly register and remove resize listener --- packages/openscd/src/addons/menu-tabs/menu-tabs.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts index 1bc37f62ef..aa302b6cbd 100644 --- a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts +++ b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts @@ -41,15 +41,15 @@ export class OscdMenuTabs extends LitElement { firstUpdated() { this.#calculateVisibleTabs(); - window.addEventListener('resize', () => this.#calculateVisibleTabs()); + window.addEventListener('resize', this.#calculateVisibleTabs); } disconnectedCallback() { - window.removeEventListener('resize', () => this.#calculateVisibleTabs()); + window.removeEventListener('resize', this.#calculateVisibleTabs); super.disconnectedCallback(); } - async #calculateVisibleTabs() { + #calculateVisibleTabs = async () => { await this.updateComplete; const visibleTabs: Plugin[] = []; @@ -91,7 +91,7 @@ export class OscdMenuTabs extends LitElement { } finally { document.body.removeChild(measurer); } - } + }; render() { if (this.activeEditor === undefined && this.editors.length > 0) { From 516001d6e274fa09f643221d982f1465ff418681 Mon Sep 17 00:00:00 2001 From: Jan <5037153+Tamriel@users.noreply.github.com> Date: Fri, 16 May 2025 15:07:56 +0200 Subject: [PATCH 4/5] style: place the overflow menu at the and of the row --- .../openscd/src/addons/menu-tabs/menu-tabs.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts index aa302b6cbd..9b63b55bb7 100644 --- a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts +++ b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts @@ -100,6 +100,16 @@ export class OscdMenuTabs extends LitElement { return html`
+ ${this.visibleTabs.map( + (editor, index) => html` + this.#activateTab(index)} + > + ` + )} ${this.hiddenTabs.length > 0 ? html` ` : ''} - ${this.visibleTabs.map( - (editor, index) => html` - this.#activateTab(index)} - > - ` - )}
`; } @@ -153,6 +153,7 @@ export class OscdMenuTabs extends LitElement { .app-bar-container { display: flex; align-items: center; + justify-content: space-between; height: 48px; background-color: var(--mdc-theme-primary, #6200ee); position: relative; From 822021fe9b084c8e630f8eced2bdc849d5ed64fa Mon Sep 17 00:00:00 2001 From: Jan <5037153+Tamriel@users.noreply.github.com> Date: Fri, 16 May 2025 15:41:05 +0200 Subject: [PATCH 5/5] style: more like mwc-tab-bar --- packages/openscd/src/addons/menu-tabs/menu-tabs.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts index 9b63b55bb7..7f6520726d 100644 --- a/packages/openscd/src/addons/menu-tabs/menu-tabs.ts +++ b/packages/openscd/src/addons/menu-tabs/menu-tabs.ts @@ -154,7 +154,7 @@ export class OscdMenuTabs extends LitElement { display: flex; align-items: center; justify-content: space-between; - height: 48px; + height: 36px; background-color: var(--mdc-theme-primary, #6200ee); position: relative; } @@ -163,13 +163,13 @@ export class OscdMenuTabs extends LitElement { --mdc-theme-on-primary: #174b46; --mdc-theme-primary: var(--mdc-theme-on-primary); --mdc-shape-small: 0px; + --mdc-button-horizontal-padding: 24px; + --mdc-typography-button-font-size: 0.9rem; white-space: nowrap; - margin: 0 10px; } mwc-button[active] { - background: #42a99f; - --mdc-theme-on-primary: white; + --mdc-theme-on-primary: #d9e0ce; } mwc-icon-button {