@ds-mo/ui 12.3.0 → 12.4.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.
Files changed (143) hide show
  1. package/dist/.build-stamp +1 -1
  2. package/dist/agent-patterns.json +89 -11
  3. package/dist/agent.json +543 -9
  4. package/dist/angular/ds-shell-app.d.ts +13 -2
  5. package/dist/angular/ds-shell-app.js +9 -5
  6. package/dist/angular/ds-shell-mobile-bar.d.ts +18 -0
  7. package/dist/angular/ds-shell-mobile-bar.js +46 -0
  8. package/dist/angular/ds-shell-mobile-nav.d.ts +23 -0
  9. package/dist/angular/ds-shell-mobile-nav.js +51 -0
  10. package/dist/angular/ds-shell-mobile-section-nav.d.ts +18 -0
  11. package/dist/angular/ds-shell-mobile-section-nav.js +42 -0
  12. package/dist/angular/ds-shell-tools.d.ts +42 -0
  13. package/dist/angular/ds-shell-tools.js +52 -0
  14. package/dist/angular/index.d.ts +1 -1
  15. package/dist/angular/index.js +4 -0
  16. package/dist/angular/proxies.d.ts +4 -0
  17. package/dist/angular/proxies.js +4 -0
  18. package/dist/components/ds-agent-response.js +1 -1
  19. package/dist/components/ds-bar-nav.js +1 -1
  20. package/dist/components/ds-bar-title.js +1 -1
  21. package/dist/components/ds-bar-workflow.js +1 -1
  22. package/dist/components/ds-chart-donut.js +1 -1
  23. package/dist/components/ds-code-block.js +1 -1
  24. package/dist/components/ds-conversation-list-item.js +1 -1
  25. package/dist/components/ds-conversation-list-item.js.map +1 -1
  26. package/dist/components/ds-markdown.js +1 -1
  27. package/dist/components/ds-menu.js +1 -1
  28. package/dist/components/ds-message-composer.js +1 -1
  29. package/dist/components/ds-message-scroller.js +1 -1
  30. package/dist/components/ds-modal.js +1 -1
  31. package/dist/components/ds-modal.js.map +1 -1
  32. package/dist/components/ds-panel-nav.js +1 -1
  33. package/dist/components/ds-panel-nav.js.map +1 -1
  34. package/dist/components/ds-panel-tool-header.js +1 -1
  35. package/dist/components/ds-panel-tool-search.js +1 -1
  36. package/dist/components/ds-panel-tools.js +1 -1
  37. package/dist/components/ds-panel-tools.js.map +1 -1
  38. package/dist/components/ds-select-multi.js +1 -1
  39. package/dist/components/ds-select-multi.js.map +1 -1
  40. package/dist/components/ds-select.js +1 -1
  41. package/dist/components/ds-select.js.map +1 -1
  42. package/dist/components/ds-shell-app.js +1 -1
  43. package/dist/components/ds-shell-app.js.map +1 -1
  44. package/dist/components/ds-shell-gradient-picker.js +1 -1
  45. package/dist/components/ds-shell-gradient-picker.js.map +1 -1
  46. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  47. package/dist/components/ds-shell-mobile-bar.d.ts +11 -0
  48. package/dist/components/ds-shell-mobile-bar.js +2 -0
  49. package/dist/components/ds-shell-mobile-bar.js.map +1 -0
  50. package/dist/components/ds-shell-mobile-nav.d.ts +11 -0
  51. package/dist/components/ds-shell-mobile-nav.js +2 -0
  52. package/dist/components/ds-shell-mobile-nav.js.map +1 -0
  53. package/dist/components/ds-shell-mobile-section-nav.d.ts +11 -0
  54. package/dist/components/ds-shell-mobile-section-nav.js +2 -0
  55. package/dist/components/ds-shell-mobile-section-nav.js.map +1 -0
  56. package/dist/components/ds-shell-page.js +1 -1
  57. package/dist/components/ds-shell-page.js.map +1 -1
  58. package/dist/components/ds-shell-tools.d.ts +11 -0
  59. package/dist/components/ds-shell-tools.js +2 -0
  60. package/dist/components/ds-shell-tools.js.map +1 -0
  61. package/dist/components/ds-swatch-picker.js +1 -1
  62. package/dist/components/ds-tab-group.js +1 -1
  63. package/dist/components/ds-tab-group.js.map +1 -1
  64. package/dist/components/ds-toast.js +1 -1
  65. package/dist/components/ds-toast.js.map +1 -1
  66. package/dist/components/ds-tooltip-data-viz.js +1 -1
  67. package/dist/components/ds-tooltip.js +1 -1
  68. package/dist/components/ds-typing-indicator.js +1 -1
  69. package/dist/components/p-B6Vuayt6.js +2 -0
  70. package/dist/components/p-B6Vuayt6.js.map +1 -0
  71. package/dist/components/p-BPS9VExm.js +2 -0
  72. package/dist/components/{p-D3DYxbC5.js.map → p-BPS9VExm.js.map} +1 -1
  73. package/dist/components/{p-Bi_bxbih.js → p-Biz6xo-z.js} +2 -2
  74. package/dist/components/p-BrusnEYP.js +2 -0
  75. package/dist/components/p-BrusnEYP.js.map +1 -0
  76. package/dist/components/p-C_0Q8Cnp.js +2 -0
  77. package/dist/components/{p-Dg3xLna0.js.map → p-C_0Q8Cnp.js.map} +1 -1
  78. package/dist/components/p-CgHTNfC1.js +2 -0
  79. package/dist/components/p-CgHTNfC1.js.map +1 -0
  80. package/dist/components/p-Co8hD3P-.js +2 -0
  81. package/dist/components/{p-DC7yOUQx.js.map → p-Co8hD3P-.js.map} +1 -1
  82. package/dist/components/{p-Cuw2Lo2g.js → p-CtD24EyI.js} +2 -2
  83. package/dist/components/{p-C0NPJ0h2.js → p-DXnFhmZF.js} +2 -2
  84. package/dist/components/p-NUNsefU1.js +2 -0
  85. package/dist/components/{p-CWlh7HAg.js.map → p-NUNsefU1.js.map} +1 -1
  86. package/dist/components/p-PZ6R_nGI.js +2 -0
  87. package/dist/components/p-PZ6R_nGI.js.map +1 -0
  88. package/dist/components/{p-J6xK6Ihq.js → p-g40DTeV8.js} +2 -2
  89. package/dist/components/p-yhj1lIPx.js +2 -0
  90. package/dist/components/p-yhj1lIPx.js.map +1 -0
  91. package/dist/docs/components.json +2143 -115
  92. package/dist/lib/components/BarNav/bar-nav-types.d.ts +2 -0
  93. package/dist/lib/components/PanelNav/panel-nav-types.d.ts +29 -0
  94. package/dist/lib/components/PanelTools/panel-tools-types.d.ts +40 -0
  95. package/dist/lib/components/TabGroup/tab-item-utils.d.ts +31 -0
  96. package/dist/lib/shell/index.d.ts +5 -0
  97. package/dist/lib/shell/index.js +36 -0
  98. package/dist/lib/shell/index.js.map +3 -3
  99. package/dist/lib/shell/shell-responsive.d.ts +13 -0
  100. package/dist/mcp-data/registry/conversation-list-item.json +1 -1
  101. package/dist/mcp-data/registry/modal.json +1 -1
  102. package/dist/mcp-data/registry/registry.json +554 -8
  103. package/dist/mcp-data/registry/shell-app.json +71 -11
  104. package/dist/mcp-data/registry/shell-mobile-bar.json +314 -0
  105. package/dist/mcp-data/registry/shell-mobile-nav.json +348 -0
  106. package/dist/mcp-data/registry/shell-mobile-section-nav.json +267 -0
  107. package/dist/mcp-data/registry/shell-page.json +1 -1
  108. package/dist/mcp-data/registry/shell-tools.json +450 -0
  109. package/dist/react/components.d.ts +4 -0
  110. package/dist/react/components.js +4 -0
  111. package/dist/react/ds-shell-app.d.ts +7 -2
  112. package/dist/react/ds-shell-app.js +1 -1
  113. package/dist/react/ds-shell-mobile-bar.d.ts +13 -0
  114. package/dist/react/ds-shell-mobile-bar.js +15 -0
  115. package/dist/react/ds-shell-mobile-nav.d.ts +15 -0
  116. package/dist/react/ds-shell-mobile-nav.js +17 -0
  117. package/dist/react/ds-shell-mobile-section-nav.d.ts +12 -0
  118. package/dist/react/ds-shell-mobile-section-nav.js +12 -0
  119. package/dist/react/ds-shell-tools.d.ts +25 -0
  120. package/dist/react/ds-shell-tools.js +17 -0
  121. package/dist/types/components/ShellApp/ShellApp.d.ts +16 -0
  122. package/dist/types/components/ShellMobileBar/ShellMobileBar.d.ts +25 -0
  123. package/dist/types/components/ShellMobileBar/index.d.ts +4 -0
  124. package/dist/types/components/ShellMobileBar/shell-mobile-bar-types.d.ts +5 -0
  125. package/dist/types/components/ShellMobileNav/ShellMobileNav.d.ts +32 -0
  126. package/dist/types/components/ShellMobileNav/index.d.ts +2 -0
  127. package/dist/types/components/ShellMobileNav/shell-mobile-nav-types.d.ts +6 -0
  128. package/dist/types/components/ShellMobileSectionNav/ShellMobileSectionNav.d.ts +30 -0
  129. package/dist/types/components/ShellMobileSectionNav/index.d.ts +2 -0
  130. package/dist/types/components/ShellTools/ShellTools.d.ts +61 -0
  131. package/dist/types/components/ShellTools/index.d.ts +3 -0
  132. package/dist/types/components.d.ts +607 -2
  133. package/dist/types/shell/index.d.ts +5 -0
  134. package/dist/types/shell/shell-responsive.d.ts +14 -0
  135. package/package.json +1 -1
  136. package/dist/components/p-CWlh7HAg.js +0 -2
  137. package/dist/components/p-D3DYxbC5.js +0 -2
  138. package/dist/components/p-DC7yOUQx.js +0 -2
  139. package/dist/components/p-Dg3xLna0.js +0 -2
  140. /package/dist/components/{p-Bi_bxbih.js.map → p-Biz6xo-z.js.map} +0 -0
  141. /package/dist/components/{p-Cuw2Lo2g.js.map → p-CtD24EyI.js.map} +0 -0
  142. /package/dist/components/{p-C0NPJ0h2.js.map → p-DXnFhmZF.js.map} +0 -0
  143. /package/dist/components/{p-J6xK6Ihq.js.map → p-g40DTeV8.js.map} +0 -0
