@ds-mo/ui 14.23.0 → 14.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.package-ready.json +1 -1
- package/dist/agent-patterns.json +10 -7
- package/dist/agent.json +59 -15
- package/dist/angular/ds-bar-title.d.ts +1 -1
- package/dist/angular/ds-bar-title.js +3 -3
- package/dist/angular/ds-button-filled.d.ts +3 -1
- package/dist/angular/ds-button-filled.js +7 -4
- package/dist/angular/ds-button-unfilled.d.ts +3 -1
- package/dist/angular/ds-button-unfilled.js +7 -4
- package/dist/components/ds-agent-questionnaire.js +1 -1
- package/dist/components/ds-agent-response.js +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-button-filled.js +1 -1
- package/dist/components/ds-button-unfilled.js +1 -1
- package/dist/components/ds-card-chart.js +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-filter-menu.js +1 -1
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-markdown.js +1 -1
- package/dist/components/ds-message-actions.js +1 -1
- package/dist/components/ds-message-composer.js +1 -1
- package/dist/components/ds-message-scroller.js +1 -1
- package/dist/components/ds-mobile-sheet-nav.js +1 -1
- package/dist/components/ds-modal.js +1 -1
- package/dist/components/ds-pagination.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-shell-app.js +1 -1
- package/dist/components/ds-shell-app.js.map +1 -1
- package/dist/components/ds-shell-tools.js +1 -1
- package/dist/components/ds-table-filter.js +1 -1
- package/dist/components/ds-table-group.js +1 -1
- package/dist/components/ds-table-saved-views.js +1 -1
- package/dist/components/ds-table-search.js +1 -1
- package/dist/components/ds-table-sort.js +1 -1
- package/dist/components/ds-table.js +1 -1
- package/dist/components/ds-table.js.map +1 -1
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/p-BR1ayqMU.js +2 -0
- package/dist/components/p-BR1ayqMU.js.map +1 -0
- package/dist/components/{p-DzVYGJJv.js → p-BSRIF4m8.js} +2 -2
- package/dist/components/{p-FitB53eM.js → p-BZlhBJZL.js} +2 -2
- package/dist/components/{p-BmqnrfTf.js → p-Bo6uW_8T.js} +2 -2
- package/dist/components/{p-BIaHDBfP.js → p-Bq8svcPs.js} +2 -2
- package/dist/components/{p-DAFR4U9P.js → p-BqbdABo3.js} +2 -2
- package/dist/components/{p-nVv71P3f.js → p-CEXchU5N.js} +2 -2
- package/dist/components/p-CEfxLsbX.js +2 -0
- package/dist/components/p-CEfxLsbX.js.map +1 -0
- package/dist/components/{p-DXbryJHz.js → p-CYMKmJ2s.js} +2 -2
- package/dist/components/{p-C13fWp2x.js → p-CZHIkVgj.js} +2 -2
- package/dist/components/{p-CL0tECXf.js → p-CZjjDhUl.js} +2 -2
- package/dist/components/{p-Bd-Kpekh.js → p-C_FFU6W3.js} +2 -2
- package/dist/components/{p-CilwchJH.js → p-CglHiyY2.js} +2 -2
- package/dist/components/{p-9M0ak2q_.js → p-DLaN9BPq.js} +2 -2
- package/dist/components/{p-CrYJnDev.js → p-DiS5BgLA.js} +2 -2
- package/dist/components/{p-DiyO47xX.js → p-PXdvBMXB.js} +2 -2
- package/dist/components/{p-7MWaWl8w.js → p-kslioYhf.js} +2 -2
- package/dist/components/p-kslioYhf.js.map +1 -0
- package/dist/docs/components.json +257 -30
- package/dist/lib/components/BarTitle/bar-title-types.d.ts +54 -1
- package/dist/lib/components/ButtonFilled/ButtonFilled.d.ts +12 -1
- package/dist/lib/components/ShellApp/shell-app-types.d.ts +3 -1
- package/dist/lib/shell/index.d.ts +1 -0
- package/dist/mcp-data/registry/bar-title.json +34 -12
- package/dist/mcp-data/registry/button-filled.json +60 -6
- package/dist/mcp-data/registry/button-unfilled.json +60 -6
- package/dist/mcp-data/registry/registry.json +58 -14
- package/dist/mcp-data/registry/shell-app.json +7 -2
- package/dist/mcp-data/registry/table.json +3 -3
- package/dist/react/ds-button-filled.d.ts +1 -0
- package/dist/react/ds-button-filled.js +4 -1
- package/dist/react/ds-button-unfilled.d.ts +1 -0
- package/dist/react/ds-button-unfilled.js +1 -0
- package/dist/styles/table.css +13 -1
- package/dist/types/components/BarTitle/BarTitle.d.ts +19 -6
- package/dist/types/components/BarTitle/bar-title-actions.d.ts +11 -0
- package/dist/types/components/BarTitle/bar-title-types.d.ts +54 -1
- package/dist/types/components/BarTitle/index.d.ts +1 -1
- package/dist/types/components/ButtonFilled/ButtonFilled.d.ts +12 -1
- package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +12 -1
- package/dist/types/components/ShellApp/ShellApp.d.ts +12 -0
- package/dist/types/components/ShellApp/shell-app-types.d.ts +3 -1
- package/dist/types/components/Table/Table.d.ts +2 -2
- package/dist/types/components/Table/table-row-view.d.ts +2 -1
- package/dist/types/components.d.ts +60 -4
- package/dist/types/shell/index.d.ts +1 -0
- package/dist/types/utils/button-story-foundation.d.ts +2 -0
- package/dist/vue/ds-bar-title.js +1 -0
- package/dist/vue/ds-button-filled.js +6 -2
- package/dist/vue/ds-button-unfilled.js +4 -0
- package/package.json +1 -1
- package/dist/components/p--68f7nIx.js +0 -2
- package/dist/components/p--68f7nIx.js.map +0 -1
- package/dist/components/p-7MWaWl8w.js.map +0 -1
- package/dist/components/p-BnTPuV3x.js +0 -2
- package/dist/components/p-BnTPuV3x.js.map +0 -1
- /package/dist/components/{p-DzVYGJJv.js.map → p-BSRIF4m8.js.map} +0 -0
- /package/dist/components/{p-FitB53eM.js.map → p-BZlhBJZL.js.map} +0 -0
- /package/dist/components/{p-BmqnrfTf.js.map → p-Bo6uW_8T.js.map} +0 -0
- /package/dist/components/{p-BIaHDBfP.js.map → p-Bq8svcPs.js.map} +0 -0
- /package/dist/components/{p-DAFR4U9P.js.map → p-BqbdABo3.js.map} +0 -0
- /package/dist/components/{p-nVv71P3f.js.map → p-CEXchU5N.js.map} +0 -0
- /package/dist/components/{p-DXbryJHz.js.map → p-CYMKmJ2s.js.map} +0 -0
- /package/dist/components/{p-C13fWp2x.js.map → p-CZHIkVgj.js.map} +0 -0
- /package/dist/components/{p-CL0tECXf.js.map → p-CZjjDhUl.js.map} +0 -0
- /package/dist/components/{p-Bd-Kpekh.js.map → p-C_FFU6W3.js.map} +0 -0
- /package/dist/components/{p-CilwchJH.js.map → p-CglHiyY2.js.map} +0 -0
- /package/dist/components/{p-9M0ak2q_.js.map → p-DLaN9BPq.js.map} +0 -0
- /package/dist/components/{p-CrYJnDev.js.map → p-DiS5BgLA.js.map} +0 -0
- /package/dist/components/{p-DiyO47xX.js.map → p-PXdvBMXB.js.map} +0 -0
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"Managed composition is the standard. Supply navigation, pageChrome, and tools objects plus named tool-view and default page-content slots.",
|
|
57
57
|
"The application router owns the current route, authorization filtering, Dashboard or Settings context, destination collections, section collections, and localized content labels.",
|
|
58
58
|
"ShellApp renders the lower-level desktop, tablet, and mobile chrome, coordinates layout, gradient alignment, transitions, shortcuts, global surfaces, optional Inbox state, focus restoration, and mobile-stage visibility.",
|
|
59
|
+
"Desktop and tablet PanelNav and PanelTools width motion is one continuous layout transition: the shell bar or title and routed page must follow the same intermediate lane widths without terminal-state snapping, overlapping chrome, transform scaling, or temporary replacement backgrounds.",
|
|
59
60
|
"The application router owns authorization, current URLs, history, and route execution; ShellApp forwards navigation and page intent events without routing.",
|
|
60
61
|
"sectionNavigation is an application-controlled preference: bar keeps route sections in BarNav, while panel renders them as nested PanelNav children and places compact BarTitle in the shell bar.",
|
|
61
62
|
"Managed ShellApp derives ShellPage desktop header placement from sectionNavigation. Panel mode removes page-local title geometry, restores the standard responsive top gutter, and preserves full-bleed page content without requiring route-specific navigation branches.",
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"Dashboard represents day-to-day operational work while Settings represents administrative and configuration work; route state selects the context.",
|
|
73
74
|
"Replace navigation and pageChrome together from one completed route state.",
|
|
74
75
|
"When pageChrome.showBack marks a detail depth, mobile chrome keeps the page heading as its identity and presents only local subsections; peer route tabs remain in desktop BarNav rather than replacing the detail heading.",
|
|
76
|
+
"Route-owned ordered page actions drive both BarTitle and MobileHeader. ShellApp forwards one flat page-action id and owns the derived mobile overflow menu so routes do not duplicate menu state or choices.",
|
|
75
77
|
"Managed ShellApp keeps the selected desktop section-navigation presentation and ShellPage present and owns page header stickiness and the content scroller.",
|
|
76
78
|
"Route-owned pageChrome independently selects ShellPage contentInset, page-header seams, scroll compaction, expanded and compact header-divider visibility, and contentSurface. ShellApp forwards them while resolving whether desktop title geometry is page-local or shell-bar-owned.",
|
|
77
79
|
"The application filters unauthorized destinations, sections, and tools before passing them into shell components."
|
|
@@ -96,6 +98,7 @@
|
|
|
96
98
|
"The routed content cell and tools cell expose separate anchored-overlay collision boundaries, so top-layer menus and tooltips remain above local clipping without crossing into neighboring shell lanes. Full-screen modal overlays remain viewport-owned.",
|
|
97
99
|
"Paper texture chrome is desktop/tablet-only and follows the fixed viewport phase of the shell chrome; mobile remains a solid primary stage.",
|
|
98
100
|
"Managed page-header capacity follows only the fixed shell breakpoint: roomy on desktop and compact on tablet. Opening PanelTools does not compact or constrain BarTitle.",
|
|
101
|
+
"Mobile keeps at most the first eligible ordered icon command visible and moves labeled buttons, menu choices, split actions, and explicit overflow commands into one shell-owned text-only page-actions menu.",
|
|
99
102
|
"The bottom bar remains visible at all route and tool depths in the foundation release."
|
|
100
103
|
],
|
|
101
104
|
"frameworkCaveats": {
|
|
@@ -667,7 +670,9 @@
|
|
|
667
670
|
"registryDependencies": [
|
|
668
671
|
"bar-nav",
|
|
669
672
|
"bar-title",
|
|
673
|
+
"button-filled",
|
|
670
674
|
"button-unfilled",
|
|
675
|
+
"menu",
|
|
671
676
|
"mobile-bar-nav",
|
|
672
677
|
"mobile-header",
|
|
673
678
|
"mobile-sheet-nav",
|
|
@@ -685,12 +690,12 @@
|
|
|
685
690
|
},
|
|
686
691
|
{
|
|
687
692
|
"path": "src/wc/components/ShellApp/ShellApp.tsx",
|
|
688
|
-
"content": "import {\n Component,\n Prop,\n Element,\n Event,\n EventEmitter,\n State,\n Watch,\n Listen,\n Method,\n h,\n Host,\n} from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport { isEditableShortcutTarget, resolveShellShortcut } from '../../shell/shell-shortcuts';\nimport type {\n PanelToolsHeaderAction,\n PanelToolsRailAccessoryActionDetail,\n PanelToolsToolId,\n} from '../PanelTools/panel-tools-types';\nimport { PANEL_TOOLS_DEFAULT_ITEMS } from '../PanelTools/panel-tools-types';\nimport type { BarTitleSectionItem } from '../BarTitle/bar-title-types';\nimport type { BarTitlePlacement } from '../BarTitle/bar-title-types';\nimport type { BarNavTab } from '../BarNav/bar-nav-types';\nimport type { BreadcrumbSelectDetail } from '../Breadcrumb/breadcrumb-types';\nimport {\n DEFAULT_SHELL_GRADIENT_PRESET,\n normalizeShellGradientPreset,\n type ShellGradientPreset,\n} from '../../shell/shell-gradient-presets';\nimport {\n CHROME_TRANSITION_END,\n CHROME_TRANSITION_START,\n ChromeTransitionDepth,\n createRafCoalescer,\n readChromeTransitionSource,\n} from '../../shell/chrome-transition';\nimport {\n isPanelNavCollapsed,\n panelWidthPxFromTokens,\n readPanelNavWidthTokens,\n type PanelNavWidthTokens,\n} from '../../shell/shell-chrome-metrics';\nimport {\n SHELL_GRADIENT_IMAGE_VAR,\n SHELL_GRADIENT_OPACITY_VAR,\n SHELL_GRADIENT_POSITION_BAR_VAR,\n SHELL_GRADIENT_POSITION_PANEL_VAR,\n SHELL_GRADIENT_SIZE_VAR,\n buildShellRadialGradient,\n readShellViewportDimensions,\n shellGradientPositionBar,\n shellGradientPositionPanel,\n shellGradientPresetOpacity,\n shellGradientSize,\n} from '../../shell/shell-gradient';\nimport {\n itemUsesShellInbox,\n resolveAvailableInboxTool,\n resolveManagedShellPageCapacity,\n resolveShellResponsiveMode,\n SHELL_DESKTOP_BREAKPOINT,\n shellMobileDestinationForTool,\n type MobileDestination,\n type ShellInboxToolId,\n type ShellResponsiveMode,\n} from '../../shell/shell-responsive';\nimport { deriveActiveIdFromUrl } from '../PanelNav/panel-nav-utils';\nimport type { PanelNavChildSelectDetail } from '../PanelNav/panel-nav-types';\nimport type { PanelNavItem } from '../PanelNav/panel-nav-types';\nimport type { PanelNavUserActionDetail } from '../PanelNav/panel-nav-types';\nimport type { MobileBarNavDestinationDetail } from '../MobileBarNav/mobile-bar-nav-types';\nimport type {\n ShellAppComposition,\n ShellNavigationConfig,\n ShellPageChromeConfig,\n ShellSectionNavigation,\n ShellToolsConfig,\n} from './shell-app-types';\nimport type { PaperTextureConfig } from '../PaperTexture/paper-texture-types';\n\n@Component({\n tag: 'ds-shell-app',\n styleUrl: 'ShellApp.css',\n scoped: true,\n})\nexport class ShellApp {\n private static readonly FOREGROUND_REFRESH_MS = 50;\n\n /** Managed renders the complete responsive chrome; slotted exposes the advanced composition. */\n @Prop({ reflect: true }) composition: ShellAppComposition = 'managed';\n\n /** Router-owned navigation data used by managed composition. */\n @Prop() navigation: ShellNavigationConfig = {};\n\n /** Desktop/tablet route sections in BarNav, or nested beneath PanelNav parents. */\n @Prop({ attribute: 'section-navigation', reflect: true })\n sectionNavigation: ShellSectionNavigation = 'bar';\n\n /** Route-owned page title and section data used by managed composition. */\n @Prop({ attribute: 'page-chrome' }) pageChrome: ShellPageChromeConfig = {};\n\n /** Product-owned global tool definitions used by managed composition. */\n @Prop() tools: ShellToolsConfig = {};\n\n /** Chrome style propagated to slotted `ds-panel-nav` and `ds-bar-nav`. */\n @Prop({ attribute: 'nav-style', reflect: true }) navStyle: NavChromeStyle = 'dashboard';\n\n /**\n * Shell chrome wash preset. `none` renders solid chrome; the remaining\n * presets use token-based washes that adapt to the active color theme.\n */\n @Prop({ attribute: 'gradient-preset', reflect: true }) gradientPreset: ShellGradientPreset =\n DEFAULT_SHELL_GRADIENT_PRESET;\n\n /** Optional decorative Paper Design texture layered above the shell wash. */\n @Prop({ attribute: 'paper-texture' }) paperTexture?: PaperTextureConfig;\n\n /** When `true` (default), registers global shell keyboard shortcuts. `[` toggles panel nav; `]` closes tools; K, A, S, M, N, and / toggle tool drawers. Modifiers are ignored so browser chords like ⌘N stay native. */\n @Prop({ attribute: 'shortcuts-enabled' }) shortcutsEnabled: boolean = true;\n\n /** Controlled mobile surface shown above the persistent bottom bar. */\n @Prop({ attribute: 'mobile-destination' })\n mobileDestination: MobileDestination = 'area';\n\n /** Controlled full-stage Mobile Sheet Nav state. */\n @Prop({ attribute: 'mobile-sheet-nav-open' })\n mobileSheetNavOpen: boolean = false;\n\n /** Emitted after crossing the fixed 768px or 1200px shell boundaries. */\n @Event() dsResponsiveModeChange!: EventEmitter<{ mode: ShellResponsiveMode }>;\n\n /** Managed primary-navigation intent; the application router performs navigation. */\n @Event() dsNavSelect!: EventEmitter<string>;\n\n /** Managed desktop BarNav or mobile section-selection intent. */\n @Event() dsTabChange!: EventEmitter<string>;\n\n /** Managed nested PanelNav child-route intent. */\n @Event() dsNavChildSelect!: EventEmitter<PanelNavChildSelectDetail>;\n\n /** Managed page-subsection intent from BarTitle or MobileHeader. */\n @Event() dsSubsectionChange!: EventEmitter<string>;\n\n /** Managed page-level Back intent. */\n @Event() dsPageBack!: EventEmitter<MouseEvent>;\n\n /** Managed breadcrumb intent from BarTitle. */\n @Event({ cancelable: true })\n dsBreadcrumbSelect!: EventEmitter<BreadcrumbSelectDetail>;\n\n /** Managed page action intent from BarTitle. */\n @Event() dsPageAction!: EventEmitter<string>;\n\n /** Managed tools selection state. */\n @Event() dsToolChange!: EventEmitter<{ id: PanelToolsToolId; selected: boolean }>;\n\n /** Managed tool-header Back intent. */\n @Event() dsHeaderBack!: EventEmitter<{ tool: PanelToolsToolId }>;\n\n /** Managed tool-header action intent. */\n @Event() dsHeaderAction!: EventEmitter<{ tool: PanelToolsToolId; id: string }>;\n\n /** Managed desktop/tablet rail accessory intent. */\n @Event() dsRailAccessoryAction!: EventEmitter<PanelToolsRailAccessoryActionDetail>;\n\n /** Managed drawer/fullscreen presentation state. */\n @Event() dsPresentationChange!: EventEmitter<{ presentation: 'drawer' | 'fullscreen' }>;\n\n /** Managed mobile Dashboard/Settings browsing intent. */\n @Event() dsBrowseContextChange!: EventEmitter<NavChromeStyle>;\n\n /** Managed account-footer action intent. */\n @Event() dsNavFooterAction!: EventEmitter<void>;\n\n /** Managed user/account trigger intent. */\n @Event() dsNavUserAction!: EventEmitter<PanelNavUserActionDetail>;\n\n @Element() el!: HTMLElement;\n @State() private toolsFullscreen = false;\n @State() private resolvedMode: ShellResponsiveMode = 'desktop';\n @State() private managedToolsOpen = false;\n @State() private managedActiveTool: PanelToolsToolId | '' = '';\n @State() private managedToolPresentation: 'drawer' | 'fullscreen' = 'drawer';\n @State() private managedMobileDestination: MobileDestination = 'area';\n @State() private managedMobileSheetNavOpen = false;\n @State() private managedInboxTool: ShellInboxToolId | '' = '';\n @State() private managedBrowseContext: NavChromeStyle = 'dashboard';\n\n private resizeObserver: ResizeObserver | null = null;\n private hasLoaded = false;\n private foregroundRefreshTimer: number | null = null;\n private readonly panelNavTransition = new ChromeTransitionDepth();\n private readonly chromeSyncCoalescer = createRafCoalescer(() => this.syncChrome());\n private panelWidthTokens: PanelNavWidthTokens = { expandedPx: 0, collapsedPx: 0 };\n private cachedViewportWidth = 0;\n private cachedViewportHeight = 0;\n private onWindowResize = () => {\n this.updateResponsiveMode();\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n private onVisualViewportChange = () => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n\n private get managed(): boolean {\n return this.composition === 'managed';\n }\n\n private get effectiveMobileDestination(): MobileDestination {\n return this.managed ? this.managedMobileDestination : this.mobileDestination;\n }\n\n private get effectiveMobileSheetNavOpen(): boolean {\n return this.managed ? this.managedMobileSheetNavOpen : this.mobileSheetNavOpen;\n }\n\n private get resolvedNavigationGroups() {\n if (this.navigation.groups?.length) return this.navigation.groups;\n return this.managedBrowseContext === 'settings'\n ? (this.navigation.settingsGroups ?? [])\n : (this.navigation.dashboardGroups ?? []);\n }\n\n private get resolvedToolItems() {\n return this.tools.items ?? PANEL_TOOLS_DEFAULT_ITEMS;\n }\n\n private get resolvedPageTabs(): BarNavTab[] {\n const children = this.currentArea.children;\n if (!children?.length) return this.pageChrome.tabs ?? [];\n return children.map(child => ({\n id: child.id,\n label: child.label,\n dot: child.dot,\n isInactive: child.isInactive,\n }));\n }\n\n private get availableInboxTools(): ShellInboxToolId[] {\n return this.resolvedToolItems\n .filter(item => !item.isInactive && itemUsesShellInbox(item))\n .map(item => item.id as ShellInboxToolId);\n }\n\n componentWillLoad() {\n if (typeof window !== 'undefined') {\n this.resolvedMode = resolveShellResponsiveMode(window.innerWidth);\n }\n this.managedBrowseContext = this.navigation.browseContext ?? this.navStyle;\n this.managedMobileDestination = this.mobileDestination;\n this.managedMobileSheetNavOpen = this.mobileSheetNavOpen;\n this.managedInboxTool = resolveAvailableInboxTool('', this.availableInboxTools);\n }\n\n componentDidLoad() {\n this.hasLoaded = true;\n this.syncSlottedNavStyle();\n this.syncSlottedMobileState();\n this.connectMetricsObserver();\n this.connectViewportListeners();\n this.connectPageLifecycleListeners();\n this.el.addEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);\n this.el.addEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);\n requestAnimationFrame(() => {\n this.cachePanelWidthTokens();\n this.scheduleChromeSync();\n });\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools');\n this.toolsFullscreen = tools?.getAttribute('presentation') === 'fullscreen';\n }\n\n @Listen('dsPresentationChange')\n handleToolsPresentation(event: CustomEvent<{ presentation?: string }>) {\n this.toolsFullscreen = event.detail?.presentation === 'fullscreen';\n }\n\n disconnectedCallback() {\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.disconnectViewportListeners();\n this.disconnectPageLifecycleListeners();\n this.cancelForegroundRefresh();\n this.chromeSyncCoalescer.cancel();\n this.el.removeEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);\n this.el.removeEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);\n }\n\n @Watch('navStyle')\n @Watch('gradientPreset')\n @Watch('paperTexture')\n @Watch('sectionNavigation')\n onShellPropsChange() {\n this.syncSlottedNavStyle();\n this.scheduleChromeSync();\n }\n\n @Watch('navigation')\n handleNavigationChange() {\n this.managedBrowseContext = this.navigation.browseContext ?? this.navStyle;\n }\n\n @Watch('tools')\n handleToolsChange() {\n if (\n this.managedActiveTool &&\n !this.resolvedToolItems.some(item => item.id === this.managedActiveTool)\n ) {\n this.managedToolsOpen = false;\n this.managedActiveTool = '';\n this.managedMobileDestination = 'area';\n }\n this.managedInboxTool = resolveAvailableInboxTool(\n this.managedInboxTool,\n this.availableInboxTools\n );\n }\n\n @Watch('mobileDestination')\n @Watch('mobileSheetNavOpen')\n onMobileStateChange(_next: unknown, previous: unknown) {\n if (this.managed) {\n this.managedMobileDestination = this.mobileDestination;\n this.managedMobileSheetNavOpen = this.mobileSheetNavOpen;\n }\n this.syncSlottedMobileState();\n if (previous === true && !this.effectiveMobileSheetNavOpen && this.resolvedMode === 'mobile') {\n requestAnimationFrame(() => {\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n focusDestination?: (destination: MobileDestination | 'sheet-nav') => Promise<void>;\n })\n | null;\n const activeElement = document.activeElement;\n if (activeElement instanceof HTMLElement && bar?.contains(activeElement)) return;\n void bar?.focusDestination?.('sheet-nav');\n });\n }\n }\n\n private updateResponsiveMode() {\n if (typeof window === 'undefined') return;\n const next = resolveShellResponsiveMode(window.innerWidth);\n if (next === this.resolvedMode) return;\n this.resolvedMode = next;\n this.syncSlottedMobileState();\n this.scheduleChromeSync();\n if (this.hasLoaded) this.dsResponsiveModeChange.emit({ mode: next });\n }\n\n private onChromeTransitionStart = (event: Event) => {\n const source = readChromeTransitionSource(event);\n if (source !== 'panel-nav') return;\n\n this.panelNavTransition.enter();\n const viewport = readShellViewportDimensions();\n this.cachedViewportWidth = viewport.width;\n this.cachedViewportHeight = viewport.height;\n this.syncChrome();\n };\n\n private onChromeTransitionEnd = (event: Event) => {\n const source = readChromeTransitionSource(event);\n if (source !== 'panel-nav') return;\n\n this.panelNavTransition.exit();\n if (!this.panelNavTransition.isActive) {\n this.scheduleChromeSync();\n }\n };\n\n /** Coalesce ResizeObserver bursts to one layout read per frame. */\n private scheduleChromeSync() {\n this.chromeSyncCoalescer.schedule();\n }\n\n private syncSlottedNavStyle() {\n const panel = this.el.querySelector('ds-panel-nav') as\n | (HTMLElement & { navStyle: NavChromeStyle })\n | null;\n const bar = this.el.querySelector('ds-bar-nav') as\n | (HTMLElement & { navStyle: NavChromeStyle })\n | null;\n if (panel) {\n panel.setAttribute('nav-style', this.navStyle);\n panel.navStyle = this.navStyle;\n }\n if (bar) {\n bar.setAttribute('nav-style', this.navStyle);\n bar.navStyle = this.navStyle;\n }\n }\n\n private syncSlottedMobileState() {\n const tools = this.el.querySelector('ds-shell-tools') as\n | (HTMLElement & { responsiveMode: ShellResponsiveMode })\n | null;\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n activeDestination: MobileDestination;\n sheetNavExpanded: boolean;\n })\n | null;\n const sheetNav = this.el.querySelector('ds-mobile-sheet-nav') as\n | (HTMLElement & { open: boolean })\n | null;\n\n if (tools) {\n tools.setAttribute('responsive-mode', this.resolvedMode);\n tools.responsiveMode = this.resolvedMode;\n }\n if (bar) {\n bar.activeDestination = this.effectiveMobileDestination;\n bar.sheetNavExpanded = this.effectiveMobileSheetNavOpen;\n }\n if (sheetNav) {\n sheetNav.open = this.effectiveMobileSheetNavOpen;\n }\n }\n\n private connectMetricsObserver() {\n this.resizeObserver = new ResizeObserver(() => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n });\n this.resizeObserver.observe(this.el);\n const panelWrap = this.el.querySelector('.shell-app__panel');\n if (panelWrap) this.resizeObserver.observe(panelWrap);\n }\n\n private connectViewportListeners() {\n if (typeof window === 'undefined') return;\n\n window.addEventListener('resize', this.onWindowResize, { passive: true });\n\n const visual = window.visualViewport;\n if (visual) {\n visual.addEventListener('resize', this.onVisualViewportChange, { passive: true });\n visual.addEventListener('scroll', this.onVisualViewportChange, { passive: true });\n }\n }\n\n private disconnectViewportListeners() {\n if (typeof window === 'undefined') return;\n\n window.removeEventListener('resize', this.onWindowResize);\n\n const visual = window.visualViewport;\n if (visual) {\n visual.removeEventListener('resize', this.onVisualViewportChange);\n visual.removeEventListener('scroll', this.onVisualViewportChange);\n }\n }\n\n private connectPageLifecycleListeners() {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);\n window.addEventListener('pageshow', this.onPageShow);\n }\n\n private disconnectPageLifecycleListeners() {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);\n window.removeEventListener('pageshow', this.onPageShow);\n }\n\n private onDocumentVisibilityChange = () => {\n if (!document.hidden) this.refreshAfterForegroundRestore();\n };\n\n private onPageShow = (event: PageTransitionEvent) => {\n if (event.persisted) this.refreshAfterForegroundRestore();\n };\n\n private cancelForegroundRefresh() {\n if (this.foregroundRefreshTimer !== null) {\n window.clearTimeout(this.foregroundRefreshTimer);\n this.foregroundRefreshTimer = null;\n }\n this.el.classList.remove('shell-app--foreground-refresh');\n }\n\n private refreshAfterForegroundRestore() {\n this.updateResponsiveMode();\n this.syncSlottedMobileState();\n this.scheduleChromeSync();\n\n if (this.resolvedMode !== 'mobile') return;\n\n /*\n * Mobile WebKit can restore a live, correctly sized DOM without repainting\n * its suspended shell layer. Re-promote the complete mobile stage for one\n * painted frame; routed and tool owners remain mounted and keep their state.\n */\n this.cancelForegroundRefresh();\n this.el.classList.add('shell-app--foreground-refresh');\n void this.el.offsetHeight;\n // A short timer spans a real compositor commit; resumed WebKit may coalesce\n // nested animation frames before presenting either one.\n this.foregroundRefreshTimer = window.setTimeout(() => {\n this.foregroundRefreshTimer = null;\n this.el.classList.remove('shell-app--foreground-refresh');\n // WebKit otherwise may keep presenting the retired composite layer.\n void this.el.querySelector<HTMLElement>('.shell-app__main')?.offsetHeight;\n }, ShellApp.FOREGROUND_REFRESH_MS);\n }\n\n private cachePanelWidthTokens() {\n const navRoot = this.el.querySelector('ds-panel-nav .panel-nav') as HTMLElement | null;\n if (!navRoot) return;\n this.panelWidthTokens = readPanelNavWidthTokens(navRoot);\n }\n\n private applyGradientVars(target: HTMLElement, vars: Record<string, string | null>) {\n const style = target.style;\n for (const [name, value] of Object.entries(vars)) {\n if (value === null) style.removeProperty(name);\n else style.setProperty(name, value);\n }\n }\n\n private clearGradientPaintVars(targets: HTMLElement[]) {\n const keys = [SHELL_GRADIENT_IMAGE_VAR, SHELL_GRADIENT_SIZE_VAR, SHELL_GRADIENT_OPACITY_VAR];\n for (const target of targets) {\n for (const key of keys) target.style.removeProperty(key);\n }\n }\n\n /** Panel/bar wash offsets for scroll fades and badge rings. */\n private syncChromeLayoutVars(\n panel: HTMLElement | null,\n bar: HTMLElement | null,\n panelPosition: string,\n barPosition: string\n ) {\n if (panel) {\n panel.style.setProperty(SHELL_GRADIENT_POSITION_PANEL_VAR, panelPosition);\n }\n if (bar) {\n bar.style.setProperty(SHELL_GRADIENT_POSITION_BAR_VAR, barPosition);\n }\n }\n\n private resolvePanelWidthPx(panelNav: HTMLElement | null): number {\n const navRoot = panelNav?.querySelector('.panel-nav') as HTMLElement | null;\n\n if (this.panelNavTransition.isActive && this.panelWidthTokens.expandedPx > 0) {\n const collapsed = isPanelNavCollapsed(panelNav, navRoot);\n return panelWidthPxFromTokens(this.panelWidthTokens, collapsed);\n }\n\n const panelWrap = this.el.querySelector('.shell-app__panel') as HTMLElement | null;\n const measured = panelWrap?.getBoundingClientRect().width ?? 0;\n if (measured > 0) return measured;\n\n if (this.panelWidthTokens.expandedPx > 0) {\n const collapsed = isPanelNavCollapsed(panelNav, navRoot);\n return panelWidthPxFromTokens(this.panelWidthTokens, collapsed);\n }\n\n return 0;\n }\n\n /** Fixed-attachment wash/grid size — always the viewport, never the shell element box. */\n private resolveViewportDimensions(): { width: number; height: number } {\n if (this.panelNavTransition.isActive && this.cachedViewportWidth > 0) {\n return {\n width: this.cachedViewportWidth,\n height: this.cachedViewportHeight,\n };\n }\n\n return readShellViewportDimensions();\n }\n\n private chromeLayerActive(): boolean {\n return this.gradientLayerActive() || this.paperTextureLayerActive();\n }\n\n private gradientLayerActive(): boolean {\n return (\n this.resolvedMode !== 'mobile' && normalizeShellGradientPreset(this.gradientPreset) !== 'none'\n );\n }\n\n private paperTextureLayerActive(): boolean {\n return this.resolvedMode !== 'mobile' && this.paperTexture != null;\n }\n\n @Listen('keydown', { target: 'window', capture: true })\n handleWindowKeyDown(e: KeyboardEvent) {\n if (!this.shortcutsEnabled) return;\n if (isEditableShortcutTarget(e.target)) return;\n\n const action = resolveShellShortcut(e);\n if (!action) return;\n\n e.preventDefault();\n\n const panel = this.el.querySelector('ds-panel-nav') as HTMLDsPanelNavElement | null;\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & {\n closeDrawer?: () => Promise<void>;\n activateTool?: (id: PanelToolsToolId) => Promise<void>;\n })\n | null;\n\n if (action === 'toggle-panel-nav') {\n void panel?.toggleCollapsed();\n return;\n }\n\n if (action === 'close-panel-tools') {\n void tools?.closeDrawer?.();\n return;\n }\n\n if (action.startsWith('open-tool:') && tools) {\n const toolId = action.slice('open-tool:'.length) as PanelToolsToolId;\n void tools.activateTool?.(toolId);\n }\n }\n\n /** Open or toggle a managed global tool from application-owned UI. */\n @Method()\n async activateTool(id: PanelToolsToolId) {\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & { activateTool?: (tool: PanelToolsToolId) => Promise<void> })\n | null;\n await tools?.activateTool?.(id);\n }\n\n /** Close whichever managed global surface is currently visible. */\n @Method()\n async closeGlobalSurface() {\n if (this.managedMobileSheetNavOpen) {\n this.managedMobileSheetNavOpen = false;\n return;\n }\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & { closeDrawer?: () => Promise<void> })\n | null;\n await tools?.closeDrawer?.();\n }\n\n /** Request drawer or fullscreen presentation for the active managed tool. */\n @Method()\n async setToolPresentation(presentation: 'drawer' | 'fullscreen') {\n this.managedToolPresentation = presentation;\n this.toolsFullscreen = presentation === 'fullscreen';\n }\n\n private handleManagedNavSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsNavSelect.emit(event.detail);\n };\n\n private handleManagedNavChildSelect = (event: CustomEvent<PanelNavChildSelectDetail>) => {\n event.stopPropagation();\n this.dsNavChildSelect.emit(event.detail);\n };\n\n private handleManagedTabChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsTabChange.emit(event.detail);\n };\n\n private handleManagedPageBack = (event: CustomEvent<MouseEvent>) => {\n event.stopPropagation();\n this.dsPageBack.emit(event.detail);\n };\n\n private handleManagedBreadcrumbSelect = (event: CustomEvent<BreadcrumbSelectDetail>) => {\n event.stopPropagation();\n const forwarded = this.dsBreadcrumbSelect.emit(event.detail);\n if (forwarded.defaultPrevented) event.preventDefault();\n };\n\n private handleManagedSubsectionChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsSubsectionChange.emit(event.detail);\n };\n\n private handleManagedPageAction = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsPageAction.emit(event.detail);\n };\n\n private handleManagedToolChange = (\n event: CustomEvent<{ id: PanelToolsToolId; selected: boolean }>\n ) => {\n event.stopPropagation();\n const { id, selected } = event.detail;\n this.managedActiveTool = id;\n this.managedToolsOpen = selected;\n const item = this.resolvedToolItems.find(candidate => candidate.id === id);\n if (item && itemUsesShellInbox(item)) {\n this.managedInboxTool = id;\n }\n this.managedMobileDestination = selected\n ? (item?.mobileDestination ?? shellMobileDestinationForTool(true, id))\n : 'area';\n this.managedMobileSheetNavOpen = false;\n this.dsToolChange.emit(event.detail);\n };\n\n private handleManagedPresentationChange = (\n event: CustomEvent<{ presentation: 'drawer' | 'fullscreen' }>\n ) => {\n event.stopPropagation();\n this.managedToolPresentation = event.detail.presentation;\n this.toolsFullscreen = event.detail.presentation === 'fullscreen';\n this.dsPresentationChange.emit(event.detail);\n };\n\n private handleManagedHeaderBack = (event: CustomEvent<{ tool: PanelToolsToolId }>) => {\n event.stopPropagation();\n this.dsHeaderBack.emit(event.detail);\n };\n\n private handleManagedHeaderAction = (\n event: CustomEvent<{ tool: PanelToolsToolId; id: string }>\n ) => {\n event.stopPropagation();\n this.dsHeaderAction.emit(event.detail);\n };\n\n private handleManagedRailAccessoryAction = (\n event: CustomEvent<PanelToolsRailAccessoryActionDetail>\n ) => {\n event.stopPropagation();\n this.dsRailAccessoryAction.emit(event.detail);\n };\n\n private handleManagedSheetToggle = (event: CustomEvent<boolean>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = event.detail;\n };\n\n private activateManagedMobileTool(destination: Exclude<MobileDestination, 'area'>): boolean {\n const canonicalId =\n destination === 'inbox'\n ? resolveAvailableInboxTool(this.managedInboxTool, this.availableInboxTools)\n : destination;\n if (!canonicalId) return false;\n\n const item = this.resolvedToolItems.find(\n candidate =>\n candidate.id === canonicalId ||\n (destination !== 'inbox' && candidate.mobileDestination === destination)\n );\n if (!item || item.isInactive) return false;\n\n const id = item.id;\n this.managedActiveTool = id;\n this.managedToolsOpen = true;\n if (itemUsesShellInbox(item)) this.managedInboxTool = id;\n this.dsToolChange.emit({ id: id as PanelToolsToolId, selected: true });\n return true;\n }\n\n private handleManagedMobileDestination = (event: CustomEvent<MobileBarNavDestinationDetail>) => {\n event.stopPropagation();\n const destination = event.detail.destination;\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = destination;\n\n if (destination === 'area') {\n this.managedToolsOpen = false;\n return;\n }\n\n this.activateManagedMobileTool(destination);\n };\n\n private handleManagedAreaSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n if (event.detail === 'help') {\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'help';\n if (this.activateManagedMobileTool('help')) return;\n }\n\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'area';\n this.managedToolsOpen = false;\n this.dsNavSelect.emit(event.detail);\n };\n\n private handleManagedBrowseContext = (event: CustomEvent<NavChromeStyle>) => {\n event.stopPropagation();\n this.managedBrowseContext = event.detail;\n this.dsBrowseContextChange.emit(event.detail);\n };\n\n private handleManagedSheetClose = (event: CustomEvent<void>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = false;\n };\n\n private handleManagedNavFooterAction = (event: CustomEvent<void>) => {\n event.stopPropagation();\n this.dsNavFooterAction.emit();\n };\n\n private handleManagedNavUserAction = (event: CustomEvent<PanelNavUserActionDetail>) => {\n event.stopPropagation();\n this.dsNavUserAction.emit(event.detail);\n };\n\n private get currentArea(): PanelNavItem {\n const groups = [\n ...(this.navigation.dashboardGroups ?? []),\n ...(this.navigation.settingsGroups ?? []),\n ...this.resolvedNavigationGroups,\n ];\n const items = groups.flatMap(group => group.items);\n const activeId =\n this.navigation.activeId ||\n deriveActiveIdFromUrl(this.navigation.currentUrl ?? this.pageChrome.currentUrl ?? '', items);\n return (\n items.find(item => item.id === activeId) ??\n items[0] ?? {\n id: 'area',\n icon: 'MapPage',\n label: this.pageChrome.heading || 'Area',\n }\n );\n }\n\n private toolDot(id: PanelToolsToolId): boolean {\n return this.resolvedToolItems.find(item => item.id === id)?.dot ?? false;\n }\n\n private syncChrome() {\n const panelNav = this.el.querySelector('ds-panel-nav') as HTMLElement | null;\n const bar = this.el.querySelector(\n 'ds-bar-nav, ds-bar-title[placement=\"shell-bar\"]'\n ) as HTMLElement | null;\n const targets = [this.el, panelNav, bar].filter((el): el is HTMLElement => el !== null);\n\n const clearLayoutVars = () => {\n if (panelNav) panelNav.style.removeProperty(SHELL_GRADIENT_POSITION_PANEL_VAR);\n if (bar) bar.style.removeProperty(SHELL_GRADIENT_POSITION_BAR_VAR);\n };\n\n const preset = normalizeShellGradientPreset(this.gradientPreset);\n if (this.resolvedMode === 'mobile') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n return;\n }\n\n const viewport = this.resolveViewportDimensions();\n if (preset === 'none') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n this.syncPaperTextureLayout(viewport);\n return;\n }\n\n const panelWidth = this.resolvePanelWidthPx(panelNav);\n\n const panelPosition = shellGradientPositionPanel();\n const barPosition = shellGradientPositionBar(panelWidth);\n\n this.syncChromeLayoutVars(panelNav, bar, panelPosition, barPosition);\n\n const image = buildShellRadialGradient(preset);\n\n const size = shellGradientSize({\n width: viewport.width,\n height: viewport.height,\n });\n const opacity = shellGradientPresetOpacity(preset);\n\n for (const target of targets) {\n if (target === this.el) {\n this.applyGradientVars(target, {\n [SHELL_GRADIENT_IMAGE_VAR]: image,\n [SHELL_GRADIENT_SIZE_VAR]: size,\n [SHELL_GRADIENT_POSITION_PANEL_VAR]: panelPosition,\n [SHELL_GRADIENT_POSITION_BAR_VAR]: barPosition,\n [SHELL_GRADIENT_OPACITY_VAR]: opacity,\n });\n continue;\n }\n\n const isBar = target === bar;\n this.applyGradientVars(target, {\n [SHELL_GRADIENT_IMAGE_VAR]: image,\n [SHELL_GRADIENT_SIZE_VAR]: size,\n [SHELL_GRADIENT_OPACITY_VAR]: opacity,\n [SHELL_GRADIENT_POSITION_PANEL_VAR]: isBar ? null : panelPosition,\n [SHELL_GRADIENT_POSITION_BAR_VAR]: isBar ? barPosition : null,\n });\n }\n\n this.syncPaperTextureLayout(viewport);\n }\n\n private syncPaperTextureLayout(viewport: { width: number; height: number }) {\n const chrome = this.el.querySelector<HTMLElement>('.shell-app__chrome');\n const paperTexture = this.el.querySelector<HTMLElement>(\n '.shell-app__chrome > ds-paper-texture'\n );\n if (!chrome || !paperTexture) return;\n\n const chromeRect = chrome.getBoundingClientRect();\n paperTexture.style.setProperty('width', `${Math.round(viewport.width)}px`);\n paperTexture.style.setProperty('height', `${Math.round(viewport.height)}px`);\n paperTexture.style.setProperty('left', `${-Math.round(chromeRect.left)}px`);\n paperTexture.style.setProperty('top', `${-Math.round(chromeRect.top)}px`);\n }\n\n private renderPaperTexture() {\n if (!this.paperTextureLayerActive()) return null;\n return <ds-paper-texture class=\"shell-app__paper-texture\" config={this.paperTexture} />;\n }\n\n private renderManagedPanelNav() {\n const navigation = this.navigation;\n return (\n <ds-panel-nav\n navStyle={this.navStyle}\n presentation={this.sectionNavigation === 'panel' ? 'nested' : 'flat'}\n groups={this.resolvedNavigationGroups}\n breakpoint={SHELL_DESKTOP_BREAKPOINT}\n routerMode={navigation.routerMode ?? 'event'}\n activeId={navigation.activeId ?? ''}\n activeChildId={this.pageChrome.value ?? ''}\n currentUrl={navigation.currentUrl ?? this.pageChrome.currentUrl ?? ''}\n storageKey={navigation.storageKey ?? ''}\n userName={navigation.userName ?? ''}\n userInitial={navigation.userInitial ?? ''}\n accountMenuExpanded={navigation.accountMenuExpanded ?? false}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n dashboardNavigationLabel={navigation.dashboardNavigationLabel ?? 'Dashboard navigation'}\n settingsNavigationLabel={navigation.settingsNavigationLabel ?? 'Settings navigation'}\n navigationItemsLabel={navigation.navigationLabel ?? 'Navigation items'}\n onDsNavSelect={this.handleManagedNavSelect}\n onDsNavChildSelect={this.handleManagedNavChildSelect}\n onDsNavFooterAction={this.handleManagedNavFooterAction}\n onDsNavUserAction={this.handleManagedNavUserAction}\n />\n );\n }\n\n private renderManagedBarNav() {\n return (\n <ds-bar-nav\n navStyle={this.navStyle}\n tabs={this.resolvedPageTabs}\n value={this.pageChrome.value ?? ''}\n heading={this.pageChrome.routeHeading ?? this.pageChrome.heading}\n basePath={this.pageChrome.basePath ?? ''}\n currentUrl={this.pageChrome.currentUrl ?? this.navigation.currentUrl ?? ''}\n onDsTabChange={this.handleManagedTabChange}\n />\n );\n }\n\n private renderManagedTopBar() {\n return this.sectionNavigation === 'panel'\n ? this.renderManagedBarTitle('shell-bar')\n : this.renderManagedBarNav();\n }\n\n private renderManagedToolSlots() {\n const ids = [...new Set(this.resolvedToolItems.map(item => item.id))].sort();\n return ids.flatMap(id => [\n <div key={`tool:${id}`} class=\"shell-app__tool-slot-proxy\" slot={id}>\n <slot name={id} />\n </div>,\n <div key={`tool-view:${id}`} class=\"shell-app__tool-slot-proxy\" slot={`${id}-view`}>\n <slot name={`${id}-view`} />\n </div>,\n ]);\n }\n\n private renderManagedTools() {\n const tools = this.tools;\n return (\n <ds-shell-tools\n responsiveMode={this.resolvedMode}\n open={this.managedToolsOpen}\n activeTool={this.managedActiveTool}\n presentation={this.managedToolPresentation}\n fullscreenHeaderMode={tools.fullscreenHeaderMode ?? 'shared'}\n items={this.resolvedToolItems}\n accessories={tools.accessories ?? []}\n headers={tools.headers ?? {}}\n storageKey={tools.storageKey ?? ''}\n toolsLabel={tools.toolsLabel ?? 'Tools'}\n toolShortcutsLabel={tools.toolShortcutsLabel ?? 'Tool shortcuts'}\n inboxLabel={tools.inboxLabel ?? 'Inbox'}\n inboxNavigationLabel={tools.inboxNavigationLabel ?? 'Inbox sections'}\n onDsToolChange={this.handleManagedToolChange}\n onDsPresentationChange={this.handleManagedPresentationChange}\n onDsHeaderBack={this.handleManagedHeaderBack}\n onDsHeaderAction={this.handleManagedHeaderAction}\n onDsRailAccessoryAction={this.handleManagedRailAccessoryAction}\n >\n {this.renderManagedToolSlots()}\n </ds-shell-tools>\n );\n }\n\n private renderManagedMobileSheetNav() {\n const navigation = this.navigation;\n return (\n <ds-mobile-sheet-nav\n open={this.managedMobileSheetNavOpen}\n browseContext={this.managedBrowseContext}\n dashboardGroups={navigation.dashboardGroups ?? navigation.groups ?? []}\n settingsGroups={navigation.settingsGroups ?? []}\n currentUrl={navigation.currentUrl ?? this.pageChrome.currentUrl ?? ''}\n routeSelectionActive={this.managedMobileDestination === 'area'}\n navigationLabel={navigation.navigationLabel ?? 'Application navigation'}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n showAccount={navigation.showMobileAccount ?? true}\n onDsAreaSelect={this.handleManagedAreaSelect}\n onDsBrowseContextChange={this.handleManagedBrowseContext}\n onDsClose={this.handleManagedSheetClose}\n />\n );\n }\n\n private renderManagedMobileBarNav() {\n const item = (id: PanelToolsToolId) =>\n this.resolvedToolItems.find(candidate => candidate.id === id);\n const label = (id: PanelToolsToolId, fallback: string) =>\n item(id)?.ariaLabel ?? item(id)?.label ?? fallback;\n const directActivity = this.resolvedToolItems.some(\n candidate => !candidate.isInactive && candidate.mobileDestination === 'activity'\n );\n\n return (\n <ds-mobile-bar-nav\n hidden={this.tools.mobileBarHidden ?? false}\n activeDestination={this.managedMobileDestination}\n currentArea={this.currentArea}\n sheetNavExpanded={this.managedMobileSheetNavOpen}\n searchLabel={label('search', 'Search')}\n activityMode={directActivity ? 'direct' : 'inbox'}\n activityLabel={label('activity', 'Activity')}\n inboxLabel={this.tools.inboxLabel ?? 'Inbox'}\n messagesLabel={label('messages', 'Messages')}\n agentsLabel={label('agents', 'Agents')}\n helpLabel={label('help', 'Help & Support')}\n searchDot={this.toolDot('search')}\n activityDot={this.toolDot('activity')}\n inboxDot={this.toolDot('stacks') || this.toolDot('activity')}\n messagesDot={this.toolDot('messages')}\n agentsDot={this.toolDot('agents')}\n onDsSheetNavToggle={this.handleManagedSheetToggle}\n onDsDestinationChange={this.handleManagedMobileDestination}\n />\n );\n }\n\n private renderManagedMobileLeading() {\n if (!this.pageChrome.showBack) {\n return <slot name=\"page-header-leading\" slot=\"leading\" />;\n }\n return [\n <ds-tooltip\n slot=\"leading\"\n label={this.pageChrome.backAriaLabel ?? 'Back'}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n variant=\"icon\"\n icon={this.pageChrome.backIcon ?? 'ChevronLeft'}\n size=\"md\"\n aria-label={this.pageChrome.backAriaLabel ?? 'Back'}\n activeFill={false}\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.dsPageBack.emit(event.detail)}\n />\n </ds-tooltip>,\n <slot name=\"page-header-leading\" slot=\"leading\" />,\n ];\n }\n\n private renderManagedMobileActions() {\n const actions = this.pageChrome.mobileActions ?? [];\n return [\n ...actions.map((action: PanelToolsHeaderAction) => (\n <ds-tooltip\n slot=\"trailing\"\n key={action.id}\n label={action.ariaLabel}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n id={action.triggerId || undefined}\n variant=\"icon\"\n icon={action.icon}\n size=\"md\"\n aria-label={action.ariaLabel}\n haspopup={action.haspopup}\n controls={action.controls}\n expanded={action.expanded}\n pressed={action.pressed}\n isInactive={action.isInactive}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n </ds-tooltip>\n )),\n <slot name=\"page-header-trailing\" slot=\"trailing\" />,\n ];\n }\n\n private get barTitleSections(): BarTitleSectionItem[] {\n return (this.pageChrome.subsections ?? []).map(item =>\n 'type' in item\n ? { type: 'divider' }\n : {\n id: item.id,\n label: item.label,\n isInactive: item.isInactive,\n }\n );\n }\n\n private renderManagedBarTitle(placement: BarTitlePlacement) {\n const page = this.pageChrome;\n return (\n <ds-bar-title\n slot={placement === 'page' ? 'header' : undefined}\n placement={placement}\n variant={placement === 'shell-bar' ? 'compact' : 'expanded'}\n heading={page.heading ?? ''}\n description={page.description ?? ''}\n showBack={page.showBack ?? false}\n backAriaLabel={page.backAriaLabel ?? 'Back'}\n backLabel={page.backLabel ?? 'Back'}\n breadcrumbs={page.breadcrumbs ?? []}\n breadcrumbAriaLabel={page.breadcrumbAriaLabel ?? 'Breadcrumb'}\n sections={this.barTitleSections}\n value={page.subvalue ?? ''}\n sectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n primaryAction={page.primaryAction ?? null}\n actions={page.actions ?? []}\n actionsAriaLabel={page.actionsAriaLabel ?? 'More page actions'}\n showDivider={page.showHeaderDivider ?? true}\n showCompactDivider={page.showCompactHeaderDivider}\n onDsBack={this.handleManagedPageBack}\n onDsBreadcrumbSelect={this.handleManagedBreadcrumbSelect}\n onDsSectionChange={this.handleManagedSubsectionChange}\n onDsAction={this.handleManagedPageAction}\n />\n );\n }\n\n private renderManagedPage() {\n const page = this.pageChrome;\n const mobileSections = page.showBack ? [] : this.resolvedPageTabs;\n return (\n <ds-shell-page\n responsiveMode={this.resolvedMode}\n headerCapacity={resolveManagedShellPageCapacity(this.resolvedMode)}\n desktopHeaderPlacement={this.sectionNavigation === 'panel' ? 'shell-bar' : 'page'}\n contentInset={page.contentInset ?? 'default'}\n contentInsetBlockStart={page.contentInsetBlockStart ?? 'default'}\n contentInsetBlockStartSize={page.contentInsetBlockStartSize}\n compactContentInsetBlockStartSize={page.compactContentInsetBlockStartSize}\n scrollCompaction={page.scrollCompaction ?? true}\n contentSurface={page.contentSurface ?? 'primary'}\n >\n {this.sectionNavigation === 'bar' ? this.renderManagedBarTitle('page') : null}\n <ds-mobile-header\n slot=\"mobile-header\"\n heading={page.heading ?? ''}\n headingLevel={page.headingLevel ?? 'h1'}\n sections={mobileSections}\n value={page.showBack ? '' : (page.value ?? '')}\n sectionsAriaLabel={page.sectionsAriaLabel ?? 'Change page section'}\n subsections={page.subsections ?? []}\n subvalue={page.subvalue ?? ''}\n subsectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n tone={page.tone ?? 'default'}\n onDsSectionChange={this.handleManagedTabChange}\n onDsSubsectionChange={this.handleManagedSubsectionChange}\n >\n {this.renderManagedMobileLeading()}\n {this.renderManagedMobileActions()}\n </ds-mobile-header>\n <slot />\n </ds-shell-page>\n );\n }\n\n private renderSlottedShell(\n shellCls: Record<string, boolean>,\n mobile: boolean,\n mobileToolActive: boolean,\n mobileStageBlocked: boolean,\n fullscreen: boolean\n ) {\n return (\n <Host class={shellCls} responsive-mode={this.resolvedMode}>\n <div class=\"shell-app__banner\">\n <slot name=\"banner\" />\n </div>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\">\n {this.renderPaperTexture()}\n </div>\n <div\n class=\"shell-app__panel\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n <slot name=\"panel\" />\n </div>\n <div class=\"shell-app__main\">\n <div\n class=\"shell-app__bar\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n <slot name=\"bar\" />\n </div>\n <div\n class=\"shell-app__tools\"\n data-ds-overlay-boundary\n aria-hidden={\n mobile && (!mobileToolActive || this.mobileSheetNavOpen) ? 'true' : undefined\n }\n inert={mobile && (!mobileToolActive || this.mobileSheetNavOpen) ? true : undefined}\n hidden={mobile && !mobileToolActive}\n >\n <slot name=\"tools\" />\n </div>\n <div\n class=\"shell-app__content\"\n data-ds-overlay-boundary\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n tabIndex={0}\n >\n <slot />\n </div>\n <div\n class=\"shell-app__mobile-sheet-nav\"\n hidden={!mobile || !this.mobileSheetNavOpen}\n aria-hidden={mobile && this.mobileSheetNavOpen ? undefined : 'true'}\n inert={mobile && this.mobileSheetNavOpen ? undefined : true}\n >\n <slot name=\"mobile-sheet-nav\" />\n </div>\n <div class=\"shell-app__mobile-bar-nav\" hidden={!mobile}>\n <slot name=\"mobile-bar-nav\" />\n </div>\n </div>\n </div>\n </Host>\n );\n }\n\n render() {\n const chromeActive = this.chromeLayerActive();\n const gradientActive = this.gradientLayerActive();\n const paperTextureActive = this.paperTextureLayerActive();\n const mobile = this.resolvedMode === 'mobile';\n const mobileToolActive = mobile && this.effectiveMobileDestination !== 'area';\n const mobileStageBlocked = mobile && (mobileToolActive || this.effectiveMobileSheetNavOpen);\n const fullscreen = !mobile && this.toolsFullscreen;\n const shellCls: Record<string, boolean> = {\n 'shell-app': true,\n 'shell-app--chrome': chromeActive,\n 'shell-app--gradient': gradientActive,\n 'shell-app--paper-texture': paperTextureActive,\n [`shell-app--${this.navStyle}`]: true,\n [`shell-app--${this.resolvedMode}`]: true,\n 'shell-app--tools-fullscreen': fullscreen,\n 'shell-app--mobile-tool-active': mobileToolActive,\n 'shell-app--mobile-sheet-nav-open': mobile && this.effectiveMobileSheetNavOpen,\n 'shell-app--managed': this.managed,\n 'shell-app--slotted': !this.managed,\n };\n\n if (!this.managed) {\n return this.renderSlottedShell(\n shellCls,\n mobile,\n mobileToolActive,\n mobileStageBlocked,\n fullscreen\n );\n }\n\n return (\n <Host class={shellCls} responsive-mode={this.resolvedMode}>\n <div class=\"shell-app__banner\">\n <slot name=\"banner\" />\n </div>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\">\n {this.renderPaperTexture()}\n </div>\n <div\n class=\"shell-app__panel\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n {this.renderManagedPanelNav()}\n </div>\n <div class=\"shell-app__main\">\n <div\n class=\"shell-app__bar\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n {this.renderManagedTopBar()}\n </div>\n <div\n class=\"shell-app__tools\"\n data-ds-overlay-boundary\n aria-hidden={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen)\n ? 'true'\n : undefined\n }\n inert={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen) ? true : undefined\n }\n hidden={mobile && !mobileToolActive}\n >\n {this.renderManagedTools()}\n </div>\n <div\n class=\"shell-app__content\"\n data-ds-overlay-boundary\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n tabIndex={0}\n >\n {this.renderManagedPage()}\n </div>\n <div\n class=\"shell-app__mobile-sheet-nav\"\n hidden={!mobile || !this.effectiveMobileSheetNavOpen}\n aria-hidden={mobile && this.effectiveMobileSheetNavOpen ? undefined : 'true'}\n inert={mobile && this.effectiveMobileSheetNavOpen ? undefined : true}\n >\n {this.renderManagedMobileSheetNav()}\n </div>\n <div class=\"shell-app__mobile-bar-nav\" hidden={!mobile}>\n {this.renderManagedMobileBarNav()}\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
|
|
693
|
+
"content": "import {\n Component,\n Prop,\n Element,\n Event,\n EventEmitter,\n State,\n Watch,\n Listen,\n Method,\n h,\n Host,\n} from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport { isEditableShortcutTarget, resolveShellShortcut } from '../../shell/shell-shortcuts';\nimport type {\n PanelToolsHeaderAction,\n PanelToolsRailAccessoryActionDetail,\n PanelToolsToolId,\n} from '../PanelTools/panel-tools-types';\nimport { PANEL_TOOLS_DEFAULT_ITEMS } from '../PanelTools/panel-tools-types';\nimport type { BarTitleActionConfigItem, BarTitleSectionItem } from '../BarTitle/bar-title-types';\nimport type { BarTitlePlacement } from '../BarTitle/bar-title-types';\nimport {\n findBarTitleAction,\n overflowBarTitleActionSections,\n resolveBarTitleActionItems,\n visibleBarTitleActions,\n} from '../BarTitle/bar-title-actions';\nimport type { BarNavTab } from '../BarNav/bar-nav-types';\nimport type { BreadcrumbSelectDetail } from '../Breadcrumb/breadcrumb-types';\nimport type { MenuItemData } from '../Menu/menu-types';\nimport {\n DEFAULT_SHELL_GRADIENT_PRESET,\n normalizeShellGradientPreset,\n type ShellGradientPreset,\n} from '../../shell/shell-gradient-presets';\nimport {\n CHROME_TRANSITION_END,\n CHROME_TRANSITION_START,\n ChromeTransitionDepth,\n createRafCoalescer,\n readChromeTransitionSource,\n} from '../../shell/chrome-transition';\nimport {\n isPanelNavCollapsed,\n panelWidthPxFromTokens,\n readPanelNavWidthTokens,\n type PanelNavWidthTokens,\n} from '../../shell/shell-chrome-metrics';\nimport {\n SHELL_GRADIENT_IMAGE_VAR,\n SHELL_GRADIENT_OPACITY_VAR,\n SHELL_GRADIENT_POSITION_BAR_VAR,\n SHELL_GRADIENT_POSITION_PANEL_VAR,\n SHELL_GRADIENT_SIZE_VAR,\n buildShellRadialGradient,\n readShellViewportDimensions,\n shellGradientPositionBar,\n shellGradientPositionPanel,\n shellGradientPresetOpacity,\n shellGradientSize,\n} from '../../shell/shell-gradient';\nimport {\n itemUsesShellInbox,\n resolveAvailableInboxTool,\n resolveManagedShellPageCapacity,\n resolveShellResponsiveMode,\n SHELL_DESKTOP_BREAKPOINT,\n shellMobileDestinationForTool,\n type MobileDestination,\n type ShellInboxToolId,\n type ShellResponsiveMode,\n} from '../../shell/shell-responsive';\nimport { deriveActiveIdFromUrl } from '../PanelNav/panel-nav-utils';\nimport type { PanelNavChildSelectDetail } from '../PanelNav/panel-nav-types';\nimport type { PanelNavItem } from '../PanelNav/panel-nav-types';\nimport type { PanelNavUserActionDetail } from '../PanelNav/panel-nav-types';\nimport type { MobileBarNavDestinationDetail } from '../MobileBarNav/mobile-bar-nav-types';\nimport type {\n ShellAppComposition,\n ShellNavigationConfig,\n ShellPageChromeConfig,\n ShellSectionNavigation,\n ShellToolsConfig,\n} from './shell-app-types';\nimport type { PaperTextureConfig } from '../PaperTexture/paper-texture-types';\n\nlet nextShellAppId = 0;\ntype FocusableButton = HTMLElement & { setFocus?: () => Promise<void> };\n\n@Component({\n tag: 'ds-shell-app',\n styleUrl: 'ShellApp.css',\n scoped: true,\n})\nexport class ShellApp {\n private static readonly FOREGROUND_REFRESH_MS = 50;\n\n /** Managed renders the complete responsive chrome; slotted exposes the advanced composition. */\n @Prop({ reflect: true }) composition: ShellAppComposition = 'managed';\n\n /** Router-owned navigation data used by managed composition. */\n @Prop() navigation: ShellNavigationConfig = {};\n\n /** Desktop/tablet route sections in BarNav, or nested beneath PanelNav parents. */\n @Prop({ attribute: 'section-navigation', reflect: true })\n sectionNavigation: ShellSectionNavigation = 'bar';\n\n /** Route-owned page title and section data used by managed composition. */\n @Prop({ attribute: 'page-chrome' }) pageChrome: ShellPageChromeConfig = {};\n\n /** Product-owned global tool definitions used by managed composition. */\n @Prop() tools: ShellToolsConfig = {};\n\n /** Chrome style propagated to slotted `ds-panel-nav` and `ds-bar-nav`. */\n @Prop({ attribute: 'nav-style', reflect: true }) navStyle: NavChromeStyle = 'dashboard';\n\n /**\n * Shell chrome wash preset. `none` renders solid chrome; the remaining\n * presets use token-based washes that adapt to the active color theme.\n */\n @Prop({ attribute: 'gradient-preset', reflect: true }) gradientPreset: ShellGradientPreset =\n DEFAULT_SHELL_GRADIENT_PRESET;\n\n /** Optional decorative Paper Design texture layered above the shell wash. */\n @Prop({ attribute: 'paper-texture' }) paperTexture?: PaperTextureConfig;\n\n /** When `true` (default), registers global shell keyboard shortcuts. `[` toggles panel nav; `]` closes tools; K, A, S, M, N, and / toggle tool drawers. Modifiers are ignored so browser chords like ⌘N stay native. */\n @Prop({ attribute: 'shortcuts-enabled' }) shortcutsEnabled: boolean = true;\n\n /** Controlled mobile surface shown above the persistent bottom bar. */\n @Prop({ attribute: 'mobile-destination' })\n mobileDestination: MobileDestination = 'area';\n\n /** Controlled full-stage Mobile Sheet Nav state. */\n @Prop({ attribute: 'mobile-sheet-nav-open' })\n mobileSheetNavOpen: boolean = false;\n\n /** Emitted after crossing the fixed 768px or 1200px shell boundaries. */\n @Event() dsResponsiveModeChange!: EventEmitter<{ mode: ShellResponsiveMode }>;\n\n /** Managed primary-navigation intent; the application router performs navigation. */\n @Event() dsNavSelect!: EventEmitter<string>;\n\n /** Managed desktop BarNav or mobile section-selection intent. */\n @Event() dsTabChange!: EventEmitter<string>;\n\n /** Managed nested PanelNav child-route intent. */\n @Event() dsNavChildSelect!: EventEmitter<PanelNavChildSelectDetail>;\n\n /** Managed page-subsection intent from BarTitle or MobileHeader. */\n @Event() dsSubsectionChange!: EventEmitter<string>;\n\n /** Managed page-level Back intent. */\n @Event() dsPageBack!: EventEmitter<MouseEvent>;\n\n /** Managed breadcrumb intent from BarTitle. */\n @Event({ cancelable: true })\n dsBreadcrumbSelect!: EventEmitter<BreadcrumbSelectDetail>;\n\n /** Managed page action intent from BarTitle. */\n @Event() dsPageAction!: EventEmitter<string>;\n\n /** Managed tools selection state. */\n @Event() dsToolChange!: EventEmitter<{ id: PanelToolsToolId; selected: boolean }>;\n\n /** Managed tool-header Back intent. */\n @Event() dsHeaderBack!: EventEmitter<{ tool: PanelToolsToolId }>;\n\n /** Managed tool-header action intent. */\n @Event() dsHeaderAction!: EventEmitter<{ tool: PanelToolsToolId; id: string }>;\n\n /** Managed desktop/tablet rail accessory intent. */\n @Event() dsRailAccessoryAction!: EventEmitter<PanelToolsRailAccessoryActionDetail>;\n\n /** Managed drawer/fullscreen presentation state. */\n @Event() dsPresentationChange!: EventEmitter<{ presentation: 'drawer' | 'fullscreen' }>;\n\n /** Managed mobile Dashboard/Settings browsing intent. */\n @Event() dsBrowseContextChange!: EventEmitter<NavChromeStyle>;\n\n /** Managed account-footer action intent. */\n @Event() dsNavFooterAction!: EventEmitter<void>;\n\n /** Managed user/account trigger intent. */\n @Event() dsNavUserAction!: EventEmitter<PanelNavUserActionDetail>;\n\n @Element() el!: HTMLElement;\n @State() private toolsFullscreen = false;\n @State() private resolvedMode: ShellResponsiveMode = 'desktop';\n @State() private managedToolsOpen = false;\n @State() private managedActiveTool: PanelToolsToolId | '' = '';\n @State() private managedToolPresentation: 'drawer' | 'fullscreen' = 'drawer';\n @State() private managedMobileDestination: MobileDestination = 'area';\n @State() private managedMobileSheetNavOpen = false;\n @State() private managedInboxTool: ShellInboxToolId | '' = '';\n @State() private managedBrowseContext: NavChromeStyle = 'dashboard';\n @State() private mobileActionMenuOpen = false;\n @State() private mobileActionMenuInitialFocusVisible = false;\n\n private readonly instanceId = nextShellAppId++;\n private readonly mobileActionMenuTriggerId = `shell-app-mobile-action-trigger-${this.instanceId}`;\n private readonly mobileActionMenuId = `shell-app-mobile-action-menu-${this.instanceId}`;\n private mobileActionTriggerEl: FocusableButton | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private hasLoaded = false;\n private foregroundRefreshTimer: number | null = null;\n private readonly panelNavTransition = new ChromeTransitionDepth();\n private readonly chromeSyncCoalescer = createRafCoalescer(() => this.syncChrome());\n private panelWidthTokens: PanelNavWidthTokens = { expandedPx: 0, collapsedPx: 0 };\n private cachedViewportWidth = 0;\n private cachedViewportHeight = 0;\n private onWindowResize = () => {\n this.updateResponsiveMode();\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n private onVisualViewportChange = () => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n\n private get managed(): boolean {\n return this.composition === 'managed';\n }\n\n private get effectiveMobileDestination(): MobileDestination {\n return this.managed ? this.managedMobileDestination : this.mobileDestination;\n }\n\n private get effectiveMobileSheetNavOpen(): boolean {\n return this.managed ? this.managedMobileSheetNavOpen : this.mobileSheetNavOpen;\n }\n\n private get resolvedNavigationGroups() {\n if (this.navigation.groups?.length) return this.navigation.groups;\n return this.managedBrowseContext === 'settings'\n ? (this.navigation.settingsGroups ?? [])\n : (this.navigation.dashboardGroups ?? []);\n }\n\n private get resolvedToolItems() {\n return this.tools.items ?? PANEL_TOOLS_DEFAULT_ITEMS;\n }\n\n private get resolvedPageTabs(): BarNavTab[] {\n const children = this.currentArea.children;\n if (!children?.length) return this.pageChrome.tabs ?? [];\n return children.map(child => ({\n id: child.id,\n label: child.label,\n dot: child.dot,\n isInactive: child.isInactive,\n }));\n }\n\n private get availableInboxTools(): ShellInboxToolId[] {\n return this.resolvedToolItems\n .filter(item => !item.isInactive && itemUsesShellInbox(item))\n .map(item => item.id as ShellInboxToolId);\n }\n\n componentWillLoad() {\n if (typeof window !== 'undefined') {\n this.resolvedMode = resolveShellResponsiveMode(window.innerWidth);\n }\n this.managedBrowseContext = this.navigation.browseContext ?? this.navStyle;\n this.managedMobileDestination = this.mobileDestination;\n this.managedMobileSheetNavOpen = this.mobileSheetNavOpen;\n this.managedInboxTool = resolveAvailableInboxTool('', this.availableInboxTools);\n }\n\n componentDidLoad() {\n this.hasLoaded = true;\n this.syncSlottedNavStyle();\n this.syncSlottedMobileState();\n this.connectMetricsObserver();\n this.connectViewportListeners();\n this.connectPageLifecycleListeners();\n this.el.addEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);\n this.el.addEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);\n requestAnimationFrame(() => {\n this.cachePanelWidthTokens();\n this.scheduleChromeSync();\n });\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools');\n this.toolsFullscreen = tools?.getAttribute('presentation') === 'fullscreen';\n }\n\n @Listen('dsPresentationChange')\n handleToolsPresentation(event: CustomEvent<{ presentation?: string }>) {\n this.toolsFullscreen = event.detail?.presentation === 'fullscreen';\n }\n\n disconnectedCallback() {\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.disconnectViewportListeners();\n this.disconnectPageLifecycleListeners();\n this.cancelForegroundRefresh();\n this.chromeSyncCoalescer.cancel();\n this.el.removeEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);\n this.el.removeEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);\n }\n\n @Watch('navStyle')\n @Watch('gradientPreset')\n @Watch('paperTexture')\n @Watch('sectionNavigation')\n onShellPropsChange() {\n this.syncSlottedNavStyle();\n this.scheduleChromeSync();\n }\n\n @Watch('navigation')\n handleNavigationChange() {\n this.managedBrowseContext = this.navigation.browseContext ?? this.navStyle;\n }\n\n @Watch('pageChrome')\n handlePageChromeChange() {\n if (this.mobileActionMenuSections.length === 0) this.mobileActionMenuOpen = false;\n }\n\n @Watch('tools')\n handleToolsChange() {\n if (\n this.managedActiveTool &&\n !this.resolvedToolItems.some(item => item.id === this.managedActiveTool)\n ) {\n this.managedToolsOpen = false;\n this.managedActiveTool = '';\n this.managedMobileDestination = 'area';\n }\n this.managedInboxTool = resolveAvailableInboxTool(\n this.managedInboxTool,\n this.availableInboxTools\n );\n }\n\n @Watch('mobileDestination')\n @Watch('mobileSheetNavOpen')\n onMobileStateChange(_next: unknown, previous: unknown) {\n if (this.managed) {\n this.managedMobileDestination = this.mobileDestination;\n this.managedMobileSheetNavOpen = this.mobileSheetNavOpen;\n }\n this.syncSlottedMobileState();\n if (previous === true && !this.effectiveMobileSheetNavOpen && this.resolvedMode === 'mobile') {\n requestAnimationFrame(() => {\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n focusDestination?: (destination: MobileDestination | 'sheet-nav') => Promise<void>;\n })\n | null;\n const activeElement = document.activeElement;\n if (activeElement instanceof HTMLElement && bar?.contains(activeElement)) return;\n void bar?.focusDestination?.('sheet-nav');\n });\n }\n }\n\n private updateResponsiveMode() {\n if (typeof window === 'undefined') return;\n const next = resolveShellResponsiveMode(window.innerWidth);\n if (next === this.resolvedMode) return;\n this.resolvedMode = next;\n this.syncSlottedMobileState();\n this.scheduleChromeSync();\n if (this.hasLoaded) this.dsResponsiveModeChange.emit({ mode: next });\n }\n\n private onChromeTransitionStart = (event: Event) => {\n const source = readChromeTransitionSource(event);\n if (source !== 'panel-nav') return;\n\n this.panelNavTransition.enter();\n const viewport = readShellViewportDimensions();\n this.cachedViewportWidth = viewport.width;\n this.cachedViewportHeight = viewport.height;\n this.syncChrome();\n };\n\n private onChromeTransitionEnd = (event: Event) => {\n const source = readChromeTransitionSource(event);\n if (source !== 'panel-nav') return;\n\n this.panelNavTransition.exit();\n if (!this.panelNavTransition.isActive) {\n this.scheduleChromeSync();\n }\n };\n\n /** Coalesce ResizeObserver bursts to one layout read per frame. */\n private scheduleChromeSync() {\n this.chromeSyncCoalescer.schedule();\n }\n\n private syncSlottedNavStyle() {\n const panel = this.el.querySelector('ds-panel-nav') as\n | (HTMLElement & { navStyle: NavChromeStyle })\n | null;\n const bar = this.el.querySelector('ds-bar-nav') as\n | (HTMLElement & { navStyle: NavChromeStyle })\n | null;\n if (panel) {\n panel.setAttribute('nav-style', this.navStyle);\n panel.navStyle = this.navStyle;\n }\n if (bar) {\n bar.setAttribute('nav-style', this.navStyle);\n bar.navStyle = this.navStyle;\n }\n }\n\n private syncSlottedMobileState() {\n const tools = this.el.querySelector('ds-shell-tools') as\n | (HTMLElement & { responsiveMode: ShellResponsiveMode })\n | null;\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n activeDestination: MobileDestination;\n sheetNavExpanded: boolean;\n })\n | null;\n const sheetNav = this.el.querySelector('ds-mobile-sheet-nav') as\n | (HTMLElement & { open: boolean })\n | null;\n\n if (tools) {\n tools.setAttribute('responsive-mode', this.resolvedMode);\n tools.responsiveMode = this.resolvedMode;\n }\n if (bar) {\n bar.activeDestination = this.effectiveMobileDestination;\n bar.sheetNavExpanded = this.effectiveMobileSheetNavOpen;\n }\n if (sheetNav) {\n sheetNav.open = this.effectiveMobileSheetNavOpen;\n }\n }\n\n private connectMetricsObserver() {\n this.resizeObserver = new ResizeObserver(() => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n });\n this.resizeObserver.observe(this.el);\n const panelWrap = this.el.querySelector('.shell-app__panel');\n if (panelWrap) this.resizeObserver.observe(panelWrap);\n }\n\n private connectViewportListeners() {\n if (typeof window === 'undefined') return;\n\n window.addEventListener('resize', this.onWindowResize, { passive: true });\n\n const visual = window.visualViewport;\n if (visual) {\n visual.addEventListener('resize', this.onVisualViewportChange, { passive: true });\n visual.addEventListener('scroll', this.onVisualViewportChange, { passive: true });\n }\n }\n\n private disconnectViewportListeners() {\n if (typeof window === 'undefined') return;\n\n window.removeEventListener('resize', this.onWindowResize);\n\n const visual = window.visualViewport;\n if (visual) {\n visual.removeEventListener('resize', this.onVisualViewportChange);\n visual.removeEventListener('scroll', this.onVisualViewportChange);\n }\n }\n\n private connectPageLifecycleListeners() {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);\n window.addEventListener('pageshow', this.onPageShow);\n }\n\n private disconnectPageLifecycleListeners() {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);\n window.removeEventListener('pageshow', this.onPageShow);\n }\n\n private onDocumentVisibilityChange = () => {\n if (!document.hidden) this.refreshAfterForegroundRestore();\n };\n\n private onPageShow = (event: PageTransitionEvent) => {\n if (event.persisted) this.refreshAfterForegroundRestore();\n };\n\n private cancelForegroundRefresh() {\n if (this.foregroundRefreshTimer !== null) {\n window.clearTimeout(this.foregroundRefreshTimer);\n this.foregroundRefreshTimer = null;\n }\n this.el.classList.remove('shell-app--foreground-refresh');\n }\n\n private refreshAfterForegroundRestore() {\n this.updateResponsiveMode();\n this.syncSlottedMobileState();\n this.scheduleChromeSync();\n\n if (this.resolvedMode !== 'mobile') return;\n\n /*\n * Mobile WebKit can restore a live, correctly sized DOM without repainting\n * its suspended shell layer. Re-promote the complete mobile stage for one\n * painted frame; routed and tool owners remain mounted and keep their state.\n */\n this.cancelForegroundRefresh();\n this.el.classList.add('shell-app--foreground-refresh');\n void this.el.offsetHeight;\n // A short timer spans a real compositor commit; resumed WebKit may coalesce\n // nested animation frames before presenting either one.\n this.foregroundRefreshTimer = window.setTimeout(() => {\n this.foregroundRefreshTimer = null;\n this.el.classList.remove('shell-app--foreground-refresh');\n // WebKit otherwise may keep presenting the retired composite layer.\n void this.el.querySelector<HTMLElement>('.shell-app__main')?.offsetHeight;\n }, ShellApp.FOREGROUND_REFRESH_MS);\n }\n\n private cachePanelWidthTokens() {\n const navRoot = this.el.querySelector('ds-panel-nav .panel-nav') as HTMLElement | null;\n if (!navRoot) return;\n this.panelWidthTokens = readPanelNavWidthTokens(navRoot);\n }\n\n private applyGradientVars(target: HTMLElement, vars: Record<string, string | null>) {\n const style = target.style;\n for (const [name, value] of Object.entries(vars)) {\n if (value === null) style.removeProperty(name);\n else style.setProperty(name, value);\n }\n }\n\n private clearGradientPaintVars(targets: HTMLElement[]) {\n const keys = [SHELL_GRADIENT_IMAGE_VAR, SHELL_GRADIENT_SIZE_VAR, SHELL_GRADIENT_OPACITY_VAR];\n for (const target of targets) {\n for (const key of keys) target.style.removeProperty(key);\n }\n }\n\n /** Panel/bar wash offsets for scroll fades and badge rings. */\n private syncChromeLayoutVars(\n panel: HTMLElement | null,\n bar: HTMLElement | null,\n panelPosition: string,\n barPosition: string\n ) {\n if (panel) {\n panel.style.setProperty(SHELL_GRADIENT_POSITION_PANEL_VAR, panelPosition);\n }\n if (bar) {\n bar.style.setProperty(SHELL_GRADIENT_POSITION_BAR_VAR, barPosition);\n }\n }\n\n private resolvePanelWidthPx(panelNav: HTMLElement | null): number {\n const navRoot = panelNav?.querySelector('.panel-nav') as HTMLElement | null;\n\n if (this.panelNavTransition.isActive && this.panelWidthTokens.expandedPx > 0) {\n const collapsed = isPanelNavCollapsed(panelNav, navRoot);\n return panelWidthPxFromTokens(this.panelWidthTokens, collapsed);\n }\n\n const panelWrap = this.el.querySelector('.shell-app__panel') as HTMLElement | null;\n const measured = panelWrap?.getBoundingClientRect().width ?? 0;\n if (measured > 0) return measured;\n\n if (this.panelWidthTokens.expandedPx > 0) {\n const collapsed = isPanelNavCollapsed(panelNav, navRoot);\n return panelWidthPxFromTokens(this.panelWidthTokens, collapsed);\n }\n\n return 0;\n }\n\n /** Fixed-attachment wash/grid size — always the viewport, never the shell element box. */\n private resolveViewportDimensions(): { width: number; height: number } {\n if (this.panelNavTransition.isActive && this.cachedViewportWidth > 0) {\n return {\n width: this.cachedViewportWidth,\n height: this.cachedViewportHeight,\n };\n }\n\n return readShellViewportDimensions();\n }\n\n private chromeLayerActive(): boolean {\n return this.gradientLayerActive() || this.paperTextureLayerActive();\n }\n\n private gradientLayerActive(): boolean {\n return (\n this.resolvedMode !== 'mobile' && normalizeShellGradientPreset(this.gradientPreset) !== 'none'\n );\n }\n\n private paperTextureLayerActive(): boolean {\n return this.resolvedMode !== 'mobile' && this.paperTexture != null;\n }\n\n @Listen('keydown', { target: 'window', capture: true })\n handleWindowKeyDown(e: KeyboardEvent) {\n if (!this.shortcutsEnabled) return;\n if (isEditableShortcutTarget(e.target)) return;\n\n const action = resolveShellShortcut(e);\n if (!action) return;\n\n e.preventDefault();\n\n const panel = this.el.querySelector('ds-panel-nav') as HTMLDsPanelNavElement | null;\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & {\n closeDrawer?: () => Promise<void>;\n activateTool?: (id: PanelToolsToolId) => Promise<void>;\n })\n | null;\n\n if (action === 'toggle-panel-nav') {\n void panel?.toggleCollapsed();\n return;\n }\n\n if (action === 'close-panel-tools') {\n void tools?.closeDrawer?.();\n return;\n }\n\n if (action.startsWith('open-tool:') && tools) {\n const toolId = action.slice('open-tool:'.length) as PanelToolsToolId;\n void tools.activateTool?.(toolId);\n }\n }\n\n /** Open or toggle a managed global tool from application-owned UI. */\n @Method()\n async activateTool(id: PanelToolsToolId) {\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & { activateTool?: (tool: PanelToolsToolId) => Promise<void> })\n | null;\n await tools?.activateTool?.(id);\n }\n\n /** Close whichever managed global surface is currently visible. */\n @Method()\n async closeGlobalSurface() {\n if (this.managedMobileSheetNavOpen) {\n this.managedMobileSheetNavOpen = false;\n return;\n }\n const tools = this.el.querySelector('ds-shell-tools, ds-panel-tools') as\n | (HTMLElement & { closeDrawer?: () => Promise<void> })\n | null;\n await tools?.closeDrawer?.();\n }\n\n /** Request drawer or fullscreen presentation for the active managed tool. */\n @Method()\n async setToolPresentation(presentation: 'drawer' | 'fullscreen') {\n this.managedToolPresentation = presentation;\n this.toolsFullscreen = presentation === 'fullscreen';\n }\n\n private handleManagedNavSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsNavSelect.emit(event.detail);\n };\n\n private handleManagedNavChildSelect = (event: CustomEvent<PanelNavChildSelectDetail>) => {\n event.stopPropagation();\n this.dsNavChildSelect.emit(event.detail);\n };\n\n private handleManagedTabChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsTabChange.emit(event.detail);\n };\n\n private handleManagedPageBack = (event: CustomEvent<MouseEvent>) => {\n event.stopPropagation();\n this.dsPageBack.emit(event.detail);\n };\n\n private handleManagedBreadcrumbSelect = (event: CustomEvent<BreadcrumbSelectDetail>) => {\n event.stopPropagation();\n const forwarded = this.dsBreadcrumbSelect.emit(event.detail);\n if (forwarded.defaultPrevented) event.preventDefault();\n };\n\n private handleManagedSubsectionChange = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsSubsectionChange.emit(event.detail);\n };\n\n private handleManagedPageAction = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.dsPageAction.emit(event.detail);\n };\n\n private handleManagedToolChange = (\n event: CustomEvent<{ id: PanelToolsToolId; selected: boolean }>\n ) => {\n event.stopPropagation();\n const { id, selected } = event.detail;\n this.managedActiveTool = id;\n this.managedToolsOpen = selected;\n const item = this.resolvedToolItems.find(candidate => candidate.id === id);\n if (item && itemUsesShellInbox(item)) {\n this.managedInboxTool = id;\n }\n this.managedMobileDestination = selected\n ? (item?.mobileDestination ?? shellMobileDestinationForTool(true, id))\n : 'area';\n this.managedMobileSheetNavOpen = false;\n this.dsToolChange.emit(event.detail);\n };\n\n private handleManagedPresentationChange = (\n event: CustomEvent<{ presentation: 'drawer' | 'fullscreen' }>\n ) => {\n event.stopPropagation();\n this.managedToolPresentation = event.detail.presentation;\n this.toolsFullscreen = event.detail.presentation === 'fullscreen';\n this.dsPresentationChange.emit(event.detail);\n };\n\n private handleManagedHeaderBack = (event: CustomEvent<{ tool: PanelToolsToolId }>) => {\n event.stopPropagation();\n this.dsHeaderBack.emit(event.detail);\n };\n\n private handleManagedHeaderAction = (\n event: CustomEvent<{ tool: PanelToolsToolId; id: string }>\n ) => {\n event.stopPropagation();\n this.dsHeaderAction.emit(event.detail);\n };\n\n private handleManagedRailAccessoryAction = (\n event: CustomEvent<PanelToolsRailAccessoryActionDetail>\n ) => {\n event.stopPropagation();\n this.dsRailAccessoryAction.emit(event.detail);\n };\n\n private handleManagedSheetToggle = (event: CustomEvent<boolean>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = event.detail;\n };\n\n private activateManagedMobileTool(destination: Exclude<MobileDestination, 'area'>): boolean {\n const canonicalId =\n destination === 'inbox'\n ? resolveAvailableInboxTool(this.managedInboxTool, this.availableInboxTools)\n : destination;\n if (!canonicalId) return false;\n\n const item = this.resolvedToolItems.find(\n candidate =>\n candidate.id === canonicalId ||\n (destination !== 'inbox' && candidate.mobileDestination === destination)\n );\n if (!item || item.isInactive) return false;\n\n const id = item.id;\n this.managedActiveTool = id;\n this.managedToolsOpen = true;\n if (itemUsesShellInbox(item)) this.managedInboxTool = id;\n this.dsToolChange.emit({ id: id as PanelToolsToolId, selected: true });\n return true;\n }\n\n private handleManagedMobileDestination = (event: CustomEvent<MobileBarNavDestinationDetail>) => {\n event.stopPropagation();\n const destination = event.detail.destination;\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = destination;\n\n if (destination === 'area') {\n this.managedToolsOpen = false;\n return;\n }\n\n this.activateManagedMobileTool(destination);\n };\n\n private handleManagedAreaSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n if (event.detail === 'help') {\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'help';\n if (this.activateManagedMobileTool('help')) return;\n }\n\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'area';\n this.managedToolsOpen = false;\n this.dsNavSelect.emit(event.detail);\n };\n\n private handleManagedBrowseContext = (event: CustomEvent<NavChromeStyle>) => {\n event.stopPropagation();\n this.managedBrowseContext = event.detail;\n this.dsBrowseContextChange.emit(event.detail);\n };\n\n private handleManagedSheetClose = (event: CustomEvent<void>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = false;\n };\n\n private handleManagedNavFooterAction = (event: CustomEvent<void>) => {\n event.stopPropagation();\n this.dsNavFooterAction.emit();\n };\n\n private handleManagedNavUserAction = (event: CustomEvent<PanelNavUserActionDetail>) => {\n event.stopPropagation();\n this.dsNavUserAction.emit(event.detail);\n };\n\n private get currentArea(): PanelNavItem {\n const groups = [\n ...(this.navigation.dashboardGroups ?? []),\n ...(this.navigation.settingsGroups ?? []),\n ...this.resolvedNavigationGroups,\n ];\n const items = groups.flatMap(group => group.items);\n const activeId =\n this.navigation.activeId ||\n deriveActiveIdFromUrl(this.navigation.currentUrl ?? this.pageChrome.currentUrl ?? '', items);\n return (\n items.find(item => item.id === activeId) ??\n items[0] ?? {\n id: 'area',\n icon: 'MapPage',\n label: this.pageChrome.heading || 'Area',\n }\n );\n }\n\n private toolDot(id: PanelToolsToolId): boolean {\n return this.resolvedToolItems.find(item => item.id === id)?.dot ?? false;\n }\n\n private syncChrome() {\n const panelNav = this.el.querySelector('ds-panel-nav') as HTMLElement | null;\n const bar = this.el.querySelector(\n 'ds-bar-nav, ds-bar-title[placement=\"shell-bar\"]'\n ) as HTMLElement | null;\n const targets = [this.el, panelNav, bar].filter((el): el is HTMLElement => el !== null);\n\n const clearLayoutVars = () => {\n if (panelNav) panelNav.style.removeProperty(SHELL_GRADIENT_POSITION_PANEL_VAR);\n if (bar) bar.style.removeProperty(SHELL_GRADIENT_POSITION_BAR_VAR);\n };\n\n const preset = normalizeShellGradientPreset(this.gradientPreset);\n if (this.resolvedMode === 'mobile') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n return;\n }\n\n const viewport = this.resolveViewportDimensions();\n if (preset === 'none') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n this.syncPaperTextureLayout(viewport);\n return;\n }\n\n const panelWidth = this.resolvePanelWidthPx(panelNav);\n\n const panelPosition = shellGradientPositionPanel();\n const barPosition = shellGradientPositionBar(panelWidth);\n\n this.syncChromeLayoutVars(panelNav, bar, panelPosition, barPosition);\n\n const image = buildShellRadialGradient(preset);\n\n const size = shellGradientSize({\n width: viewport.width,\n height: viewport.height,\n });\n const opacity = shellGradientPresetOpacity(preset);\n\n for (const target of targets) {\n if (target === this.el) {\n this.applyGradientVars(target, {\n [SHELL_GRADIENT_IMAGE_VAR]: image,\n [SHELL_GRADIENT_SIZE_VAR]: size,\n [SHELL_GRADIENT_POSITION_PANEL_VAR]: panelPosition,\n [SHELL_GRADIENT_POSITION_BAR_VAR]: barPosition,\n [SHELL_GRADIENT_OPACITY_VAR]: opacity,\n });\n continue;\n }\n\n const isBar = target === bar;\n this.applyGradientVars(target, {\n [SHELL_GRADIENT_IMAGE_VAR]: image,\n [SHELL_GRADIENT_SIZE_VAR]: size,\n [SHELL_GRADIENT_OPACITY_VAR]: opacity,\n [SHELL_GRADIENT_POSITION_PANEL_VAR]: isBar ? null : panelPosition,\n [SHELL_GRADIENT_POSITION_BAR_VAR]: isBar ? barPosition : null,\n });\n }\n\n this.syncPaperTextureLayout(viewport);\n }\n\n private syncPaperTextureLayout(viewport: { width: number; height: number }) {\n const chrome = this.el.querySelector<HTMLElement>('.shell-app__chrome');\n const paperTexture = this.el.querySelector<HTMLElement>(\n '.shell-app__chrome > ds-paper-texture'\n );\n if (!chrome || !paperTexture) return;\n\n const chromeRect = chrome.getBoundingClientRect();\n paperTexture.style.setProperty('width', `${Math.round(viewport.width)}px`);\n paperTexture.style.setProperty('height', `${Math.round(viewport.height)}px`);\n paperTexture.style.setProperty('left', `${-Math.round(chromeRect.left)}px`);\n paperTexture.style.setProperty('top', `${-Math.round(chromeRect.top)}px`);\n }\n\n private renderPaperTexture() {\n if (!this.paperTextureLayerActive()) return null;\n return <ds-paper-texture class=\"shell-app__paper-texture\" config={this.paperTexture} />;\n }\n\n private renderManagedPanelNav() {\n const navigation = this.navigation;\n return (\n <ds-panel-nav\n navStyle={this.navStyle}\n presentation={this.sectionNavigation === 'panel' ? 'nested' : 'flat'}\n groups={this.resolvedNavigationGroups}\n breakpoint={SHELL_DESKTOP_BREAKPOINT}\n routerMode={navigation.routerMode ?? 'event'}\n activeId={navigation.activeId ?? ''}\n activeChildId={this.pageChrome.value ?? ''}\n currentUrl={navigation.currentUrl ?? this.pageChrome.currentUrl ?? ''}\n storageKey={navigation.storageKey ?? ''}\n userName={navigation.userName ?? ''}\n userInitial={navigation.userInitial ?? ''}\n accountMenuExpanded={navigation.accountMenuExpanded ?? false}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n dashboardNavigationLabel={navigation.dashboardNavigationLabel ?? 'Dashboard navigation'}\n settingsNavigationLabel={navigation.settingsNavigationLabel ?? 'Settings navigation'}\n navigationItemsLabel={navigation.navigationLabel ?? 'Navigation items'}\n onDsNavSelect={this.handleManagedNavSelect}\n onDsNavChildSelect={this.handleManagedNavChildSelect}\n onDsNavFooterAction={this.handleManagedNavFooterAction}\n onDsNavUserAction={this.handleManagedNavUserAction}\n />\n );\n }\n\n private renderManagedBarNav() {\n return (\n <ds-bar-nav\n navStyle={this.navStyle}\n tabs={this.resolvedPageTabs}\n value={this.pageChrome.value ?? ''}\n heading={this.pageChrome.routeHeading ?? this.pageChrome.heading}\n basePath={this.pageChrome.basePath ?? ''}\n currentUrl={this.pageChrome.currentUrl ?? this.navigation.currentUrl ?? ''}\n onDsTabChange={this.handleManagedTabChange}\n />\n );\n }\n\n private renderManagedTopBar() {\n return this.sectionNavigation === 'panel'\n ? this.renderManagedBarTitle('shell-bar')\n : this.renderManagedBarNav();\n }\n\n private renderManagedToolSlots() {\n const ids = [...new Set(this.resolvedToolItems.map(item => item.id))].sort();\n return ids.flatMap(id => [\n <div key={`tool:${id}`} class=\"shell-app__tool-slot-proxy\" slot={id}>\n <slot name={id} />\n </div>,\n <div key={`tool-view:${id}`} class=\"shell-app__tool-slot-proxy\" slot={`${id}-view`}>\n <slot name={`${id}-view`} />\n </div>,\n ]);\n }\n\n private renderManagedTools() {\n const tools = this.tools;\n return (\n <ds-shell-tools\n responsiveMode={this.resolvedMode}\n open={this.managedToolsOpen}\n activeTool={this.managedActiveTool}\n presentation={this.managedToolPresentation}\n fullscreenHeaderMode={tools.fullscreenHeaderMode ?? 'shared'}\n items={this.resolvedToolItems}\n accessories={tools.accessories ?? []}\n headers={tools.headers ?? {}}\n storageKey={tools.storageKey ?? ''}\n toolsLabel={tools.toolsLabel ?? 'Tools'}\n toolShortcutsLabel={tools.toolShortcutsLabel ?? 'Tool shortcuts'}\n inboxLabel={tools.inboxLabel ?? 'Inbox'}\n inboxNavigationLabel={tools.inboxNavigationLabel ?? 'Inbox sections'}\n onDsToolChange={this.handleManagedToolChange}\n onDsPresentationChange={this.handleManagedPresentationChange}\n onDsHeaderBack={this.handleManagedHeaderBack}\n onDsHeaderAction={this.handleManagedHeaderAction}\n onDsRailAccessoryAction={this.handleManagedRailAccessoryAction}\n >\n {this.renderManagedToolSlots()}\n </ds-shell-tools>\n );\n }\n\n private renderManagedMobileSheetNav() {\n const navigation = this.navigation;\n return (\n <ds-mobile-sheet-nav\n open={this.managedMobileSheetNavOpen}\n browseContext={this.managedBrowseContext}\n dashboardGroups={navigation.dashboardGroups ?? navigation.groups ?? []}\n settingsGroups={navigation.settingsGroups ?? []}\n currentUrl={navigation.currentUrl ?? this.pageChrome.currentUrl ?? ''}\n routeSelectionActive={this.managedMobileDestination === 'area'}\n navigationLabel={navigation.navigationLabel ?? 'Application navigation'}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n showAccount={navigation.showMobileAccount ?? true}\n onDsAreaSelect={this.handleManagedAreaSelect}\n onDsBrowseContextChange={this.handleManagedBrowseContext}\n onDsClose={this.handleManagedSheetClose}\n />\n );\n }\n\n private renderManagedMobileBarNav() {\n const item = (id: PanelToolsToolId) =>\n this.resolvedToolItems.find(candidate => candidate.id === id);\n const label = (id: PanelToolsToolId, fallback: string) =>\n item(id)?.ariaLabel ?? item(id)?.label ?? fallback;\n const directActivity = this.resolvedToolItems.some(\n candidate => !candidate.isInactive && candidate.mobileDestination === 'activity'\n );\n\n return (\n <ds-mobile-bar-nav\n hidden={this.tools.mobileBarHidden ?? false}\n activeDestination={this.managedMobileDestination}\n currentArea={this.currentArea}\n sheetNavExpanded={this.managedMobileSheetNavOpen}\n searchLabel={label('search', 'Search')}\n activityMode={directActivity ? 'direct' : 'inbox'}\n activityLabel={label('activity', 'Activity')}\n inboxLabel={this.tools.inboxLabel ?? 'Inbox'}\n messagesLabel={label('messages', 'Messages')}\n agentsLabel={label('agents', 'Agents')}\n helpLabel={label('help', 'Help & Support')}\n searchDot={this.toolDot('search')}\n activityDot={this.toolDot('activity')}\n inboxDot={this.toolDot('stacks') || this.toolDot('activity')}\n messagesDot={this.toolDot('messages')}\n agentsDot={this.toolDot('agents')}\n onDsSheetNavToggle={this.handleManagedSheetToggle}\n onDsDestinationChange={this.handleManagedMobileDestination}\n />\n );\n }\n\n private renderManagedMobileLeading() {\n if (!this.pageChrome.showBack) {\n return <slot name=\"page-header-leading\" slot=\"leading\" />;\n }\n return [\n <ds-tooltip\n slot=\"leading\"\n label={this.pageChrome.backAriaLabel ?? 'Back'}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n variant=\"icon\"\n icon={this.pageChrome.backIcon ?? 'ChevronLeft'}\n size=\"md\"\n aria-label={this.pageChrome.backAriaLabel ?? 'Back'}\n activeFill={false}\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) => this.dsPageBack.emit(event.detail)}\n />\n </ds-tooltip>,\n <slot name=\"page-header-leading\" slot=\"leading\" />,\n ];\n }\n\n private renderManagedMobileActions() {\n const legacyActions = this.pageChrome.mobileActions ?? [];\n const visibleActions = visibleBarTitleActions(this.resolvedHeaderActions, 'mobile');\n return [\n ...legacyActions.map((action: PanelToolsHeaderAction) => (\n <ds-tooltip\n slot=\"trailing\"\n key={action.id}\n label={action.ariaLabel}\n side=\"bottom\"\n size=\"sm\"\n >\n <ds-button-unfilled\n id={action.triggerId || undefined}\n variant=\"icon\"\n icon={action.icon}\n size=\"md\"\n aria-label={action.ariaLabel}\n haspopup={action.haspopup}\n controls={action.controls}\n expanded={action.expanded}\n pressed={action.pressed}\n isInactive={action.isInactive}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n </ds-tooltip>\n )),\n ...visibleActions.map(action => {\n if (action.type !== 'icon') return null;\n const button =\n (action.appearance ?? 'unfilled') === 'filled' ? (\n <ds-button-filled\n variant=\"icon\"\n icon={action.icon}\n aria-label={action.ariaLabel}\n size=\"md\"\n intent={action.intent ?? 'brand'}\n contrast={action.contrast ?? 'bold'}\n isInactive={action.isInactive}\n isLoading={action.isLoading}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n ) : (\n <ds-button-unfilled\n variant=\"icon\"\n icon={action.icon}\n aria-label={action.ariaLabel}\n size=\"md\"\n isInactive={action.isInactive}\n isLoading={action.isLoading}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsPageAction.emit(action.id)}\n />\n );\n return (\n <ds-tooltip slot=\"trailing\" key={action.id} label={action.label} side=\"bottom\" size=\"sm\">\n {button}\n </ds-tooltip>\n );\n }),\n ...(this.mobileActionMenuSections.length > 0\n ? [\n <ds-tooltip slot=\"trailing\" label=\"Page options\" side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n ref={el => {\n this.mobileActionTriggerEl = el ?? null;\n }}\n id={this.mobileActionMenuTriggerId}\n variant=\"icon\"\n icon=\"Ellipses\"\n aria-label={this.pageChrome.actionsAriaLabel ?? 'More page actions'}\n size=\"md\"\n activeFill={false}\n hasBorder={false}\n haspopup=\"menu\"\n controls={this.mobileActionMenuId}\n expanded={this.mobileActionMenuOpen}\n onDsClick={(event: CustomEvent<MouseEvent>) => {\n this.mobileActionMenuInitialFocusVisible = event.detail.detail === 0;\n this.mobileActionMenuOpen = !this.mobileActionMenuOpen;\n }}\n />\n </ds-tooltip>,\n ]\n : []),\n <slot name=\"page-header-trailing\" slot=\"trailing\" />,\n ];\n }\n\n private get resolvedHeaderActions(): BarTitleActionConfigItem[] {\n return resolveBarTitleActionItems(\n this.pageChrome.actionItems,\n this.pageChrome.primaryAction ?? null,\n this.pageChrome.actions ?? []\n );\n }\n\n private get mobileActionMenuSections() {\n return overflowBarTitleActionSections(this.resolvedHeaderActions, 'mobile');\n }\n\n private closeMobileActionMenu = () => {\n this.mobileActionMenuOpen = false;\n };\n\n private handleMobileActionSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail.value ?? '');\n const action = findBarTitleAction(this.resolvedHeaderActions, id);\n if (!action || action.isInactive || ('isLoading' in action && action.isLoading)) return;\n this.mobileActionMenuOpen = false;\n this.dsPageAction.emit(id);\n requestAnimationFrame(() => void this.mobileActionTriggerEl?.setFocus?.());\n };\n\n private renderManagedMobileActionMenu() {\n if (this.mobileActionMenuSections.length === 0) return null;\n return (\n <ds-menu\n id={this.mobileActionMenuId}\n anchorId={this.mobileActionMenuTriggerId}\n align=\"end\"\n menuLabel={this.pageChrome.actionsAriaLabel ?? 'More page actions'}\n open={this.mobileActionMenuOpen}\n initialFocusVisible={this.mobileActionMenuInitialFocusVisible}\n sections={this.mobileActionMenuSections}\n onDsClose={this.closeMobileActionMenu}\n onDsSelect={this.handleMobileActionSelect}\n />\n );\n }\n\n private get barTitleSections(): BarTitleSectionItem[] {\n return (this.pageChrome.subsections ?? []).map(item =>\n 'type' in item\n ? { type: 'divider' }\n : {\n id: item.id,\n label: item.label,\n isInactive: item.isInactive,\n }\n );\n }\n\n private renderManagedBarTitle(placement: BarTitlePlacement) {\n const page = this.pageChrome;\n return (\n <ds-bar-title\n ref={el => {\n if (el) {\n (el as HTMLElement & { actionItems?: BarTitleActionConfigItem[] }).actionItems =\n page.actionItems;\n }\n }}\n slot={placement === 'page' ? 'header' : undefined}\n placement={placement}\n variant={placement === 'shell-bar' ? 'compact' : 'expanded'}\n heading={page.heading ?? ''}\n description={page.description ?? ''}\n showBack={page.showBack ?? false}\n backAriaLabel={page.backAriaLabel ?? 'Back'}\n backLabel={page.backLabel ?? 'Back'}\n breadcrumbs={page.breadcrumbs ?? []}\n breadcrumbAriaLabel={page.breadcrumbAriaLabel ?? 'Breadcrumb'}\n sections={this.barTitleSections}\n value={page.subvalue ?? ''}\n sectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n primaryAction={page.primaryAction ?? null}\n actions={page.actions ?? []}\n actionsAriaLabel={page.actionsAriaLabel ?? 'More page actions'}\n showDivider={page.showHeaderDivider ?? true}\n showCompactDivider={page.showCompactHeaderDivider}\n onDsBack={this.handleManagedPageBack}\n onDsBreadcrumbSelect={this.handleManagedBreadcrumbSelect}\n onDsSectionChange={this.handleManagedSubsectionChange}\n onDsAction={this.handleManagedPageAction}\n />\n );\n }\n\n private renderManagedPage() {\n const page = this.pageChrome;\n const mobileSections = page.showBack ? [] : this.resolvedPageTabs;\n return [\n <ds-shell-page\n responsiveMode={this.resolvedMode}\n headerCapacity={resolveManagedShellPageCapacity(this.resolvedMode)}\n desktopHeaderPlacement={this.sectionNavigation === 'panel' ? 'shell-bar' : 'page'}\n contentInset={page.contentInset ?? 'default'}\n contentInsetBlockStart={page.contentInsetBlockStart ?? 'default'}\n contentInsetBlockStartSize={page.contentInsetBlockStartSize}\n compactContentInsetBlockStartSize={page.compactContentInsetBlockStartSize}\n scrollCompaction={page.scrollCompaction ?? true}\n contentSurface={page.contentSurface ?? 'primary'}\n >\n {this.sectionNavigation === 'bar' ? this.renderManagedBarTitle('page') : null}\n <ds-mobile-header\n slot=\"mobile-header\"\n heading={page.heading ?? ''}\n headingLevel={page.headingLevel ?? 'h1'}\n sections={mobileSections}\n value={page.showBack ? '' : (page.value ?? '')}\n sectionsAriaLabel={page.sectionsAriaLabel ?? 'Change page section'}\n subsections={page.subsections ?? []}\n subvalue={page.subvalue ?? ''}\n subsectionsAriaLabel={page.subsectionsAriaLabel ?? 'Change page subsection'}\n tone={page.tone ?? 'default'}\n onDsSectionChange={this.handleManagedTabChange}\n onDsSubsectionChange={this.handleManagedSubsectionChange}\n >\n {this.renderManagedMobileLeading()}\n {this.renderManagedMobileActions()}\n </ds-mobile-header>\n <slot />\n </ds-shell-page>,\n this.renderManagedMobileActionMenu(),\n ];\n }\n\n private renderSlottedShell(\n shellCls: Record<string, boolean>,\n mobile: boolean,\n mobileToolActive: boolean,\n mobileStageBlocked: boolean,\n fullscreen: boolean\n ) {\n return (\n <Host class={shellCls} responsive-mode={this.resolvedMode}>\n <div class=\"shell-app__banner\">\n <slot name=\"banner\" />\n </div>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\">\n {this.renderPaperTexture()}\n </div>\n <div\n class=\"shell-app__panel\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n <slot name=\"panel\" />\n </div>\n <div class=\"shell-app__main\">\n <div\n class=\"shell-app__bar\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n <slot name=\"bar\" />\n </div>\n <div\n class=\"shell-app__tools\"\n data-ds-overlay-boundary\n aria-hidden={\n mobile && (!mobileToolActive || this.mobileSheetNavOpen) ? 'true' : undefined\n }\n inert={mobile && (!mobileToolActive || this.mobileSheetNavOpen) ? true : undefined}\n hidden={mobile && !mobileToolActive}\n >\n <slot name=\"tools\" />\n </div>\n <div\n class=\"shell-app__content\"\n data-ds-overlay-boundary\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n tabIndex={0}\n >\n <slot />\n </div>\n <div\n class=\"shell-app__mobile-sheet-nav\"\n hidden={!mobile || !this.mobileSheetNavOpen}\n aria-hidden={mobile && this.mobileSheetNavOpen ? undefined : 'true'}\n inert={mobile && this.mobileSheetNavOpen ? undefined : true}\n >\n <slot name=\"mobile-sheet-nav\" />\n </div>\n <div class=\"shell-app__mobile-bar-nav\" hidden={!mobile}>\n <slot name=\"mobile-bar-nav\" />\n </div>\n </div>\n </div>\n </Host>\n );\n }\n\n render() {\n const chromeActive = this.chromeLayerActive();\n const gradientActive = this.gradientLayerActive();\n const paperTextureActive = this.paperTextureLayerActive();\n const mobile = this.resolvedMode === 'mobile';\n const mobileToolActive = mobile && this.effectiveMobileDestination !== 'area';\n const mobileStageBlocked = mobile && (mobileToolActive || this.effectiveMobileSheetNavOpen);\n const fullscreen = !mobile && this.toolsFullscreen;\n const shellCls: Record<string, boolean> = {\n 'shell-app': true,\n 'shell-app--chrome': chromeActive,\n 'shell-app--gradient': gradientActive,\n 'shell-app--paper-texture': paperTextureActive,\n [`shell-app--${this.navStyle}`]: true,\n [`shell-app--${this.resolvedMode}`]: true,\n 'shell-app--tools-fullscreen': fullscreen,\n 'shell-app--mobile-tool-active': mobileToolActive,\n 'shell-app--mobile-sheet-nav-open': mobile && this.effectiveMobileSheetNavOpen,\n 'shell-app--managed': this.managed,\n 'shell-app--slotted': !this.managed,\n };\n\n if (!this.managed) {\n return this.renderSlottedShell(\n shellCls,\n mobile,\n mobileToolActive,\n mobileStageBlocked,\n fullscreen\n );\n }\n\n return (\n <Host class={shellCls} responsive-mode={this.resolvedMode}>\n <div class=\"shell-app__banner\">\n <slot name=\"banner\" />\n </div>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\">\n {this.renderPaperTexture()}\n </div>\n <div\n class=\"shell-app__panel\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n {this.renderManagedPanelNav()}\n </div>\n <div class=\"shell-app__main\">\n <div\n class=\"shell-app__bar\"\n aria-hidden={fullscreen ? 'true' : undefined}\n inert={fullscreen ? true : undefined}\n >\n {this.renderManagedTopBar()}\n </div>\n <div\n class=\"shell-app__tools\"\n data-ds-overlay-boundary\n aria-hidden={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen)\n ? 'true'\n : undefined\n }\n inert={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen) ? true : undefined\n }\n hidden={mobile && !mobileToolActive}\n >\n {this.renderManagedTools()}\n </div>\n <div\n class=\"shell-app__content\"\n data-ds-overlay-boundary\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n tabIndex={0}\n >\n {this.renderManagedPage()}\n </div>\n <div\n class=\"shell-app__mobile-sheet-nav\"\n hidden={!mobile || !this.effectiveMobileSheetNavOpen}\n aria-hidden={mobile && this.effectiveMobileSheetNavOpen ? undefined : 'true'}\n inert={mobile && this.effectiveMobileSheetNavOpen ? undefined : true}\n >\n {this.renderManagedMobileSheetNav()}\n </div>\n <div class=\"shell-app__mobile-bar-nav\" hidden={!mobile}>\n {this.renderManagedMobileBarNav()}\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
|
|
689
694
|
"type": "registry:ui"
|
|
690
695
|
},
|
|
691
696
|
{
|
|
692
697
|
"path": "src/wc/components/ShellApp/shell-app-types.ts",
|
|
693
|
-
"content": "import type { BarNavTab } from '../BarNav/bar-nav-types';\nimport type {
|
|
698
|
+
"content": "import type { BarNavTab } from '../BarNav/bar-nav-types';\nimport type {\n BarTitleActionConfigItem,\n BarTitleActionItem,\n BarTitlePrimaryAction,\n} from '../BarTitle/bar-title-types';\nimport type { BreadcrumbItem } from '../Breadcrumb/breadcrumb-types';\nimport type { MobileHeaderTone } from '../MobileHeader/mobile-header-types';\nimport type { PanelNavGroup, PanelNavItem, PanelNavRouterMode } from '../PanelNav/panel-nav-types';\nimport type {\n PanelToolsHeaders,\n PanelToolsHeaderAction,\n PanelToolsItem,\n PanelToolsRailAccessory,\n} from '../PanelTools/panel-tools-types';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport type { ShellPageContentInset, ShellPageContentSurface } from '../ShellPage/shell-page-types';\n\nexport type ShellAppComposition = 'managed' | 'slotted';\nexport type ShellSectionNavigation = 'bar' | 'panel';\n\n/**\n * Router-owned application navigation supplied to managed ShellApp.\n * ShellApp renders the responsive chrome but never performs route changes.\n */\nexport interface ShellNavigationConfig {\n /** Groups shown in the current desktop/tablet PanelNav context. */\n groups?: PanelNavGroup[];\n /** Complete Dashboard collection used by the mobile navigation sheet. */\n dashboardGroups?: PanelNavGroup[];\n /** Complete Settings collection used by the mobile navigation sheet. */\n settingsGroups?: PanelNavGroup[];\n /** Current application URL used for active-item matching. */\n currentUrl?: string;\n /** Optional explicit active destination when URL matching is unavailable. */\n activeId?: string;\n /** Current Dashboard or Settings browsing context. */\n browseContext?: NavChromeStyle;\n /** Managed shells default to event routing so the application router stays authoritative. */\n routerMode?: PanelNavRouterMode;\n /** Persisted desktop PanelNav collapse preference key. */\n storageKey?: string;\n userName?: string;\n userInitial?: string;\n accountMenuExpanded?: boolean;\n dashboardLabel?: string;\n settingsLabel?: string;\n accountLabel?: string;\n /** Hide the mobile-sheet Account shortcut when Settings already owns that destination. */\n showMobileAccount?: boolean;\n navigationLabel?: string;\n dashboardNavigationLabel?: string;\n settingsNavigationLabel?: string;\n}\n\n/**\n * Route-owned page chrome. The same data drives desktop BarNav/BarTitle and\n * the intentionally different mobile MobileHeader presentation.\n */\nexport interface ShellPageChromeConfig {\n heading?: string;\n /** Desktop/tablet route-navigation heading when it differs from the page h1. */\n routeHeading?: string;\n description?: string;\n headingLevel?: 'h1' | 'h2';\n tabs?: BarNavTab[];\n value?: string;\n basePath?: string;\n currentUrl?: string;\n sectionsAriaLabel?: string;\n subsections?: BarNavTab[];\n subvalue?: string;\n subsectionsAriaLabel?: string;\n showBack?: boolean;\n backAriaLabel?: string;\n backIcon?: string;\n backLabel?: string;\n breadcrumbs?: BreadcrumbItem[];\n breadcrumbAriaLabel?: string;\n primaryAction?: BarTitlePrimaryAction | null;\n actions?: BarTitleActionItem[];\n /** Ordered desktop, tablet, and mobile page-action model. */\n actionItems?: BarTitleActionConfigItem[];\n /** Icon actions rendered in the mobile header's trailing lane. */\n mobileActions?: PanelToolsHeaderAction[];\n actionsAriaLabel?: string;\n tone?: MobileHeaderTone;\n contentInset?: ShellPageContentInset;\n /** Remove only the content gutter adjacent to the page header. */\n contentInsetBlockStart?: ShellPageContentInset;\n /** Exact roomy block-start content inset when the responsive default is not appropriate. */\n contentInsetBlockStartSize?: string;\n /** Exact block-start content inset while page-title capacity is compact or constrained. */\n compactContentInsetBlockStartSize?: string;\n /** Allow a roomy page title to compact in response to page scrolling. */\n scrollCompaction?: boolean;\n /** Draw the divider beneath the desktop/tablet page title. */\n showHeaderDivider?: boolean;\n /** Override divider visibility while the page title is compact or constrained. */\n showCompactHeaderDivider?: boolean;\n /** Canvas surface painted by ShellPage around and beneath routed content. */\n contentSurface?: ShellPageContentSurface;\n}\n\n/**\n * Product-owned global tools supplied to managed ShellApp. Tool view content\n * remains mounted in the corresponding named `*-view` slots.\n */\nexport interface ShellToolsConfig {\n items?: PanelToolsItem[];\n /** Desktop/tablet-only direct rail intents and decorative group boundaries. */\n accessories?: PanelToolsRailAccessory[];\n headers?: PanelToolsHeaders;\n storageKey?: string;\n fullscreenHeaderMode?: 'shared' | 'split';\n toolsLabel?: string;\n toolShortcutsLabel?: string;\n inboxLabel?: string;\n inboxNavigationLabel?: string;\n /** Temporarily conceal the mobile bar while a tool-owned keyboard interaction needs the space. */\n mobileBarHidden?: boolean;\n}\n\nexport interface ShellNavigationSelectDetail {\n id: string;\n item?: PanelNavItem;\n}\n",
|
|
694
699
|
"type": "registry:ui"
|
|
695
700
|
}
|
|
696
701
|
]
|