@ds-mo/ui 9.0.0 → 9.1.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 (122) hide show
  1. package/README.md +28 -0
  2. package/dist/.build-stamp +1 -1
  3. package/dist/agent-patterns.json +233 -0
  4. package/dist/agent.json +155 -19
  5. package/dist/angular/ds-menu.d.ts +6 -1
  6. package/dist/angular/ds-menu.js +7 -4
  7. package/dist/angular/ds-swatch-picker.d.ts +15 -0
  8. package/dist/angular/ds-swatch-picker.js +42 -0
  9. package/dist/angular/index.d.ts +1 -1
  10. package/dist/angular/index.js +1 -0
  11. package/dist/angular/proxies.d.ts +1 -0
  12. package/dist/angular/proxies.js +1 -0
  13. package/dist/components/ds-app-shell.js +1 -1
  14. package/dist/components/ds-bar-nav.js +1 -1
  15. package/dist/components/ds-bar-nav.js.map +1 -1
  16. package/dist/components/ds-menu.js +1 -1
  17. package/dist/components/ds-panel-nav.js +1 -1
  18. package/dist/components/ds-panel-nav.js.map +1 -1
  19. package/dist/components/ds-select-multi.js +1 -1
  20. package/dist/components/ds-select-multi.js.map +1 -1
  21. package/dist/components/ds-select.js +1 -1
  22. package/dist/components/ds-select.js.map +1 -1
  23. package/dist/components/ds-shell-gradient-picker.js +1 -1
  24. package/dist/components/ds-shell-gradient-picker.js.map +1 -1
  25. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  26. package/dist/components/ds-shell-gradient-swatch.js.map +1 -1
  27. package/dist/components/ds-slider.js +1 -1
  28. package/dist/components/ds-slider.js.map +1 -1
  29. package/dist/components/ds-swatch-picker.d.ts +11 -0
  30. package/dist/components/ds-swatch-picker.js +2 -0
  31. package/dist/components/ds-swatch-picker.js.map +1 -0
  32. package/dist/components/ds-tooltip-data-viz.js +1 -1
  33. package/dist/components/p-D1HFMTdC.js +2 -0
  34. package/dist/components/p-D1HFMTdC.js.map +1 -0
  35. package/dist/components/p-HBIxvS5C.js +2 -0
  36. package/dist/components/p-HBIxvS5C.js.map +1 -0
  37. package/dist/components/p-l6PmnW_P.js +2 -0
  38. package/dist/components/p-l6PmnW_P.js.map +1 -0
  39. package/dist/components/p-tQ1QHeOE.js +2 -0
  40. package/dist/components/p-tQ1QHeOE.js.map +1 -0
  41. package/dist/docs/components.json +268 -30
  42. package/dist/lib/components/Menu/menu-placement.d.ts +6 -3
  43. package/dist/lib/shell/index.d.ts +2 -2
  44. package/dist/lib/shell/index.js +40 -39
  45. package/dist/lib/shell/index.js.map +3 -3
  46. package/dist/lib/shell/shell-gradient-presets.d.ts +15 -0
  47. package/dist/lib/utils/choice-popup-alignment.d.ts +20 -0
  48. package/dist/lib/utils/index.d.ts +2 -0
  49. package/dist/lib/utils/index.js +16 -0
  50. package/dist/lib/utils/index.js.map +3 -3
  51. package/dist/mcp/cli.js +31409 -0
  52. package/dist/mcp-data/registry/app-shell.json +210 -0
  53. package/dist/mcp-data/registry/badge.json +289 -0
  54. package/dist/mcp-data/registry/banner.json +206 -0
  55. package/dist/mcp-data/registry/bar-nav.json +341 -0
  56. package/dist/mcp-data/registry/button-filled.json +386 -0
  57. package/dist/mcp-data/registry/button-unfilled.json +537 -0
  58. package/dist/mcp-data/registry/card-data-viz-donut.json +143 -0
  59. package/dist/mcp-data/registry/card-setting.json +194 -0
  60. package/dist/mcp-data/registry/card-shell-data-viz.json +173 -0
  61. package/dist/mcp-data/registry/card.json +126 -0
  62. package/dist/mcp-data/registry/chart-bar.json +121 -0
  63. package/dist/mcp-data/registry/chart-donut.json +257 -0
  64. package/dist/mcp-data/registry/chart-legend.json +209 -0
  65. package/dist/mcp-data/registry/chart-line.json +153 -0
  66. package/dist/mcp-data/registry/checkbox.json +361 -0
  67. package/dist/mcp-data/registry/chip.json +276 -0
  68. package/dist/mcp-data/registry/divider.json +225 -0
  69. package/dist/mcp-data/registry/empty-state.json +197 -0
  70. package/dist/mcp-data/registry/field.json +222 -0
  71. package/dist/mcp-data/registry/icon.json +242 -0
  72. package/dist/mcp-data/registry/input.json +597 -0
  73. package/dist/mcp-data/registry/loader.json +192 -0
  74. package/dist/mcp-data/registry/menu.json +459 -0
  75. package/dist/mcp-data/registry/modal.json +150 -0
  76. package/dist/mcp-data/registry/panel-nav.json +622 -0
  77. package/dist/mcp-data/registry/panel-sub-nav.json +249 -0
  78. package/dist/mcp-data/registry/panel-tools.json +374 -0
  79. package/dist/mcp-data/registry/radio.json +374 -0
  80. package/dist/mcp-data/registry/registry.json +5389 -0
  81. package/dist/mcp-data/registry/select-multi.json +755 -0
  82. package/dist/mcp-data/registry/select.json +740 -0
  83. package/dist/mcp-data/registry/shell-gradient-picker.json +195 -0
  84. package/dist/mcp-data/registry/shell-gradient-swatch.json +215 -0
  85. package/dist/mcp-data/registry/skeleton.json +282 -0
  86. package/dist/mcp-data/registry/slider.json +657 -0
  87. package/dist/mcp-data/registry/swatch-picker.json +231 -0
  88. package/dist/mcp-data/registry/switch.json +361 -0
  89. package/dist/mcp-data/registry/tab-group.json +255 -0
  90. package/dist/mcp-data/registry/tag.json +352 -0
  91. package/dist/mcp-data/registry/text.json +371 -0
  92. package/dist/mcp-data/registry/tooltip-data-viz.json +158 -0
  93. package/dist/mcp-data/registry/tooltip.json +311 -0
  94. package/dist/react/components.d.ts +1 -0
  95. package/dist/react/components.js +1 -0
  96. package/dist/react/ds-menu.d.ts +1 -0
  97. package/dist/react/ds-menu.js +2 -1
  98. package/dist/react/ds-swatch-picker.d.ts +12 -0
  99. package/dist/react/ds-swatch-picker.js +12 -0
  100. package/dist/types/components/Menu/Menu.d.ts +7 -1
  101. package/dist/types/components/Menu/menu-placement.d.ts +6 -3
  102. package/dist/types/components/Menu/menu-types.d.ts +14 -1
  103. package/dist/types/components/ShellGradientPicker/ShellGradientPicker.d.ts +2 -5
  104. package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +1 -0
  105. package/dist/types/components/SwatchPicker/SwatchPicker.d.ts +21 -0
  106. package/dist/types/components/SwatchPicker/swatch-picker-types.d.ts +26 -0
  107. package/dist/types/components.d.ts +117 -0
  108. package/dist/types/shell/index.d.ts +2 -2
  109. package/dist/types/shell/shell-gradient-presets.d.ts +15 -0
  110. package/dist/types/utils/choice-popup-alignment.d.ts +21 -0
  111. package/dist/types/utils/index.d.ts +2 -0
  112. package/package.json +6 -2
  113. package/dist/components/p-Bafp65bw.js +0 -2
  114. package/dist/components/p-Bafp65bw.js.map +0 -1
  115. package/dist/components/p-BdnR3jxi.js +0 -2
  116. package/dist/components/p-BdnR3jxi.js.map +0 -1
  117. package/dist/components/p-C_cu-HIT.js +0 -2
  118. package/dist/components/p-C_cu-HIT.js.map +0 -1
  119. package/dist/components/p-D0B9uDTP.js +0 -2
  120. package/dist/components/p-D0B9uDTP.js.map +0 -1
  121. package/dist/components/p-DF0yLZG_.js +0 -2
  122. package/dist/components/p-DF0yLZG_.js.map +0 -1