@@ -16,7 +16,7 @@
16
16
  "summary": "The single required workspace root for standard authenticated application pages, coordinating primary and secondary navigation, tools chrome, responsive composition, content geometry, shared styling, and shell behavior.",
17
17
  "useWhen": [
18
18
  "Building any standard authenticated product or workspace page with CompoMo.",
19
- "PanelNav, BarNav, PanelTools, page content, shared chrome, responsive behavior, and shell shortcuts must be composed as one application frame.",
19
+ "PanelNav, BarNav, ShellTools, mobile navigation, page content, shared chrome, responsive behavior, and shell shortcuts must be composed as one application frame.",
20
20
  "Future application templates need the supported base-page composition boundary."
21
21
  ],
22
22
  "avoidWhen": [
@@ -28,6 +28,10 @@
28
28
  "component:ds-panel-nav",
29
29
  "component:ds-bar-nav",
30
30
  "component:ds-panel-tools",
31
+ "component:ds-shell-tools",
32
+ "component:ds-shell-mobile-nav",
33
+ "component:ds-shell-mobile-section-nav",
34
+ "component:ds-shell-mobile-bar",
31
35
  "component:ds-shell-page",
32
36
  "component:ds-swatch-picker"
33
37
  ],
@@ -38,16 +42,17 @@
38
42
  "accessibility": [
39
43
  "Keep primary destinations in PanelNav and secondary route sections in BarNav.",
40
44
  "Ensure the default content region contains the page's single main landmark.",
41
- "Keep PanelNav, BarNav, and PanelTools chrome available in the shell's supported desktop and tablet compositions.",
45
+ "Keep PanelNav, BarNav, and PanelTools-compatible chrome available in desktop and tablet compositions and the persistent mobile bar available below 768px.",
42
46
  "Do not override browser keyboard chords when enabling shell shortcuts.",
43
47
  "Require Search and Help as the minimum PanelTools actions and provide accessible labels for every tool.",
44
48
  "Preserve focus and accessible names across navigation, collapse, overflow, and responsive presentation changes."
45
49
  ],
