@ds-mo/ui 13.10.0 → 13.11.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 +4 -4
- package/dist/agent.json +16 -8
- package/dist/angular/ds-mobile-bar-nav.d.ts +1 -1
- package/dist/angular/ds-mobile-bar-nav.js +3 -3
- package/dist/components/ds-agent-activity.js +1 -1
- package/dist/components/ds-agent-response.js +1 -1
- package/dist/components/ds-agent-source-list.js +1 -1
- package/dist/components/ds-agent-tool-call.js +1 -1
- package/dist/components/ds-attachment-list.js +1 -1
- package/dist/components/ds-avatar.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-overview.js +1 -1
- package/dist/components/ds-card-overview.js.map +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-empty-state.js +1 -1
- package/dist/components/ds-icon.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-bar-nav.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-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-tab-group.js +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/{p-BtS5na9O.js → p-BRajO-Za.js} +2 -2
- package/dist/components/{p-BQDi0v_h.js → p-BX57q1Lo.js} +2 -2
- package/dist/components/{p-BkWhTEqe.js → p-B_nHX-BW.js} +2 -2
- package/dist/components/{p-Dv8_hmUZ.js → p-C5gZHZHU.js} +2 -2
- package/dist/components/{p-HIg1XX62.js → p-CIlO_j3q.js} +2 -2
- package/dist/components/p-CIlO_j3q.js.map +1 -0
- package/dist/components/{p-CnbrksLl.js → p-CVmtbj-6.js} +2 -2
- package/dist/components/{p-BjoyHNaM.js → p-CdFnvBem.js} +2 -2
- package/dist/components/{p-BP-EtYbR.js → p-DAbaMtEX.js} +2 -2
- package/dist/components/{p-KFHFBznh.js → p-DBJJiSP0.js} +2 -2
- package/dist/components/{p-D8qgVmHn.js → p-DBchq8Zw.js} +2 -2
- package/dist/components/{p-BYB0f1_F.js → p-DCN04sgt.js} +2 -2
- package/dist/components/{p-D2dQyoyK.js → p-DGkWMhHO.js} +2 -2
- package/dist/components/{p-2inC7Jm-.js → p-DJGdoPMG.js} +2 -2
- package/dist/components/p-DNpE0RBz.js +2 -0
- package/dist/components/p-DNpE0RBz.js.map +1 -0
- package/dist/components/{p-BjIYFiuQ.js → p-DcmqsAMP.js} +2 -2
- package/dist/components/p-DkhE3BsH.js +2 -0
- package/dist/components/p-DkhE3BsH.js.map +1 -0
- package/dist/components/{p-B4VlfBNc.js → p-Doz2uwRv.js} +2 -2
- package/dist/components/p-DumWT7NN.js +2 -0
- package/dist/components/p-DumWT7NN.js.map +1 -0
- package/dist/components/{p-Cog5NTLa.js → p-Xftomzsv.js} +2 -2
- package/dist/components/{p-DJ0XMH9E.js → p-c1pY-Mqq.js} +2 -2
- package/dist/docs/components.json +74 -8
- package/dist/lib/components/PanelTools/panel-tools-types.d.ts +3 -3
- package/dist/lib/shell/index.js +12 -11
- package/dist/lib/shell/index.js.map +2 -2
- package/dist/lib/shell/shell-responsive.d.ts +1 -1
- package/dist/mcp-data/registry/agent-activity.json +1 -1
- package/dist/mcp-data/registry/agent-response.json +1 -1
- package/dist/mcp-data/registry/agent-source-list.json +1 -1
- package/dist/mcp-data/registry/agent-tool-call.json +1 -1
- package/dist/mcp-data/registry/attachment-list.json +1 -1
- package/dist/mcp-data/registry/avatar.json +1 -1
- package/dist/mcp-data/registry/badge.json +1 -1
- package/dist/mcp-data/registry/banner.json +1 -1
- package/dist/mcp-data/registry/bar-nav.json +1 -1
- package/dist/mcp-data/registry/bar-title.json +1 -1
- package/dist/mcp-data/registry/bar-workflow.json +1 -1
- package/dist/mcp-data/registry/breadcrumb.json +1 -1
- package/dist/mcp-data/registry/button-filled.json +1 -1
- package/dist/mcp-data/registry/button-unfilled.json +1 -1
- package/dist/mcp-data/registry/card-chart.json +1 -1
- package/dist/mcp-data/registry/card-overview.json +4 -4
- package/dist/mcp-data/registry/card-setting.json +1 -1
- package/dist/mcp-data/registry/chart-legend.json +1 -1
- package/dist/mcp-data/registry/chart.json +1 -1
- package/dist/mcp-data/registry/checkbox.json +1 -1
- package/dist/mcp-data/registry/chip.json +1 -1
- package/dist/mcp-data/registry/code-block.json +1 -1
- package/dist/mcp-data/registry/conversation-list-item.json +1 -1
- package/dist/mcp-data/registry/conversation-list-section.json +1 -1
- package/dist/mcp-data/registry/conversation-list.json +1 -1
- package/dist/mcp-data/registry/divider.json +1 -1
- package/dist/mcp-data/registry/empty-state.json +1 -1
- package/dist/mcp-data/registry/field.json +1 -1
- package/dist/mcp-data/registry/icon.json +2 -2
- package/dist/mcp-data/registry/input.json +1 -1
- package/dist/mcp-data/registry/loader.json +1 -1
- package/dist/mcp-data/registry/markdown.json +1 -1
- package/dist/mcp-data/registry/menu.json +1 -1
- package/dist/mcp-data/registry/message-actions.json +1 -1
- package/dist/mcp-data/registry/message-bubble.json +1 -1
- package/dist/mcp-data/registry/message-composer.json +1 -1
- package/dist/mcp-data/registry/message-scroller.json +1 -1
- package/dist/mcp-data/registry/message.json +1 -1
- package/dist/mcp-data/registry/mobile-bar-nav.json +39 -7
- package/dist/mcp-data/registry/mobile-header.json +1 -1
- package/dist/mcp-data/registry/mobile-section-switcher.json +1 -1
- package/dist/mcp-data/registry/mobile-sheet-nav.json +1 -1
- package/dist/mcp-data/registry/modal.json +1 -1
- package/dist/mcp-data/registry/panel-nav.json +1 -1
- package/dist/mcp-data/registry/panel-sub-nav.json +1 -1
- package/dist/mcp-data/registry/panel-tool-header.json +1 -1
- package/dist/mcp-data/registry/panel-tool-search.json +1 -1
- package/dist/mcp-data/registry/panel-tools.json +3 -3
- package/dist/mcp-data/registry/radio.json +1 -1
- package/dist/mcp-data/registry/registry.json +16 -8
- package/dist/mcp-data/registry/scroll-overlay.json +1 -1
- package/dist/mcp-data/registry/select.json +1 -1
- package/dist/mcp-data/registry/shell-app.json +4 -4
- package/dist/mcp-data/registry/shell-page.json +1 -1
- package/dist/mcp-data/registry/shell-tools.json +4 -4
- package/dist/mcp-data/registry/skeleton.json +1 -1
- package/dist/mcp-data/registry/slider.json +1 -1
- package/dist/mcp-data/registry/swatch-picker.json +1 -1
- package/dist/mcp-data/registry/switch.json +1 -1
- package/dist/mcp-data/registry/tab-group.json +1 -1
- package/dist/mcp-data/registry/tag.json +1 -1
- package/dist/mcp-data/registry/text.json +1 -1
- package/dist/mcp-data/registry/toast.json +1 -1
- package/dist/mcp-data/registry/tooltip-chart.json +1 -1
- package/dist/mcp-data/registry/tooltip.json +1 -1
- package/dist/mcp-data/registry/typing-indicator.json +1 -1
- package/dist/types/components/CardOverview/card-overview-types.d.ts +0 -2
- package/dist/types/components/MobileBarNav/MobileBarNav.d.ts +4 -2
- package/dist/types/components/PanelTools/panel-tools-types.d.ts +3 -3
- package/dist/types/components.d.ts +20 -2
- package/dist/types/shell/shell-responsive.d.ts +1 -1
- package/package.json +4 -4
- package/dist/components/p-B16OuqyJ.js +0 -2
- package/dist/components/p-B16OuqyJ.js.map +0 -1
- package/dist/components/p-BAfG3M8z.js +0 -2
- package/dist/components/p-BAfG3M8z.js.map +0 -1
- package/dist/components/p-BnQXAbif.js +0 -2
- package/dist/components/p-BnQXAbif.js.map +0 -1
- package/dist/components/p-HIg1XX62.js.map +0 -1
- /package/dist/components/{p-BtS5na9O.js.map → p-BRajO-Za.js.map} +0 -0
- /package/dist/components/{p-BQDi0v_h.js.map → p-BX57q1Lo.js.map} +0 -0
- /package/dist/components/{p-BkWhTEqe.js.map → p-B_nHX-BW.js.map} +0 -0
- /package/dist/components/{p-Dv8_hmUZ.js.map → p-C5gZHZHU.js.map} +0 -0
- /package/dist/components/{p-CnbrksLl.js.map → p-CVmtbj-6.js.map} +0 -0
- /package/dist/components/{p-BjoyHNaM.js.map → p-CdFnvBem.js.map} +0 -0
- /package/dist/components/{p-BP-EtYbR.js.map → p-DAbaMtEX.js.map} +0 -0
- /package/dist/components/{p-KFHFBznh.js.map → p-DBJJiSP0.js.map} +0 -0
- /package/dist/components/{p-D8qgVmHn.js.map → p-DBchq8Zw.js.map} +0 -0
- /package/dist/components/{p-BYB0f1_F.js.map → p-DCN04sgt.js.map} +0 -0
- /package/dist/components/{p-D2dQyoyK.js.map → p-DGkWMhHO.js.map} +0 -0
- /package/dist/components/{p-2inC7Jm-.js.map → p-DJGdoPMG.js.map} +0 -0
- /package/dist/components/{p-BjIYFiuQ.js.map → p-DcmqsAMP.js.map} +0 -0
- /package/dist/components/{p-B4VlfBNc.js.map → p-Doz2uwRv.js.map} +0 -0
- /package/dist/components/{p-Cog5NTLa.js.map → p-Xftomzsv.js.map} +0 -0
- /package/dist/components/{p-DJ0XMH9E.js.map → p-c1pY-Mqq.js.map} +0 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"peerDependencies": {
|
|
14
14
|
"required": [
|
|
15
15
|
"@ds-mo/tokens ^6.0.0",
|
|
16
|
-
"@ds-mo/icons ^6.
|
|
16
|
+
"@ds-mo/icons ^6.5.0"
|
|
17
17
|
],
|
|
18
18
|
"optional": [],
|
|
19
19
|
"frameworks": "None required for Custom Elements; generated React and Angular adapters are included."
|
|
@@ -4733,7 +4733,7 @@
|
|
|
4733
4733
|
{
|
|
4734
4734
|
"name": "mobile-bar-nav",
|
|
4735
4735
|
"title": "MobileBarNav",
|
|
4736
|
-
"description": "Persistent mobile shell navigation for Menu, the router-derived current area, Search,
|
|
4736
|
+
"description": "Persistent mobile shell navigation for Menu, the router-derived current area, Search, Inbox, Messages, and Agents.",
|
|
4737
4737
|
"type": "registry:ui",
|
|
4738
4738
|
"meta": {
|
|
4739
4739
|
"library": "@ds-mo/ui",
|
|
@@ -4744,7 +4744,7 @@
|
|
|
4744
4744
|
"intent": {
|
|
4745
4745
|
"audience": "specialized",
|
|
4746
4746
|
"status": "experimental",
|
|
4747
|
-
"summary": "Persistent mobile shell navigation for Menu, the router-derived current area, Search,
|
|
4747
|
+
"summary": "Persistent mobile shell navigation for Menu, the router-derived current area, Search, Inbox, Messages, and Agents.",
|
|
4748
4748
|
"useWhen": [
|
|
4749
4749
|
"ShellApp is rendering its dedicated below-768px application composition."
|
|
4750
4750
|
],
|
|
@@ -4763,7 +4763,7 @@
|
|
|
4763
4763
|
"Only the active destination exposes aria-current; Menu exposes its controlled pane through aria-expanded."
|
|
4764
4764
|
],
|
|
4765
4765
|
"states": [
|
|
4766
|
-
"The fixed groups are Menu plus current area, followed by Search, Inbox, and Agents.",
|
|
4766
|
+
"The fixed groups are Menu plus current area, followed by Search, Inbox, Messages, and Agents.",
|
|
4767
4767
|
"Selection promotes foreground to primary without painting a selected fill; unselected destinations use tertiary foreground.",
|
|
4768
4768
|
"Opening Mobile Sheet Nav replaces the Menu glyph with Cross while preserving its accessible name.",
|
|
4769
4769
|
"Reselecting an active destination does not reset its nested state.",
|
|
@@ -4814,6 +4814,14 @@
|
|
|
4814
4814
|
"type": "string",
|
|
4815
4815
|
"required": false
|
|
4816
4816
|
},
|
|
4817
|
+
"messagesDot": {
|
|
4818
|
+
"type": "boolean",
|
|
4819
|
+
"required": false
|
|
4820
|
+
},
|
|
4821
|
+
"messagesLabel": {
|
|
4822
|
+
"type": "string",
|
|
4823
|
+
"required": false
|
|
4824
|
+
},
|
|
4817
4825
|
"searchDot": {
|
|
4818
4826
|
"type": "boolean",
|
|
4819
4827
|
"required": false
|
|
@@ -6040,7 +6048,7 @@
|
|
|
6040
6048
|
"states": [
|
|
6041
6049
|
"Every tool has a stable explicit id and accessible label. The application filters definitions for authorization and entitlement.",
|
|
6042
6050
|
"Tool order, header or footer rail placement, shortcut, and mobile grouping come from explicit item metadata rather than id-based layout assumptions.",
|
|
6043
|
-
"PANEL_TOOLS_DEFAULT_ITEMS is the canonical Lab recipe: Search pinned first, Help pinned last, current shortcuts, and
|
|
6051
|
+
"PANEL_TOOLS_DEFAULT_ITEMS is the canonical Lab recipe: Search pinned first, Messages before Agents, Help pinned last, current shortcuts, and only Stacks and Activity grouped under mobile Inbox.",
|
|
6044
6052
|
"Only one drawer is open at a time; selecting another tool switches directly and selecting the active tool closes to rail-only mode.",
|
|
6045
6053
|
"The drawer starts closed, remains open across ordinary route changes, and retains every mounted tool's independent conversation, query, scroll, and work state.",
|
|
6046
6054
|
"When storageKey is set, persist only the last active tool in this browser and still restore the drawer closed after reload.",
|
|
@@ -7094,17 +7102,17 @@
|
|
|
7094
7102
|
"accessibility": [
|
|
7095
7103
|
"Only the active mobile tool view participates in focus or the accessibility tree.",
|
|
7096
7104
|
"Header Back and application-owned header actions preserve the PanelTools event contract.",
|
|
7097
|
-
"Inbox uses the MobileSectionSwitcher popup trigger for
|
|
7105
|
+
"Inbox uses the MobileSectionSwitcher popup trigger for Stacks and Activity and never relies on a gesture-only interaction; Messages remains a direct mobile destination."
|
|
7098
7106
|
],
|
|
7099
7107
|
"states": [
|
|
7100
7108
|
"The application owns open and activeTool and replaces headers when tool depth changes.",
|
|
7101
|
-
"Items with mobileDestination inbox map to one mobile Inbox destination while retaining the actual active tool id; the canonical recipe groups
|
|
7109
|
+
"Items with mobileDestination inbox map to one mobile Inbox destination while retaining the actual active tool id; the canonical recipe groups Stacks and Activity, while Messages remains a separate destination.",
|
|
7102
7110
|
"Stable tool ids own named slots; label, order, rail placement, shortcut, and mobile destination are explicit item data.",
|
|
7103
7111
|
"Switching responsive mode must not recreate application-owned slotted product elements."
|
|
7104
7112
|
],
|
|
7105
7113
|
"responsiveBehavior": [
|
|
7106
7114
|
"Desktop and tablet compose PanelTools-compatible rail and drawer chrome.",
|
|
7107
|
-
"Mobile renders one centered MobileHeader for every tool
|
|
7115
|
+
"Mobile renders one centered MobileHeader for every tool; Stacks and Activity use Inbox section titles, while Messages remains a direct tool destination."
|
|
7108
7116
|
],
|
|
7109
7117
|
"frameworkCaveats": {
|
|
7110
7118
|
"customElements": [
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
},
|
|
134
134
|
"mobileDestination": {
|
|
135
135
|
"type": "MobileDestination",
|
|
136
|
-
"resolvedType": "\"agents\" | \"area\" | \"help\" | \"inbox\" | \"search\"",
|
|
136
|
+
"resolvedType": "\"agents\" | \"area\" | \"help\" | \"inbox\" | \"messages\" | \"search\"",
|
|
137
137
|
"attribute": "mobile-destination",
|
|
138
138
|
"default": "'area'",
|
|
139
139
|
"required": false,
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
},
|
|
347
347
|
"mobileDestination": {
|
|
348
348
|
"type": "MobileDestination",
|
|
349
|
-
"resolvedType": "\"agents\" | \"area\" | \"help\" | \"inbox\" | \"search\"",
|
|
349
|
+
"resolvedType": "\"agents\" | \"area\" | \"help\" | \"inbox\" | \"messages\" | \"search\"",
|
|
350
350
|
"attribute": "mobile-destination",
|
|
351
351
|
"default": "'area'",
|
|
352
352
|
"required": false,
|
|
@@ -561,7 +561,7 @@
|
|
|
561
561
|
"peerDependencies": {
|
|
562
562
|
"required": [
|
|
563
563
|
"@ds-mo/tokens ^6.0.0",
|
|
564
|
-
"@ds-mo/icons ^6.
|
|
564
|
+
"@ds-mo/icons ^6.5.0"
|
|
565
565
|
],
|
|
566
566
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
567
567
|
}
|
|
@@ -591,7 +591,7 @@
|
|
|
591
591
|
},
|
|
592
592
|
{
|
|
593
593
|
"path": "src/wc/components/ShellApp/ShellApp.tsx",
|
|
594
|
-
"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 {\n isEditableShortcutTarget,\n resolveShellShortcut,\n} from '../../shell/shell-shortcuts';\nimport type {\n PanelToolsToolId,\n PanelToolsHeaderAction,\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 { 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 isShellInboxTool,\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 { 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 ShellToolsConfig,\n} from './shell-app-types';\n\n@Component({\n tag: 'ds-shell-app',\n styleUrl: 'ShellApp.css',\n scoped: true,\n})\nexport class ShellApp {\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 /** 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 /** 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 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 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 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 availableInboxTools(): ShellInboxToolId[] {\n return this.resolvedToolItems\n .filter(\n item =>\n !item.isInactive &&\n (item.mobileDestination === 'inbox' || isShellInboxTool(item.id))\n )\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.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.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 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 (\n previous === true &&\n !this.effectiveMobileSheetNavOpen &&\n this.resolvedMode === 'mobile'\n ) {\n requestAnimationFrame(() => {\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n focusDestination?: (\n destination: MobileDestination | 'sheet-nav'\n ) => 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 if (readChromeTransitionSource(event) !== '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 if (readChromeTransitionSource(event) !== '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 (HTMLElement & { navStyle: NavChromeStyle }) | null;\n const bar = this.el.querySelector('ds-bar-nav') as (HTMLElement & { navStyle: NavChromeStyle }) | 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 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 = [\n SHELL_GRADIENT_IMAGE_VAR,\n SHELL_GRADIENT_SIZE_VAR,\n SHELL_GRADIENT_OPACITY_VAR,\n ];\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 (\n this.resolvedMode !== 'mobile' &&\n normalizeShellGradientPreset(this.gradientPreset) !== 'none'\n );\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 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 = (\n event: CustomEvent<BreadcrumbSelectDetail>\n ) => {\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?.mobileDestination === 'inbox' || isShellInboxTool(id)) {\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 handleManagedSheetToggle = (event: CustomEvent<boolean>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = event.detail;\n if (event.detail) {\n this.managedMobileDestination = 'area';\n this.managedToolsOpen = false;\n }\n };\n\n private handleManagedMobileDestination = (\n event: CustomEvent<MobileBarNavDestinationDetail>\n ) => {\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 const canonicalId =\n destination === 'inbox'\n ? resolveAvailableInboxTool(this.managedInboxTool, this.availableInboxTools)\n : destination;\n if (!canonicalId) return;\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;\n const id = item.id;\n this.managedActiveTool = id;\n this.managedToolsOpen = true;\n if (isShellInboxTool(id)) this.managedInboxTool = id;\n this.dsToolChange.emit({ id: id as PanelToolsToolId, selected: true });\n };\n\n private handleManagedAreaSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'area';\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 = (\n event: CustomEvent<PanelNavUserActionDetail>\n ) => {\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(\n this.navigation.currentUrl ?? this.pageChrome.currentUrl ?? '',\n items\n );\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('ds-bar-nav') 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 (preset === 'none' || this.resolvedMode === 'mobile') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n return;\n }\n\n const viewport = this.resolveViewportDimensions();\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.tagName.toLowerCase() === 'ds-bar-nav';\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\n private renderManagedPanelNav() {\n const navigation = this.navigation;\n return (\n <ds-panel-nav\n navStyle={this.navStyle}\n groups={this.resolvedNavigationGroups}\n breakpoint={SHELL_DESKTOP_BREAKPOINT}\n routerMode={navigation.routerMode ?? 'event'}\n activeId={navigation.activeId ?? ''}\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={\n navigation.dashboardNavigationLabel ?? 'Dashboard navigation'\n }\n settingsNavigationLabel={\n navigation.settingsNavigationLabel ?? 'Settings navigation'\n }\n navigationItemsLabel={navigation.navigationLabel ?? 'Navigation items'}\n onDsNavSelect={this.handleManagedNavSelect}\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.pageChrome.tabs ?? []}\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 renderManagedToolSlots() {\n const ids = this.resolvedToolItems.map(item => item.id);\n return ids.flatMap(id => [\n <slot name={id} slot={id} />,\n <slot name={`${id}-view`} slot={`${id}-view`} />,\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 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 >\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 navigationLabel={navigation.navigationLabel ?? 'Application navigation'}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n onDsAreaSelect={this.handleManagedAreaSelect}\n onDsBrowseContextChange={this.handleManagedBrowseContext}\n onDsClose={this.handleManagedSheetClose}\n />\n );\n }\n\n private renderManagedMobileBarNav() {\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={this.tools.items?.find(item => item.id === 'search')?.ariaLabel ?? 'Search'}\n agentsLabel={this.tools.items?.find(item => item.id === 'agents')?.ariaLabel ?? 'Agents'}\n inboxLabel={this.tools.inboxLabel ?? 'Inbox'}\n helpLabel={this.tools.items?.find(item => item.id === 'help')?.ariaLabel ?? 'Help & Support'}\n searchDot={this.toolDot('search')}\n agentsDot={this.toolDot('agents')}\n inboxDot={\n this.toolDot('messages') || this.toolDot('stacks') || this.toolDot('activity')\n }\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>) =>\n this.dsPageBack.emit(event.detail)\n }\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 isActive={!!action.expanded}\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 renderManagedPage() {\n const page = this.pageChrome;\n const mobileSections = page.showBack ? [] : (page.tabs ?? []);\n const barTitleSections: BarTitleSectionItem[] = (page.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 return (\n <ds-shell-page\n responsiveMode={this.resolvedMode}\n headerCapacity={resolveManagedShellPageCapacity(\n this.resolvedMode,\n this.managedToolsOpen\n )}\n contentInset={page.contentInset ?? 'default'}\n >\n <ds-bar-title\n slot=\"header\"\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={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 onDsBack={this.handleManagedPageBack}\n onDsBreadcrumbSelect={this.handleManagedBreadcrumbSelect}\n onDsSectionChange={this.handleManagedSubsectionChange}\n onDsAction={this.handleManagedPageAction}\n />\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={\n page.subsectionsAriaLabel ?? 'Change page subsection'\n }\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 <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 aria-hidden={\n mobile && (!mobileToolActive || this.mobileSheetNavOpen)\n ? 'true'\n : undefined\n }\n inert={\n mobile && (!mobileToolActive || this.mobileSheetNavOpen)\n ? true\n : undefined\n }\n hidden={mobile && !mobileToolActive}\n >\n <slot name=\"tools\" />\n </div>\n <div\n class=\"shell-app__content ds-focus-ring\"\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={\n mobile && this.mobileSheetNavOpen ? undefined : 'true'\n }\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 mobile = this.resolvedMode === 'mobile';\n const mobileToolActive = mobile && this.effectiveMobileDestination !== 'area';\n const mobileStageBlocked =\n mobile && (mobileToolActive || this.effectiveMobileSheetNavOpen);\n const fullscreen = !mobile && this.toolsFullscreen;\n const shellCls: Record<string, boolean> = {\n 'shell-app': true,\n 'shell-app--gradient': chromeActive,\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':\n 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 <div class=\"shell-app__panel\" aria-hidden={fullscreen ? 'true' : undefined} inert={fullscreen ? true : undefined}>\n {this.renderManagedPanelNav()}\n </div>\n <div class=\"shell-app__main\">\n <div class=\"shell-app__bar\" aria-hidden={fullscreen ? 'true' : undefined} inert={fullscreen ? true : undefined}>\n {this.renderManagedBarNav()}\n </div>\n <div\n class=\"shell-app__tools\"\n aria-hidden={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen)\n ? 'true'\n : undefined\n }\n inert={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen)\n ? true\n : undefined\n }\n hidden={mobile && !mobileToolActive}\n >\n {this.renderManagedTools()}\n </div>\n <div\n class=\"shell-app__content ds-focus-ring\"\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={\n mobile && this.effectiveMobileSheetNavOpen ? undefined : 'true'\n }\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",
|
|
594
|
+
"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 {\n isEditableShortcutTarget,\n resolveShellShortcut,\n} from '../../shell/shell-shortcuts';\nimport type {\n PanelToolsToolId,\n PanelToolsHeaderAction,\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 { 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 isShellInboxTool,\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 { 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 ShellToolsConfig,\n} from './shell-app-types';\n\n@Component({\n tag: 'ds-shell-app',\n styleUrl: 'ShellApp.css',\n scoped: true,\n})\nexport class ShellApp {\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 /** 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 /** 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 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 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 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 availableInboxTools(): ShellInboxToolId[] {\n return this.resolvedToolItems\n .filter(\n item =>\n !item.isInactive &&\n (item.mobileDestination === 'inbox' || isShellInboxTool(item.id))\n )\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.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.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 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 (\n previous === true &&\n !this.effectiveMobileSheetNavOpen &&\n this.resolvedMode === 'mobile'\n ) {\n requestAnimationFrame(() => {\n const bar = this.el.querySelector('ds-mobile-bar-nav') as\n | (HTMLElement & {\n focusDestination?: (\n destination: MobileDestination | 'sheet-nav'\n ) => 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 if (readChromeTransitionSource(event) !== '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 if (readChromeTransitionSource(event) !== '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 (HTMLElement & { navStyle: NavChromeStyle }) | null;\n const bar = this.el.querySelector('ds-bar-nav') as (HTMLElement & { navStyle: NavChromeStyle }) | 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 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 = [\n SHELL_GRADIENT_IMAGE_VAR,\n SHELL_GRADIENT_SIZE_VAR,\n SHELL_GRADIENT_OPACITY_VAR,\n ];\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 (\n this.resolvedMode !== 'mobile' &&\n normalizeShellGradientPreset(this.gradientPreset) !== 'none'\n );\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 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 = (\n event: CustomEvent<BreadcrumbSelectDetail>\n ) => {\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?.mobileDestination === 'inbox' || isShellInboxTool(id)) {\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 handleManagedSheetToggle = (event: CustomEvent<boolean>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = event.detail;\n if (event.detail) {\n this.managedMobileDestination = 'area';\n this.managedToolsOpen = false;\n }\n };\n\n private handleManagedMobileDestination = (\n event: CustomEvent<MobileBarNavDestinationDetail>\n ) => {\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 const canonicalId =\n destination === 'inbox'\n ? resolveAvailableInboxTool(this.managedInboxTool, this.availableInboxTools)\n : destination;\n if (!canonicalId) return;\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;\n const id = item.id;\n this.managedActiveTool = id;\n this.managedToolsOpen = true;\n if (isShellInboxTool(id)) this.managedInboxTool = id;\n this.dsToolChange.emit({ id: id as PanelToolsToolId, selected: true });\n };\n\n private handleManagedAreaSelect = (event: CustomEvent<string>) => {\n event.stopPropagation();\n this.managedMobileSheetNavOpen = false;\n this.managedMobileDestination = 'area';\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 = (\n event: CustomEvent<PanelNavUserActionDetail>\n ) => {\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(\n this.navigation.currentUrl ?? this.pageChrome.currentUrl ?? '',\n items\n );\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('ds-bar-nav') 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 (preset === 'none' || this.resolvedMode === 'mobile') {\n this.clearGradientPaintVars(targets);\n clearLayoutVars();\n return;\n }\n\n const viewport = this.resolveViewportDimensions();\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.tagName.toLowerCase() === 'ds-bar-nav';\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\n private renderManagedPanelNav() {\n const navigation = this.navigation;\n return (\n <ds-panel-nav\n navStyle={this.navStyle}\n groups={this.resolvedNavigationGroups}\n breakpoint={SHELL_DESKTOP_BREAKPOINT}\n routerMode={navigation.routerMode ?? 'event'}\n activeId={navigation.activeId ?? ''}\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={\n navigation.dashboardNavigationLabel ?? 'Dashboard navigation'\n }\n settingsNavigationLabel={\n navigation.settingsNavigationLabel ?? 'Settings navigation'\n }\n navigationItemsLabel={navigation.navigationLabel ?? 'Navigation items'}\n onDsNavSelect={this.handleManagedNavSelect}\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.pageChrome.tabs ?? []}\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 renderManagedToolSlots() {\n const ids = this.resolvedToolItems.map(item => item.id);\n return ids.flatMap(id => [\n <slot name={id} slot={id} />,\n <slot name={`${id}-view`} slot={`${id}-view`} />,\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 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 >\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 navigationLabel={navigation.navigationLabel ?? 'Application navigation'}\n dashboardLabel={navigation.dashboardLabel ?? 'Dashboard'}\n settingsLabel={navigation.settingsLabel ?? 'Settings'}\n accountLabel={navigation.accountLabel ?? 'Account'}\n onDsAreaSelect={this.handleManagedAreaSelect}\n onDsBrowseContextChange={this.handleManagedBrowseContext}\n onDsClose={this.handleManagedSheetClose}\n />\n );\n }\n\n private renderManagedMobileBarNav() {\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={this.tools.items?.find(item => item.id === 'search')?.ariaLabel ?? 'Search'}\n inboxLabel={this.tools.inboxLabel ?? 'Inbox'}\n messagesLabel={this.tools.items?.find(item => item.id === 'messages')?.ariaLabel ?? 'Messages'}\n agentsLabel={this.tools.items?.find(item => item.id === 'agents')?.ariaLabel ?? 'Agents'}\n helpLabel={this.tools.items?.find(item => item.id === 'help')?.ariaLabel ?? 'Help & Support'}\n searchDot={this.toolDot('search')}\n inboxDot={\n this.toolDot('stacks') || this.toolDot('activity')\n }\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>) =>\n this.dsPageBack.emit(event.detail)\n }\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 isActive={!!action.expanded}\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 renderManagedPage() {\n const page = this.pageChrome;\n const mobileSections = page.showBack ? [] : (page.tabs ?? []);\n const barTitleSections: BarTitleSectionItem[] = (page.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 return (\n <ds-shell-page\n responsiveMode={this.resolvedMode}\n headerCapacity={resolveManagedShellPageCapacity(\n this.resolvedMode,\n this.managedToolsOpen\n )}\n contentInset={page.contentInset ?? 'default'}\n >\n <ds-bar-title\n slot=\"header\"\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={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 onDsBack={this.handleManagedPageBack}\n onDsBreadcrumbSelect={this.handleManagedBreadcrumbSelect}\n onDsSectionChange={this.handleManagedSubsectionChange}\n onDsAction={this.handleManagedPageAction}\n />\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={\n page.subsectionsAriaLabel ?? 'Change page subsection'\n }\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 <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 aria-hidden={\n mobile && (!mobileToolActive || this.mobileSheetNavOpen)\n ? 'true'\n : undefined\n }\n inert={\n mobile && (!mobileToolActive || this.mobileSheetNavOpen)\n ? true\n : undefined\n }\n hidden={mobile && !mobileToolActive}\n >\n <slot name=\"tools\" />\n </div>\n <div\n class=\"shell-app__content ds-focus-ring\"\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={\n mobile && this.mobileSheetNavOpen ? undefined : 'true'\n }\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 mobile = this.resolvedMode === 'mobile';\n const mobileToolActive = mobile && this.effectiveMobileDestination !== 'area';\n const mobileStageBlocked =\n mobile && (mobileToolActive || this.effectiveMobileSheetNavOpen);\n const fullscreen = !mobile && this.toolsFullscreen;\n const shellCls: Record<string, boolean> = {\n 'shell-app': true,\n 'shell-app--gradient': chromeActive,\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':\n 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 <div class=\"shell-app__panel\" aria-hidden={fullscreen ? 'true' : undefined} inert={fullscreen ? true : undefined}>\n {this.renderManagedPanelNav()}\n </div>\n <div class=\"shell-app__main\">\n <div class=\"shell-app__bar\" aria-hidden={fullscreen ? 'true' : undefined} inert={fullscreen ? true : undefined}>\n {this.renderManagedBarNav()}\n </div>\n <div\n class=\"shell-app__tools\"\n aria-hidden={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen)\n ? 'true'\n : undefined\n }\n inert={\n mobile && (!mobileToolActive || this.effectiveMobileSheetNavOpen)\n ? true\n : undefined\n }\n hidden={mobile && !mobileToolActive}\n >\n {this.renderManagedTools()}\n </div>\n <div\n class=\"shell-app__content ds-focus-ring\"\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={\n mobile && this.effectiveMobileSheetNavOpen ? undefined : 'true'\n }\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",
|
|
595
595
|
"type": "registry:ui"
|
|
596
596
|
},
|
|
597
597
|
{
|
|
@@ -41,17 +41,17 @@
|
|
|
41
41
|
"accessibility": [
|
|
42
42
|
"Only the active mobile tool view participates in focus or the accessibility tree.",
|
|
43
43
|
"Header Back and application-owned header actions preserve the PanelTools event contract.",
|
|
44
|
-
"Inbox uses the MobileSectionSwitcher popup trigger for
|
|
44
|
+
"Inbox uses the MobileSectionSwitcher popup trigger for Stacks and Activity and never relies on a gesture-only interaction; Messages remains a direct mobile destination."
|
|
45
45
|
],
|
|
46
46
|
"states": [
|
|
47
47
|
"The application owns open and activeTool and replaces headers when tool depth changes.",
|
|
48
|
-
"Items with mobileDestination inbox map to one mobile Inbox destination while retaining the actual active tool id; the canonical recipe groups
|
|
48
|
+
"Items with mobileDestination inbox map to one mobile Inbox destination while retaining the actual active tool id; the canonical recipe groups Stacks and Activity, while Messages remains a separate destination.",
|
|
49
49
|
"Stable tool ids own named slots; label, order, rail placement, shortcut, and mobile destination are explicit item data.",
|
|
50
50
|
"Switching responsive mode must not recreate application-owned slotted product elements."
|
|
51
51
|
],
|
|
52
52
|
"responsiveBehavior": [
|
|
53
53
|
"Desktop and tablet compose PanelTools-compatible rail and drawer chrome.",
|
|
54
|
-
"Mobile renders one centered MobileHeader for every tool
|
|
54
|
+
"Mobile renders one centered MobileHeader for every tool; Stacks and Activity use Inbox section titles, while Messages remains a direct tool destination."
|
|
55
55
|
],
|
|
56
56
|
"frameworkCaveats": {
|
|
57
57
|
"customElements": [
|
|
@@ -391,7 +391,7 @@
|
|
|
391
391
|
"peerDependencies": {
|
|
392
392
|
"required": [
|
|
393
393
|
"@ds-mo/tokens ^6.0.0",
|
|
394
|
-
"@ds-mo/icons ^6.
|
|
394
|
+
"@ds-mo/icons ^6.5.0"
|
|
395
395
|
],
|
|
396
396
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
397
397
|
}
|
|
@@ -9,8 +9,6 @@ export type SafetyScoreLevel = 'fair' | 'good' | 'excellent';
|
|
|
9
9
|
* product decision rather than a design-system one.
|
|
10
10
|
*/
|
|
11
11
|
export interface OverviewScore {
|
|
12
|
-
/** Category before the trend and figure, for example `Safety score`. */
|
|
13
|
-
label: string;
|
|
14
12
|
/** Display-ready headline figure. */
|
|
15
13
|
value: string | number;
|
|
16
14
|
/** Change against the comparison period. Omit when there is nothing to report. */
|
|
@@ -9,12 +9,14 @@ export declare class MobileBarNav {
|
|
|
9
9
|
sheetNavExpanded: boolean;
|
|
10
10
|
menuLabel: string;
|
|
11
11
|
searchLabel: string;
|
|
12
|
-
agentsLabel: string;
|
|
13
12
|
inboxLabel: string;
|
|
13
|
+
messagesLabel: string;
|
|
14
|
+
agentsLabel: string;
|
|
14
15
|
helpLabel: string;
|
|
15
16
|
searchDot: boolean;
|
|
16
|
-
agentsDot: boolean;
|
|
17
17
|
inboxDot: boolean;
|
|
18
|
+
messagesDot: boolean;
|
|
19
|
+
agentsDot: boolean;
|
|
18
20
|
private pressedItemId;
|
|
19
21
|
dsSheetNavToggle: EventEmitter<boolean>;
|
|
20
22
|
dsDestinationChange: EventEmitter<MobileBarNavDestinationDetail>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Application-owned tool id for the shell rail and responsive tool views. */
|
|
2
2
|
export type PanelToolsToolId = string;
|
|
3
3
|
/** Canonical Motive shell recipe used when item metadata is omitted. */
|
|
4
|
-
export type CanonicalPanelToolsToolId = 'search' | '
|
|
4
|
+
export type CanonicalPanelToolsToolId = 'search' | 'messages' | 'agents' | 'stacks' | 'activity' | 'help';
|
|
5
5
|
export declare const PANEL_TOOLS_TOOL_IDS: CanonicalPanelToolsToolId[];
|
|
6
6
|
/** Rail header slot — mirrors panel-nav M logo row. */
|
|
7
7
|
export declare const PANEL_TOOLS_PRIMARY_TOOL_ID: CanonicalPanelToolsToolId;
|
|
@@ -21,8 +21,8 @@ export interface PanelToolsItem {
|
|
|
21
21
|
order?: number;
|
|
22
22
|
/** Optional shortcut label displayed by the tooltip. */
|
|
23
23
|
shortcutKey?: string;
|
|
24
|
-
/** Mobile destination recipe; canonical ids retain their
|
|
25
|
-
mobileDestination?: 'search' | '
|
|
24
|
+
/** Mobile destination recipe; canonical ids retain their explicit defaults. */
|
|
25
|
+
mobileDestination?: 'search' | 'inbox' | 'messages' | 'agents' | 'help';
|
|
26
26
|
/** Whether this rail button is currently pressed/active. */
|
|
27
27
|
selected?: boolean;
|
|
28
28
|
/** Show a notification dot. */
|
|
@@ -1426,6 +1426,14 @@ export namespace Components {
|
|
|
1426
1426
|
* @default 'Menu'
|
|
1427
1427
|
*/
|
|
1428
1428
|
"menuLabel": string;
|
|
1429
|
+
/**
|
|
1430
|
+
* @default false
|
|
1431
|
+
*/
|
|
1432
|
+
"messagesDot": boolean;
|
|
1433
|
+
/**
|
|
1434
|
+
* @default 'Messages'
|
|
1435
|
+
*/
|
|
1436
|
+
"messagesLabel": string;
|
|
1429
1437
|
/**
|
|
1430
1438
|
* @default false
|
|
1431
1439
|
*/
|
|
@@ -5286,6 +5294,14 @@ declare namespace LocalJSX {
|
|
|
5286
5294
|
* @default 'Menu'
|
|
5287
5295
|
*/
|
|
5288
5296
|
"menuLabel"?: string;
|
|
5297
|
+
/**
|
|
5298
|
+
* @default false
|
|
5299
|
+
*/
|
|
5300
|
+
"messagesDot"?: boolean;
|
|
5301
|
+
/**
|
|
5302
|
+
* @default 'Messages'
|
|
5303
|
+
*/
|
|
5304
|
+
"messagesLabel"?: string;
|
|
5289
5305
|
"onDsDestinationChange"?: (event: DsMobileBarNavCustomEvent<MobileBarNavDestinationDetail>) => void;
|
|
5290
5306
|
"onDsSheetNavToggle"?: (event: DsMobileBarNavCustomEvent<boolean>) => void;
|
|
5291
5307
|
/**
|
|
@@ -7033,12 +7049,14 @@ declare namespace LocalJSX {
|
|
|
7033
7049
|
"sheetNavExpanded": boolean;
|
|
7034
7050
|
"menuLabel": string;
|
|
7035
7051
|
"searchLabel": string;
|
|
7036
|
-
"agentsLabel": string;
|
|
7037
7052
|
"inboxLabel": string;
|
|
7053
|
+
"messagesLabel": string;
|
|
7054
|
+
"agentsLabel": string;
|
|
7038
7055
|
"helpLabel": string;
|
|
7039
7056
|
"searchDot": boolean;
|
|
7040
|
-
"agentsDot": boolean;
|
|
7041
7057
|
"inboxDot": boolean;
|
|
7058
|
+
"messagesDot": boolean;
|
|
7059
|
+
"agentsDot": boolean;
|
|
7042
7060
|
}
|
|
7043
7061
|
interface DsMobileHeaderAttributes {
|
|
7044
7062
|
"heading": string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PanelToolsToolId } from '../components/PanelTools/panel-tools-types';
|
|
2
2
|
export type ShellResponsiveMode = 'desktop' | 'tablet' | 'mobile';
|
|
3
|
-
export type MobileDestination = 'area' | 'search' | '
|
|
3
|
+
export type MobileDestination = 'area' | 'search' | 'inbox' | 'messages' | 'agents' | 'help';
|
|
4
4
|
export type ShellInboxToolId = PanelToolsToolId;
|
|
5
5
|
export declare const SHELL_DESKTOP_BREAKPOINT = 1200;
|
|
6
6
|
export declare const SHELL_MOBILE_BREAKPOINT = 768;
|