@@ -0,0 +1,374 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "panel-tools",
4
+ "title": "PanelTools",
5
+ "description": "Required AppShell utility rail and persistent drawer host for the fixed set of global Search, Agents, Messages, Stacks, Activity, and Help products.",
6
+ "type": "registry:ui",
7
+ "meta": {
8
+ "library": "@ds-mo/ui",
9
+ "distribution": "npm",
10
+ "storybook": "https://zainadeel.github.io/compomo/?path=/story/panel-tools",
11
+ "source": "src/wc/components/PanelTools/PanelTools.tsx",
12
+ "intentStatus": "complete",
13
+ "intent": {
14
+ "audience": "specialized",
15
+ "status": "stable",
16
+ "summary": "Required AppShell utility rail and persistent drawer host for the fixed set of global Search, Agents, Messages, Stacks, Activity, and Help products.",
17
+ "useWhen": [
18
+ "Composing the required global tools region inside the application's single AppShell.",
19
+ "Users need to move between application-wide utilities without losing each tool's in-progress state.",
20
+ "Search, AI conversations, user messages, review stacks, notifications, or help content must remain available alongside route content."
21
+ ],
22
+ "avoidWhen": [
23
+ "The action is specific to the current page or selected content rather than globally available across the application.",
24
+ "The item navigates to a primary or secondary route; use PanelNav or BarNav instead.",
25
+ "A consumer wants to add arbitrary tools, reorder fixed tools, or repurpose a semantic tool id.",
26
+ "Building the mobile tools experience; future mobile AppShell composition uses a different bottom-bar and panel pattern.",
27
+ "PanelTools would be mounted outside the application's single AppShell."
28
+ ],
29
+ "alternatives": [
30
+ {
31
+ "when": "The action navigates to a top-level application destination.",
32
+ "component": "component:ds-panel-nav",
33
+ "reason": "PanelNav exclusively owns primary application navigation."
34
+ },
35
+ {
36
+ "when": "The action navigates between route sections of the active destination.",
37
+ "component": "component:ds-bar-nav",
38
+ "reason": "BarNav exclusively owns secondary route navigation."
39
+ },
40
+ {
41
+ "when": "Temporary contextual actions or options are needed.",
42
+ "component": "component:ds-menu",
43
+ "reason": "Menu owns transient contextual choices rather than persistent global products."
44
+ }
45
+ ],
46
+ "commonlyComposedWith": [
47
+ "component:ds-app-shell",
48
+ "component:ds-panel-nav",
49
+ "component:ds-bar-nav"
50
+ ],
51
+ "patterns": [
52
+ "pattern:application-shell"
53
+ ],
54
+ "accessibility": [
55
+ "Provide localized accessible labels for the rail, shortcut group, and every rendered tool.",
56
+ "Keep Search first in the rail header and Help last in the rail footer so keyboard and visual order remain predictable.",
57
+ "Keep focus on the selected rail action when opening a drawer and allow normal Tab navigation into its content.",
58
+ "Do not trigger global shortcuts while focus is in an editable control.",
59
+ "Use notification dots only as supplemental unread or new-content status and never as the sole status indication.",
60
+ "Explain temporarily inactive tools through an accessible label or adjacent product guidance; omit unauthorized tools entirely."
61
+ ],
62
+ "states": [
63
+ "Search and Help are required; optional Agents, Messages, Stacks, and Activity tools are filtered by the application for authorization and entitlement.",
64
+ "Use only the fixed semantic tool ids and canonical order: Search, Agents, Messages, Stacks, Activity, Help; do not reorder, duplicate, or repurpose them.",
65
+ "Search is application-wide; Agents is user-to-AI chat; Messages is user-to-user chat; Stacks is a specialized inbox of grouped application events for review; Activity is the notification center; Help is drawer content that may link to external resources.",
66
+ "Only one drawer is open at a time; selecting another tool switches directly and selecting the active tool closes to rail-only mode.",
67
+ "The drawer starts closed, remains open across ordinary route changes, and retains every mounted tool's independent conversation, query, scroll, and work state.",
68
+ "When storageKey is set, persist only the last active tool in this browser and still restore the drawer closed after reload.",
69
+ "The application owns unread state; opening Messages, Agents, Stacks, or Activity does not automatically clear its notification dot.",
70
+ "If an active optional tool becomes unavailable, PanelTools closes the drawer, clears activeTool, and emits its deselection.",
71
+ "Use isInactive only for a temporarily unavailable visible tool; omit tools the user is not authorized to access.",
72
+ "The application owns slotted tool products and data while PanelTools owns rail ordering, drawer selection, open state, motion, and shell events."
73
+ ],
74
+ "responsiveBehavior": [
75
+ "On desktop at 1200px and wider, the open drawer reduces main-content width and uses --dimension-panel-width-sm (400px).",
76
+ "On tablet from 768px through 1199px, the open drawer also reduces main-content width and uses --dimension-panel-width-xs (300px).",
77
+ "AppShell owns the responsive drawer-width variable and fixed breakpoint policy; consumers must not hardcode alternate drawer widths.",
78
+ "Below 768px, do not reuse desktop PanelTools: future AppShell mobile composition uses a bottom bar for Search, Agents, Notifications, and Stacks, while Help moves into mobile primary navigation.",
79
+ "Keep named-slot content mounted while tools switch or the drawer closes so users can return to in-progress global work.",
80
+ "Use fixed drawer widths for now; do not add user resizing or width persistence."
81
+ ],
82
+ "frameworkCaveats": {
83
+ "customElements": [
84
+ "Assign items as a JavaScript property for structured data; items-json is the string fallback.",
85
+ "Keep PanelTools mounted at AppShell scope across route changes and provide each product through its matching named slot."
86
+ ],
87
+ "react": [
88
+ "Pass the fixed item collection through the generated React wrapper and keep named-slot product content mounted at AppShell scope.",
89
+ "Use storageKey only for same-browser last-tool continuity; do not persist the open drawer."
90
+ ],
91
+ "angular": [
92
+ "Import DsPanelTools from @ds-mo/ui/angular/ds-panel-tools and bind items as a property.",
93
+ "Keep the component in the persistent shell layout rather than recreating it in routed page components."
94
+ ]
95
+ },
96
+ "references": [
97
+ {
98
+ "label": "Executable Storybook examples",
99
+ "path": "src/wc/components/PanelTools/PanelTools.stories.ts"
100
+ },
101
+ {
102
+ "label": "Framework and shell integration",
103
+ "path": "docs/framework-integration.md"
104
+ },
105
+ {
106
+ "label": "Application shell composition",
107
+ "path": "agent/patterns/application-shell/pattern.agent.json"
108
+ }
109
+ ]
110
+ },
111
+ "api": {
112
+ "props": {
113
+ "activeTool": {
114
+ "type": "PanelToolsToolId | ''",
115
+ "resolvedType": "\"\" | \"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\"",
116
+ "attribute": "active-tool",
117
+ "default": "''",
118
+ "required": false,
119
+ "mutable": true,
120
+ "description": "Active tool view — `search`, `agents`, `messages`, `stacks`, `activity`, or `help`."
121
+ },
122
+ "items": {
123
+ "type": "PanelToolsItem[]",
124
+ "resolvedType": "PanelToolsItem[]",
125
+ "default": "[]",
126
+ "required": false,
127
+ "mutable": false,
128
+ "description": "Rail items rendered in the right column.\nSet via JS property: `el.items = [...]`. Replace the array reference to update."
129
+ },
130
+ "itemsJson": {
131
+ "type": "string",
132
+ "resolvedType": "string",
133
+ "attribute": "items-json",
134
+ "default": "''",
135
+ "required": false,
136
+ "mutable": false,
137
+ "description": "JSON fallback for `items` — useful when framework bindings don't propagate arrays."
138
+ },
139
+ "open": {
140
+ "type": "boolean",
141
+ "resolvedType": "boolean",
142
+ "attribute": "open",
143
+ "default": "false",
144
+ "required": false,
145
+ "mutable": true,
146
+ "description": "When false, only the icon rail is shown."
147
+ },
148
+ "storageKey": {
149
+ "type": "string",
150
+ "resolvedType": "string",
151
+ "attribute": "storage-key",
152
+ "default": "''",
153
+ "required": false,
154
+ "mutable": false,
155
+ "description": "Optional localStorage key for the last active tool. The drawer always starts\nclosed; only the tool identity is restored for continuity within this browser."
156
+ },
157
+ "toolShortcutsLabel": {
158
+ "type": "string",
159
+ "resolvedType": "string",
160
+ "attribute": "tool-shortcuts-label",
161
+ "default": "'Tool shortcuts'",
162
+ "required": false,
163
+ "mutable": false
164
+ },
165
+ "toolsLabel": {
166
+ "type": "string",
167
+ "resolvedType": "string",
168
+ "attribute": "tools-label",
169
+ "default": "'Tools'",
170
+ "required": false,
171
+ "mutable": false
172
+ }
173
+ },
174
+ "events": [
175
+ {
176
+ "name": "dsChromeTransitionEnd",
177
+ "detail": "ChromeTransitionDetail",
178
+ "bubbles": true,
179
+ "cancelable": true,
180
+ "composed": true
181
+ },
182
+ {
183
+ "name": "dsChromeTransitionStart",
184
+ "detail": "ChromeTransitionDetail",
185
+ "bubbles": true,
186
+ "cancelable": true,
187
+ "composed": true,
188
+ "description": "Bubbling lifecycle — `ds-bar-nav` defers overflow checks during drawer motion."
189
+ },
190
+ {
191
+ "name": "dsToolChange",
192
+ "detail": "{\n id: PanelToolsToolId;\n selected: boolean;\n }",
193
+ "bubbles": true,
194
+ "cancelable": true,
195
+ "composed": true,
196
+ "description": "Emitted when a rail button is toggled. Detail = { id, selected }."
197
+ }
198
+ ],
199
+ "methods": [
200
+ {
201
+ "name": "activateTool",
202
+ "signature": "activateTool(id: PanelToolsToolId) => Promise<void>",
203
+ "description": "Toggle any rail tool open/closed — shell shortcuts K/A/S/M/N call this."
204
+ },
205
+ {
206
+ "name": "closeDrawer",
207
+ "signature": "closeDrawer() => Promise<void>",
208
+ "description": "Close the tools drawer when open — used by shell keyboard shortcuts."
209
+ }
210
+ ],
211
+ "slots": []
212
+ },
213
+ "props": {
214
+ "activeTool": {
215
+ "type": "PanelToolsToolId | ''",
216
+ "resolvedType": "\"\" | \"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\"",
217
+ "attribute": "active-tool",
218
+ "default": "''",
219
+ "required": false,
220
+ "mutable": true,
221
+ "description": "Active tool view — `search`, `agents`, `messages`, `stacks`, `activity`, or `help`."
222
+ },
223
+ "items": {
224
+ "type": "PanelToolsItem[]",
225
+ "resolvedType": "PanelToolsItem[]",
226
+ "default": "[]",
227
+ "required": false,
228
+ "mutable": false,
229
+ "description": "Rail items rendered in the right column.\nSet via JS property: `el.items = [...]`. Replace the array reference to update."
230
+ },
231
+ "itemsJson": {
232
+ "type": "string",
233
+ "resolvedType": "string",
234
+ "attribute": "items-json",
235
+ "default": "''",
236
+ "required": false,
237
+ "mutable": false,
238
+ "description": "JSON fallback for `items` — useful when framework bindings don't propagate arrays."
239
+ },
240
+ "open": {
241
+ "type": "boolean",
242
+ "resolvedType": "boolean",
243
+ "attribute": "open",
244
+ "default": "false",
245
+ "required": false,
246
+ "mutable": true,
247
+ "description": "When false, only the icon rail is shown."
248
+ },
249
+ "storageKey": {
250
+ "type": "string",
251
+ "resolvedType": "string",
252
+ "attribute": "storage-key",
253
+ "default": "''",
254
+ "required": false,
255
+ "mutable": false,
256
+ "description": "Optional localStorage key for the last active tool. The drawer always starts\nclosed; only the tool identity is restored for continuity within this browser."
257
+ },
258
+ "toolShortcutsLabel": {
259
+ "type": "string",
260
+ "resolvedType": "string",
261
+ "attribute": "tool-shortcuts-label",
262
+ "default": "'Tool shortcuts'",
263
+ "required": false,
264
+ "mutable": false
265
+ },
266
+ "toolsLabel": {
267
+ "type": "string",
268
+ "resolvedType": "string",
269
+ "attribute": "tools-label",
270
+ "default": "'Tools'",
271
+ "required": false,
272
+ "mutable": false
273
+ }
274
+ },
275
+ "events": [
276
+ {
277
+ "name": "dsChromeTransitionEnd",
278
+ "detail": "ChromeTransitionDetail",
279
+ "bubbles": true,
280
+ "cancelable": true,
281
+ "composed": true
282
+ },
283
+ {
284
+ "name": "dsChromeTransitionStart",
285
+ "detail": "ChromeTransitionDetail",
286
+ "bubbles": true,
287
+ "cancelable": true,
288
+ "composed": true,
289
+ "description": "Bubbling lifecycle — `ds-bar-nav` defers overflow checks during drawer motion."
290
+ },
291
+ {
292
+ "name": "dsToolChange",
293
+ "detail": "{\n id: PanelToolsToolId;\n selected: boolean;\n }",
294
+ "bubbles": true,
295
+ "cancelable": true,
296
+ "composed": true,
297
+ "description": "Emitted when a rail button is toggled. Detail = { id, selected }."
298
+ }
299
+ ],
300
+ "methods": [
301
+ {
302
+ "name": "activateTool",
303
+ "signature": "activateTool(id: PanelToolsToolId) => Promise<void>",
304
+ "description": "Toggle any rail tool open/closed — shell shortcuts K/A/S/M/N call this."
305
+ },
306
+ {
307
+ "name": "closeDrawer",
308
+ "signature": "closeDrawer() => Promise<void>",
309
+ "description": "Close the tools drawer when open — used by shell keyboard shortcuts."
310
+ }
311
+ ],
312
+ "slots": [],
313
+ "exports": {
314
+ "customElement": "ds-panel-tools",
315
+ "react": "DsPanelTools",
316
+ "angular": "DsPanelTools"
317
+ },
318
+ "consumption": {
319
+ "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
320
+ "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
321
+ "customElements": {
322
+ "import": "import '@ds-mo/ui/dist/components/ds-panel-tools.js';",
323
+ "example": "<ds-panel-tools></ds-panel-tools>"
324
+ },
325
+ "react": {
326
+ "import": "import { DsPanelTools } from '@ds-mo/ui/react';",
327
+ "example": "<DsPanelTools />"
328
+ },
329
+ "angular": {
330
+ "import": "import { DsPanelTools } from '@ds-mo/ui/angular/ds-panel-tools';",
331
+ "example": "<ds-panel-tools></ds-panel-tools>"
332
+ },
333
+ "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: items.",
334
+ "peerDependencies": {
335
+ "required": [
336
+ "@ds-mo/tokens ^3.1.1",
337
+ "@ds-mo/icons ^6.2.0"
338
+ ],
339
+ "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
340
+ }
341
+ }
342
+ },
343
+ "dependencies": [
344
+ "@ds-mo/ui",
345
+ "@ds-mo/tokens"
346
+ ],
347
+ "registryDependencies": [
348
+ "button-unfilled",
349
+ "text",
350
+ "tooltip"
351
+ ],
352
+ "files": [
353
+ {
354
+ "path": "src/wc/components/PanelTools/PanelTools.css",
355
+ "content": ":host {\n display: block;\n flex: 0 0 auto;\n box-sizing: border-box;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n --_panel-tools-rail-width: var(--dimension-size-600);\n --_panel-tools-drawer-width: var(\n --ds-shell-panel-tools-width,\n var(--dimension-panel-width-sm)\n );\n --_panel-tools-border: var(--dimension-stroke-width-012, 1px) solid var(--color-border-tertiary);\n --_panel-tools-surface-bg: var(--color-background-secondary);\n /* Match panel-nav expand timing (medium-1); easing differs by direction. */\n --_panel-tools-expand-dur: var(--effect-animation-duration-medium-1, 300ms);\n}\n\n.panel-tools__layout {\n display: flex;\n flex-direction: row-reverse;\n align-items: stretch;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n/* Clip frame — only max-width animates (slide reveal, same as pre-rail :host). */\n.panel-tools__drawer {\n flex: 0 0 auto;\n max-width: 0;\n min-width: 0;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n box-sizing: border-box;\n transition: none;\n}\n\n:host(.panel-tools--ready) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-in-out, ease);\n}\n\n:host(.panel-tools--ready.panel-tools--motion-opening) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-out, ease-out);\n}\n\n:host(.panel-tools--ready.panel-tools--motion-closing) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-in, ease-in);\n}\n\n:host(.panel-tools--open) .panel-tools__drawer {\n max-width: var(--_panel-tools-drawer-width);\n}\n\n.panel-tools__drawer--visible {\n border-left: var(--_panel-tools-border);\n}\n\n/* Fixed-width surface — AppShell supplies 400px desktop / 300px tablet tokens. */\n.panel-tools__drawer-surface {\n display: flex;\n flex-direction: column;\n width: var(--_panel-tools-drawer-width);\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n background-color: var(--_panel-tools-surface-bg);\n}\n\n/* Shell-owned chrome (bg + wash) — drawer + rail are transparent frames. */\n:host-context(ds-app-shell:not([gradient-preset='none'])) .panel-tools__drawer-surface,\n:host-context(ds-app-shell:not([gradient-preset='none'])) .panel-tools__rail {\n background-color: transparent;\n}\n\n/* Aligns with ds-bar-nav row — same height, inset, border, and title token. */\n.panel-tools__header {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n height: var(--dimension-size-600);\n padding: var(--dimension-space-100);\n border-bottom: var(--_panel-tools-border);\n color: var(--color-foreground-primary);\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n/*\n * Drawer title — match rail action height (size-400) and BarNav heading inset.\n * Use `ds-text.` so scoped specificity beats Text's `:host(.ds-text--inline-box)`\n * (class-only `.panel-tools__title` ties and loses when Text CSS loads later).\n */\nds-text.panel-tools__title {\n display: inline-flex;\n align-items: center;\n height: var(--dimension-size-400);\n padding-inline: var(--dimension-space-100);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/*\n * Fade title + slot body with the drawer slide (same dur/easing).\n * Header border stays opaque — only inner content opacity animates.\n */\nds-text.panel-tools__title,\n.panel-tools__body {\n opacity: 0;\n transition: none;\n}\n\n:host(.panel-tools--ready) ds-text.panel-tools__title,\n:host(.panel-tools--ready) .panel-tools__body {\n transition: opacity var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-in-out, ease);\n}\n\n:host(.panel-tools--ready.panel-tools--motion-opening) ds-text.panel-tools__title,\n:host(.panel-tools--ready.panel-tools--motion-opening) .panel-tools__body {\n transition: opacity var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-out, ease-out);\n}\n\n:host(.panel-tools--ready.panel-tools--motion-closing) ds-text.panel-tools__title,\n:host(.panel-tools--ready.panel-tools--motion-closing) .panel-tools__body {\n transition: opacity var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-in, ease-in);\n}\n\n:host(.panel-tools--open) ds-text.panel-tools__title,\n:host(.panel-tools--open) .panel-tools__body {\n opacity: 1;\n}\n\n.panel-tools__body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n position: relative;\n}\n\n/* Skip paint while fully closed; cleared during open/close motion for WYSIWYG slide. */\n:host(.panel-tools--drawer-resting) .panel-tools__body {\n content-visibility: auto;\n contain-intrinsic-size: auto 200px;\n}\n\n.panel-tools__view {\n display: none;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n padding: var(--dimension-space-200);\n color: var(--color-foreground-secondary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n}\n\n.panel-tools__view ::slotted(*) {\n margin: 0;\n color: var(--color-foreground-secondary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n}\n\n.panel-tools__view--active {\n display: block;\n}\n\n/* Rail — mirrors collapsed panel-nav width, padding, and item rhythm. */\n.panel-tools__rail {\n flex: 0 0 var(--_panel-tools-rail-width);\n width: var(--_panel-tools-rail-width);\n min-width: var(--_panel-tools-rail-width);\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n height: 100%;\n min-height: 0;\n border-left: var(--_panel-tools-border);\n background-color: var(--_panel-tools-surface-bg);\n}\n\n.panel-tools__rail-header {\n display: flex;\n align-items: center;\n justify-content: center;\n height: var(--dimension-size-600);\n padding: var(--dimension-space-100);\n flex-shrink: 0;\n box-sizing: border-box;\n border-bottom: var(--_panel-tools-border);\n}\n\n.panel-tools__rail-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n padding: var(--dimension-space-100);\n box-sizing: border-box;\n scrollbar-width: none;\n}\n\n.panel-tools__rail-body::-webkit-scrollbar {\n display: none;\n}\n\n/* Flush to the bottom of the rail — mirrors Search header pin. */\n.panel-tools__rail-footer {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n margin-top: auto;\n padding: var(--dimension-space-100);\n box-sizing: border-box;\n}\n\n.panel-tools__rail-action {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: var(--dimension-size-400);\n flex-shrink: 0;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host(.panel-tools--ready.panel-tools--motion-opening) .panel-tools__drawer,\n :host(.panel-tools--ready.panel-tools--motion-closing) .panel-tools__drawer,\n :host(.panel-tools--ready.panel-tools--motion-opening) ds-text.panel-tools__title,\n :host(.panel-tools--ready.panel-tools--motion-opening) .panel-tools__body,\n :host(.panel-tools--ready.panel-tools--motion-closing) ds-text.panel-tools__title,\n :host(.panel-tools--ready.panel-tools--motion-closing) .panel-tools__body {\n transition: none;\n }\n}\n",
356
+ "type": "registry:ui"
357
+ },
358
+ {
359
+ "path": "src/wc/components/PanelTools/PanelTools.tsx",
360
+ "content": "import { Component, Prop, Event, EventEmitter, Element, State, Watch, Method, h, Host } from '@stencil/core';\nimport type { ChromeTransitionDetail } from '../../shell/chrome-transition';\nimport {\n PANEL_TOOLS_FOOTER_TOOL_ID,\n PANEL_TOOLS_LABELS,\n PANEL_TOOLS_PRIMARY_TOOL_ID,\n PANEL_TOOLS_SHORTCUTS,\n type PanelToolsItem,\n type PanelToolsToolId,\n} from './panel-tools-types';\nimport {\n isPanelToolsToolId,\n orderPanelToolsItems,\n parsePanelToolsItems,\n panelToolsDrawerResting,\n panelToolsDrawerTransitionMs,\n reconcilePanelToolsAvailability,\n resolvePanelToolActivation,\n} from './panel-tools-utils';\n\n@Component({\n tag: 'ds-panel-tools',\n styleUrl: 'PanelTools.css',\n scoped: true,\n})\nexport class PanelTools {\n @Element() el!: HTMLElement;\n\n /** When false, only the icon rail is shown. */\n @Prop({ mutable: true, reflect: true }) open: boolean = false;\n\n /** Active tool view — `search`, `agents`, `messages`, `stacks`, `activity`, or `help`. */\n @Prop({ mutable: true, attribute: 'active-tool', reflect: true })\n activeTool: PanelToolsToolId | '' = '';\n\n /**\n * Rail items rendered in the right column.\n * Set via JS property: `el.items = [...]`. Replace the array reference to update.\n */\n @Prop() items: PanelToolsItem[] = [];\n\n /** JSON fallback for `items` — useful when framework bindings don't propagate arrays. */\n @Prop({ attribute: 'items-json' }) itemsJson: string = '';\n @Prop() toolsLabel: string = 'Tools';\n @Prop() toolShortcutsLabel: string = 'Tool shortcuts';\n\n /**\n * Optional localStorage key for the last active tool. The drawer always starts\n * closed; only the tool identity is restored for continuity within this browser.\n */\n @Prop({ attribute: 'storage-key' }) storageKey: string = '';\n\n /** Emitted when a rail button is toggled. Detail = { id, selected }. */\n @Event({ bubbles: true, composed: true }) dsToolChange!: EventEmitter<{\n id: PanelToolsToolId;\n selected: boolean;\n }>;\n\n /** Bubbling lifecycle — `ds-bar-nav` defers overflow checks during drawer motion. */\n @Event({ bubbles: true, composed: true })\n dsChromeTransitionStart!: EventEmitter<ChromeTransitionDetail>;\n\n @Event({ bubbles: true, composed: true })\n dsChromeTransitionEnd!: EventEmitter<ChromeTransitionDetail>;\n\n /** Arms open vs close easing for the in-flight width transition. */\n @State() private motion: 'opening' | 'closing' | 'idle' = 'idle';\n\n /** Suppresses width transition until the host has painted its initial open state. */\n @State() private readyForMotion = false;\n\n @State() private rovingIndex = 0;\n\n private motionEnableGeneration = 0;\n private transitionGeneration = 0;\n private transitionFallbackTimer: number | null = null;\n /** Ignores the old transition's terminal event when reversing direction. */\n private ignoreReplacementTerminal = false;\n\n private get railItems(): PanelToolsItem[] {\n return parsePanelToolsItems(this.items, this.itemsJson);\n }\n\n private get orderedRailItems(): PanelToolsItem[] {\n const railItems = orderPanelToolsItems(this.railItems);\n const headerItem = railItems.find(item => item.id === PANEL_TOOLS_PRIMARY_TOOL_ID);\n const footerItem = railItems.find(item => item.id === PANEL_TOOLS_FOOTER_TOOL_ID);\n const bodyItems = railItems.filter(\n item => item.id !== PANEL_TOOLS_PRIMARY_TOOL_ID && item.id !== PANEL_TOOLS_FOOTER_TOOL_ID,\n );\n const ordered: PanelToolsItem[] = [];\n if (headerItem) ordered.push(headerItem);\n ordered.push(...bodyItems);\n if (footerItem) ordered.push(footerItem);\n return ordered;\n }\n\n disconnectedCallback() {\n this.el.removeEventListener('transitionend', this.handleTransitionEnd);\n this.el.removeEventListener('transitioncancel', this.handleTransitionEnd);\n this.clearTransitionCompletion();\n this.motionEnableGeneration += 1;\n }\n\n private deferMotionEnable() {\n if (this.readyForMotion) return;\n const generation = ++this.motionEnableGeneration;\n requestAnimationFrame(() => {\n if (generation !== this.motionEnableGeneration) return;\n requestAnimationFrame(() => {\n if (generation !== this.motionEnableGeneration) return;\n this.readyForMotion = true;\n });\n });\n }\n\n connectedCallback() {\n this.el.addEventListener('transitionend', this.handleTransitionEnd);\n this.el.addEventListener('transitioncancel', this.handleTransitionEnd);\n }\n\n componentWillLoad() {\n this.restoreLastActiveTool();\n }\n\n @Watch('open')\n openChanged(isOpen: boolean, wasOpen?: boolean) {\n if (this.readyForMotion && wasOpen !== undefined && wasOpen !== isOpen) {\n this.startDrawerTransition(isOpen ? 'opening' : 'closing');\n }\n this.deferMotionEnable();\n }\n\n @Watch('activeTool')\n activeToolChanged() {\n this.persistLastActiveTool();\n this.deferMotionEnable();\n }\n\n @Watch('storageKey')\n storageKeyChanged() {\n this.restoreLastActiveTool();\n }\n\n componentDidLoad() {\n this.deferMotionEnable();\n }\n\n @Watch('items')\n @Watch('itemsJson')\n itemsChanged() {\n this.rovingIndex = 0;\n this.reconcileActiveTool();\n this.deferMotionEnable();\n }\n\n private restoreLastActiveTool() {\n if (!this.storageKey || this.activeTool) return;\n try {\n const stored = localStorage.getItem(this.storageKey);\n if (isPanelToolsToolId(stored)) this.activeTool = stored;\n } catch { /* localStorage unavailable */ }\n }\n\n private persistLastActiveTool() {\n if (!this.storageKey || !this.activeTool) return;\n try { localStorage.setItem(this.storageKey, this.activeTool); } catch { /* unavailable */ }\n }\n\n private clearPersistedTool() {\n if (!this.storageKey) return;\n try { localStorage.removeItem(this.storageKey); } catch { /* unavailable */ }\n }\n\n private reconcileActiveTool() {\n const next = reconcilePanelToolsAvailability(this.railItems, this.open, this.activeTool);\n if (!next.removedTool) return;\n this.clearPersistedTool();\n this.open = next.open;\n this.activeTool = next.activeTool;\n this.dsToolChange.emit({ id: next.removedTool, selected: false });\n }\n\n private startDrawerTransition(phase: 'opening' | 'closing') {\n const drawer = this.el.querySelector('.panel-tools__drawer') as HTMLElement | null;\n const widthBefore = drawer?.getBoundingClientRect().width ?? 0;\n if (this.motion !== 'idle') {\n this.finishDrawerTransition();\n this.ignoreReplacementTerminal = true;\n }\n this.motion = phase;\n this.dsChromeTransitionStart.emit({ source: 'panel-tools', phase });\n const generation = ++this.transitionGeneration;\n\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (generation !== this.transitionGeneration || this.motion !== phase) return;\n if (!drawer) {\n this.finishDrawerTransition();\n return;\n }\n\n const widthAfter = drawer.getBoundingClientRect().width;\n if (Math.abs(widthAfter - widthBefore) < 1) {\n this.finishDrawerTransition();\n return;\n }\n\n const fallbackMs = panelToolsDrawerTransitionMs(getComputedStyle(drawer));\n if (fallbackMs <= 0) {\n this.finishDrawerTransition();\n return;\n }\n this.transitionFallbackTimer = window.setTimeout(() => {\n this.finishDrawerTransition();\n }, fallbackMs);\n });\n });\n }\n\n private handleTransitionEnd = (event: TransitionEvent) => {\n if (event.target !== this.el.querySelector('.panel-tools__drawer')) return;\n if (event.propertyName !== 'max-width') return;\n if (this.ignoreReplacementTerminal) {\n this.ignoreReplacementTerminal = false;\n return;\n }\n this.finishDrawerTransition();\n };\n\n private finishDrawerTransition() {\n const wasTransitioning = this.motion !== 'idle';\n this.clearTransitionCompletion();\n if (!wasTransitioning) return;\n this.ignoreReplacementTerminal = false;\n this.motion = 'idle';\n this.dsChromeTransitionEnd.emit({ source: 'panel-tools' });\n }\n\n private clearTransitionCompletion() {\n this.transitionGeneration += 1;\n if (this.transitionFallbackTimer !== null) {\n window.clearTimeout(this.transitionFallbackTimer);\n this.transitionFallbackTimer = null;\n }\n }\n\n /** Rail selection follows `open` immediately — independent of the slide animation. */\n private isRailSelected(id: PanelToolsToolId): boolean {\n return this.open && this.activeTool === id;\n }\n\n /** Drawer body stays mounted while the clip frame animates closed. */\n private isDrawerPresent(): boolean {\n return this.open || this.motion === 'closing';\n }\n\n private isViewActive(id: PanelToolsToolId): boolean {\n return this.isDrawerPresent() && this.activeTool === id;\n }\n\n private headerLabel(): string {\n if (!this.isDrawerPresent() || !this.activeTool) return '';\n return PANEL_TOOLS_LABELS[this.activeTool as PanelToolsToolId] ?? '';\n }\n\n private handleToolChange = (id: PanelToolsToolId) => {\n const next = resolvePanelToolActivation(this.open, this.activeTool, id);\n this.open = next.open;\n this.activeTool = next.activeTool;\n this.dsToolChange.emit({ id, selected: next.selected });\n };\n\n /** Toggle any rail tool open/closed — shell shortcuts K/A/S/M/N call this. */\n @Method()\n async activateTool(id: PanelToolsToolId) {\n const item = this.railItems.find(entry => entry.id === id);\n if (!item || item.isInactive) return;\n this.handleToolChange(id);\n }\n\n /** Close the tools drawer when open — used by shell keyboard shortcuts. */\n @Method()\n async closeDrawer() {\n if (!this.open) return;\n const id = this.activeTool;\n this.open = false;\n if (id) {\n this.dsToolChange.emit({ id, selected: false });\n }\n }\n\n private focusRailAt(index: number) {\n const items = this.orderedRailItems;\n if (!items.length) return;\n const bounded = Math.max(0, Math.min(index, items.length - 1));\n if (bounded === this.rovingIndex) return;\n this.rovingIndex = bounded;\n const actions = Array.from(\n this.el.querySelectorAll<HTMLElement>('.panel-tools__rail-action .button-icon'),\n );\n actions[bounded]?.focus({ preventScroll: true });\n }\n\n private handleRailKeyDown = (e: KeyboardEvent, index: number) => {\n const items = this.orderedRailItems;\n if (!items.length) return;\n\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.handleToolChange(items[index].id);\n return;\n }\n\n if (e.key === 'ArrowDown') {\n if (index >= items.length - 1) return;\n e.preventDefault();\n this.focusRailAt(index + 1);\n return;\n }\n\n if (e.key === 'ArrowUp') {\n if (index <= 0) return;\n e.preventDefault();\n this.focusRailAt(index - 1);\n }\n };\n\n private renderRailAction(item: PanelToolsItem, index: number) {\n const label = item.ariaLabel ?? PANEL_TOOLS_LABELS[item.id];\n return (\n <ds-tooltip\n key={item.id}\n label={label}\n shortcutKey={PANEL_TOOLS_SHORTCUTS[item.id]}\n side=\"left\"\n size=\"sm\"\n >\n <ds-button-unfilled variant=\"icon\"\n class=\"panel-tools__rail-action\"\n icon={item.icon}\n isActive={this.isRailSelected(item.id)}\n activeFill={false}\n hasBorder={false}\n dot={item.dot ?? false}\n isInactive={item.isInactive}\n focusTabIndex={index === this.rovingIndex ? 0 : -1}\n aria-label={label}\n pressed={this.isRailSelected(item.id)}\n onFocusin={() => { this.rovingIndex = index; }}\n onKeyDown={(e: KeyboardEvent) => this.handleRailKeyDown(e, index)}\n onDsClick={() => this.handleToolChange(item.id)}\n />\n </ds-tooltip>\n );\n }\n\n render() {\n const headerLabel = this.headerLabel();\n const orderedRailItems = this.orderedRailItems;\n const headerItem = orderedRailItems.find(item => item.id === PANEL_TOOLS_PRIMARY_TOOL_ID);\n const footerItem = orderedRailItems.find(item => item.id === PANEL_TOOLS_FOOTER_TOOL_ID);\n const bodyItems = orderedRailItems.filter(\n item => item.id !== PANEL_TOOLS_PRIMARY_TOOL_ID && item.id !== PANEL_TOOLS_FOOTER_TOOL_ID,\n );\n const footerIndex = footerItem\n ? (headerItem ? 1 : 0) + bodyItems.length\n : -1;\n const showDrawerChrome = this.isDrawerPresent();\n const drawerResting = panelToolsDrawerResting(this.open, this.motion);\n\n return (\n <Host\n class={{\n 'panel-tools': true,\n 'panel-tools--open': this.open,\n 'panel-tools--ready': this.readyForMotion,\n 'panel-tools--motion-opening': this.motion === 'opening',\n 'panel-tools--motion-closing': this.motion === 'closing',\n 'panel-tools--drawer-resting': drawerResting,\n }}\n role=\"complementary\"\n aria-label={this.toolsLabel}\n >\n <div class=\"panel-tools__layout\">\n <nav class=\"panel-tools__rail\" aria-label={this.toolShortcutsLabel}>\n {headerItem ? (\n <div class=\"panel-tools__rail-header\">\n {this.renderRailAction(headerItem, 0)}\n </div>\n ) : null}\n <div class=\"panel-tools__rail-body\">\n {bodyItems.map((item, bodyIdx) =>\n this.renderRailAction(item, headerItem ? bodyIdx + 1 : bodyIdx),\n )}\n </div>\n {footerItem ? (\n <div class=\"panel-tools__rail-footer\">\n {this.renderRailAction(footerItem, footerIndex)}\n </div>\n ) : null}\n </nav>\n\n <div\n class={{\n 'panel-tools__drawer': true,\n 'panel-tools__drawer--visible': showDrawerChrome,\n }}\n aria-hidden={showDrawerChrome ? null : 'true'}\n inert={showDrawerChrome ? undefined : true}\n >\n <div class=\"panel-tools__drawer-surface\">\n <header class=\"panel-tools__header\">\n <ds-text\n class=\"panel-tools__title\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis\n color=\"primary\"\n wrap=\"nowrap\"\n >\n {headerLabel}\n </ds-text>\n </header>\n <div class=\"panel-tools__body\">\n <div\n class={{\n 'panel-tools__view': true,\n 'panel-tools__view--active': this.isViewActive('search'),\n }}\n hidden={!this.isViewActive('search')}\n >\n <slot name=\"search\" />\n </div>\n <div\n class={{\n 'panel-tools__view': true,\n 'panel-tools__view--active': this.isViewActive('messages'),\n }}\n hidden={!this.isViewActive('messages')}\n >\n <slot name=\"messages\" />\n </div>\n <div\n class={{\n 'panel-tools__view': true,\n 'panel-tools__view--active': this.isViewActive('stacks'),\n }}\n hidden={!this.isViewActive('stacks')}\n >\n <slot name=\"stacks\" />\n </div>\n <div\n class={{\n 'panel-tools__view': true,\n 'panel-tools__view--active': this.isViewActive('activity'),\n }}\n hidden={!this.isViewActive('activity')}\n >\n <slot name=\"activity\" />\n </div>\n <div\n class={{\n 'panel-tools__view': true,\n 'panel-tools__view--active': this.isViewActive('agents'),\n }}\n hidden={!this.isViewActive('agents')}\n >\n <slot name=\"agents\" />\n </div>\n <div\n class={{\n 'panel-tools__view': true,\n 'panel-tools__view--active': this.isViewActive('help'),\n }}\n hidden={!this.isViewActive('help')}\n >\n <slot name=\"help\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
361
+ "type": "registry:ui"
362
+ },
363
+ {
364
+ "path": "src/wc/components/PanelTools/panel-tools-types.ts",
365
+ "content": "/** Tool ids for the `ds-panel-tools` rail and drawer views. */\nexport type PanelToolsToolId =\n | 'search'\n | 'agents'\n | 'messages'\n | 'stacks'\n | 'activity'\n | 'help';\n\nexport const PANEL_TOOLS_TOOL_IDS: PanelToolsToolId[] = [\n 'search',\n 'agents',\n 'messages',\n 'stacks',\n 'activity',\n 'help',\n];\n\n/** Rail header slot — mirrors panel-nav M logo row. */\nexport const PANEL_TOOLS_PRIMARY_TOOL_ID: PanelToolsToolId = 'search';\n\n/** Rail footer slot — flush to the bottom of the tools column. */\nexport const PANEL_TOOLS_FOOTER_TOOL_ID: PanelToolsToolId = 'help';\n\nexport const PANEL_TOOLS_LABELS: Record<PanelToolsToolId, string> = {\n search: 'Search',\n messages: 'Messages',\n stacks: 'Stacks',\n activity: 'Activity',\n agents: 'Agents',\n help: 'Help & Support',\n};\n\nexport const PANEL_TOOLS_SHORTCUTS: Partial<Record<PanelToolsToolId, string>> = {\n search: 'K',\n agents: 'A',\n messages: 'M',\n stacks: 'S',\n activity: 'N',\n help: '?',\n};\n\nexport interface PanelToolsItem {\n id: PanelToolsToolId;\n /** Icon name for <ds-icon>. */\n icon: string;\n /** Whether this rail button is currently pressed/active. */\n selected?: boolean;\n /** Show a notification dot. */\n dot?: boolean;\n isInactive?: boolean;\n ariaLabel?: string;\n}\n",
366
+ "type": "registry:ui"
367
+ },
368
+ {
369
+ "path": "src/wc/components/PanelTools/panel-tools-utils.ts",
370
+ "content": "import { parseJsonArrayProp } from '../BarNav/bar-nav-utils';\nimport { parseCssTimeMs } from '../../utils/resolve-css-time-ms';\nimport {\n PANEL_TOOLS_TOOL_IDS,\n type PanelToolsItem,\n type PanelToolsToolId,\n} from './panel-tools-types';\n\nexport function parsePanelToolsItems(\n items: PanelToolsItem[],\n itemsJson: string,\n): PanelToolsItem[] {\n if (itemsJson) {\n return parseJsonArrayProp<PanelToolsItem>(itemsJson, []);\n }\n return items ?? [];\n}\n\nexport function orderPanelToolsItems(items: PanelToolsItem[]): PanelToolsItem[] {\n const firstById = new Map<PanelToolsToolId, PanelToolsItem>();\n for (const item of items) {\n if (!firstById.has(item.id)) firstById.set(item.id, item);\n }\n return PANEL_TOOLS_TOOL_IDS.flatMap(id => {\n const item = firstById.get(id);\n return item ? [item] : [];\n });\n}\n\nexport function isPanelToolsToolId(value: string | null): value is PanelToolsToolId {\n return value !== null && PANEL_TOOLS_TOOL_IDS.includes(value as PanelToolsToolId);\n}\n\nexport function reconcilePanelToolsAvailability(\n items: PanelToolsItem[],\n open: boolean,\n activeTool: PanelToolsToolId | '',\n): { open: boolean; activeTool: PanelToolsToolId | ''; removedTool: PanelToolsToolId | '' } {\n if (!activeTool || items.some(item => item.id === activeTool)) {\n return { open, activeTool, removedTool: '' };\n }\n return { open: false, activeTool: '', removedTool: activeTool };\n}\n\nexport function shouldResyncPanelToolsItems(\n prev: PanelToolsItem[],\n next: PanelToolsItem[],\n): boolean {\n if (prev.length !== next.length) return true;\n return next.some((item, index) => {\n const prior = prev[index];\n if (!prior) return true;\n return (\n prior.id !== item.id ||\n prior.icon !== item.icon ||\n prior.selected !== item.selected ||\n prior.dot !== item.dot ||\n prior.isInactive !== item.isInactive ||\n prior.ariaLabel !== item.ariaLabel\n );\n });\n}\n\nexport type PanelToolsMotion = 'opening' | 'closing' | 'idle';\n\nexport interface PanelToolsTransitionStyle {\n transitionProperty: string;\n transitionDuration: string;\n transitionDelay: string;\n}\n\n/** Maximum computed time for the drawer's max-width transition, including delay. */\nexport function panelToolsDrawerTransitionMs(style: PanelToolsTransitionStyle): number {\n const properties = style.transitionProperty.split(',').map(item => item.trim());\n const durations = style.transitionDuration.split(',').map(item => parseCssTimeMs(item.trim(), 0));\n const delays = style.transitionDelay.split(',').map(item => parseCssTimeMs(item.trim(), 0));\n\n return properties.reduce((max, property, index) => {\n if (property !== 'all' && property !== 'max-width') return max;\n const duration = durations[index % durations.length] ?? 0;\n const delay = delays[index % delays.length] ?? 0;\n return Math.max(max, duration + delay, 0);\n }, 0);\n}\n\n/** True when the drawer clip is fully closed — safe to skip painting slot content. */\nexport function panelToolsDrawerResting(open: boolean, motion: PanelToolsMotion): boolean {\n return !open && motion === 'idle';\n}\n\n/** Toggle or switch rail tool selection — repeat activation closes the active tool. */\nexport function resolvePanelToolActivation(\n open: boolean,\n activeTool: PanelToolsToolId | '',\n id: PanelToolsToolId,\n): { open: boolean; activeTool: PanelToolsToolId; selected: boolean } {\n if (open && activeTool === id) {\n return { open: false, activeTool: id, selected: false };\n }\n return { open: true, activeTool: id, selected: true };\n}\n",
371
+ "type": "registry:ui"
372
+ }
373
+ ]
374
+ }