46
50
  "states": [
47
51
  "Render exactly one ShellApp as the workspace root and never nest it.",
48
- "PanelNav, BarNav, and PanelTools are required shell regions; PanelTools contains at least Search and Help even when no additional product tools apply.",
52
+ "PanelNav, BarNav, and ShellTools are the responsive shell regions; ShellTools contains at least Search and Help even when no additional product tools apply.",
49
53
  "The application router owns the current route, authorization filtering, Dashboard or Settings context, destination collections, section collections, and localized content labels.",
50
- "ShellApp propagates navStyle and coordinates layout, gradient alignment, transitions, metrics, shortcuts, and responsive composition.",
54
+ "ShellApp propagates navStyle and resolved responsive mode and coordinates layout, gradient alignment, transitions, metrics, shortcuts, and mobile-stage visibility.",
55
+ "The application controls mobileDestination and mobileNavigationOpen and keeps router state independent from global tool-surface changes.",
51
56
  "Set gradientPreset to the single resolved global chrome preference: neutral by default, none for solid chrome, or cool, neutral, warm, and fresh for token-based washes.",
52
57
  "The application owns gradient preference persistence, cross-tab synchronization, and server or account precedence; ShellApp owns rendering the resolved preset across authenticated routes.",
53
58
  "Dashboard represents day-to-day operational work while Settings represents administrative and configuration work; route state selects the context.",
@@ -60,9 +65,10 @@
60
65
  "ShellApp owns the fixed system breakpoints and resolves desktop at 1200px and wider, tablet from 768px through 1199px, and mobile below 768px.",
61
66
  "On desktop, PanelNav defaults expanded, permits user collapse or expansion, and respects the locally persisted desktop preference.",
62
67
  "On tablet, ShellApp keeps PanelNav permanently collapsed and prevents user expansion without overwriting the desktop preference.",
63
- "On mobile, ShellApp remains the page root but replaces PanelNav and desktop navigation composition with a future dedicated mobile navigation design.",
68
+ "On mobile, ShellApp hides and inerts PanelNav and BarNav, uses a solid primary stage, and composes MobileNav, MobileSectionNav, ShellTools, and the persistent five-item MobileBar.",
69
+ "Mobile global surfaces conceal and inert the routed workspace without unmounting it, preserving nested routes and scroll state.",
64
70
  "Keep PanelNav width, BarNav height, PanelTools chrome, and main-content origin stable except during intentional responsive or collapse transitions.",
65
- "The current runtime does not yet implement the complete desktop, tablet, and mobile composition contract; consuming applications must enforce missing breakpoint behavior until ShellApp provides it."
71
+ "The bottom bar remains visible at all route and tool depths in the foundation release."
66
72
  ],
67
73
  "frameworkCaveats": {
68
74
  "customElements": [
@@ -70,7 +76,7 @@
70
76
  "Stamp route-derived nav-style before components connect when deterministic first paint is required."
71
77
  ],
72
78
  "react": [
73
- "Use generated wrappers and preserve native slot names for PanelNav, BarNav, PanelTools, and main content.",
79
+ "Use generated wrappers and preserve native slot names for desktop navigation, responsive tools, mobile navigation, and main content.",
74
80
  "Resolve all shell inputs from one router state before rendering the composition."
75
81
  ],
76
82
  "angular": [
@@ -109,6 +115,24 @@
109
115
  "mutable": false,
110
116
  "description": "Shell chrome wash preset. `none` renders solid chrome; the remaining\npresets use token-based washes that adapt to the active color theme."
111
117
  },
118
+ "mobileDestination": {
119
+ "type": "ShellMobileDestination",
120
+ "resolvedType": "\"agents\" | \"area\" | \"inbox\" | \"search\"",
121
+ "attribute": "mobile-destination",
122
+ "default": "'area'",
123
+ "required": false,
124
+ "mutable": false,
125
+ "description": "Controlled mobile surface shown above the persistent bottom bar."
126
+ },
127
+ "mobileNavigationOpen": {
128
+ "type": "boolean",
129
+ "resolvedType": "boolean",
130
+ "attribute": "mobile-navigation-open",
131
+ "default": "false",
132
+ "required": false,
133
+ "mutable": false,
134
+ "description": "Controlled full-stage mobile navigation-pane state."
135
+ },
112
136
  "navStyle": {
113
137
  "type": "NavChromeStyle",
114
138
  "resolvedType": "\"dashboard\" | \"settings\"",
@@ -128,7 +152,16 @@
128
152
  "description": "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."
129
153
  }
130
154
  },
131
- "events": [],
155
+ "events": [
156
+ {
157
+ "name": "dsResponsiveModeChange",
158
+ "detail": "{ mode: ShellResponsiveMode }",
159
+ "bubbles": true,
160
+ "cancelable": true,
161
+ "composed": true,
162
+ "description": "Emitted after crossing the fixed 768px or 1200px shell boundaries."
163
+ }
164
+ ],
132
165
  "methods": [],
133
166
  "slots": []
134
167
  },
@@ -142,6 +175,24 @@
142
175
  "mutable": false,
143
176
  "description": "Shell chrome wash preset. `none` renders solid chrome; the remaining\npresets use token-based washes that adapt to the active color theme."
144
177
  },
178
+ "mobileDestination": {
179
+ "type": "ShellMobileDestination",
180
+ "resolvedType": "\"agents\" | \"area\" | \"inbox\" | \"search\"",
181
+ "attribute": "mobile-destination",
182
+ "default": "'area'",
183
+ "required": false,
184
+ "mutable": false,
185
+ "description": "Controlled mobile surface shown above the persistent bottom bar."
186
+ },
187
+ "mobileNavigationOpen": {
188
+ "type": "boolean",
189
+ "resolvedType": "boolean",
190
+ "attribute": "mobile-navigation-open",
191
+ "default": "false",
192
+ "required": false,
193
+ "mutable": false,
194
+ "description": "Controlled full-stage mobile navigation-pane state."
195
+ },
145
196
  "navStyle": {
146
197
  "type": "NavChromeStyle",
147
198
  "resolvedType": "\"dashboard\" | \"settings\"",
@@ -161,7 +212,16 @@
161
212
  "description": "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."
162
213
  }
163
214
  },
164
- "events": [],
215
+ "events": [
216
+ {
217
+ "name": "dsResponsiveModeChange",
218
+ "detail": "{ mode: ShellResponsiveMode }",
219
+ "bubbles": true,
220
+ "cancelable": true,
221
+ "composed": true,
222
+ "description": "Emitted after crossing the fixed 768px or 1200px shell boundaries."
223
+ }
224
+ ],
165
225
  "methods": [],
166
226
  "slots": [],
