@ds-mo/ui 1.8.1 → 1.9.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 (79) hide show
  1. package/dist/.build-stamp +1 -1
  2. package/dist/components/ds-app-shell.js +1 -1
  3. package/dist/components/ds-app-shell.js.map +1 -1
  4. package/dist/components/ds-badge.js +1 -1
  5. package/dist/components/ds-banner.js +1 -1
  6. package/dist/components/ds-bar-nav-action.js +1 -1
  7. package/dist/components/ds-bar-nav.js +1 -1
  8. package/dist/components/ds-bar-nav.js.map +1 -1
  9. package/dist/components/ds-fade.js +1 -1
  10. package/dist/components/ds-fade.js.map +1 -1
  11. package/dist/components/ds-menu.js +1 -1
  12. package/dist/components/ds-modal.js +1 -1
  13. package/dist/components/ds-panel-nav.js +1 -1
  14. package/dist/components/ds-panel-nav.js.map +1 -1
  15. package/dist/components/ds-panel-tools.js +1 -1
  16. package/dist/components/ds-panel-tools.js.map +1 -1
  17. package/dist/components/ds-select.js +1 -1
  18. package/dist/components/ds-slider.js +1 -1
  19. package/dist/components/ds-slider.js.map +1 -1
  20. package/dist/components/ds-tab-group-nav.js +1 -1
  21. package/dist/components/ds-tab-group.js +1 -1
  22. package/dist/components/ds-toast-provider.js +1 -1
  23. package/dist/components/ds-toggle.js +1 -1
  24. package/dist/components/ds-tooltip.js +1 -1
  25. package/dist/components/{p-BvmooLHV.js → p-C1cBr-Ri.js} +2 -2
  26. package/dist/components/{p-DrvpsLK6.js → p-CCZFMcLe.js} +2 -2
  27. package/dist/components/p-CiEc8Nwx.js +2 -0
  28. package/dist/components/p-CiEc8Nwx.js.map +1 -0
  29. package/dist/components/{p-C4VSrrAB.js → p-CuuuDjJJ.js} +2 -2
  30. package/dist/components/p-Dl1GvZQ2.js +2 -0
  31. package/dist/components/p-Dl1GvZQ2.js.map +1 -0
  32. package/dist/components/{p-DZDXwEMg.js → p-DlsexsFg.js} +2 -2
  33. package/dist/components/p-g6MCxfm4.js +2 -0
  34. package/dist/components/p-g6MCxfm4.js.map +1 -0
  35. package/dist/types/components/AppShell/AppShell.d.ts +7 -5
  36. package/dist/types/components/BarNav/BarNav.d.ts +2 -4
  37. package/dist/types/components/PanelNav/PanelNav.d.ts +4 -13
  38. package/dist/types/components/PanelNav/index.d.ts +1 -1
  39. package/dist/types/components/PanelNav/panel-nav-utils.d.ts +0 -9
  40. package/dist/types/components/PanelTools/PanelTools.d.ts +29 -11
  41. package/dist/types/components/PanelTools/index.d.ts +2 -2
  42. package/dist/types/components/PanelTools/panel-tools-types.d.ts +15 -2
  43. package/dist/types/components/PanelTools/panel-tools-utils.d.ts +4 -0
  44. package/dist/types/components.d.ts +67 -21
  45. package/dist/types/nav/badge-gradient-ring.d.ts +1 -1
  46. package/dist/types/nav/index.d.ts +1 -1
  47. package/dist/types/nav/nav-chrome.d.ts +4 -8
  48. package/dist/types/nav/shell-gradient.d.ts +6 -3
  49. package/package.json +1 -1
  50. package/src/angular/proxies.ts +12 -3
  51. package/src/react/ds-panel-tools.ts +4 -3
  52. package/src/wc/components/AppShell/AppShell.tsx +54 -38
  53. package/src/wc/components/Badge/Badge.tsx +3 -12
  54. package/src/wc/components/BarNav/BarNav.tsx +6 -18
  55. package/src/wc/components/PanelNav/PanelNav.tsx +23 -103
  56. package/src/wc/components/PanelNav/index.ts +0 -1
  57. package/src/wc/components/PanelNav/panel-nav-utils.ts +0 -13
  58. package/src/wc/components/PanelTools/PanelTools.tsx +162 -80
  59. package/src/wc/components/PanelTools/index.ts +6 -2
  60. package/src/wc/components/PanelTools/panel-tools-types.ts +18 -3
  61. package/src/wc/components/PanelTools/panel-tools-utils.ts +31 -0
  62. package/src/wc/components.d.ts +67 -21
  63. package/src/wc/nav/badge-gradient-ring.ts +3 -8
  64. package/src/wc/nav/index.ts +3 -1
  65. package/src/wc/nav/nav-chrome.ts +19 -17
  66. package/src/wc/nav/shell-gradient.ts +9 -4
  67. package/src/wc/nav/shell-view-transition.ts +13 -6
  68. package/dist/components/p-B8VZmH43.js +0 -2
  69. package/dist/components/p-B8VZmH43.js.map +0 -1
  70. package/dist/components/p-BucV1U6A.js +0 -2
  71. package/dist/components/p-BucV1U6A.js.map +0 -1
  72. package/dist/components/p-CX7-_qOW.js +0 -2
  73. package/dist/components/p-CX7-_qOW.js.map +0 -1
  74. package/dist/components/p-TtnqbcnR.js +0 -2
  75. package/dist/components/p-TtnqbcnR.js.map +0 -1
  76. /package/dist/components/{p-BvmooLHV.js.map → p-C1cBr-Ri.js.map} +0 -0
  77. /package/dist/components/{p-DrvpsLK6.js.map → p-CCZFMcLe.js.map} +0 -0
  78. /package/dist/components/{p-C4VSrrAB.js.map → p-CuuuDjJJ.js.map} +0 -0
  79. /package/dist/components/{p-DZDXwEMg.js.map → p-DlsexsFg.js.map} +0 -0
