From 4b735b768a2612341c0c0721517ceb198ddc904b Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Wed, 8 Apr 2026 08:05:53 +0000 Subject: [PATCH] feat(dees-simple-appdash): add nested sidebar subviews and preserve submit labels from slotted text --- changelog.md | 6 + ts_web/00_commitinfo_data.ts | 2 +- .../dees-form-submit/dees-form-submit.ts | 17 ++- .../dees-simple-appdash.demo.ts | 27 ++++- .../dees-simple-appdash.ts | 106 ++++++++++++++++-- 5 files changed, 145 insertions(+), 13 deletions(-) diff --git a/changelog.md b/changelog.md index 513b71c..dcaa387 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 2026-04-08 - 3.68.0 - feat(dees-simple-appdash) +add nested sidebar subviews and preserve submit labels from slotted text + +- support grouped navigation items with expandable subviews and parent-to-first-subview fallback in the app dashboard +- allow dees-form-submit to derive its button text from light DOM content when no explicit text property is set + ## 2026-04-07 - 3.67.1 - fix(repo) no changes to commit diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 64e6666..02dd8d7 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@design.estate/dees-catalog', - version: '3.67.1', + version: '3.68.0', description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' } diff --git a/ts_web/elements/00group-form/dees-form-submit/dees-form-submit.ts b/ts_web/elements/00group-form/dees-form-submit/dees-form-submit.ts index e0ba632..039c7e2 100644 --- a/ts_web/elements/00group-form/dees-form-submit/dees-form-submit.ts +++ b/ts_web/elements/00group-form/dees-form-submit/dees-form-submit.ts @@ -75,12 +75,23 @@ export class DeesFormSubmit extends DeesElement { .text=${this.text} ?disabled=${this.disabled} @clicked=${this.submit} - > - - + > `; } + public async firstUpdated() { + // Capture light DOM text content as the button label. dees-button wipes + // its own light DOM during extractLightDom(), so we cannot simply forward + // a into it — we have to hoist the text onto the .text property + // ourselves before handing it to dees-button. + if (!this.text) { + const slotText = this.textContent?.trim(); + if (slotText) { + this.text = slotText; + } + } + } + public async submit() { if (this.disabled) { return; diff --git a/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.demo.ts b/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.demo.ts index 2cb7d2a..4572216 100644 --- a/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.demo.ts +++ b/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.demo.ts @@ -353,12 +353,35 @@ export const demoFunc = () => html` name: 'Analytics', iconName: 'lucide:lineChart', element: DemoViewAnalytics, + subViews: [ + { + name: 'Overview', + iconName: 'lucide:activity', + element: DemoViewAnalytics, + }, + { + name: 'Reports', + iconName: 'lucide:fileText', + element: DemoViewDashboard, + }, + ], }, { name: 'Settings', iconName: 'lucide:settings', - element: DemoViewSettings, - } + subViews: [ + { + name: 'Profile', + iconName: 'lucide:user', + element: DemoViewSettings, + }, + { + name: 'Billing', + iconName: 'lucide:creditCard', + element: DemoViewSettings, + }, + ], + }, ] as IView[]} @logout=${() => { console.log('Logout event triggered'); diff --git a/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.ts b/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.ts index ba8ae4d..9ba939a 100644 --- a/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.ts +++ b/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.ts @@ -26,7 +26,8 @@ declare global { export interface IView { name: string; iconName?: string; - element: DeesElement['constructor']['prototype']; + element?: DeesElement['constructor']['prototype']; + subViews?: IView[]; } export type TGlobalMessageType = 'info' | 'success' | 'warning' | 'error'; @@ -250,6 +251,55 @@ export class DeesSimpleAppDash extends DeesElement { white-space: nowrap; } + .viewTab .chevron { + flex: 0 0 auto; + font-size: 14px; + opacity: 0.5; + transform: rotate(-90deg); + transition: transform 0.2s ease, opacity 0.15s ease; + } + + .viewTab.hasSubs:hover .chevron { + opacity: 0.75; + } + + .viewTab.hasSubs.groupActive .chevron { + transform: rotate(0deg); + opacity: 0.9; + } + + .subViews { + display: flex; + flex-direction: column; + gap: 2px; + margin: 2px 0 4px 12px; + padding-left: 12px; + position: relative; + } + + .subViews::before { + content: ''; + position: absolute; + left: 0; + top: 4px; + bottom: 4px; + width: 1px; + background: var(--dees-color-border-default); + } + + .viewTab.sub { + padding: 8px 12px; + font-size: 12px; + } + + .viewTab.sub dees-icon { + font-size: 14px; + } + + .viewTab.sub.selected::before { + left: -12px; + } + .appActions { padding: 12px 8px; border-top: 1px solid var(--dees-color-border-default); @@ -563,10 +613,12 @@ export class DeesSimpleAppDash extends DeesElement {
- ${this.viewTabs.map( - (view) => html` + ${this.viewTabs.map((view) => { + const hasSubs = !!view.subViews?.length; + const groupActive = hasSubs && this.isGroupActive(view); + return html`
this.loadView(view)} > ${view.iconName ? html` @@ -575,9 +627,34 @@ export class DeesSimpleAppDash extends DeesElement { `} ${view.name} + ${hasSubs ? html` + + ` : ''}
- ` - )} + ${hasSubs && groupActive ? html` +
+ ${view.subViews!.map( + (sub) => html` +
{ + e.stopPropagation(); + this.loadView(sub); + }} + > + ${sub.iconName ? html` + + ` : html` + + `} + ${sub.name} +
+ ` + )} +
+ ` : ''} + `; + })}
@@ -771,8 +848,23 @@ export class DeesSimpleAppDash extends DeesElement { } + private isGroupActive(view: IView): boolean { + if (this.selectedView === view) return true; + return view.subViews?.some((sv) => sv === this.selectedView) ?? false; + } + private currentView!: DeesElement; public async loadView(viewArg: IView) { + // Group-only parent: resolve to first sub view with an element + if (!viewArg.element && viewArg.subViews?.length) { + const firstNavigable = viewArg.subViews.find((sv) => sv.element); + if (firstNavigable) { + return this.loadView(firstNavigable); + } + return; // nothing navigable — ignore click + } + if (!viewArg.element) return; // safety: no element and no subs → no-op + const appcontent = this.shadowRoot!.querySelector('.appcontent')!; const view = new viewArg.element(); if (this.currentView) { @@ -781,7 +873,7 @@ export class DeesSimpleAppDash extends DeesElement { appcontent.appendChild(view); this.currentView = view; this.selectedView = viewArg; - + // Emit view-select event this.dispatchEvent(new CustomEvent('view-select', { detail: { view: viewArg },