167
227
  "exports": {
@@ -201,12 +261,12 @@
201
261
  "files": [
202
262
  {
203
263
  "path": "src/wc/components/ShellApp/ShellApp.css",
204
- "content": ":host {\n display: block;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n overflow: hidden;\n overscroll-behavior: none;\n --ds-shell-panel-tools-width: var(--dimension-panel-width-xs);\n}\n\n.shell-app {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n overflow: hidden;\n overscroll-behavior: none;\n font-family: var(--typography-font-family, system-ui);\n}\n\n:host {\n --_shell-chrome-bg: var(--color-background-secondary);\n}\n\n.shell-app__row {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n height: 100%;\n position: relative;\n}\n\n/*\n * One chrome layer behind the shell row. Wash uses background-attachment: fixed\n * so the pattern is locked to the viewport — panel/tools collapse only changes the\n * transparent window, not the pattern phase. JS sets wash background-size from\n * readShellViewportDimensions(), not the shell element box.\n */\n.shell-app__chrome {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n background-color: var(--_shell-chrome-bg);\n}\n\n:host(.shell-app--gradient) .shell-app__chrome::before {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-position: 0 0;\n background-attachment: fixed;\n z-index: 1;\n opacity: var(--ds-shell-gradient-opacity, 1);\n background-image: var(--ds-shell-gradient-image, none);\n background-size: var(--ds-shell-gradient-size, auto);\n background-repeat: no-repeat;\n}\n\n.shell-app__panel {\n flex: 0 0 auto;\n min-height: 0;\n height: 100%;\n position: relative;\n /* Above main so resize handle line can paint over the workspace edge */\n z-index: 2;\n}\n\n.shell-app__panel ::slotted(ds-panel-nav) {\n display: block;\n height: 100%;\n}\n\n.shell-app__main {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-areas:\n 'bar tools'\n 'content tools';\n position: relative;\n z-index: 1;\n}\n\n.shell-app__bar {\n grid-area: bar;\n flex: 0 0 auto;\n min-width: 0;\n}\n\n.shell-app__bar ::slotted(ds-bar-nav) {\n display: block;\n}\n\n.shell-app__content {\n grid-area: content;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: none;\n /* Limit reflow to the page column — not on __main (breaks tools flex shrink). */\n contain: layout;\n}\n\n/* Opaque page surface — masks the fixed chrome layer in the content column. */\n.shell-app__content {\n position: relative;\n z-index: 1;\n background-color: var(--color-background-primary);\n}\n\n.shell-app__tools {\n grid-area: tools;\n flex: 0 0 auto;\n min-width: 0;\n min-height: 0;\n height: 100%;\n display: flex;\n position: relative;\n z-index: 0;\n overflow: hidden;\n}\n\n.shell-app__tools ::slotted(ds-panel-tools) {\n display: block;\n height: 100%;\n min-height: 0;\n}\n\n:host(.shell-app--tools-fullscreen) .shell-app__panel,\n:host(.shell-app--tools-fullscreen) .shell-app__bar,\n:host(.shell-app--tools-fullscreen) .shell-app__content {\n visibility: hidden;\n}\n",
264
+ "content": ":host {\n display: block;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n overflow: hidden;\n overscroll-behavior: none;\n --ds-shell-panel-tools-width: var(--dimension-panel-width-xs);\n}\n\n.shell-app {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n overflow: hidden;\n overscroll-behavior: none;\n font-family: var(--typography-font-family, system-ui);\n}\n\n:host {\n --_shell-chrome-bg: var(--color-background-secondary);\n}\n\n.shell-app__row {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n height: 100%;\n position: relative;\n}\n\n/*\n * One chrome layer behind the shell row. Wash uses background-attachment: fixed\n * so the pattern is locked to the viewport — panel/tools collapse only changes the\n * transparent window, not the pattern phase. JS sets wash background-size from\n * readShellViewportDimensions(), not the shell element box.\n */\n.shell-app__chrome {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n background-color: var(--_shell-chrome-bg);\n}\n\n:host(.shell-app--gradient) .shell-app__chrome::before {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-position: 0 0;\n background-attachment: fixed;\n z-index: 1;\n opacity: var(--ds-shell-gradient-opacity, 1);\n background-image: var(--ds-shell-gradient-image, none);\n background-size: var(--ds-shell-gradient-size, auto);\n background-repeat: no-repeat;\n}\n\n.shell-app__panel {\n flex: 0 0 auto;\n min-height: 0;\n height: 100%;\n position: relative;\n /* Above main so resize handle line can paint over the workspace edge */\n z-index: 2;\n}\n\n.shell-app__panel ::slotted(ds-panel-nav) {\n display: block;\n height: 100%;\n}\n\n.shell-app__main {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-areas:\n 'bar tools'\n 'content tools';\n position: relative;\n z-index: 1;\n}\n\n.shell-app__mobile-section,\n.shell-app__mobile-navigation,\n.shell-app__mobile-bar {\n display: none;\n}\n\n.shell-app__bar {\n grid-area: bar;\n flex: 0 0 auto;\n min-width: 0;\n}\n\n.shell-app__bar ::slotted(ds-bar-nav) {\n display: block;\n}\n\n.shell-app__content {\n grid-area: content;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: none;\n /* Limit reflow to the page column — not on __main (breaks tools flex shrink). */\n contain: layout;\n}\n\n/* Opaque page surface — masks the fixed chrome layer in the content column. */\n.shell-app__content {\n position: relative;\n z-index: 1;\n background-color: var(--color-background-primary);\n}\n\n.shell-app__tools {\n grid-area: tools;\n flex: 0 0 auto;\n min-width: 0;\n min-height: 0;\n height: 100%;\n display: flex;\n position: relative;\n z-index: 0;\n overflow: hidden;\n}\n\n.shell-app__tools ::slotted(ds-panel-tools) {\n display: block;\n height: 100%;\n min-height: 0;\n}\n\n.shell-app__tools ::slotted(ds-shell-tools) {\n display: block;\n height: 100%;\n min-height: 0;\n}\n\n:host(.shell-app--tools-fullscreen) .shell-app__panel,\n:host(.shell-app--tools-fullscreen) .shell-app__bar,\n:host(.shell-app--tools-fullscreen) .shell-app__content {\n visibility: hidden;\n}\n\n:host(.shell-app--mobile) {\n --_shell-chrome-bg: var(--color-background-primary);\n}\n\n:host(.shell-app--mobile) .shell-app__chrome,\n:host(.shell-app--mobile) .shell-app__panel,\n:host(.shell-app--mobile) .shell-app__bar {\n display: none;\n}\n\n:host(.shell-app--mobile) .shell-app__main {\n width: 100%;\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr) auto;\n grid-template-areas:\n 'mobile-section'\n 'stage'\n 'mobile-bar';\n background: var(--color-background-primary);\n}\n\n:host(.shell-app--mobile) .shell-app__mobile-section {\n grid-area: mobile-section;\n display: block;\n min-width: 0;\n position: relative;\n z-index: 1;\n}\n\n:host(.shell-app--mobile) .shell-app__content,\n:host(.shell-app--mobile) .shell-app__tools,\n:host(.shell-app--mobile) .shell-app__mobile-navigation {\n grid-area: stage;\n min-width: 0;\n min-height: 0;\n height: 100%;\n position: relative;\n}\n\n:host(.shell-app--mobile) .shell-app__content {\n z-index: 1;\n overflow: auto;\n}\n\n:host(.shell-app--mobile) .shell-app__tools {\n z-index: 2;\n width: 100%;\n overflow: hidden;\n background: var(--color-background-primary);\n}\n\n:host(.shell-app--mobile) .shell-app__mobile-navigation {\n display: block;\n z-index: 3;\n overflow: hidden;\n background: var(--color-background-primary);\n}\n\n:host(.shell-app--mobile) .shell-app__mobile-bar {\n grid-area: mobile-bar;\n display: block;\n min-width: 0;\n position: relative;\n z-index: 4;\n}\n\n:host(.shell-app--mobile) .shell-app__mobile-section[hidden],\n:host(.shell-app--mobile) .shell-app__tools[hidden],\n:host(.shell-app--mobile) .shell-app__mobile-navigation[hidden],\n:host(.shell-app--mobile) .shell-app__mobile-bar[hidden] {\n display: none;\n}\n\n:host(.shell-app--mobile-tool-active) .shell-app__content,\n:host(.shell-app--mobile-navigation-open) .shell-app__content,\n:host(.shell-app--mobile-navigation-open) .shell-app__tools {\n visibility: hidden;\n}\n",
205
265
  "type": "registry:ui"
206
266
  },
207
267
  {
208
268
  "path": "src/wc/components/ShellApp/ShellApp.tsx",
209
- "content": "import { Component, Prop, Element, State, Watch, Listen, h, Host } from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport {\n isEditableShortcutTarget,\n resolveShellShortcut,\n} from '../../shell/shell-shortcuts';\nimport type { PanelToolsToolId } from '../PanelTools/panel-tools-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';\n\n@Component({\n tag: 'ds-shell-app',\n styleUrl: 'ShellApp.css',\n scoped: true,\n})\nexport class ShellApp {\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 @Element() el!: HTMLElement;\n @State() private toolsFullscreen = false;\n\n private resizeObserver: ResizeObserver | null = null;\n private readonly panelNavTransition = new ChromeTransitionDepth();\n private readonly chromeSyncCoalescer = createRafCoalescer(() => this.syncChrome());\n private panelWidthTokens: PanelNavWidthTokens = { expandedPx: 0, collapsedPx: 0 };\n private cachedViewportWidth = 0;\n private cachedViewportHeight = 0;\n private onWindowResize = () => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n private onVisualViewportChange = () => {\n if (this.panelNavTransition.isActive) return;\n this.scheduleChromeSync();\n };\n\n componentDidLoad() {\n this.syncSlottedNavStyle();\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-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 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 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 normalizeShellGradientPreset(this.gradientPreset) !== 'none';\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-panel-tools') as HTMLDsPanelToolsElement | 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 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') {\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 render() {\n const chromeActive = this.chromeLayerActive();\n const shellCls: Record<string, boolean> = {\n 'shell-app': true,\n 'shell-app--gradient': chromeActive,\n [`shell-app--${this.navStyle}`]: true,\n 'shell-app--tools-fullscreen': this.toolsFullscreen,\n };\n\n return (\n <Host class={shellCls}>\n <div class=\"shell-app__row\">\n <div class=\"shell-app__chrome\" aria-hidden=\"true\" />\n <div class=\"shell-app__panel\" aria-hidden={this.toolsFullscreen ? 'true' : undefined} inert={this.toolsFullscreen ? true : undefined}>\n <slot name=\"panel\" />\n </div>\n <div class=\"shell-app__main\">\n <div class=\"shell-app__bar\" aria-hidden={this.toolsFullscreen ? 'true' : undefined} inert={this.toolsFullscreen ? true : undefined}>\n <slot name=\"bar\" />\n </div>\n <div class=\"shell-app__tools\">\n <slot name=\"tools\" />\n </div>\n <div class=\"shell-app__content\" aria-hidden={this.toolsFullscreen ? 'true' : undefined} inert={this.toolsFullscreen ? true : undefined}>\n <slot />\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
269
+ "content": "import {\n Component,\n Prop,\n Element,\n Event,\n EventEmitter,\n State,\n Watch,\n Listen,\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 { PanelToolsToolId } from '../PanelTools/panel-tools-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 resolveShellResponsiveMode,\n type ShellMobileDestination,\n type ShellResponsiveMode,\n} from '../../shell/shell-responsive';\n\n@Component({\n tag: 'ds-shell-app',\n styleUrl: 'ShellApp.css',\n scoped: true,\n})\nexport class ShellApp {\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: ShellMobileDestination = 'area';\n\n /** Controlled full-stage mobile navigation-pane state. */\n @Prop({ attribute: 'mobile-navigation-open' })\n mobileNavigationOpen: boolean = false;\n\n /** Emitted after crossing the fixed 768px or 1200px shell boundaries. */\n @Event() dsResponsiveModeChange!: EventEmitter<{ mode: ShellResponsiveMode }>;\n\n @Element() el!: HTMLElement;\n @State() private toolsFullscreen = false;\n @State() private resolvedMode: ShellResponsiveMode = 'desktop';\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 componentWillLoad() {\n if (typeof window !== 'undefined') {\n this.resolvedMode = resolveShellResponsiveMode(window.innerWidth);\n }\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('mobileDestination')\n @Watch('mobileNavigationOpen')\n onMobileStateChange(_next: unknown, previous: unknown) {\n this.syncSlottedMobileState();\n if (\n previous === true &&\n !this.mobileNavigationOpen &&\n this.resolvedMode === 'mobile'\n ) {\n requestAnimationFrame(() => {\n const bar = this.el.querySelector('ds-shell-mobile-bar') as\n | (HTMLElement & {\n focusDestination?: (\n destination: ShellMobileDestination | 'navigation'\n ) => Promise<void>;\n })\n | null;\n const activeElement = document.activeElement;\n if (activeElement instanceof HTMLElement && bar?.contains(activeElement)) return;\n void bar?.focusDestination?.('navigation');\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-shell-mobile-bar') as\n | (HTMLElement & {\n activeDestination: ShellMobileDestination;\n navigationExpanded: boolean;\n })\n | null;\n const navigation = this.el.querySelector('ds-shell-mobile-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.mobileDestination;\n bar.navigationExpanded = this.mobileNavigationOpen;\n }\n if (navigation) {\n navigation.open = this.mobileNavigationOpen;\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 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 render() {\n const chromeActive = this.chromeLayerActive();\n const mobile = this.resolvedMode === 'mobile';\n const mobileToolActive = mobile && this.mobileDestination !== 'area';\n const mobileStageBlocked = mobile && (mobileToolActive || this.mobileNavigationOpen);\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-navigation-open': mobile && this.mobileNavigationOpen,\n };\n\n return (\n <Host class={shellCls} responsive-mode={this.resolvedMode}>\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 <slot name=\"panel\" />\n </div>\n <div class=\"shell-app__main\">\n <div class=\"shell-app__bar\" aria-hidden={fullscreen ? 'true' : undefined} inert={fullscreen ? true : undefined}>\n <slot name=\"bar\" />\n </div>\n <div\n class=\"shell-app__mobile-section\"\n hidden={!mobile || this.mobileDestination !== 'area' || this.mobileNavigationOpen}\n >\n <slot name=\"mobile-section-nav\" />\n </div>\n <div\n class=\"shell-app__tools\"\n aria-hidden={mobile && (!mobileToolActive || this.mobileNavigationOpen) ? 'true' : undefined}\n inert={mobile && (!mobileToolActive || this.mobileNavigationOpen) ? true : undefined}\n hidden={mobile && !mobileToolActive}\n >\n <slot name=\"tools\" />\n </div>\n <div\n class=\"shell-app__content\"\n aria-hidden={fullscreen || mobileStageBlocked ? 'true' : undefined}\n inert={fullscreen || mobileStageBlocked ? true : undefined}\n >\n <slot />\n </div>\n <div\n class=\"shell-app__mobile-navigation\"\n hidden={!mobile || !this.mobileNavigationOpen}\n aria-hidden={mobile && this.mobileNavigationOpen ? undefined : 'true'}\n inert={mobile && this.mobileNavigationOpen ? undefined : true}\n >\n <slot name=\"mobile-navigation\" />\n </div>\n <div class=\"shell-app__mobile-bar\" hidden={!mobile}>\n <slot name=\"mobile-bar\" />\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
210
270
  "type": "registry:ui"
211
271
  }
212
272
  ]
@@ -0,0 +1,314 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "shell-mobile-bar",
4
+ "title": "ShellMobileBar",
5
+ "description": "Persistent mobile shell navigation for Menu, the router-derived current area, Search, Agents, and Inbox.",
6
+ "type": "registry:ui",
7
+ "meta": {
8
+ "library": "@ds-mo/ui",
9
+ "distribution": "npm",
10
+ "storybook": "https://zainadeel.github.io/compomo/?path=/story/shell-mobile-bar",
11
+ "source": "src/wc/components/ShellMobileBar/ShellMobileBar.tsx",
12
+ "intentStatus": "complete",
13
+ "intent": {
14
+ "audience": "specialized",
15
+ "status": "experimental",
16
+ "summary": "Persistent mobile shell navigation for Menu, the router-derived current area, Search, Agents, and Inbox.",
17
+ "useWhen": [
18
+ "ShellApp is rendering its dedicated below-768px application composition."
19
+ ],
20
+ "avoidWhen": [
21
+ "Rendering desktop or tablet shell chrome.",
22
+ "An arbitrary set of product tabs is required."
23
+ ],
24
+ "commonlyComposedWith": [
25
+ "component:ds-shell-app"
26
+ ],
27
+ "patterns": [
28
+ "pattern:application-shell"
29
+ ],
30
+ "accessibility": [
31
+ "Every item keeps a visible label and accessible name.",
32
+ "Only the active destination exposes aria-current; Menu exposes its controlled pane through aria-expanded."
33
+ ],
34
+ "states": [
35
+ "The item order is fixed: Menu, current area, Search, Agents, Inbox.",
36
+ "Reselecting an active destination does not reset its nested state."
37
+ ],
38
+ "responsiveBehavior": [
39
+ "Render only below the mobile breakpoint and preserve safe-area clearance."
40
+ ],
41
+ "references": [
42
+ {
43
+ "label": "Storybook",
44
+ "path": "src/wc/components/ShellMobileBar/ShellMobileBar.stories.ts"
45
+ }
46
+ ]
47
+ },
48
+ "api": {
49
+ "props": {
50
+ "activeDestination": {
51
+ "type": "ShellMobileDestination",
52
+ "resolvedType": "\"agents\" | \"area\" | \"inbox\" | \"search\"",
53
+ "attribute": "active-destination",
54
+ "default": "'area'",
55
+ "required": false,
56
+ "mutable": false
57
+ },
58
+ "agentsDot": {
59
+ "type": "boolean",
60
+ "resolvedType": "boolean",
61
+ "attribute": "agents-dot",
62
+ "default": "false",
63
+ "required": false,
64
+ "mutable": false
65
+ },
66
+ "agentsLabel": {
67
+ "type": "string",
68
+ "resolvedType": "string",
69
+ "attribute": "agents-label",
70
+ "default": "'Agents'",
71
+ "required": false,
72
+ "mutable": false
73
+ },
74
+ "currentArea": {
75
+ "type": "PanelNavItem",
76
+ "resolvedType": "PanelNavItem",
77
+ "default": "{\n id: 'tracking',\n icon: 'MapPage',\n label: 'Tracking',\n }",
78
+ "required": false,
79
+ "mutable": false
80
+ },
81
+ "inboxDot": {
82
+ "type": "boolean",
83
+ "resolvedType": "boolean",
84
+ "attribute": "inbox-dot",
85
+ "default": "false",
86
+ "required": false,
87
+ "mutable": false
88
+ },
89
+ "inboxLabel": {
90
+ "type": "string",
91
+ "resolvedType": "string",
92
+ "attribute": "inbox-label",
93
+ "default": "'Inbox'",
94
+ "required": false,
95
+ "mutable": false
96
+ },
97
+ "menuLabel": {
98
+ "type": "string",
99
+ "resolvedType": "string",
100
+ "attribute": "menu-label",
101
+ "default": "'Menu'",
102
+ "required": false,
103
+ "mutable": false
104
+ },
105
+ "navigationExpanded": {
106
+ "type": "boolean",
107
+ "resolvedType": "boolean",
108
+ "attribute": "navigation-expanded",
109
+ "default": "false",
110
+ "required": false,
111
+ "mutable": false
112
+ },
113
+ "searchDot": {
114
+ "type": "boolean",
115
+ "resolvedType": "boolean",
116
+ "attribute": "search-dot",
117
+ "default": "false",
118
+ "required": false,
119
+ "mutable": false
120
+ },
121
+ "searchLabel": {
122
+ "type": "string",
123
+ "resolvedType": "string",
124
+ "attribute": "search-label",
125
+ "default": "'Search'",
126
+ "required": false,
127
+ "mutable": false
128
+ }
129
+ },
130
+ "events": [
131
+ {
132
+ "name": "dsDestinationChange",
133
+ "detail": "ShellMobileBarDestinationDetail",
134
+ "bubbles": true,
135
+ "cancelable": true,
136
+ "composed": true
137
+ },
138
+ {
139
+ "name": "dsNavigationToggle",
140
+ "detail": "boolean",
141
+ "bubbles": true,
142
+ "cancelable": true,
143
+ "composed": true
144
+ }
145
+ ],
146
+ "methods": [
147
+ {
148
+ "name": "focusDestination",
149
+ "signature": "focusDestination(destination: ShellMobileDestination | \"navigation\") => Promise<void>"
150
+ }
151
+ ],
152
+ "slots": []
153
+ },
154
+ "props": {
155
+ "activeDestination": {
156
+ "type": "ShellMobileDestination",
157
+ "resolvedType": "\"agents\" | \"area\" | \"inbox\" | \"search\"",
158
+ "attribute": "active-destination",
159
+ "default": "'area'",
160
+ "required": false,
161
+ "mutable": false
162
+ },
163
+ "agentsDot": {
164
+ "type": "boolean",
165
+ "resolvedType": "boolean",
166
+ "attribute": "agents-dot",
167
+ "default": "false",
168
+ "required": false,
169
+ "mutable": false
170
+ },
171
+ "agentsLabel": {
172
+ "type": "string",
173
+ "resolvedType": "string",
174
+ "attribute": "agents-label",
175
+ "default": "'Agents'",
176
+ "required": false,
177
+ "mutable": false
178
+ },
179
+ "currentArea": {
180
+ "type": "PanelNavItem",
181
+ "resolvedType": "PanelNavItem",
182
+ "default": "{\n id: 'tracking',\n icon: 'MapPage',\n label: 'Tracking',\n }",
183
+ "required": false,
184
+ "mutable": false
185
+ },
186
+ "inboxDot": {
187
+ "type": "boolean",
188
+ "resolvedType": "boolean",
189
+ "attribute": "inbox-dot",
190
+ "default": "false",
191
+ "required": false,
192
+ "mutable": false
193
+ },
194
+ "inboxLabel": {
195
+ "type": "string",
196
+ "resolvedType": "string",
197
+ "attribute": "inbox-label",
198
+ "default": "'Inbox'",
199
+ "required": false,
200
+ "mutable": false
201
+ },
202
+ "menuLabel": {
203
+ "type": "string",
204
+ "resolvedType": "string",
205
+ "attribute": "menu-label",
206
+ "default": "'Menu'",
207
+ "required": false,
208
+ "mutable": false
209
+ },
210
+ "navigationExpanded": {
211
+ "type": "boolean",
212
+ "resolvedType": "boolean",
213
+ "attribute": "navigation-expanded",
214
+ "default": "false",
215
+ "required": false,
216
+ "mutable": false
217
+ },
218
+ "searchDot": {
219
+ "type": "boolean",
220
+ "resolvedType": "boolean",
221
+ "attribute": "search-dot",
222
+ "default": "false",
223
+ "required": false,
224
+ "mutable": false
225
+ },
226
+ "searchLabel": {
227
+ "type": "string",
228
+ "resolvedType": "string",
229
+ "attribute": "search-label",
230
+ "default": "'Search'",
231
+ "required": false,
232
+ "mutable": false
233
+ }
234
+ },
235
+ "events": [
236
+ {
237
+ "name": "dsDestinationChange",
238
+ "detail": "ShellMobileBarDestinationDetail",
239
+ "bubbles": true,
240
+ "cancelable": true,
241
+ "composed": true
242
+ },
243
+ {
244
+ "name": "dsNavigationToggle",
245
+ "detail": "boolean",
246
+ "bubbles": true,
247
+ "cancelable": true,
248
+ "composed": true
249
+ }
250
+ ],
251
+ "methods": [
252
+ {
253
+ "name": "focusDestination",
254
+ "signature": "focusDestination(destination: ShellMobileDestination | \"navigation\") => Promise<void>"
255
+ }
256
+ ],
257
+ "slots": [],
258
+ "exports": {
259
+ "customElement": "ds-shell-mobile-bar",
260
+ "react": "DsShellMobileBar",
261
+ "angular": "DsShellMobileBar"
262
+ },
263
+ "consumption": {
264
+ "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
265
+ "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
266
+ "customElements": {
267
+ "import": "import '@ds-mo/ui/dist/components/ds-shell-mobile-bar.js';",
268
+ "example": "<ds-shell-mobile-bar></ds-shell-mobile-bar>"
269
+ },
270
+ "react": {
271
+ "import": "import { DsShellMobileBar } from '@ds-mo/ui/react';",
272
+ "example": "<DsShellMobileBar />"
273
+ },
274
+ "angular": {
275
+ "import": "import { DsShellMobileBar } from '@ds-mo/ui/angular/ds-shell-mobile-bar';",
276
+ "example": "<ds-shell-mobile-bar></ds-shell-mobile-bar>"
277
+ },
278
+ "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: currentArea.",
279
+ "peerDependencies": {
280
+ "required": [
281
+ "@ds-mo/tokens ^3.1.1",
282
+ "@ds-mo/icons ^6.4.0"
283
+ ],
284
+ "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
285
+ }
286
+ }
287
+ },
288
+ "dependencies": [
289
+ "@ds-mo/ui",
290
+ "@ds-mo/tokens",
291
+ "@ds-mo/icons"
292
+ ],
293
+ "registryDependencies": [
294
+ "icon",
295
+ "text"
296
+ ],
297
+ "files": [
298
+ {
299
+ "path": "src/wc/components/ShellMobileBar/ShellMobileBar.css",
300
+ "content": "@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n\n:host {\n display: block;\n min-width: 0;\n background: var(--color-background-primary);\n color: var(--color-foreground-secondary);\n}\n\n.shell-mobile-bar {\n display: grid;\n grid-template-columns: repeat(5, minmax(0, 1fr));\n align-items: stretch;\n min-width: 0;\n padding: var(--dimension-space-050) var(--dimension-space-050)\n max(var(--dimension-space-050), env(safe-area-inset-bottom));\n border-top: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n background: var(--color-background-primary);\n box-sizing: border-box;\n}\n\n.shell-mobile-bar__item {\n appearance: none;\n min-width: 0;\n min-height: var(--dimension-size-600);\n padding: var(--dimension-space-050) var(--dimension-space-025);\n border: 0;\n border-radius: var(--dimension-radius-100);\n background: transparent;\n color: inherit;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n gap: var(--dimension-space-025);\n cursor: pointer;\n}\n\n.shell-mobile-bar__item--selected {\n color: var(--color-foreground-primary);\n}\n\n.shell-mobile-bar__item ds-text {\n display: block;\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.shell-mobile-bar__icon {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dimension-iconography-md);\n height: var(--dimension-iconography-md);\n}\n\n.shell-mobile-bar__dot {\n position: absolute;\n inset-block-start: calc(var(--dimension-space-025) * -1);\n inset-inline-end: calc(var(--dimension-space-025) * -1);\n width: var(--dimension-size-075);\n height: var(--dimension-size-075);\n border-radius: var(--dimension-radius-half);\n background: var(--color-background-bold-brand);\n box-shadow: 0 0 0 var(--dimension-stroke-width-025) var(--color-background-primary);\n}\n",
301
+ "type": "registry:ui"
302
+ },
303
+ {
304
+ "path": "src/wc/components/ShellMobileBar/ShellMobileBar.tsx",
305
+ "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n} from '@stencil/core';\nimport {\n shouldEmitMobileDestinationChange,\n type ShellMobileDestination,\n} from '../../shell/shell-responsive';\nimport type { PanelNavItem } from '../PanelNav/panel-nav-types';\nimport type { ShellMobileBarDestinationDetail } from './shell-mobile-bar-types';\n\ninterface DestinationConfig {\n id: ShellMobileDestination;\n icon: string;\n label: string;\n dot: boolean;\n}\n\n@Component({\n tag: 'ds-shell-mobile-bar',\n styleUrl: 'ShellMobileBar.css',\n scoped: true,\n})\nexport class ShellMobileBar {\n @Element() el!: HTMLElement;\n\n @Prop() activeDestination: ShellMobileDestination = 'area';\n @Prop() currentArea: PanelNavItem = {\n id: 'tracking',\n icon: 'MapPage',\n label: 'Tracking',\n };\n @Prop() navigationExpanded: boolean = false;\n @Prop() menuLabel: string = 'Menu';\n @Prop() searchLabel: string = 'Search';\n @Prop() agentsLabel: string = 'Agents';\n @Prop() inboxLabel: string = 'Inbox';\n @Prop() searchDot: boolean = false;\n @Prop() agentsDot: boolean = false;\n @Prop() inboxDot: boolean = false;\n\n @Event() dsNavigationToggle!: EventEmitter<boolean>;\n @Event() dsDestinationChange!: EventEmitter<ShellMobileBarDestinationDetail>;\n\n private destinationConfig(): DestinationConfig[] {\n return [\n {\n id: 'area',\n icon: this.currentArea.icon || 'MapPage',\n label: this.currentArea.label || 'Area',\n dot: false,\n },\n { id: 'search', icon: 'MagnifyingGlass', label: this.searchLabel, dot: this.searchDot },\n { id: 'agents', icon: 'AI', label: this.agentsLabel, dot: this.agentsDot },\n { id: 'inbox', icon: 'Inbox', label: this.inboxLabel, dot: this.inboxDot },\n ];\n }\n\n private selectDestination(destination: ShellMobileDestination) {\n if (\n !shouldEmitMobileDestinationChange(\n this.activeDestination,\n destination,\n this.navigationExpanded\n )\n ) {\n return;\n }\n this.dsDestinationChange.emit({ destination });\n }\n\n @Method()\n async focusDestination(destination: ShellMobileDestination | 'navigation') {\n const target = this.el.querySelector<HTMLElement>(\n `#${\n destination === 'navigation'\n ? 'ds-shell-mobile-navigation-trigger'\n : `ds-shell-mobile-${destination}-trigger`\n }`\n );\n target?.focus({ preventScroll: true });\n }\n\n private renderDestination(item: DestinationConfig) {\n const selected = !this.navigationExpanded && item.id === this.activeDestination;\n return (\n <button\n id={`ds-shell-mobile-${item.id}-trigger`}\n type=\"button\"\n class={{\n 'shell-mobile-bar__item': true,\n 'shell-mobile-bar__item--selected': selected,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': selected,\n }}\n aria-current={selected ? 'page' : undefined}\n onClick={() => this.selectDestination(item.id)}\n >\n <span class=\"shell-mobile-bar__icon\">\n <ds-icon name={item.icon} size=\"md\" color=\"inherit\" />\n {item.dot && (\n <span class=\"shell-mobile-bar__dot\" aria-hidden=\"true\" />\n )}\n </span>\n <ds-text as=\"span\" variant=\"text-caption\" color=\"inherit\">\n {item.label}\n </ds-text>\n </button>\n );\n }\n\n render() {\n return (\n <Host>\n <nav class=\"shell-mobile-bar\" aria-label=\"Primary\">\n <button\n id=\"ds-shell-mobile-navigation-trigger\"\n type=\"button\"\n class={{\n 'shell-mobile-bar__item': true,\n 'shell-mobile-bar__item--selected': this.navigationExpanded,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': this.navigationExpanded,\n }}\n aria-expanded={String(this.navigationExpanded)}\n aria-controls=\"ds-shell-mobile-navigation\"\n onClick={() => this.dsNavigationToggle.emit(!this.navigationExpanded)}\n >\n <span class=\"shell-mobile-bar__icon\">\n <ds-icon name=\"Hamburger\" size=\"md\" color=\"inherit\" />\n </span>\n <ds-text\n as=\"span\"\n variant=\"text-caption\"\n color=\"inherit\"\n >\n {this.menuLabel}\n </ds-text>\n </button>\n {this.destinationConfig().map(item => this.renderDestination(item))}\n </nav>\n </Host>\n );\n }\n}\n",
306
+ "type": "registry:ui"
307
+ },
308
+ {
309
+ "path": "src/wc/components/ShellMobileBar/shell-mobile-bar-types.ts",
310
+ "content": "import type { ShellMobileDestination } from '../../shell/shell-responsive';\n\nexport interface ShellMobileBarDestinationDetail {\n destination: ShellMobileDestination;\n}\n",
311
+ "type": "registry:ui"
312
+ }
313
+ ]
314
+ }