@ds-mo/ui 2.2.1 → 2.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 (46) 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-bar-nav.js +1 -1
  5. package/dist/components/ds-bar-nav.js.map +1 -1
  6. package/dist/components/ds-button-unfilled-icon.js +1 -1
  7. package/dist/components/ds-checkbox.js +1 -1
  8. package/dist/components/ds-field.js +1 -1
  9. package/dist/components/ds-field.js.map +1 -1
  10. package/dist/components/ds-panel-nav.js +1 -1
  11. package/dist/components/ds-panel-nav.js.map +1 -1
  12. package/dist/components/ds-panel-tools.js +1 -1
  13. package/dist/components/ds-panel-tools.js.map +1 -1
  14. package/dist/components/ds-slider.js +1 -1
  15. package/dist/components/ds-tab-group-nav.js +1 -1
  16. package/dist/components/ds-toggle.js +1 -1
  17. package/dist/components/index.js +1 -1
  18. package/dist/components/index.js.map +1 -1
  19. package/dist/components/{p-Bl-GTn9s.js → p-BTndnbR_.js} +2 -2
  20. package/dist/components/p-BTndnbR_.js.map +1 -0
  21. package/dist/components/p-D4zKc1RW.js +2 -0
  22. package/dist/components/p-D4zKc1RW.js.map +1 -0
  23. package/dist/types/components/AppShell/AppShell.d.ts +3 -0
  24. package/dist/types/components/BarNav/BarNav.d.ts +4 -0
  25. package/dist/types/components/ButtonUnfilledIcon/ButtonUnfilledIcon.d.ts +5 -0
  26. package/dist/types/components/PanelNav/PanelNav.d.ts +8 -2
  27. package/dist/types/components/PanelTools/PanelTools.d.ts +8 -0
  28. package/dist/types/components/PanelTools/panel-tools-utils.d.ts +7 -1
  29. package/dist/types/components/TabGroupNav/TabGroupNav.d.ts +22 -3
  30. package/dist/types/components.d.ts +62 -0
  31. package/dist/types/nav/shell-shortcuts.d.ts +10 -0
  32. package/package.json +2 -2
  33. package/src/angular/proxies.ts +17 -9
  34. package/src/react/ds-tab-group-nav.ts +8 -2
  35. package/src/wc/components/AppShell/AppShell.tsx +43 -8
  36. package/src/wc/components/BarNav/BarNav.tsx +33 -2
  37. package/src/wc/components/ButtonUnfilledIcon/ButtonUnfilledIcon.tsx +7 -0
  38. package/src/wc/components/PanelNav/PanelNav.tsx +116 -22
  39. package/src/wc/components/PanelTools/PanelTools.tsx +91 -23
  40. package/src/wc/components/PanelTools/panel-tools-utils.ts +13 -1
  41. package/src/wc/components/TabGroupNav/TabGroupNav.tsx +164 -22
  42. package/src/wc/components.d.ts +62 -0
  43. package/src/wc/nav/shell-shortcuts.ts +63 -0
  44. package/dist/components/p-Bl-GTn9s.js.map +0 -1
  45. package/dist/components/p-CZpCmCiq.js +0 -2
  46. package/dist/components/p-CZpCmCiq.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { Component, Prop, Event, EventEmitter, Element, Listen, Watch, h, Host } from '@stencil/core';