@@ -1,8 +1,11 @@
1
- import { Component, Prop, Element, State, Watch, h, Host } from '@stencil/core';
1
+ import { Component, Prop, Event, EventEmitter, Element, State, Watch, h, Host } from '@stencil/core';
2
2
  import {
3
3
  PANEL_TOOLS_LABELS,
4
+ PANEL_TOOLS_PRIMARY_TOOL_ID,
5
+ type PanelToolsItem,
4
6
  type PanelToolsToolId,
5
7
  } from './panel-tools-types';
8
+ import { parsePanelToolsItems } from './panel-tools-utils';
6
9
 
7
10
  @Component({
8
11
  tag: 'ds-panel-tools',
@@ -12,26 +15,38 @@ import {
12
15
  export class PanelTools {
13
16
  @Element() el!: HTMLElement;
14
17
 
15
- /** When false, width animates to 0. */
16
- @Prop({ reflect: true }) open: boolean = false;
18
+ /** When false, only the icon rail is shown. */
19
+ @Prop({ mutable: true, reflect: true }) open: boolean = false;
17
20
 
18
- /** Active tool view — `search`, `messages`, `stacks`, `activity`, or `agents`. */
19
- @Prop({ attribute: 'active-tool', reflect: true }) activeTool: PanelToolsToolId | '' = '';
21
+ /** Active tool view — `search`, `agents`, `messages`, `stacks`, or `activity`. */
22
+ @Prop({ mutable: true, attribute: 'active-tool', reflect: true })
23
+ activeTool: PanelToolsToolId | '' = '';
20
24
 
21
- /** Stays true until the close width transition finishes. */
22
- @State() private expanded = false;
25
+ /**
26
+ * Rail items rendered in the right column.
27
+ * Set via JS property: `el.items = [...]`. Replace the array reference to update.
28
+ */
29
+ @Prop() items: PanelToolsItem[] = [];
30
+
31
+ /** JSON fallback for `items` — useful when framework bindings don't propagate arrays. */
32
+ @Prop({ attribute: 'items-json' }) itemsJson: string = '';
33
+
34
+ /** Emitted when a rail button is toggled. Detail = { id, selected }. */
35
+ @Event({ bubbles: true, composed: true }) dsToolChange!: EventEmitter<{
36
+ id: PanelToolsToolId;
37
+ selected: boolean;
38
+ }>;
39
+
40
+ /** Arms open vs close easing for the in-flight width transition. */
41
+ @State() private motion: 'opening' | 'closing' | 'idle' = 'idle';
23
42
 
24
43
  /** Suppresses width transition until the host has painted its initial open state. */
25
44
  @State() private readyForMotion = false;
26
45
 
27
46
  private motionEnableGeneration = 0;
28
47
 
29
- componentWillLoad() {
30
- if (this.open) this.expanded = true;
31
- }
32
-
33
- componentDidLoad() {
34
- this.deferMotionEnable();
48
+ private get railItems(): PanelToolsItem[] {
49
+ return parsePanelToolsItems(this.items, this.itemsJson);
35
50
  }
36
51
 
37
52
  disconnectedCallback() {
@@ -39,7 +54,6 @@ export class PanelTools {
39
54
  this.motionEnableGeneration += 1;
40
55
  }
41
56
 
42
- /** Two rAFs after the last pre-ready prop change — host bindings may land late on refresh. */
43
57
  private deferMotionEnable() {
44
58
  if (this.readyForMotion) return;
45
59
  const generation = ++this.motionEnableGeneration;
@@ -57,8 +71,10 @@ export class PanelTools {
57
71
  }
58
72
 
59
73
  @Watch('open')
60
- openChanged(isOpen: boolean) {
61
- if (isOpen) this.expanded = true;
74
+ openChanged(isOpen: boolean, wasOpen?: boolean) {
75
+ if (this.readyForMotion && wasOpen !== undefined && wasOpen !== isOpen) {
76
+ this.motion = isOpen ? 'opening' : 'closing';
77
+ }
62
78
  this.deferMotionEnable();
63
79
  }
64
80
 
@@ -67,27 +83,73 @@ export class PanelTools {
67
83
  this.deferMotionEnable();
68
84
  }
69
85
 
86
+ componentDidLoad() {
87
+ this.deferMotionEnable();
88
+ }
89
+
90
+ @Watch('items')
91
+ @Watch('itemsJson')
92
+ itemsChanged() {
93
+ this.deferMotionEnable();
94
+ }
95
+
70
96
  private handleTransitionEnd = (event: TransitionEvent) => {
71
- if (event.target !== this.el) return;
97
+ if (event.target !== this.el.querySelector('.panel-tools__drawer')) return;
72
98
  if (event.propertyName !== 'max-width') return;
73
- if (!this.open) this.expanded = false;
99
+ this.motion = 'idle';
74
100
  };
75
101
 
76
- private isShowingContent(): boolean {
77
- return this.open || this.expanded;
102
+ /** Rail selection follows `open` immediately — independent of the slide animation. */
103
+ private isRailSelected(id: PanelToolsToolId): boolean {
104
+ return this.open && this.activeTool === id;
78
105
  }
79
106
 
80
- private isActiveTool(id: PanelToolsToolId): boolean {
81
- return this.isShowingContent() && this.activeTool === id;
107
+ /** Drawer body stays mounted while the clip frame animates closed. */
108
+ private isDrawerPresent(): boolean {
109
+ return this.open || this.motion === 'closing';
110
+ }
111
+
112
+ private isViewActive(id: PanelToolsToolId): boolean {
113
+ return this.isDrawerPresent() && this.activeTool === id;
82
114
  }
83
115
 
84
116
  private headerLabel(): string {
85
- if (!this.activeTool) return '';
117
+ if (!this.isDrawerPresent() || !this.activeTool) return '';
86
118
  return PANEL_TOOLS_LABELS[this.activeTool as PanelToolsToolId] ?? '';
87
119
  }
88
120
 
121
+ private handleToolChange = (id: PanelToolsToolId) => {
122
+ const selected = !this.isRailSelected(id);
123
+ if (selected) {
124
+ this.open = true;
125
+ this.activeTool = id;
126
+ } else {
127
+ this.open = false;
128
+ }
129
+ this.dsToolChange.emit({ id, selected });
130
+ };
131
+
132
+ private renderRailAction(item: PanelToolsItem) {
133
+ return (
134
+ <ds-bar-nav-action
135
+ key={item.id}
136
+ class="panel-tools__rail-action"
137
+ icon={item.icon}
138
+ selected={this.isRailSelected(item.id)}
139
+ dot={item.dot ?? false}
140
+ inactive={item.inactive}
141
+ aria-label={item.ariaLabel ?? PANEL_TOOLS_LABELS[item.id]}
142
+ onDsChange={() => this.handleToolChange(item.id)}
143
+ />
144
+ );
145
+ }
146
+
89
147
  render() {
90
- const showingContent = this.isShowingContent();
148
+ const headerLabel = this.headerLabel();
149
+ const railItems = this.railItems;
150
+ const headerItem = railItems.find(item => item.id === PANEL_TOOLS_PRIMARY_TOOL_ID);
151
+ const bodyItems = railItems.filter(item => item.id !== PANEL_TOOLS_PRIMARY_TOOL_ID);
152
+ const showDrawerChrome = this.isDrawerPresent();
91
153
 
92
154
  return (
93
155
  <Host
@@ -95,67 +157,87 @@ export class PanelTools {
95
157
  'panel-tools': true,
96
158
  'panel-tools--open': this.open,
97
159
  'panel-tools--ready': this.readyForMotion,
160
+ 'panel-tools--motion-opening': this.motion === 'opening',
161
+ 'panel-tools--motion-closing': this.motion === 'closing',
98
162
  }}
99
163
  role="complementary"
100
- aria-label={this.headerLabel() || 'Tools'}
101
- aria-hidden={showingContent ? null : 'true'}
164
+ aria-label="Tools"
102
165
  >
103
- <div class="panel-tools__inner">
104
- <header class="panel-tools__header">
105
- <span class="text-body-medium-emphasis">{this.headerLabel()}</span>
106
- </header>
107
- <div class="panel-tools__body">
108
- <div
109
- class={{
110
- 'panel-tools__view': true,
111
- 'panel-tools__view--active': this.isActiveTool('search'),
112
- 'text-body-medium': true,
113
- }}
114
- hidden={!this.isActiveTool('search')}
115
- >
116
- <slot name="search" />
117
- </div>
118
- <div
119
- class={{
120
- 'panel-tools__view': true,
121
- 'panel-tools__view--active': this.isActiveTool('messages'),
122
- 'text-body-medium': true,
123
- }}
124
- hidden={!this.isActiveTool('messages')}
125
- >
126
- <slot name="messages" />
127
- </div>
128
- <div
129
- class={{
130
- 'panel-tools__view': true,
131
- 'panel-tools__view--active': this.isActiveTool('stacks'),
132
- 'text-body-medium': true,
133
- }}
134
- hidden={!this.isActiveTool('stacks')}
135
- >
136
- <slot name="stacks" />
137
- </div>
138
- <div
139
- class={{
140
- 'panel-tools__view': true,
141
- 'panel-tools__view--active': this.isActiveTool('activity'),
142
- 'text-body-medium': true,
143
- }}
144
- hidden={!this.isActiveTool('activity')}
145
- >
146
- <slot name="activity" />
147
- </div>
148
- <div
149
- class={{
150
- 'panel-tools__view': true,
151
- 'panel-tools__view--active': this.isActiveTool('agents'),
152
- 'text-body-medium': true,
153
- }}
154
- hidden={!this.isActiveTool('agents')}
155
- >
156
- <slot name="agents" />
166
+ <div class="panel-tools__layout">
167
+ <div
168
+ class={{
169
+ 'panel-tools__drawer': true,
170
+ 'panel-tools__drawer--visible': showDrawerChrome,
171
+ }}
172
+ aria-hidden={showDrawerChrome ? null : 'true'}
173
+ >
174
+ <div class="panel-tools__drawer-surface">
175
+ <header class="panel-tools__header">
176
+ <span class="panel-tools__title text-body-medium-emphasis">{headerLabel}</span>
177
+ </header>
178
+ <div class="panel-tools__body">
179
+ <div
180
+ class={{
181
+ 'panel-tools__view': true,
182
+ 'panel-tools__view--active': this.isViewActive('search'),
183
+ 'text-body-medium': true,
184
+ }}
185
+ hidden={!this.isViewActive('search')}
186
+ >
187
+ <slot name="search" />
188
+ </div>
189
+ <div
190
+ class={{
191
+ 'panel-tools__view': true,
192
+ 'panel-tools__view--active': this.isViewActive('messages'),
193
+ 'text-body-medium': true,
194
+ }}
195
+ hidden={!this.isViewActive('messages')}
196
+ >
197
+ <slot name="messages" />
198
+ </div>
199
+ <div
200
+ class={{
201
+ 'panel-tools__view': true,
202
+ 'panel-tools__view--active': this.isViewActive('stacks'),
203
+ 'text-body-medium': true,
204
+ }}
205
+ hidden={!this.isViewActive('stacks')}
206
+ >
207
+ <slot name="stacks" />
208
+ </div>
209
+ <div
210
+ class={{
211
+ 'panel-tools__view': true,
212
+ 'panel-tools__view--active': this.isViewActive('activity'),
213
+ 'text-body-medium': true,
214
+ }}
215
+ hidden={!this.isViewActive('activity')}
216
+ >
217
+ <slot name="activity" />
218
+ </div>
219
+ <div
220
+ class={{
221
+ 'panel-tools__view': true,
222
+ 'panel-tools__view--active': this.isViewActive('agents'),
223
+ 'text-body-medium': true,
224
+ }}
225
+ hidden={!this.isViewActive('agents')}
226
+ >
227
+ <slot name="agents" />
228
+ </div>
229
+ </div>
157
230
  </div>
158
231
  </div>
232
+
233
+ <nav class="panel-tools__rail" aria-label="Tool shortcuts">
234
+ {headerItem ? (
235
+ <div class="panel-tools__rail-header">{this.renderRailAction(headerItem)}</div>
236
+ ) : null}
237
+ <div class="panel-tools__rail-body">
238
+ {bodyItems.map(item => this.renderRailAction(item))}
239
+ </div>
240
+ </nav>
159
241
  </div>
160
242
  </Host>
161
243
  );
@@ -1,2 +1,6 @@
1
- export type { PanelToolsToolId } from './panel-tools-types';
2
- export { PANEL_TOOLS_LABELS, PANEL_TOOLS_TOOL_IDS } from './panel-tools-types';
1
+ export type { PanelToolsItem, PanelToolsToolId } from './panel-tools-types';
2
+ export {
3
+ PANEL_TOOLS_LABELS,
4
+ PANEL_TOOLS_PRIMARY_TOOL_ID,
5
+ PANEL_TOOLS_TOOL_IDS,
6
+ } from './panel-tools-types';
@@ -1,14 +1,17 @@
1
- /** Bar-nav tool ids that map to `ds-panel-tools` views. */
2
- export type PanelToolsToolId = 'search' | 'messages' | 'stacks' | 'activity' | 'agents';
1
+ /** Tool ids for the `ds-panel-tools` rail and drawer views. */
2
+ export type PanelToolsToolId = 'search' | 'agents' | 'messages' | 'stacks' | 'activity';
3
3
 
4
4
  export const PANEL_TOOLS_TOOL_IDS: PanelToolsToolId[] = [
5
5
  'search',
6
+ 'agents',
6
7
  'messages',
7
8
  'stacks',
8
9
  'activity',
9
- 'agents',
10
10
  ];
11
11
 
12
+ /** Rail header slot — mirrors panel-nav M logo row. */
13
+ export const PANEL_TOOLS_PRIMARY_TOOL_ID: PanelToolsToolId = 'search';
14
+
12
15
  export const PANEL_TOOLS_LABELS: Record<PanelToolsToolId, string> = {
13
16
  search: 'Search',
14
17
  messages: 'Messages',
@@ -16,3 +19,15 @@ export const PANEL_TOOLS_LABELS: Record<PanelToolsToolId, string> = {
16
19
  activity: 'Activity',
17
20
  agents: 'Agents',
18
21
  };
22
+
23
+ export interface PanelToolsItem {
24
+ id: PanelToolsToolId;
25
+ /** Icon name for <ds-icon>. */
26
+ icon: string;
27
+ /** Whether this rail button is currently pressed/active. */
28
+ selected?: boolean;
29
+ /** Show a notification dot. */
30
+ dot?: boolean;
31
+ inactive?: boolean;
32
+ ariaLabel?: string;
33
+ }
@@ -0,0 +1,31 @@
1
+ import { parseJsonArrayProp } from '../BarNav/bar-nav-utils';
2
+ import type { PanelToolsItem } from './panel-tools-types';
3
+
4
+ export function parsePanelToolsItems(
5
+ items: PanelToolsItem[],
6
+ itemsJson: string,
7
+ ): PanelToolsItem[] {
8
+ if (itemsJson) {
9
+ return parseJsonArrayProp<PanelToolsItem>(itemsJson, []);
10
+ }
11
+ return items ?? [];
12
+ }
13
+
14
+ export function shouldResyncPanelToolsItems(
15
+ prev: PanelToolsItem[],
16
+ next: PanelToolsItem[],
17
+ ): boolean {
18
+ if (prev.length !== next.length) return true;
19
+ return next.some((item, index) => {
20
+ const prior = prev[index];
21
+ if (!prior) return true;
22
+ return (
23
+ prior.id !== item.id ||
24
+ prior.icon !== item.icon ||
25
+ prior.selected !== item.selected ||
26
+ prior.dot !== item.dot ||
27
+ prior.inactive !== item.inactive ||
28
+ prior.ariaLabel !== item.ariaLabel
29
+ );
30
+ });
31
+ }
@@ -26,7 +26,7 @@ import { MenuItemData, MenuSection } from "./components/Menu/menu-types";
26
26
  import { MenuAlign, MenuSide } from "./components/Menu/menu-position";
27
27
  import { ModalWidth } from "./components/Modal/Modal";
28
28
  import { PanelNavGroup, PanelNavRouterMode, PanelNavUserActionDetail } from "./components/PanelNav/panel-nav-types";
29
- import { PanelToolsToolId } from "./components/PanelTools/panel-tools-types";
29
+ import { PanelToolsItem, PanelToolsToolId } from "./components/PanelTools/panel-tools-types";
30
30
  import { RadioOption } from "./components/RadioGroup/RadioGroup";
31
31
  import { ScrollbarVariant } from "./components/Scrollbar/Scrollbar";
32
32
  import { SelectOption } from "./components/Select/Select";
@@ -63,7 +63,7 @@ export { MenuItemData, MenuSection } from "./components/Menu/menu-types";
63
63
  export { MenuAlign, MenuSide } from "./components/Menu/menu-position";
64
64
  export { ModalWidth } from "./components/Modal/Modal";
65
65
  export { PanelNavGroup, PanelNavRouterMode, PanelNavUserActionDetail } from "./components/PanelNav/panel-nav-types";
66
- export { PanelToolsToolId } from "./components/PanelTools/panel-tools-types";
66
+ export { PanelToolsItem, PanelToolsToolId } from "./components/PanelTools/panel-tools-types";
67
67
  export { RadioOption } from "./components/RadioGroup/RadioGroup";
68
68
  export { ScrollbarVariant } from "./components/Scrollbar/Scrollbar";
69
69
  export { SelectOption } from "./components/Select/Select";
@@ -97,7 +97,7 @@ export namespace Components {
97
97
  }
98
98
  interface DsAppShell {
99
99
  /**
100
- * When `true`, paints the shared L-shaped radial wash on panel + bar backgrounds.
100
+ * When `true`, paints the shared chrome surface (bg + wash + grid) behind panel, bar, and tools.
101
101
  * @default false
102
102
  */
103
103
  "gradient": boolean;
@@ -108,7 +108,7 @@ export namespace Components {
108
108
  "gradientSrc": string;
109
109
  /**
110
110
  * Chrome style propagated to slotted `ds-panel-nav` and `ds-bar-nav`.
111
- * @default 'navigation'
111
+ * @default 'dashboard'
112
112
  */
113
113
  "navStyle": NavChromeStyle;
114
114
  }
@@ -210,8 +210,8 @@ export namespace Components {
210
210
  */
211
211
  "heading": string | undefined;
212
212
  /**
213
- * Chrome style: `navigation` = navigation tokens, `default` = app tokens. Property: `navStyle`. HTML attribute: `nav-style`.
214
- * @default 'default'
213
+ * Style slot: `dashboard` or `settings`. Colors match for now.
214
+ * @default 'dashboard'
215
215
  */
216
216
  "navStyle": NavChromeStyle;
217
217
  /**
@@ -647,7 +647,7 @@ export namespace Components {
647
647
  */
648
648
  "currentUrl": string;
649
649
  /**
650
- * When `true`, the component does not run its own View Transition on style change it just updates the rendered surface synchronously. Use this when the host app orchestrates the page transition itself (e.g. Angular Router's `withViewTransitions`), so the two don't fight or nest.
650
+ * When `true`, style changes apply synchronously host app owns view transitions.
651
651
  * @default false
652
652
  */
653
653
  "disableViewTransition": boolean;
@@ -657,8 +657,8 @@ export namespace Components {
657
657
  */
658
658
  "groups": string | PanelNavGroup[];
659
659
  /**
660
- * Chrome style: `navigation` = navigation tokens, `default` = standard app tokens. Property: `navStyle`. HTML attribute: `nav-style`.
661
- * @default 'navigation'
660
+ * Style slot: `dashboard` or `settings`. Colors match for now; class hooks reserved for texture/glyph layers. Property: `navStyle`. Attribute: `nav-style`.
661
+ * @default 'dashboard'
662
662
  */
663
663
  "navStyle": NavChromeStyle;
664
664
  /**
@@ -684,12 +684,22 @@ export namespace Components {
684
684
  }
685
685
  interface DsPanelTools {
686
686
  /**
687
- * Active tool view — `search`, `messages`, `stacks`, `activity`, or `agents`.
687
+ * Active tool view — `search`, `agents`, `messages`, `stacks`, or `activity`.
688
688
  * @default ''
689
689
  */
690
690
  "activeTool": PanelToolsToolId | '';
691
691
  /**
692
- * When false, width animates to 0.
692
+ * Rail items rendered in the right column. Set via JS property: `el.items = [...]`. Replace the array reference to update.
693
+ * @default []
694
+ */
695
+ "items": PanelToolsItem[];
696
+ /**
697
+ * JSON fallback for `items` — useful when framework bindings don't propagate arrays.
698
+ * @default ''
699
+ */
700
+ "itemsJson": string;
701
+ /**
702
+ * When false, only the icon rail is shown.
693
703
  * @default false
694
704
  */
695
705
  "open": boolean;
@@ -1134,6 +1144,10 @@ export interface DsPanelNavCustomEvent<T> extends CustomEvent<T> {
1134
1144
  detail: T;
1135
1145
  target: HTMLDsPanelNavElement;
1136
1146
  }
1147
+ export interface DsPanelToolsCustomEvent<T> extends CustomEvent<T> {
1148
+ detail: T;
1149
+ target: HTMLDsPanelToolsElement;
1150
+ }
1137
1151
  export interface DsRadioGroupCustomEvent<T> extends CustomEvent<T> {
1138
1152
  detail: T;
1139
1153
  target: HTMLDsRadioGroupElement;
@@ -1479,7 +1493,21 @@ declare global {
1479
1493
  prototype: HTMLDsPanelNavElement;
1480
1494
  new (): HTMLDsPanelNavElement;
1481
1495
  };
1496
+ interface HTMLDsPanelToolsElementEventMap {
1497
+ "dsToolChange": {
1498
+ id: PanelToolsToolId;
1499
+ selected: boolean;
1500
+ };
1501
+ }
1482
1502
  interface HTMLDsPanelToolsElement extends Components.DsPanelTools, HTMLStencilElement {
1503
+ addEventListener<K extends keyof HTMLDsPanelToolsElementEventMap>(type: K, listener: (this: HTMLDsPanelToolsElement, ev: DsPanelToolsCustomEvent<HTMLDsPanelToolsElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
1504
+ addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
1505
+ addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
1506
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
1507
+ removeEventListener<K extends keyof HTMLDsPanelToolsElementEventMap>(type: K, listener: (this: HTMLDsPanelToolsElement, ev: DsPanelToolsCustomEvent<HTMLDsPanelToolsElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
1508
+ removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
1509
+ removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
1510
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
1483
1511
  }
1484
1512
  var HTMLDsPanelToolsElement: {
1485
1513
  prototype: HTMLDsPanelToolsElement;
@@ -1747,7 +1775,7 @@ declare namespace LocalJSX {
1747
1775
  }
1748
1776
  interface DsAppShell {
1749
1777
  /**
1750
- * When `true`, paints the shared L-shaped radial wash on panel + bar backgrounds.
1778
+ * When `true`, paints the shared chrome surface (bg + wash + grid) behind panel, bar, and tools.
1751
1779
  * @default false
1752
1780
  */
1753
1781
  "gradient"?: boolean;
@@ -1758,7 +1786,7 @@ declare namespace LocalJSX {
1758
1786
  "gradientSrc"?: string;
1759
1787
  /**
1760
1788
  * Chrome style propagated to slotted `ds-panel-nav` and `ds-bar-nav`.
1761
- * @default 'navigation'
1789
+ * @default 'dashboard'
1762
1790
  */
1763
1791
  "navStyle"?: NavChromeStyle;
1764
1792
  }
@@ -1861,8 +1889,8 @@ declare namespace LocalJSX {
1861
1889
  */
1862
1890
  "heading"?: string | undefined;
1863
1891
  /**
1864
- * Chrome style: `navigation` = navigation tokens, `default` = app tokens. Property: `navStyle`. HTML attribute: `nav-style`.
1865
- * @default 'default'
1892
+ * Style slot: `dashboard` or `settings`. Colors match for now.
1893
+ * @default 'dashboard'
1866
1894
  */
1867
1895
  "navStyle"?: NavChromeStyle;
1868
1896
  /**
@@ -2336,7 +2364,7 @@ declare namespace LocalJSX {
2336
2364
  */
2337
2365
  "currentUrl"?: string;
2338
2366
  /**
2339
- * When `true`, the component does not run its own View Transition on style change it just updates the rendered surface synchronously. Use this when the host app orchestrates the page transition itself (e.g. Angular Router's `withViewTransitions`), so the two don't fight or nest.
2367
+ * When `true`, style changes apply synchronously host app owns view transitions.
2340
2368
  * @default false
2341
2369
  */
2342
2370
  "disableViewTransition"?: boolean;
@@ -2346,8 +2374,8 @@ declare namespace LocalJSX {
2346
2374
  */
2347
2375
  "groups"?: string | PanelNavGroup[];
2348
2376
  /**
2349
- * Chrome style: `navigation` = navigation tokens, `default` = standard app tokens. Property: `navStyle`. HTML attribute: `nav-style`.
2350
- * @default 'navigation'
2377
+ * Style slot: `dashboard` or `settings`. Colors match for now; class hooks reserved for texture/glyph layers. Property: `navStyle`. Attribute: `nav-style`.
2378
+ * @default 'dashboard'
2351
2379
  */
2352
2380
  "navStyle"?: NavChromeStyle;
2353
2381
  /**
@@ -2389,12 +2417,29 @@ declare namespace LocalJSX {
2389
2417
  }
2390
2418
  interface DsPanelTools {
2391
2419
  /**
2392
- * Active tool view — `search`, `messages`, `stacks`, `activity`, or `agents`.
2420
+ * Active tool view — `search`, `agents`, `messages`, `stacks`, or `activity`.
2393
2421
  * @default ''
2394
2422
  */
2395
2423
  "activeTool"?: PanelToolsToolId | '';
2396
2424
  /**
2397
- * When false, width animates to 0.
2425
+ * Rail items rendered in the right column. Set via JS property: `el.items = [...]`. Replace the array reference to update.
2426
+ * @default []
2427
+ */
2428
+ "items"?: PanelToolsItem[];
2429
+ /**
2430
+ * JSON fallback for `items` — useful when framework bindings don't propagate arrays.
2431
+ * @default ''
2432
+ */
2433
+ "itemsJson"?: string;
2434
+ /**
2435
+ * Emitted when a rail button is toggled. Detail = { id, selected }.
2436
+ */
2437
+ "onDsToolChange"?: (event: DsPanelToolsCustomEvent<{
2438
+ id: PanelToolsToolId;
2439
+ selected: boolean;
2440
+ }>) => void;
2441
+ /**
2442
+ * When false, only the icon rail is shown.
2398
2443
  * @default false
2399
2444
  */
2400
2445
  "open"?: boolean;
@@ -2832,11 +2877,11 @@ declare namespace LocalJSX {
2832
2877
  "dismissLabel": string;
2833
2878
  }
2834
2879
  interface DsBarNavAttributes {
2880
+ "navStyle": NavChromeStyle;
2835
2881
  "tabsJson": string;
2836
2882
  "value": string;
2837
2883
  "actionsJson": string;
2838
2884
  "heading": string | undefined;
2839
- "navStyle": NavChromeStyle;
2840
2885
  "basePath": string;
2841
2886
  "currentUrl": string;
2842
2887
  }
@@ -2989,6 +3034,7 @@ declare namespace LocalJSX {
2989
3034
  interface DsPanelToolsAttributes {
2990
3035
  "open": boolean;
2991
3036
  "activeTool": PanelToolsToolId | '';
3037
+ "itemsJson": string;
2992
3038
  }
2993
3039
  interface DsRadioGroupAttributes {
2994
3040
  "value": string;
@@ -95,17 +95,12 @@ export function readShellGradientPosition(surface: GradientSurface): string {
95
95
  return styles.getPropertyValue(surface.positionVar).trim() || '0 0';
96
96
  }
97
97
 
98
- /** Stamp `--_badge-gradient-position` on the badge host. */
98
+ /** Stamp `--_badge-gradient-position` on the badge host (fixed viewport chrome). */
99
99
  export function syncBadgeGradientPosition(badgeHost: HTMLElement): void {
100
- const surface = findGradientSurface(badgeHost);
101
- if (!surface) {
100
+ if (!isShellGradientActive(badgeHost)) {
102
101
  badgeHost.style.removeProperty(BADGE_GRADIENT_POSITION_VAR);
103
102
  return;
104
103
  }
105
104
 
106
- const shellPosition = readShellGradientPosition(surface);
107
- badgeHost.style.setProperty(
108
- BADGE_GRADIENT_POSITION_VAR,
109
- badgeGradientPosition(badgeHost, surface, shellPosition),
110
- );
105
+ badgeHost.style.setProperty(BADGE_GRADIENT_POSITION_VAR, '0 0');
111
106
  }
@@ -25,12 +25,14 @@ export {
25
25
  SHELL_GRADIENT_POSITION_PANEL_VAR,
26
26
  SHELL_GRADIENT_POSITION_BAR_VAR,
27
27
  SHELL_GRADIENT_OPACITY_VAR,
28
+ SHELL_CHROME_SURFACE_POSITION_VAR,
28
29
  SHELL_GRADIENT_OPACITY,
29
30
  buildShellRadialGradient,
30
- shellGradientImageForStyle,
31
+ shellGradientImage,
31
32
  shellGradientSize,
32
33
  shellGradientPositionPanel,
33
34
  shellGradientPositionBar,
35
+ shellChromeSurfacePosition,
34
36
  } from './shell-gradient';
35
37
  export type { ShellGradientLayout } from './shell-gradient';
36
38
  export {