1
+ import { Component, Prop, Event, EventEmitter, Element, Listen, Watch, Method, State, h, Host } from '@stencil/core';
2
2
  import {
3
3
  getSelectableTabs,
4
4
  isTabDivider,
@@ -23,28 +23,39 @@ export class TabGroupNav {
23
23
  @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;
24
24
  @Prop() orientation: 'horizontal' | 'vertical' = 'horizontal';
25
25
 
26
+ /**
27
+ * When `false`, arrow keys move focus only — Space/Enter (or click) commits selection.
28
+ * Used by bar nav where each tab is a full page transition.
29
+ */
30
+ @Prop() selectionFollowsFocus: boolean = true;
31
+
32
+ /** When `false`, every tab uses `tabindex="-1"` (another chrome control owns the tab stop). */
33
+ @Prop() rovingEnabled: boolean = true;
34
+
26
35
  @Event() dsChange!: EventEmitter<string>;
27
36
 
37
+ /** Fired when arrow navigation reaches the first/last tab in manual selection mode. */
38
+ @Event() dsRovingExit!: EventEmitter<'start' | 'end'>;
39
+
40
+ @State() private focusedTabId: string = '';
41
+
28
42
  private get selectableTabs(): TabItemTab[] {
29
43
  return getSelectableTabs(this.tabs);
30
44
  }
31
45
 
32
- private selectTab(id: string, options?: { focus?: boolean }) {
33
- this.value = id;
34
- this.dsChange.emit(id);
35
- if (options?.focus !== false) {
36
- this.focusTab(id);
37
- }
38
- }
39
-
40
- private focusTab(id: string) {
41
- const btn = this.el.querySelector(`[data-tab-id="${id}"]`) as HTMLElement | null;
42
- btn?.focus({ preventScroll: true });
46
+ componentWillLoad() {
47
+ this.syncFocusedTabId(this.value);
43
48
  }
44
49
 
45
- /** Keep focus on the selected tab when value changes externally (e.g. BarNav menu). */
46
50
  @Watch('value')
47
51
  onValueChange(next: string, prev: string | undefined) {
52
+ if (!this.selectionFollowsFocus) {
53
+ if (prev === undefined || next !== this.focusedTabId) {
54
+ this.syncFocusedTabId(next);
55
+ }
56
+ return;
57
+ }
58
+
48
59
  if (prev === undefined || next === prev) return;
49
60
 
50
61
  requestAnimationFrame(() => {
@@ -63,6 +74,64 @@ export class TabGroupNav {
63
74
  });
64
75
  }
65
76
 
77
+ @Watch('tabs')
78
+ onTabsChange() {
79
+ if (!this.selectableTabs.some(tab => tab.id === this.focusedTabId)) {
80
+ this.syncFocusedTabId(this.value);
81
+ }
82
+ }
83
+
84
+ @Method()
85
+ async focusTab(id: string) {
86
+ this.focusedTabId = id;
87
+ const btn = this.el.querySelector(`[data-tab-id="${id}"]`) as HTMLElement | null;
88
+ btn?.focus({ preventScroll: true });
89
+ }
90
+
91
+ @Method()
92
+ async focusLastTab() {
93
+ const tabs = this.selectableTabs;
94
+ const last = [...tabs].reverse().find(tab => !tab.disabled);
95
+ if (last) await this.focusTab(last.id);
96
+ }
97
+
98
+ @Method()
99
+ async focusFirstTab() {
100
+ const first = this.selectableTabs.find(tab => !tab.disabled);
101
+ if (first) await this.focusTab(first.id);
102
+ }
103
+
104
+ private syncFocusedTabId(preferred: string) {
105
+ const tabs = this.selectableTabs;
106
+ if (tabs.some(tab => tab.id === preferred && !tab.disabled)) {
107
+ this.focusedTabId = preferred;
108
+ return;
109
+ }
110
+ const first = tabs.find(tab => !tab.disabled);
111
+ this.focusedTabId = first?.id ?? '';
112
+ }
113
+
114
+ private selectTab(id: string, options?: { focus?: boolean }) {
115
+ this.value = id;
116
+ this.focusedTabId = id;
117
+ this.dsChange.emit(id);
118
+ if (options?.focus !== false) {
119
+ void this.focusTab(id);
120
+ }
121
+ }
122
+
123
+ private getFocusedIndex(): number {
124
+ return this.selectableTabs.findIndex(tab => tab.id === this.focusedTabId);
125
+ }
126
+
127
+ private findEnabledLinear(from: number, step: 1 | -1): number | null {
128
+ const tabs = this.selectableTabs;
129
+ for (let i = from + step; i >= 0 && i < tabs.length; i += step) {
130
+ if (!tabs[i]?.disabled) return i;
131
+ }
132
+ return null;
133
+ }
134
+
66
135
  /**
67
136
  * Find the next non-disabled tab index relative to `from`, stepping by `step`.
68
137
  * Returns `from` unchanged if no other enabled tab exists.
@@ -77,27 +146,77 @@ export class TabGroupNav {
77
146
  return from;
78
147
  }
79
148
 
149
+ private moveFocusToIndex(nextIdx: number) {
150
+ const next = this.selectableTabs[nextIdx];
151
+ if (!next) return;
152
+ this.focusedTabId = next.id;
153
+ void this.focusTab(next.id);
154
+ }
155
+
156
+ private activateFocusedTab(e: KeyboardEvent) {
157
+ if (e.key !== 'Enter' && e.key !== ' ') return;
158
+ const tab = this.selectableTabs.find(item => item.id === this.focusedTabId);
159
+ if (!tab || tab.disabled) return;
160
+ e.preventDefault();
161
+ this.selectTab(tab.id, { focus: true });
162
+ }
163
+
80
164
  @Listen('keydown')
81
165
  handleKeyDown(e: KeyboardEvent) {
82
166
  const tabs = this.selectableTabs;
83
167
  if (!tabs.length) return;
84
168
 
85
- const currentIdx = tabs.findIndex(t => t.id === this.value);
86
- const isVertical = this.orientation === 'vertical';
169
+ if (!this.selectionFollowsFocus) {
170
+ if (e.key === 'Enter' || e.key === ' ') {
171
+ this.activateFocusedTab(e);
172
+ return;
173
+ }
174
+ }
175
+
176
+ const currentIdx = this.selectionFollowsFocus
177
+ ? tabs.findIndex(t => t.id === this.value)
178
+ : this.getFocusedIndex();
179
+ if (currentIdx < 0) return;
87
180
 
181
+ const isVertical = this.orientation === 'vertical';
88
182
  let nextIdx: number | null = null;
183
+ let exit: 'start' | 'end' | null = null;
89
184
 
90
185
  if (!isVertical) {
91
186
  if (e.key === 'ArrowRight') {
92
- nextIdx = this.findEnabled(currentIdx, 1);
187
+ if (this.selectionFollowsFocus) {
188
+ nextIdx = this.findEnabled(currentIdx, 1);
189
+ } else {
190
+ const candidate = this.findEnabledLinear(currentIdx, 1);
191
+ if (candidate === null) exit = 'end';
192
+ else nextIdx = candidate;
193
+ }
93
194
  } else if (e.key === 'ArrowLeft') {
94
- nextIdx = this.findEnabled(currentIdx, -1);
195
+ if (this.selectionFollowsFocus) {
196
+ nextIdx = this.findEnabled(currentIdx, -1);
197
+ } else {
198
+ const candidate = this.findEnabledLinear(currentIdx, -1);
199
+ if (candidate === null) exit = 'start';
200
+ else nextIdx = candidate;
201
+ }
95
202
  }
96
203
  } else {
97
204
  if (e.key === 'ArrowDown') {
98
- nextIdx = this.findEnabled(currentIdx, 1);
205
+ if (this.selectionFollowsFocus) {
206
+ nextIdx = this.findEnabled(currentIdx, 1);
207
+ } else {
208
+ const candidate = this.findEnabledLinear(currentIdx, 1);
209
+ if (candidate === null) exit = 'end';
210
+ else nextIdx = candidate;
211
+ }
99
212
  } else if (e.key === 'ArrowUp') {
100
- nextIdx = this.findEnabled(currentIdx, -1);
213
+ if (this.selectionFollowsFocus) {
214
+ nextIdx = this.findEnabled(currentIdx, -1);
215
+ } else {
216
+ const candidate = this.findEnabledLinear(currentIdx, -1);
217
+ if (candidate === null) exit = 'start';
218
+ else nextIdx = candidate;
219
+ }
101
220
  }
102
221
  }
103
222
 
@@ -109,10 +228,20 @@ export class TabGroupNav {
109
228
  nextIdx = lastEnabled === -1 ? null : tabs.length - 1 - lastEnabled;
110
229
  }
111
230
 
112
- if (nextIdx !== null) {
231
+ if (exit) {
232
+ e.preventDefault();
233
+ this.dsRovingExit.emit(exit);
234
+ return;
235
+ }
236
+
237
+ if (nextIdx !== null && nextIdx !== currentIdx) {
113
238
  e.preventDefault();
114
239
  const next = tabs[nextIdx];
115
- this.selectTab(next.id);
240
+ if (this.selectionFollowsFocus) {
241
+ this.selectTab(next.id);
242
+ } else {
243
+ this.moveFocusToIndex(nextIdx);
244
+ }
116
245
  }
117
246
  }
118
247
 
@@ -123,6 +252,14 @@ export class TabGroupNav {
123
252
  return `on-${this.background}`;
124
253
  }
125
254
 
255
+ private tabIndexForTab(tab: TabItemTab): number {
256
+ if (!this.rovingEnabled) return -1;
257
+ if (this.selectionFollowsFocus) {
258
+ return tab.id === this.value ? 0 : -1;
259
+ }
260
+ return tab.id === this.focusedTabId ? 0 : -1;
261
+ }
262
+
126
263
  render() {
127
264
  const bgClass = this.getBgClass();
128
265
  const isVertical = this.orientation === 'vertical';
@@ -174,8 +311,13 @@ export class TabGroupNav {
174
311
  aria-disabled={tab.disabled || undefined}
175
312
  aria-controls={tab.panelId ?? undefined}
176
313
  disabled={tab.disabled}
177
- tabIndex={isSelected ? 0 : -1}
314
+ tabIndex={this.tabIndexForTab(tab)}
178
315
  onClick={() => !tab.disabled && this.selectTab(tab.id)}
316
+ onFocus={() => {
317
+ if (!this.selectionFollowsFocus) {
318
+ this.focusedTabId = tab.id;
319
+ }
320
+ }}
179
321
  >
180
322
  <span class={{
181
323
  tab__label: true,
@@ -118,6 +118,11 @@ export namespace Components {
118
118
  * @default 'dashboard'
119
119
  */
120
120
  "navStyle": NavChromeStyle;
121
+ /**
122
+ * When `true` (default), registers global shell keyboard shortcuts. Tool chords (⌘/Ctrl+K/A/S/M/N) toggle their drawer open and closed; ⌘/Ctrl+[ toggles panel nav; ⌘/Ctrl+] closes any open tool drawer.
123
+ * @default true
124
+ */
125
+ "shortcutsEnabled": boolean;
121
126
  }
122
127
  interface DsBadge {
123
128
  /**
@@ -332,6 +337,10 @@ export namespace Components {
332
337
  */
333
338
  "dot": boolean;
334
339
  "expanded": boolean | undefined;
340
+ /**
341
+ * Native `tabindex` for roving keyboard groups in shell chrome. Omit for the default button tab stop (`0`).
342
+ */
343
+ "focusTabIndex"?: number;
335
344
  /**
336
345
  * Show a 1px tertiary border without changing the interaction model.
337
346
  * @default false
@@ -693,6 +702,10 @@ export namespace Components {
693
702
  * @default ''
694
703
  */
695
704
  "storageKey": string;
705
+ /**
706
+ * Toggle expanded/collapsed panel nav — used by shell keyboard shortcuts.
707
+ */
708
+ "toggleCollapsed": () => Promise<void>;
696
709
  /**
697
710
  * Single character shown in the collapsed avatar
698
711
  * @default ''
@@ -705,11 +718,19 @@ export namespace Components {
705
718
  "userName": string;
706
719
  }
707
720
  interface DsPanelTools {
721
+ /**
722
+ * Toggle any rail tool open/closed — shell shortcuts ⌘/Ctrl+K/A/S/M/N call this.
723
+ */
724
+ "activateTool": (id: PanelToolsToolId) => Promise<void>;
708
725
  /**
709
726
  * Active tool view — `search`, `agents`, `messages`, `stacks`, or `activity`.
710
727
  * @default ''
711
728
  */
712
729
  "activeTool": PanelToolsToolId | '';
730
+ /**
731
+ * Close the tools drawer when open — used by shell keyboard shortcuts.
732
+ */
733
+ "closeDrawer": () => Promise<void>;
713
734
  /**
714
735
  * Rail items rendered in the right column. Set via JS property: `el.items = [...]`. Replace the array reference to update.
715
736
  * @default []
@@ -890,10 +911,23 @@ export namespace Components {
890
911
  "ariaLabel": string | undefined;
891
912
  "ariaLabelledby": string | undefined;
892
913
  "background": TabGroupNavBackground | undefined;
914
+ "focusFirstTab": () => Promise<void>;
915
+ "focusLastTab": () => Promise<void>;
916
+ "focusTab": (id: string) => Promise<void>;
893
917
  /**
894
918
  * @default 'horizontal'
895
919
  */
896
920
  "orientation": 'horizontal' | 'vertical';
921
+ /**
922
+ * When `false`, every tab uses `tabindex="-1"` (another chrome control owns the tab stop).
923
+ * @default true
924
+ */
925
+ "rovingEnabled": boolean;
926
+ /**
927
+ * When `false`, arrow keys move focus only — Space/Enter (or click) commits selection. Used by bar nav where each tab is a full page transition.
928
+ * @default true
929
+ */
930
+ "selectionFollowsFocus": boolean;
897
931
  /**
898
932
  * @default []
899
933
  */
@@ -1627,6 +1661,7 @@ declare global {
1627
1661
  };
1628
1662
  interface HTMLDsTabGroupNavElementEventMap {
1629
1663
  "dsChange": string;
1664
+ "dsRovingExit": 'start' | 'end';
1630
1665
  }
1631
1666
  interface HTMLDsTabGroupNavElement extends Components.DsTabGroupNav, HTMLStencilElement {
1632
1667
  addEventListener<K extends keyof HTMLDsTabGroupNavElementEventMap>(type: K, listener: (this: HTMLDsTabGroupNavElement, ev: DsTabGroupNavCustomEvent<HTMLDsTabGroupNavElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
@@ -1820,6 +1855,11 @@ declare namespace LocalJSX {
1820
1855
  * @default 'dashboard'
1821
1856
  */
1822
1857
  "navStyle"?: NavChromeStyle;
1858
+ /**
1859
+ * When `true` (default), registers global shell keyboard shortcuts. Tool chords (⌘/Ctrl+K/A/S/M/N) toggle their drawer open and closed; ⌘/Ctrl+[ toggles panel nav; ⌘/Ctrl+] closes any open tool drawer.
1860
+ * @default true
1861
+ */
1862
+ "shortcutsEnabled"?: boolean;
1823
1863
  }
1824
1864
  interface DsBadge {
1825
1865
  /**
@@ -2047,6 +2087,10 @@ declare namespace LocalJSX {
2047
2087
  */
2048
2088
  "dot"?: boolean;
2049
2089
  "expanded"?: boolean | undefined;
2090
+ /**
2091
+ * Native `tabindex` for roving keyboard groups in shell chrome. Omit for the default button tab stop (`0`).
2092
+ */
2093
+ "focusTabIndex"?: number;
2050
2094
  /**
2051
2095
  * Show a 1px tertiary border without changing the interaction model.
2052
2096
  * @default false
@@ -2665,10 +2709,24 @@ declare namespace LocalJSX {
2665
2709
  "ariaLabelledby"?: string | undefined;
2666
2710
  "background"?: TabGroupNavBackground | undefined;
2667
2711
  "onDsChange"?: (event: DsTabGroupNavCustomEvent<string>) => void;
2712
+ /**
2713
+ * Fired when arrow navigation reaches the first/last tab in manual selection mode.
2714
+ */
2715
+ "onDsRovingExit"?: (event: DsTabGroupNavCustomEvent<'start' | 'end'>) => void;
2668
2716
  /**
2669
2717
  * @default 'horizontal'
2670
2718
  */
2671
2719
  "orientation"?: 'horizontal' | 'vertical';
2720
+ /**
2721
+ * When `false`, every tab uses `tabindex="-1"` (another chrome control owns the tab stop).
2722
+ * @default true
2723
+ */
2724
+ "rovingEnabled"?: boolean;
2725
+ /**
2726
+ * When `false`, arrow keys move focus only — Space/Enter (or click) commits selection. Used by bar nav where each tab is a full page transition.
2727
+ * @default true
2728
+ */
2729
+ "selectionFollowsFocus"?: boolean;
2672
2730
  /**
2673
2731
  * @default []
2674
2732
  */
@@ -2906,6 +2964,7 @@ declare namespace LocalJSX {
2906
2964
  "gradient": boolean;
2907
2965
  "grid": boolean;
2908
2966
  "gradientSrc": string;
2967
+ "shortcutsEnabled": boolean;
2909
2968
  }
2910
2969
  interface DsBadgeAttributes {
2911
2970
  "variant": BadgeVariant;
@@ -2977,6 +3036,7 @@ declare namespace LocalJSX {
2977
3036
  "expanded": boolean | undefined;
2978
3037
  "haspopup": string | undefined;
2979
3038
  "pressed": boolean | undefined;
3039
+ "focusTabIndex": number;
2980
3040
  }
2981
3041
  interface DsCardAttributes {
2982
3042
  "elevation": CardElevation;
@@ -3153,6 +3213,8 @@ declare namespace LocalJSX {
3153
3213
  "ariaLabel": string | undefined;
3154
3214
  "ariaLabelledby": string | undefined;
3155
3215
  "orientation": 'horizontal' | 'vertical';
3216
+ "selectionFollowsFocus": boolean;
3217
+ "rovingEnabled": boolean;
3156
3218
  }
3157
3219
  interface DsTableAttributes {
3158
3220
  "loading": boolean;
@@ -0,0 +1,63 @@
1
+ import type { PanelToolsToolId } from '../components/PanelTools/panel-tools-types';
2
+
3
+ export type ShellShortcutAction =
4
+ | 'toggle-panel-nav'
5
+ | 'close-panel-tools'
6
+ | `open-tool:${PanelToolsToolId}`;
7
+
8
+ const TOOL_SHORTCUT_KEYS: Record<string, PanelToolsToolId> = {
9
+ k: 'search',
10
+ a: 'agents',
11
+ s: 'stacks',
12
+ m: 'messages',
13
+ n: 'activity',
14
+ };
15
+
16
+ /** Tool shortcut chords (⌘/Ctrl+K/A/S/M/N) toggle open/closed via `ds-panel-tools.activateTool`. */
17
+
18
+ /** True when Cmd (macOS) or Ctrl (Windows/Linux) is held without Alt/Shift. */
19
+ export function isShellShortcutModifier(
20
+ e: Pick<KeyboardEvent, 'metaKey' | 'ctrlKey' | 'altKey' | 'shiftKey'>,
21
+ ): boolean {
22
+ return (e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey;
23
+ }
24
+
25
+ /** Skip shell shortcuts while typing in an editable control. */
26
+ export function isEditableShortcutTarget(target: EventTarget | null): boolean {
27
+ if (!target || typeof target !== 'object') return false;
28
+ if (!('closest' in target) || typeof target.closest !== 'function') return false;
29
+
30
+ const el = target as Element & { isContentEditable?: boolean };
31
+
32
+ const editable = el.closest(
33
+ 'input, textarea, select, [contenteditable=""], [contenteditable="true"], [contenteditable="plaintext-only"]',
34
+ );
35
+ if (editable) return true;
36
+
37
+ const role = el.getAttribute('role');
38
+ if (role === 'textbox' || role === 'combobox' || role === 'searchbox') return true;
39
+
40
+ return Boolean(el.isContentEditable);
41
+ }
42
+
43
+ function normalizedShortcutKey(e: Pick<KeyboardEvent, 'key' | 'code'>): string {
44
+ if (e.key === '[' || e.code === 'BracketLeft') return '[';
45
+ if (e.key === ']' || e.code === 'BracketRight') return ']';
46
+ return e.key.length === 1 ? e.key.toLowerCase() : e.key.toLowerCase();
47
+ }
48
+
49
+ /** Resolve a shell chrome shortcut, or `null` when the chord is not handled. */
50
+ export function resolveShellShortcut(
51
+ e: Pick<KeyboardEvent, 'key' | 'code' | 'metaKey' | 'ctrlKey' | 'altKey' | 'shiftKey'>,
52
+ ): ShellShortcutAction | null {
53
+ if (!isShellShortcutModifier(e)) return null;
54
+
55
+ const key = normalizedShortcutKey(e);
56
+ if (key === '[') return 'toggle-panel-nav';
57
+ if (key === ']') return 'close-panel-tools';
58
+
59
+ const toolId = TOOL_SHORTCUT_KEYS[key];
60
+ if (toolId) return `open-tool:${toolId}`;
61
+
62
+ return null;
63
+ }
@@ -1 +0,0 @@
1
- {"version":3,"names":["buttonUnfilledIconCss","ButtonUnfilledIcon","__stencil_proxyCustomElement","HTMLElement","constructor","registerHost","this","icon","isActive","activeFill","hasBorder","dot","inactive","type","ariaLabel","buttonEl","handleClick","event","dsClick","emit","dsChange","setFocus","focus","render","bg","background","cls","h","Host","key","tabIndex","ref","el","class","disabled","controls","expanded","undefined","String","haspopup","pressed","onClick","name","size","color","variant","label"],"sources":["src/wc/components/ButtonUnfilledIcon/ButtonUnfilledIcon.css?tag=ds-button-unfilled-icon&encapsulation=scoped","src/wc/components/ButtonUnfilledIcon/ButtonUnfilledIcon.tsx"],"sourcesContent":["@import '../../utils/focus-ring.css';\n\n:host {\n display: inline-flex;\n flex: 0 0 auto;\n width: var(--dimension-size-400);\n height: var(--dimension-size-400);\n outline: none;\n --ds-button-unfilled-icon-fg: var(--color-foreground-secondary);\n --ds-button-unfilled-icon-fg-active: var(--color-foreground-primary);\n --ds-button-unfilled-icon-active-bg: var(--color-interaction-active);\n --ds-button-unfilled-icon-hover-bg: var(--color-interaction-hover);\n --ds-button-unfilled-icon-pressed-bg: var(--color-interaction-pressed);\n --ds-button-unfilled-icon-dot-ring: var(--color-background-secondary);\n --ds-button-unfilled-icon-border: var(--color-border-tertiary);\n}\n\n.button-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dimension-size-400);\n height: var(--dimension-size-400);\n padding: var(--dimension-space-075);\n background: transparent;\n border: var(--dimension-stroke-width-012, 1px) solid transparent;\n border-radius: var(--dimension-radius-025);\n color: var(--ds-button-unfilled-icon-fg);\n cursor: pointer;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n position: relative;\n overflow: hidden;\n transition: color var(--effect-motion-short-2);\n}\n\n.button-icon::before,\n.button-icon::after {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background: transparent;\n pointer-events: none;\n transition: none;\n z-index: 1;\n}\n\n.button-icon::before {\n z-index: 0;\n}\n\n.button-icon--active-fill::before {\n background: var(--ds-button-unfilled-icon-active-bg);\n}\n\n.button-icon:hover:not(.button-icon--inactive)::after {\n background: var(--ds-button-unfilled-icon-hover-bg);\n transition: none;\n}\n\n.button-icon:active:not(.button-icon--inactive)::after {\n background: var(--ds-button-unfilled-icon-pressed-bg);\n transition: none;\n}\n\n.button-icon--active {\n color: var(--ds-button-unfilled-icon-fg-active);\n}\n\n.button-icon--bordered {\n border-color: var(--ds-button-unfilled-icon-border);\n}\n\n.button-icon--bordered::after {\n inset: var(--dimension-stroke-width-012, 1px);\n}\n\n.button-icon--bordered::before {\n inset: var(--dimension-stroke-width-012, 1px);\n}\n\n.button-icon--inactive {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n}\n\n.button-icon--on-medium {\n --ds-button-unfilled-icon-active-bg: var(--color-interaction-on-medium-background-active);\n --ds-button-unfilled-icon-hover-bg: var(--color-interaction-on-medium-background-hover);\n --ds-button-unfilled-icon-pressed-bg: var(--color-interaction-on-medium-background-pressed);\n --ds-button-unfilled-icon-dot-ring: var(--color-background-medium-neutral);\n --ds-focus-ring-color: var(--color-interaction-on-medium-background-focus);\n}\n\n.button-icon--on-bold {\n --ds-button-unfilled-icon-fg: var(--color-foreground-on-bold-background-secondary);\n --ds-button-unfilled-icon-fg-active: var(--color-foreground-on-bold-background-primary);\n --ds-button-unfilled-icon-active-bg: var(--color-interaction-on-bold-background-active);\n --ds-button-unfilled-icon-hover-bg: var(--color-interaction-on-bold-background-hover);\n --ds-button-unfilled-icon-pressed-bg: var(--color-interaction-on-bold-background-pressed);\n --ds-button-unfilled-icon-dot-ring: var(--color-background-bold-neutral);\n --ds-focus-ring-color: var(--color-interaction-on-bold-background-focus);\n}\n\n.button-icon--on-strong {\n --ds-button-unfilled-icon-fg: var(--color-foreground-on-bold-background-secondary);\n --ds-button-unfilled-icon-fg-active: var(--color-foreground-on-bold-background-primary);\n --ds-button-unfilled-icon-active-bg: var(--color-interaction-on-strong-background-active);\n --ds-button-unfilled-icon-hover-bg: var(--color-interaction-on-strong-background-hover);\n --ds-button-unfilled-icon-pressed-bg: var(--color-interaction-on-strong-background-pressed);\n --ds-button-unfilled-icon-dot-ring: var(--color-background-strong-neutral);\n --ds-focus-ring-color: var(--color-interaction-on-strong-background-focus);\n}\n\n.button-icon--on-always-dark {\n --ds-button-unfilled-icon-fg: var(--color-always-dark-foreground-secondary);\n --ds-button-unfilled-icon-fg-active: var(--color-always-dark-foreground-primary);\n --ds-button-unfilled-icon-active-bg: var(--color-always-dark-interaction-active);\n --ds-button-unfilled-icon-hover-bg: var(--color-always-dark-interaction-hover);\n --ds-button-unfilled-icon-pressed-bg: var(--color-always-dark-interaction-pressed);\n --ds-button-unfilled-icon-dot-ring: var(--color-always-dark-background);\n --ds-focus-ring-color: var(--color-always-dark-interaction-focus);\n}\n\n.button-icon--on-navigation {\n --ds-button-unfilled-icon-fg: var(--color-navigation-foreground-secondary);\n --ds-button-unfilled-icon-fg-active: var(--color-navigation-foreground-primary);\n --ds-button-unfilled-icon-active-bg: var(--color-navigation-interaction-active);\n --ds-button-unfilled-icon-hover-bg: var(--color-navigation-interaction-hover);\n --ds-button-unfilled-icon-pressed-bg: var(--color-navigation-interaction-pressed);\n --ds-button-unfilled-icon-dot-ring: var(--color-navigation-background);\n --ds-focus-ring-color: var(--color-navigation-interaction-focus);\n}\n\n.button-icon__icon-wrap {\n position: relative;\n width: var(--dimension-iconography-md);\n height: var(--dimension-iconography-md);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n z-index: 2;\n}\n\n.button-icon__dot {\n position: absolute;\n top: 0;\n right: 0;\n pointer-events: none;\n}\n","import { Component, Element, Event, EventEmitter, h, Host, Method, Prop } from '@stencil/core';\n\nexport type ButtonUnfilledIconBackground =\n | 'faint'\n | 'medium'\n | 'bold'\n | 'strong'\n | 'always-dark'\n | 'navigation';\n\n@Component({\n tag: 'ds-button-unfilled-icon',\n styleUrl: 'ButtonUnfilledIcon.css',\n scoped: true,\n})\nexport class ButtonUnfilledIcon {\n @Element() el!: HTMLElement;\n\n /** Icon name passed to <ds-icon>. */\n @Prop() icon: string = '';\n\n /** Active/selected visual state. */\n @Prop() isActive: boolean = false;\n\n /** When active, render the active interaction fill. Shell chrome can disable this while keeping active icon colour. */\n @Prop() activeFill: boolean = true;\n\n /** Show a 1px tertiary border without changing the interaction model. */\n @Prop() hasBorder: boolean = false;\n\n /** Show a notification dot at the top-right of the icon zone. */\n @Prop() dot: boolean = false;\n\n /** Disables interaction. */\n @Prop() inactive: boolean = false;\n\n /** Native button type. */\n @Prop() type: 'button' | 'submit' | 'reset' = 'button';\n\n /** Parent surface context — adjusts hover/press/focus colours for coloured backgrounds. */\n @Prop() background: ButtonUnfilledIconBackground | undefined;\n\n @Prop({ attribute: 'aria-label' }) ariaLabel: string = 'action';\n @Prop() controls: string | undefined;\n @Prop() expanded: boolean | undefined;\n @Prop() haspopup: string | undefined;\n @Prop() pressed: boolean | undefined;\n\n @Event() dsClick!: EventEmitter<MouseEvent>;\n @Event() dsChange!: EventEmitter<boolean>;\n\n private buttonEl: HTMLButtonElement | null = null;\n\n @Method()\n async setFocus() {\n this.buttonEl?.focus();\n }\n\n private handleClick = (event: MouseEvent) => {\n if (this.inactive) return;\n this.dsClick.emit(event);\n this.dsChange.emit(!this.isActive);\n };\n\n render() {\n const bg = this.background;\n\n const cls: Record<string, boolean> = {\n 'button-icon': true,\n 'ds-focus-ring-inset': true,\n 'button-icon--active': this.isActive,\n 'button-icon--active-fill': this.isActive && this.activeFill,\n 'button-icon--bordered': this.hasBorder,\n 'button-icon--inactive': this.inactive,\n 'button-icon--on-medium': bg === 'medium',\n 'button-icon--on-bold': bg === 'bold',\n 'button-icon--on-strong': bg === 'strong',\n 'button-icon--on-always-dark': bg === 'always-dark',\n 'button-icon--on-navigation': bg === 'navigation',\n };\n\n return (\n <Host tabIndex={-1}>\n <button\n ref={el => {\n this.buttonEl = el ?? null;\n }}\n type={this.type}\n class={cls}\n disabled={this.inactive}\n aria-label={this.ariaLabel}\n aria-controls={this.controls}\n aria-expanded={this.expanded === undefined ? undefined : String(this.expanded)}\n aria-haspopup={this.haspopup}\n aria-pressed={this.pressed === undefined ? undefined : String(this.pressed)}\n onClick={this.handleClick}\n >\n <span class=\"button-icon__icon-wrap\">\n <ds-icon name={this.icon} size=\"md\" color=\"inherit\" />\n {this.dot && (\n <ds-badge\n class=\"button-icon__dot\"\n variant=\"dot\"\n background=\"var(--ds-button-unfilled-icon-dot-ring)\"\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </span>\n </button>\n </Host>\n );\n }\n}\n"],"mappings":"qIAAA,MAAMA,EAAwB,IAAM,q/M,MCevBC,EAAkBC,EAAA,MAAAD,UAAAE,EAL/B,WAAAC,CAAAC,G,4GASUC,KAAAC,KAAe,GAGfD,KAAAE,SAAoB,MAGpBF,KAAAG,WAAsB,KAGtBH,KAAAI,UAAqB,MAGrBJ,KAAAK,IAAe,MAGfL,KAAAM,SAAoB,MAGpBN,KAAAO,KAAsC,SAKXP,KAAAQ,UAAoB,SAS/CR,KAAAS,SAAqC,KAOrCT,KAAAU,YAAeC,IACrB,GAAIX,KAAKM,SAAU,OACnBN,KAAKY,QAAQC,KAAKF,GAClBX,KAAKc,SAASD,MAAMb,KAAKE,SAAS,CAoDrC,CA3DC,cAAMa,GACJf,KAAKS,UAAUO,O,CASjB,MAAAC,GACE,MAAMC,EAAKlB,KAAKmB,WAEhB,MAAMC,EAA+B,CACnC,cAAe,KACf,sBAAuB,KACvB,sBAAuBpB,KAAKE,SAC5B,2BAA4BF,KAAKE,UAAYF,KAAKG,WAClD,wBAAyBH,KAAKI,UAC9B,wBAAyBJ,KAAKM,SAC9B,yBAA0BY,IAAO,SACjC,uBAAwBA,IAAO,OAC/B,yBAA0BA,IAAO,SACjC,8BAA+BA,IAAO,cACtC,6BAA8BA,IAAO,cAGvC,OACEG,EAACC,EAAI,CAAAC,IAAA,2CAACC,UAAU,GACdH,EAAA,UAAAE,IAAA,2CACEE,IAAKC,IACH1B,KAAKS,SAAWiB,GAAM,IAAI,EAE5BnB,KAAMP,KAAKO,KACXoB,MAAOP,EACPQ,SAAU5B,KAAKM,SAAQ,aACXN,KAAKQ,UAAS,gBACXR,KAAK6B,SAAQ,gBACb7B,KAAK8B,WAAaC,UAAYA,UAAYC,OAAOhC,KAAK8B,UAAS,gBAC/D9B,KAAKiC,SAAQ,eACdjC,KAAKkC,UAAYH,UAAYA,UAAYC,OAAOhC,KAAKkC,SACnEC,QAASnC,KAAKU,aAEdW,EAAA,QAAAE,IAAA,2CAAMI,MAAM,0BACVN,EAAA,WAAAE,IAAA,2CAASa,KAAMpC,KAAKC,KAAMoC,KAAK,KAAKC,MAAM,YACzCtC,KAAKK,KACJgB,EAAA,YAAAE,IAAA,2CACEI,MAAM,mBACNY,QAAQ,MACRpB,WAAW,0CACXqB,MAAM,GAAE,cACI,W","ignoreList":[]}
@@ -1,2 +0,0 @@
1
- import{p as o,H as r,c as a,h as n,a as t,t as s}from"./index.js";import{g as e,i}from"./p-BQq26pt9.js";import{d}from"./p-CiEc8Nwx.js";const c=()=>`.sc-ds-tab-group-nav-h{--ds-focus-ring-width:var(--dimension-stroke-width-025, 2px);--ds-focus-ring-offset:var(--dimension-space-025, 2px);--ds-focus-ring-color:var(--color-interaction-focus)}.ds-focus-ring.sc-ds-tab-group-nav:focus-visible{outline:var(--ds-focus-ring-width) solid var(--ds-focus-ring-color);outline-offset:var(--ds-focus-ring-offset)}.ds-focus-ring.ds-focus-ring--visible.sc-ds-tab-group-nav{outline:var(--ds-focus-ring-width) solid var(--ds-focus-ring-color);outline-offset:var(--ds-focus-ring-offset)}.ds-focus-ring-inset.sc-ds-tab-group-nav:focus-visible{outline:none}.ds-focus-ring-inset.sc-ds-tab-group-nav:focus-visible::after{outline:var(--ds-focus-ring-width) solid var(--ds-focus-ring-color);outline-offset:calc(-1 * var(--ds-focus-ring-width))}.ds-focus-ring-inset.ds-focus-ring--visible.sc-ds-tab-group-nav{outline:none}.ds-focus-ring-inset.ds-focus-ring--visible.sc-ds-tab-group-nav::after{outline:var(--ds-focus-ring-width) solid var(--ds-focus-ring-color);outline-offset:calc(-1 * var(--ds-focus-ring-width))}.sc-ds-tab-group-nav-h{display:block}.tab-list.sc-ds-tab-group-nav{display:flex;align-items:center;gap:var(--dimension-space-050);position:relative}.tab-list--vertical.sc-ds-tab-group-nav{flex-direction:column;align-items:stretch}.tab.sc-ds-tab-group-nav{display:flex;align-items:center;justify-content:center;gap:var(--dimension-space-050);padding:var(--dimension-space-075);background-color:transparent;border:none;border-radius:var(--dimension-radius-025);cursor:pointer;-webkit-user-select:none;user-select:none;color:var(--color-foreground-secondary);position:relative;transition:color var(--effect-motion-short-2);flex-shrink:0;height:var(--dimension-size-400);min-width:var(--dimension-size-400);box-sizing:border-box;--_hover-bg:var(--color-interaction-hover);--_pressed-bg:var(--color-interaction-pressed);--_dot:var(--color-foreground-bold-brand);--_dot-ring:var(--color-background-secondary)}.tab.sc-ds-tab-group-nav::after{content:'';position:absolute;inset:0;border-radius:var(--dimension-radius-025, 2px);background:transparent;pointer-events:none;transition:none}.tab.sc-ds-tab-group-nav:hover::after{background:var(--_hover-bg);transition:none}.tab.sc-ds-tab-group-nav:active::after{background:var(--_pressed-bg);transition:none}.tab--selected.sc-ds-tab-group-nav{color:var(--color-foreground-primary)}.tab.sc-ds-tab-group-nav:disabled,.tab[aria-disabled="true"].sc-ds-tab-group-nav{opacity:0.5;cursor:not-allowed;pointer-events:none}.tab-list--vertical.sc-ds-tab-group-nav .tab.sc-ds-tab-group-nav{justify-content:flex-start}.tab__label.sc-ds-tab-group-nav{position:relative;display:inline-flex;align-items:center;padding:0 var(--dimension-space-025);white-space:nowrap}.tab__label--dot.sc-ds-tab-group-nav{padding-right:calc(var(--dimension-space-025) + var(--dimension-space-050))}.tab__dot.sc-ds-tab-group-nav{position:absolute;top:0;right:0;pointer-events:none}.on-medium.sc-ds-tab-group-nav{--_hover-bg:var(--color-interaction-on-medium-background-hover);--_pressed-bg:var(--color-interaction-on-medium-background-pressed);--_dot-ring:var(--color-background-medium-neutral);--ds-focus-ring-color:var(--color-interaction-on-medium-background-focus)}.on-bold.sc-ds-tab-group-nav{--_hover-bg:var(--color-interaction-on-bold-background-hover);--_pressed-bg:var(--color-interaction-on-bold-background-pressed);--_dot-ring:var(--color-background-bold-neutral);--ds-focus-ring-color:var(--color-interaction-on-bold-background-focus);color:var(--color-foreground-on-bold-background-secondary)}.on-bold.tab--selected.sc-ds-tab-group-nav{color:var(--color-foreground-on-bold-background-primary)}.on-strong.sc-ds-tab-group-nav{--_hover-bg:var(--color-interaction-on-strong-background-hover);--_pressed-bg:var(--color-interaction-on-strong-background-pressed);--_dot-ring:var(--color-background-strong-neutral);--ds-focus-ring-color:var(--color-interaction-on-strong-background-focus);color:var(--color-foreground-on-bold-background-secondary)}.on-strong.tab--selected.sc-ds-tab-group-nav{color:var(--color-foreground-on-bold-background-primary)}.on-always-dark.sc-ds-tab-group-nav{--_hover-bg:var(--color-always-dark-interaction-hover);--_pressed-bg:var(--color-always-dark-interaction-pressed);--_dot-ring:var(--color-always-dark-background);--ds-focus-ring-color:var(--color-always-dark-interaction-focus);color:var(--color-always-dark-foreground-secondary)}.on-always-dark.tab--selected.sc-ds-tab-group-nav{color:var(--color-always-dark-foreground-primary)}.on-navigation.sc-ds-tab-group-nav{color:var(--color-navigation-foreground-secondary);--_hover-bg:var(--color-navigation-interaction-hover);--_pressed-bg:var(--color-navigation-interaction-pressed);--_dot:var(--color-navigation-foreground-brand);--_dot-ring:var(--color-navigation-background);--ds-focus-ring-color:var(--color-navigation-interaction-focus)}.on-navigation.tab--selected.sc-ds-tab-group-nav{color:var(--color-navigation-foreground-primary)}.tab-list--on-navigation.sc-ds-tab-group-nav .tab-divider__line.sc-ds-tab-group-nav{background-color:var(--color-navigation-border-tertiary)}.tab-divider.sc-ds-tab-group-nav{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:var(--dimension-space-100);height:var(--dimension-size-400);padding-block:var(--dimension-space-075);flex-shrink:0;pointer-events:none}.tab-divider__line.sc-ds-tab-group-nav{width:var(--dimension-stroke-width-012);align-self:stretch;background-color:var(--color-border-secondary)}.tab-divider--vertical.sc-ds-tab-group-nav{width:auto;height:var(--dimension-space-100);align-self:stretch;padding-block:0;padding-inline:var(--dimension-space-075)}.tab-divider--vertical.sc-ds-tab-group-nav .tab-divider__line.sc-ds-tab-group-nav{width:auto;height:var(--dimension-stroke-width-012);align-self:stretch}`;const l=o(class o extends r{constructor(o){super();if(o!==false){this.__registerHost()}this.dsChange=a(this,"dsChange");this.value="";this.tabs=[];this.orientation="horizontal"}get selectableTabs(){return e(this.tabs)}selectTab(o,r){this.value=o;this.dsChange.emit(o);if(r?.focus!==false){this.focusTab(o)}}focusTab(o){const r=this.el.querySelector(`[data-tab-id="${o}"]`);r?.focus({preventScroll:true})}onValueChange(o,r){if(r===undefined||o===r)return;requestAnimationFrame((()=>{const r=document.activeElement;if(!r||!this.el.contains(r))return;const a=r.getAttribute("data-tab-id");if(a===o)return;const n=this.el.querySelector(`[data-tab-id="${o}"]`);if(n){n.focus({preventScroll:true})}else{r.blur()}}))}findEnabled(o,r){const a=this.selectableTabs;const n=a.length;for(let t=0;t<n;t++){const s=(o+r*(t+1)+n*(t+1))%n;if(!a[s]?.disabled)return s}return o}handleKeyDown(o){const r=this.selectableTabs;if(!r.length)return;const a=r.findIndex((o=>o.id===this.value));const n=this.orientation==="vertical";let t=null;if(!n){if(o.key==="ArrowRight"){t=this.findEnabled(a,1)}else if(o.key==="ArrowLeft"){t=this.findEnabled(a,-1)}}else{if(o.key==="ArrowDown"){t=this.findEnabled(a,1)}else if(o.key==="ArrowUp"){t=this.findEnabled(a,-1)}}if(o.key==="Home"){const o=r.findIndex((o=>!o.disabled));t=o===-1?null:o}else if(o.key==="End"){const o=[...r].reverse().findIndex((o=>!o.disabled));t=o===-1?null:r.length-1-o}if(t!==null){o.preventDefault();const a=r[t];this.selectTab(a.id)}}getBgClass(){if(!this.background||this.background==="faint")return"";if(this.background==="always-dark")return"on-always-dark";if(this.background==="navigation")return"on-navigation";return`on-${this.background}`}render(){const o=this.getBgClass();const r=this.orientation==="vertical";const a=this.background==="navigation";return n(t,{key:"c68d7a69a3c05c7e85e6ab4cf2be6d54351c55c1",class:"tab-group-nav-host"},n("div",{key:"4bf5c4ebc83ca93c0d7da6783f446de04a74854d",role:"tablist",class:{"tab-list":true,"tab-list--vertical":r,"tab-list--on-navigation":a},"aria-label":this.ariaLabel,"aria-labelledby":this.ariaLabelledby,"aria-orientation":r?"vertical":undefined},this.tabs.map(((a,t)=>{if(i(a)){return n("div",{key:`divider-${t}`,class:{"tab-divider":true,"tab-divider--vertical":r},"aria-hidden":"true"},n("div",{class:"tab-divider__line"}))}const s=a.id===this.value;return n("button",{key:a.id,type:"button",role:"tab","data-tab-id":a.id,class:{tab:true,"tab--selected":s,"ds-focus-ring-inset":true,[o]:!!o},"aria-selected":s,"aria-disabled":a.disabled||undefined,"aria-controls":a.panelId??undefined,disabled:a.disabled,tabIndex:s?0:-1,onClick:()=>!a.disabled&&this.selectTab(a.id)},n("span",{class:{tab__label:true,"tab__label--dot":!!a.dot,[s?"text-body-medium-emphasis":"text-body-medium"]:true}},a.label,a.dot&&n("ds-badge",{class:"tab__dot",variant:"dot",background:"var(--_dot-ring)",label:"","aria-hidden":"true"})))}))))}get el(){return this}static get watchers(){return{value:[{onValueChange:0}]}}static get style(){return c()}},[2,"ds-tab-group-nav",{value:[1025],tabs:[16],background:[1],ariaLabel:[1,"aria-label"],ariaLabelledby:[1,"aria-labelledby"],orientation:[1]},[[0,"keydown","handleKeyDown"]],{value:[{onValueChange:0}]}]);function u(){if(typeof customElements==="undefined"){return}const o=["ds-tab-group-nav","ds-badge"];o.forEach((o=>{switch(o){case"ds-tab-group-nav":if(!customElements.get(s(o))){customElements.define(s(o),l)}break;case"ds-badge":if(!customElements.get(s(o))){d()}break}}))}u();export{l as T,u as d};
2
- //# sourceMappingURL=p-CZpCmCiq.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["tabGroupNavCss","TabGroupNav","__stencil_proxyCustomElement","HTMLElement","constructor","registerHost","this","value","tabs","orientation","selectableTabs","getSelectableTabs","selectTab","id","options","dsChange","emit","focus","focusTab","btn","el","querySelector","preventScroll","onValueChange","next","prev","undefined","requestAnimationFrame","active","document","activeElement","contains","activeId","getAttribute","nextTab","blur","findEnabled","from","step","len","length","n","i","disabled","handleKeyDown","e","currentIdx","findIndex","t","isVertical","nextIdx","key","first","lastEnabled","reverse","preventDefault","getBgClass","background","render","bgClass","isNavigation","h","Host","class","role","ariaLabel","ariaLabelledby","map","tab","index","isTabDivider","isSelected","type","panelId","tabIndex","onClick","tab__label","dot","label","variant"],"sources":["src/wc/components/TabGroupNav/TabGroupNav.css?tag=ds-tab-group-nav&encapsulation=scoped","src/wc/components/TabGroupNav/TabGroupNav.tsx"],"sourcesContent":["@import '../../utils/focus-ring.css';\n\n:host {\n display: block;\n}\n\n.tab-list {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-050);\n position: relative;\n}\n\n/* Vertical orientation */\n.tab-list--vertical {\n flex-direction: column;\n align-items: stretch;\n}\n\n.tab {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--dimension-space-050);\n padding: var(--dimension-space-075);\n background-color: transparent;\n border: none;\n border-radius: var(--dimension-radius-025);\n cursor: pointer;\n -webkit-user-select: none;\n user-select: none;\n color: var(--color-foreground-secondary);\n position: relative;\n /* Only transition color — hover bg is instant (see ::after below) */\n transition: color var(--effect-motion-short-2);\n flex-shrink: 0;\n height: var(--dimension-size-400);\n min-width: var(--dimension-size-400);\n box-sizing: border-box;\n --_hover-bg: var(--color-interaction-hover);\n --_pressed-bg: var(--color-interaction-pressed);\n --_dot: var(--color-foreground-bold-brand);\n --_dot-ring: var(--color-background-secondary);\n}\n\n/* Interaction layer — same pattern as PanelNav */\n.tab::after {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: var(--dimension-radius-025, 2px);\n background: transparent;\n pointer-events: none;\n transition: none;\n}\n\n.tab:hover::after { background: var(--_hover-bg); transition: none; }\n.tab:active::after { background: var(--_pressed-bg); transition: none; }\n\n.tab--selected { color: var(--color-foreground-primary); }\n\n.tab:disabled,\n.tab[aria-disabled=\"true\"] {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* Vertical tabs read as a list — left-align the label */\n.tab-list--vertical .tab { justify-content: flex-start; }\n\n/* Label — dot anchors top-right like BarNav action icon zone */\n.tab__label {\n position: relative;\n display: inline-flex;\n align-items: center;\n padding: 0 var(--dimension-space-025);\n white-space: nowrap;\n}\n\n/* Clearance for top-right dot — slightly less than space-025 + space-075 */\n.tab__label--dot {\n padding-right: calc(var(--dimension-space-025) + var(--dimension-space-050));\n}\n\n/* Notification dot — top-right corner of the label zone */\n.tab__dot {\n position: absolute;\n top: 0;\n right: 0;\n pointer-events: none;\n}\n\n/* Background context */\n.on-medium {\n --_hover-bg: var(--color-interaction-on-medium-background-hover);\n --_pressed-bg: var(--color-interaction-on-medium-background-pressed);\n --_dot-ring: var(--color-background-medium-neutral);\n --ds-focus-ring-color: var(--color-interaction-on-medium-background-focus);\n}\n\n.on-bold {\n --_hover-bg: var(--color-interaction-on-bold-background-hover);\n --_pressed-bg: var(--color-interaction-on-bold-background-pressed);\n --_dot-ring: var(--color-background-bold-neutral);\n --ds-focus-ring-color: var(--color-interaction-on-bold-background-focus);\n color: var(--color-foreground-on-bold-background-secondary);\n}\n\n.on-bold.tab--selected { color: var(--color-foreground-on-bold-background-primary); }\n\n.on-strong {\n --_hover-bg: var(--color-interaction-on-strong-background-hover);\n --_pressed-bg: var(--color-interaction-on-strong-background-pressed);\n --_dot-ring: var(--color-background-strong-neutral);\n --ds-focus-ring-color: var(--color-interaction-on-strong-background-focus);\n color: var(--color-foreground-on-bold-background-secondary);\n}\n\n.on-strong.tab--selected { color: var(--color-foreground-on-bold-background-primary); }\n\n.on-always-dark {\n --_hover-bg: var(--color-always-dark-interaction-hover);\n --_pressed-bg: var(--color-always-dark-interaction-pressed);\n --_dot-ring: var(--color-always-dark-background);\n --ds-focus-ring-color: var(--color-always-dark-interaction-focus);\n color: var(--color-always-dark-foreground-secondary);\n}\n\n.on-always-dark.tab--selected { color: var(--color-always-dark-foreground-primary); }\n\n.on-navigation {\n color: var(--color-navigation-foreground-secondary);\n --_hover-bg: var(--color-navigation-interaction-hover);\n --_pressed-bg: var(--color-navigation-interaction-pressed);\n --_dot: var(--color-navigation-foreground-brand);\n --_dot-ring: var(--color-navigation-background);\n --ds-focus-ring-color: var(--color-navigation-interaction-focus);\n}\n\n.on-navigation.tab--selected { color: var(--color-navigation-foreground-primary); }\n\n.tab-list--on-navigation .tab-divider__line {\n background-color: var(--color-navigation-border-tertiary);\n}\n\n/* In-tablist visual group separator — not focusable, skipped by keyboard nav */\n.tab-divider {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--dimension-space-100);\n height: var(--dimension-size-400);\n padding-block: var(--dimension-space-075);\n flex-shrink: 0;\n pointer-events: none;\n}\n\n.tab-divider__line {\n width: var(--dimension-stroke-width-012);\n align-self: stretch;\n background-color: var(--color-border-secondary);\n}\n\n.tab-divider--vertical {\n width: auto;\n height: var(--dimension-space-100);\n align-self: stretch;\n padding-block: 0;\n padding-inline: var(--dimension-space-075);\n}\n\n.tab-divider--vertical .tab-divider__line {\n width: auto;\n height: var(--dimension-stroke-width-012);\n align-self: stretch;\n}\n","import { Component, Prop, Event, EventEmitter, Element, Listen, Watch, h, Host } from '@stencil/core';\nimport {\n getSelectableTabs,\n isTabDivider,\n type TabItem,\n type TabItemTab,\n} from '../TabGroup/tab-item-utils';\n\nexport type TabGroupNavBackground = 'faint' | 'medium' | 'bold' | 'strong' | 'always-dark' | 'navigation';\n\n@Component({\n tag: 'ds-tab-group-nav',\n styleUrl: 'TabGroupNav.css',\n scoped: true,\n})\nexport class TabGroupNav {\n @Element() el!: HTMLElement;\n\n @Prop({ mutable: true }) value: string = '';\n @Prop() tabs: TabItem[] = [];\n @Prop() background: TabGroupNavBackground | undefined;\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | undefined;\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n @Prop() orientation: 'horizontal' | 'vertical' = 'horizontal';\n\n @Event() dsChange!: EventEmitter<string>;\n\n private get selectableTabs(): TabItemTab[] {\n return getSelectableTabs(this.tabs);\n }\n\n private selectTab(id: string, options?: { focus?: boolean }) {\n this.value = id;\n this.dsChange.emit(id);\n if (options?.focus !== false) {\n this.focusTab(id);\n }\n }\n\n private focusTab(id: string) {\n const btn = this.el.querySelector(`[data-tab-id=\"${id}\"]`) as HTMLElement | null;\n btn?.focus({ preventScroll: true });\n }\n\n /** Keep focus on the selected tab when value changes externally (e.g. BarNav menu). */\n @Watch('value')\n onValueChange(next: string, prev: string | undefined) {\n if (prev === undefined || next === prev) return;\n\n requestAnimationFrame(() => {\n const active = document.activeElement as HTMLElement | null;\n if (!active || !this.el.contains(active)) return;\n\n const activeId = active.getAttribute('data-tab-id');\n if (activeId === next) return;\n\n const nextTab = this.el.querySelector(`[data-tab-id=\"${next}\"]`) as HTMLElement | null;\n if (nextTab) {\n nextTab.focus({ preventScroll: true });\n } else {\n active.blur();\n }\n });\n }\n\n /**\n * Find the next non-disabled tab index relative to `from`, stepping by `step`.\n * Returns `from` unchanged if no other enabled tab exists.\n */\n private findEnabled(from: number, step: 1 | -1): number {\n const tabs = this.selectableTabs;\n const len = tabs.length;\n for (let n = 0; n < len; n++) {\n const i = (from + step * (n + 1) + len * (n + 1)) % len;\n if (!tabs[i]?.disabled) return i;\n }\n return from;\n }\n\n @Listen('keydown')\n handleKeyDown(e: KeyboardEvent) {\n const tabs = this.selectableTabs;\n if (!tabs.length) return;\n\n const currentIdx = tabs.findIndex(t => t.id === this.value);\n const isVertical = this.orientation === 'vertical';\n\n let nextIdx: number | null = null;\n\n if (!isVertical) {\n if (e.key === 'ArrowRight') {\n nextIdx = this.findEnabled(currentIdx, 1);\n } else if (e.key === 'ArrowLeft') {\n nextIdx = this.findEnabled(currentIdx, -1);\n }\n } else {\n if (e.key === 'ArrowDown') {\n nextIdx = this.findEnabled(currentIdx, 1);\n } else if (e.key === 'ArrowUp') {\n nextIdx = this.findEnabled(currentIdx, -1);\n }\n }\n\n if (e.key === 'Home') {\n const first = tabs.findIndex(t => !t.disabled);\n nextIdx = first === -1 ? null : first;\n } else if (e.key === 'End') {\n const lastEnabled = [...tabs].reverse().findIndex(t => !t.disabled);\n nextIdx = lastEnabled === -1 ? null : tabs.length - 1 - lastEnabled;\n }\n\n if (nextIdx !== null) {\n e.preventDefault();\n const next = tabs[nextIdx];\n this.selectTab(next.id);\n }\n }\n\n private getBgClass(): string {\n if (!this.background || this.background === 'faint') return '';\n if (this.background === 'always-dark') return 'on-always-dark';\n if (this.background === 'navigation') return 'on-navigation';\n return `on-${this.background}`;\n }\n\n render() {\n const bgClass = this.getBgClass();\n const isVertical = this.orientation === 'vertical';\n const isNavigation = this.background === 'navigation';\n\n return (\n <Host class=\"tab-group-nav-host\">\n <div\n role=\"tablist\"\n class={{\n 'tab-list': true,\n 'tab-list--vertical': isVertical,\n 'tab-list--on-navigation': isNavigation,\n }}\n aria-label={this.ariaLabel}\n aria-labelledby={this.ariaLabelledby}\n aria-orientation={isVertical ? 'vertical' : undefined}\n >\n {this.tabs.map((tab, index) => {\n if (isTabDivider(tab)) {\n return (\n <div\n key={`divider-${index}`}\n class={{\n 'tab-divider': true,\n 'tab-divider--vertical': isVertical,\n }}\n aria-hidden=\"true\"\n >\n <div class=\"tab-divider__line\" />\n </div>\n );\n }\n\n const isSelected = tab.id === this.value;\n return (\n <button\n key={tab.id}\n type=\"button\"\n role=\"tab\"\n data-tab-id={tab.id}\n class={{\n tab: true,\n 'tab--selected': isSelected,\n 'ds-focus-ring-inset': true,\n [bgClass]: !!bgClass,\n }}\n aria-selected={isSelected}\n aria-disabled={tab.disabled || undefined}\n aria-controls={tab.panelId ?? undefined}\n disabled={tab.disabled}\n tabIndex={isSelected ? 0 : -1}\n onClick={() => !tab.disabled && this.selectTab(tab.id)}\n >\n <span class={{\n tab__label: true,\n 'tab__label--dot': !!tab.dot,\n [isSelected ? 'text-body-medium-emphasis' : 'text-body-medium']: true,\n }}>\n {tab.label}\n {tab.dot && (\n <ds-badge\n class=\"tab__dot\"\n variant=\"dot\"\n background=\"var(--_dot-ring)\"\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </span>\n </button>\n );\n })}\n </div>\n </Host>\n );\n }\n}\n"],"mappings":"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","ignoreList":[]}