@ds-mo/ui 2.3.0 → 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.
- package/dist/.build-stamp +1 -1
- package/dist/components/ds-app-shell.js +1 -1
- package/dist/components/ds-app-shell.js.map +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-nav.js.map +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-panel-tools.js.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/types/components/AppShell/AppShell.d.ts +3 -0
- package/dist/types/components/PanelNav/PanelNav.d.ts +2 -1
- package/dist/types/components/PanelTools/PanelTools.d.ts +4 -0
- package/dist/types/components/PanelTools/panel-tools-utils.d.ts +7 -1
- package/dist/types/components.d.ts +23 -0
- package/dist/types/nav/shell-shortcuts.d.ts +10 -0
- package/package.json +2 -2
- package/src/angular/proxies.ts +6 -4
- package/src/wc/components/AppShell/AppShell.tsx +38 -1
- package/src/wc/components/PanelNav/PanelNav.tsx +20 -22
- package/src/wc/components/PanelTools/PanelTools.tsx +29 -11
- package/src/wc/components/PanelTools/panel-tools-utils.ts +13 -1
- package/src/wc/components.d.ts +23 -0
- package/src/wc/nav/shell-shortcuts.ts +63 -0
|
@@ -11,6 +11,8 @@ export declare class AppShell {
|
|
|
11
11
|
* When set, overrides the built-in radial wash.
|
|
12
12
|
*/
|
|
13
13
|
gradientSrc: string;
|
|
14
|
+
/** 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. */
|
|
15
|
+
shortcutsEnabled: boolean;
|
|
14
16
|
el: HTMLElement;
|
|
15
17
|
private resizeObserver;
|
|
16
18
|
private readonly panelNavTransition;
|
|
@@ -40,6 +42,7 @@ export declare class AppShell {
|
|
|
40
42
|
/** Fixed-attachment wash/grid size — always the viewport, never the shell element box. */
|
|
41
43
|
private resolveViewportDimensions;
|
|
42
44
|
private chromeLayerActive;
|
|
45
|
+
handleWindowKeyDown(e: KeyboardEvent): void;
|
|
43
46
|
private syncChrome;
|
|
44
47
|
render(): any;
|
|
45
48
|
}
|
|
@@ -90,7 +90,6 @@ export declare class PanelNav {
|
|
|
90
90
|
/** Centralised toggle: updates `collapsed`, emits `dsNavToggle`, optionally persists. */
|
|
91
91
|
private applyToggle;
|
|
92
92
|
private getAllItems;
|
|
93
|
-
private getRovingTotal;
|
|
94
93
|
private getFooterRovingIndex;
|
|
95
94
|
private getUserRovingIndex;
|
|
96
95
|
private getRovingElement;
|
|
@@ -101,6 +100,8 @@ export declare class PanelNav {
|
|
|
101
100
|
private handleBodyScroll;
|
|
102
101
|
private handleItemClick;
|
|
103
102
|
private handleToggle;
|
|
103
|
+
/** Toggle expanded/collapsed panel nav — used by shell keyboard shortcuts. */
|
|
104
|
+
toggleCollapsed(): Promise<void>;
|
|
104
105
|
private handleFooterAction;
|
|
105
106
|
private handleUserAction;
|
|
106
107
|
private clearEdgeOverlayTimer;
|
|
@@ -45,6 +45,10 @@ export declare class PanelTools {
|
|
|
45
45
|
private isViewActive;
|
|
46
46
|
private headerLabel;
|
|
47
47
|
private handleToolChange;
|
|
48
|
+
/** Toggle any rail tool open/closed — shell shortcuts ⌘/Ctrl+K/A/S/M/N call this. */
|
|
49
|
+
activateTool(id: PanelToolsToolId): Promise<void>;
|
|
50
|
+
/** Close the tools drawer when open — used by shell keyboard shortcuts. */
|
|
51
|
+
closeDrawer(): Promise<void>;
|
|
48
52
|
private focusRailAt;
|
|
49
53
|
private handleRailKeyDown;
|
|
50
54
|
private renderRailAction;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type { PanelToolsItem } from './panel-tools-types';
|
|
1
|
+
import type { PanelToolsItem, PanelToolsToolId } from './panel-tools-types';
|
|
2
2
|
export declare function parsePanelToolsItems(items: PanelToolsItem[], itemsJson: string): PanelToolsItem[];
|
|
3
3
|
export declare function shouldResyncPanelToolsItems(prev: PanelToolsItem[], next: PanelToolsItem[]): boolean;
|
|
4
4
|
export type PanelToolsMotion = 'opening' | 'closing' | 'idle';
|
|
5
5
|
/** True when the drawer clip is fully closed — safe to skip painting slot content. */
|
|
6
6
|
export declare function panelToolsDrawerResting(open: boolean, motion: PanelToolsMotion): boolean;
|
|
7
|
+
/** Toggle or switch rail tool selection — repeat activation closes the active tool. */
|
|
8
|
+
export declare function resolvePanelToolActivation(open: boolean, activeTool: PanelToolsToolId | '', id: PanelToolsToolId): {
|
|
9
|
+
open: boolean;
|
|
10
|
+
activeTool: PanelToolsToolId;
|
|
11
|
+
selected: boolean;
|
|
12
|
+
};
|
|
7
13
|
//# sourceMappingURL=panel-tools-utils.d.ts.map
|
|
@@ -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
|
/**
|
|
@@ -697,6 +702,10 @@ export namespace Components {
|
|
|
697
702
|
* @default ''
|
|
698
703
|
*/
|
|
699
704
|
"storageKey": string;
|
|
705
|
+
/**
|
|
706
|
+
* Toggle expanded/collapsed panel nav — used by shell keyboard shortcuts.
|
|
707
|
+
*/
|
|
708
|
+
"toggleCollapsed": () => Promise<void>;
|
|
700
709
|
/**
|
|
701
710
|
* Single character shown in the collapsed avatar
|
|
702
711
|
* @default ''
|
|
@@ -709,11 +718,19 @@ export namespace Components {
|
|
|
709
718
|
"userName": string;
|
|
710
719
|
}
|
|
711
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>;
|
|
712
725
|
/**
|
|
713
726
|
* Active tool view — `search`, `agents`, `messages`, `stacks`, or `activity`.
|
|
714
727
|
* @default ''
|
|
715
728
|
*/
|
|
716
729
|
"activeTool": PanelToolsToolId | '';
|
|
730
|
+
/**
|
|
731
|
+
* Close the tools drawer when open — used by shell keyboard shortcuts.
|
|
732
|
+
*/
|
|
733
|
+
"closeDrawer": () => Promise<void>;
|
|
717
734
|
/**
|
|
718
735
|
* Rail items rendered in the right column. Set via JS property: `el.items = [...]`. Replace the array reference to update.
|
|
719
736
|
* @default []
|
|
@@ -1838,6 +1855,11 @@ declare namespace LocalJSX {
|
|
|
1838
1855
|
* @default 'dashboard'
|
|
1839
1856
|
*/
|
|
1840
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;
|
|
1841
1863
|
}
|
|
1842
1864
|
interface DsBadge {
|
|
1843
1865
|
/**
|
|
@@ -2942,6 +2964,7 @@ declare namespace LocalJSX {
|
|
|
2942
2964
|
"gradient": boolean;
|
|
2943
2965
|
"grid": boolean;
|
|
2944
2966
|
"gradientSrc": string;
|
|
2967
|
+
"shortcutsEnabled": boolean;
|
|
2945
2968
|
}
|
|
2946
2969
|
interface DsBadgeAttributes {
|
|
2947
2970
|
"variant": BadgeVariant;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PanelToolsToolId } from '../components/PanelTools/panel-tools-types';
|
|
2
|
+
export type ShellShortcutAction = 'toggle-panel-nav' | 'close-panel-tools' | `open-tool:${PanelToolsToolId}`;
|
|
3
|
+
/** Tool shortcut chords (⌘/Ctrl+K/A/S/M/N) toggle open/closed via `ds-panel-tools.activateTool`. */
|
|
4
|
+
/** True when Cmd (macOS) or Ctrl (Windows/Linux) is held without Alt/Shift. */
|
|
5
|
+
export declare function isShellShortcutModifier(e: Pick<KeyboardEvent, 'metaKey' | 'ctrlKey' | 'altKey' | 'shiftKey'>): boolean;
|
|
6
|
+
/** Skip shell shortcuts while typing in an editable control. */
|
|
7
|
+
export declare function isEditableShortcutTarget(target: EventTarget | null): boolean;
|
|
8
|
+
/** Resolve a shell chrome shortcut, or `null` when the chord is not handled. */
|
|
9
|
+
export declare function resolveShellShortcut(e: Pick<KeyboardEvent, 'key' | 'code' | 'metaKey' | 'ctrlKey' | 'altKey' | 'shiftKey'>): ShellShortcutAction | null;
|
|
10
|
+
//# sourceMappingURL=shell-shortcuts.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ds-mo/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "CompoMo — composable web components styled with TokoMo design tokens",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web-components",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"pretest": "node scripts/generate-icon-catalog.mjs",
|
|
70
70
|
"build": "stencil build && node scripts/patch-index-types.mjs && node scripts/verify-icons-externalized.mjs && node scripts/write-build-stamp.mjs",
|
|
71
71
|
"verify:pack": "node scripts/verify-npm-pack.mjs && node scripts/verify-nav-import.mjs",
|
|
72
|
-
"test": "node --import tsx/esm --test tests/panel-nav-utils.test.ts tests/panel-tools.test.ts tests/shell-view-transition.test.ts tests/shell-gradient.test.ts tests/shell-chrome-metrics.test.ts tests/shell-chrome-layer.test.ts tests/chrome-transition.test.ts tests/bar-nav-overflow-hysteresis.test.ts tests/badge-gradient-ring.test.ts tests/scroll-edge-fade.test.ts tests/resolve-css-length-px.test.ts tests/resolve-css-time-ms.test.ts tests/overlay-positioning.test.ts tests/menu-position.test.ts tests/bar-nav-utils.test.ts tests/bar-nav-tabs-menu-utils.test.ts tests/bar-nav-dom-utils.test.ts tests/icon-catalog.test.ts",
|
|
72
|
+
"test": "node --import tsx/esm --test tests/panel-nav-utils.test.ts tests/panel-tools.test.ts tests/shell-shortcuts.test.ts tests/shell-view-transition.test.ts tests/shell-gradient.test.ts tests/shell-chrome-metrics.test.ts tests/shell-chrome-layer.test.ts tests/chrome-transition.test.ts tests/bar-nav-overflow-hysteresis.test.ts tests/badge-gradient-ring.test.ts tests/scroll-edge-fade.test.ts tests/resolve-css-length-px.test.ts tests/resolve-css-time-ms.test.ts tests/overlay-positioning.test.ts tests/menu-position.test.ts tests/bar-nav-utils.test.ts tests/bar-nav-tabs-menu-utils.test.ts tests/bar-nav-dom-utils.test.ts tests/icon-catalog.test.ts",
|
|
73
73
|
"test:e2e": "npm run build && playwright test",
|
|
74
74
|
"test:e2e:install": "playwright install chromium",
|
|
75
75
|
"build:docs": "stencil build --docs",
|
package/src/angular/proxies.ts
CHANGED
|
@@ -36,14 +36,14 @@ export declare interface DsAccordion extends Components.DsAccordion {
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
@ProxyCmp({
|
|
39
|
-
inputs: ['gradient', 'gradientSrc', 'grid', 'navStyle']
|
|
39
|
+
inputs: ['gradient', 'gradientSrc', 'grid', 'navStyle', 'shortcutsEnabled']
|
|
40
40
|
})
|
|
41
41
|
@Component({
|
|
42
42
|
selector: 'ds-app-shell',
|
|
43
43
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
44
44
|
template: '<ng-content></ng-content>',
|
|
45
45
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
|
46
|
-
inputs: ['gradient', 'gradientSrc', 'grid', 'navStyle'],
|
|
46
|
+
inputs: ['gradient', 'gradientSrc', 'grid', 'navStyle', 'shortcutsEnabled'],
|
|
47
47
|
standalone: false
|
|
48
48
|
})
|
|
49
49
|
export class DsAppShell {
|
|
@@ -639,7 +639,8 @@ export declare interface DsPagination extends Components.DsPagination {
|
|
|
639
639
|
|
|
640
640
|
|
|
641
641
|
@ProxyCmp({
|
|
642
|
-
inputs: ['activeId', 'breakpoint', 'collapsed', 'currentUrl', 'disableViewTransition', 'groups', 'navStyle', 'routerMode', 'storageKey', 'userInitial', 'userName']
|
|
642
|
+
inputs: ['activeId', 'breakpoint', 'collapsed', 'currentUrl', 'disableViewTransition', 'groups', 'navStyle', 'routerMode', 'storageKey', 'userInitial', 'userName'],
|
|
643
|
+
methods: ['toggleCollapsed']
|
|
643
644
|
})
|
|
644
645
|
@Component({
|
|
645
646
|
selector: 'ds-panel-nav',
|
|
@@ -695,7 +696,8 @@ export declare interface DsPanelNav extends Components.DsPanelNav {
|
|
|
695
696
|
|
|
696
697
|
|
|
697
698
|
@ProxyCmp({
|
|
698
|
-
inputs: ['activeTool', 'items', 'itemsJson', 'open']
|
|
699
|
+
inputs: ['activeTool', 'items', 'itemsJson', 'open'],
|
|
700
|
+
methods: ['activateTool', 'closeDrawer']
|
|
699
701
|
})
|
|
700
702
|
@Component({
|
|
701
703
|
selector: 'ds-panel-tools',
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { Component, Prop, Element, Watch, h, Host } from '@stencil/core';
|
|
1
|
+
import { Component, Prop, Element, Watch, Listen, h, Host } from '@stencil/core';
|
|
2
2
|
import type { NavChromeStyle } from '../../nav/nav-chrome';
|
|
3
|
+
import {
|
|
4
|
+
isEditableShortcutTarget,
|
|
5
|
+
resolveShellShortcut,
|
|
6
|
+
} from '../../nav/shell-shortcuts';
|
|
7
|
+
import type { PanelToolsToolId } from '../PanelTools/panel-tools-types';
|
|
3
8
|
import {
|
|
4
9
|
CHROME_TRANSITION_END,
|
|
5
10
|
CHROME_TRANSITION_START,
|
|
@@ -48,6 +53,9 @@ export class AppShell {
|
|
|
48
53
|
*/
|
|
49
54
|
@Prop() gradientSrc: string = '';
|
|
50
55
|
|
|
56
|
+
/** 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. */
|
|
57
|
+
@Prop({ attribute: 'shortcuts-enabled' }) shortcutsEnabled: boolean = true;
|
|
58
|
+
|
|
51
59
|
@Element() el!: HTMLElement;
|
|
52
60
|
|
|
53
61
|
private resizeObserver: ResizeObserver | null = null;
|
|
@@ -242,6 +250,35 @@ export class AppShell {
|
|
|
242
250
|
return this.gradient || this.grid;
|
|
243
251
|
}
|
|
244
252
|
|
|
253
|
+
@Listen('keydown', { target: 'window', capture: true })
|
|
254
|
+
handleWindowKeyDown(e: KeyboardEvent) {
|
|
255
|
+
if (!this.shortcutsEnabled) return;
|
|
256
|
+
if (isEditableShortcutTarget(e.target)) return;
|
|
257
|
+
|
|
258
|
+
const action = resolveShellShortcut(e);
|
|
259
|
+
if (!action) return;
|
|
260
|
+
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
|
|
263
|
+
const panel = this.el.querySelector('ds-panel-nav') as HTMLDsPanelNavElement | null;
|
|
264
|
+
const tools = this.el.querySelector('ds-panel-tools') as HTMLDsPanelToolsElement | null;
|
|
265
|
+
|
|
266
|
+
if (action === 'toggle-panel-nav') {
|
|
267
|
+
void panel?.toggleCollapsed();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (action === 'close-panel-tools') {
|
|
272
|
+
void tools?.closeDrawer();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (action.startsWith('open-tool:') && tools) {
|
|
277
|
+
const toolId = action.slice('open-tool:'.length) as PanelToolsToolId;
|
|
278
|
+
void tools.activateTool(toolId);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
245
282
|
private syncChrome() {
|
|
246
283
|
const panelNav = this.el.querySelector('ds-panel-nav') as HTMLElement | null;
|
|
247
284
|
const bar = this.el.querySelector('ds-bar-nav') as HTMLElement | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, Prop, Event, EventEmitter, Watch, State, Element, h, Host } from '@stencil/core';
|
|
1
|
+
import { Component, Prop, Event, EventEmitter, Watch, State, Element, Method, h, Host } from '@stencil/core';
|
|
2
2
|
import type { ChromeTransitionDetail } from '../../nav/chrome-transition';
|
|
3
3
|
import type { NavChromeStyle } from '../../nav/nav-chrome';
|
|
4
4
|
import {
|
|
@@ -288,10 +288,6 @@ export class PanelNav {
|
|
|
288
288
|
return this.parsedGroups.flatMap(g => g.items);
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
private getRovingTotal(): number {
|
|
292
|
-
return 1 + this.getAllItems().length + 2;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
291
|
private getFooterRovingIndex(): number {
|
|
296
292
|
return 1 + this.getAllItems().length;
|
|
297
293
|
}
|
|
@@ -351,35 +347,29 @@ export class PanelNav {
|
|
|
351
347
|
return;
|
|
352
348
|
}
|
|
353
349
|
|
|
354
|
-
const total = this.getRovingTotal();
|
|
355
350
|
const footerIdx = this.getFooterRovingIndex();
|
|
356
351
|
const userIdx = this.getUserRovingIndex();
|
|
357
|
-
let next: number;
|
|
352
|
+
let next: number | undefined;
|
|
358
353
|
|
|
359
354
|
switch (e.key) {
|
|
360
355
|
case 'ArrowDown':
|
|
361
|
-
|
|
356
|
+
if (index === userIdx) return;
|
|
362
357
|
if (index === footerIdx) next = userIdx;
|
|
363
|
-
else
|
|
364
|
-
else next = (index + 1) % total;
|
|
358
|
+
else next = index + 1;
|
|
365
359
|
break;
|
|
366
360
|
case 'ArrowUp':
|
|
367
|
-
|
|
368
|
-
if (index ===
|
|
369
|
-
else if (index ===
|
|
370
|
-
else next =
|
|
361
|
+
if (index === 0) return;
|
|
362
|
+
if (index === userIdx) next = footerIdx;
|
|
363
|
+
else if (index === footerIdx) next = footerIdx - 1;
|
|
364
|
+
else next = index - 1;
|
|
371
365
|
break;
|
|
372
366
|
case 'ArrowRight':
|
|
373
|
-
if (index === footerIdx)
|
|
374
|
-
|
|
375
|
-
next = userIdx;
|
|
376
|
-
} else return;
|
|
367
|
+
if (index === footerIdx) next = userIdx;
|
|
368
|
+
else return;
|
|
377
369
|
break;
|
|
378
370
|
case 'ArrowLeft':
|
|
379
|
-
if (index === userIdx)
|
|
380
|
-
|
|
381
|
-
next = footerIdx;
|
|
382
|
-
} else return;
|
|
371
|
+
if (index === userIdx) next = footerIdx;
|
|
372
|
+
else return;
|
|
383
373
|
break;
|
|
384
374
|
case 'Home':
|
|
385
375
|
e.preventDefault();
|
|
@@ -393,6 +383,8 @@ export class PanelNav {
|
|
|
393
383
|
return;
|
|
394
384
|
}
|
|
395
385
|
|
|
386
|
+
if (next === undefined || next === index) return;
|
|
387
|
+
e.preventDefault();
|
|
396
388
|
this.focusRovingAt(next);
|
|
397
389
|
}
|
|
398
390
|
|
|
@@ -417,6 +409,12 @@ export class PanelNav {
|
|
|
417
409
|
this.applyToggle(!this.collapsed);
|
|
418
410
|
}
|
|
419
411
|
|
|
412
|
+
/** Toggle expanded/collapsed panel nav — used by shell keyboard shortcuts. */
|
|
413
|
+
@Method()
|
|
414
|
+
async toggleCollapsed() {
|
|
415
|
+
this.handleToggle();
|
|
416
|
+
}
|
|
417
|
+
|
|
420
418
|
private handleFooterAction() {
|
|
421
419
|
this.dsNavFooterAction.emit();
|
|
422
420
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, Prop, Event, EventEmitter, Element, State, Watch, h, Host } from '@stencil/core';
|
|
1
|
+
import { Component, Prop, Event, EventEmitter, Element, State, Watch, Method, h, Host } from '@stencil/core';
|
|
2
2
|
import type { ChromeTransitionDetail } from '../../nav/chrome-transition';
|
|
3
3
|
import {
|
|
4
4
|
PANEL_TOOLS_LABELS,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type PanelToolsItem,
|
|
7
7
|
type PanelToolsToolId,
|
|
8
8
|
} from './panel-tools-types';
|
|
9
|
-
import { parsePanelToolsItems, panelToolsDrawerResting } from './panel-tools-utils';
|
|
9
|
+
import { parsePanelToolsItems, panelToolsDrawerResting, resolvePanelToolActivation } from './panel-tools-utils';
|
|
10
10
|
|
|
11
11
|
@Component({
|
|
12
12
|
tag: 'ds-panel-tools',
|
|
@@ -142,20 +142,36 @@ export class PanelTools {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
private handleToolChange = (id: PanelToolsToolId) => {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
} else {
|
|
150
|
-
this.open = false;
|
|
151
|
-
}
|
|
152
|
-
this.dsToolChange.emit({ id, selected });
|
|
145
|
+
const next = resolvePanelToolActivation(this.open, this.activeTool, id);
|
|
146
|
+
this.open = next.open;
|
|
147
|
+
this.activeTool = next.activeTool;
|
|
148
|
+
this.dsToolChange.emit({ id, selected: next.selected });
|
|
153
149
|
};
|
|
154
150
|
|
|
151
|
+
/** Toggle any rail tool open/closed — shell shortcuts ⌘/Ctrl+K/A/S/M/N call this. */
|
|
152
|
+
@Method()
|
|
153
|
+
async activateTool(id: PanelToolsToolId) {
|
|
154
|
+
const item = this.railItems.find(entry => entry.id === id);
|
|
155
|
+
if (!item || item.inactive) return;
|
|
156
|
+
this.handleToolChange(id);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Close the tools drawer when open — used by shell keyboard shortcuts. */
|
|
160
|
+
@Method()
|
|
161
|
+
async closeDrawer() {
|
|
162
|
+
if (!this.open) return;
|
|
163
|
+
const id = this.activeTool;
|
|
164
|
+
this.open = false;
|
|
165
|
+
if (id) {
|
|
166
|
+
this.dsToolChange.emit({ id, selected: false });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
155
170
|
private focusRailAt(index: number) {
|
|
156
171
|
const items = this.orderedRailItems;
|
|
157
172
|
if (!items.length) return;
|
|
158
|
-
const bounded = ((index
|
|
173
|
+
const bounded = Math.max(0, Math.min(index, items.length - 1));
|
|
174
|
+
if (bounded === this.rovingIndex) return;
|
|
159
175
|
this.rovingIndex = bounded;
|
|
160
176
|
const actions = Array.from(
|
|
161
177
|
this.el.querySelectorAll<HTMLElement>('.panel-tools__rail-action .button-icon'),
|
|
@@ -174,12 +190,14 @@ export class PanelTools {
|
|
|
174
190
|
}
|
|
175
191
|
|
|
176
192
|
if (e.key === 'ArrowDown') {
|
|
193
|
+
if (index >= items.length - 1) return;
|
|
177
194
|
e.preventDefault();
|
|
178
195
|
this.focusRailAt(index + 1);
|
|
179
196
|
return;
|
|
180
197
|
}
|
|
181
198
|
|
|
182
199
|
if (e.key === 'ArrowUp') {
|
|
200
|
+
if (index <= 0) return;
|
|
183
201
|
e.preventDefault();
|
|
184
202
|
this.focusRailAt(index - 1);
|
|
185
203
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseJsonArrayProp } from '../BarNav/bar-nav-utils';
|
|
2
|
-
import type { PanelToolsItem } from './panel-tools-types';
|
|
2
|
+
import type { PanelToolsItem, PanelToolsToolId } from './panel-tools-types';
|
|
3
3
|
|
|
4
4
|
export function parsePanelToolsItems(
|
|
5
5
|
items: PanelToolsItem[],
|
|
@@ -36,3 +36,15 @@ export type PanelToolsMotion = 'opening' | 'closing' | 'idle';
|
|
|
36
36
|
export function panelToolsDrawerResting(open: boolean, motion: PanelToolsMotion): boolean {
|
|
37
37
|
return !open && motion === 'idle';
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/** Toggle or switch rail tool selection — repeat activation closes the active tool. */
|
|
41
|
+
export function resolvePanelToolActivation(
|
|
42
|
+
open: boolean,
|
|
43
|
+
activeTool: PanelToolsToolId | '',
|
|
44
|
+
id: PanelToolsToolId,
|
|
45
|
+
): { open: boolean; activeTool: PanelToolsToolId; selected: boolean } {
|
|
46
|
+
if (open && activeTool === id) {
|
|
47
|
+
return { open: false, activeTool: id, selected: false };
|
|
48
|
+
}
|
|
49
|
+
return { open: true, activeTool: id, selected: true };
|
|
50
|
+
}
|
package/src/wc/components.d.ts
CHANGED
|
@@ -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
|
/**
|
|
@@ -697,6 +702,10 @@ export namespace Components {
|
|
|
697
702
|
* @default ''
|
|
698
703
|
*/
|
|
699
704
|
"storageKey": string;
|
|
705
|
+
/**
|
|
706
|
+
* Toggle expanded/collapsed panel nav — used by shell keyboard shortcuts.
|
|
707
|
+
*/
|
|
708
|
+
"toggleCollapsed": () => Promise<void>;
|
|
700
709
|
/**
|
|
701
710
|
* Single character shown in the collapsed avatar
|
|
702
711
|
* @default ''
|
|
@@ -709,11 +718,19 @@ export namespace Components {
|
|
|
709
718
|
"userName": string;
|
|
710
719
|
}
|
|
711
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>;
|
|
712
725
|
/**
|
|
713
726
|
* Active tool view — `search`, `agents`, `messages`, `stacks`, or `activity`.
|
|
714
727
|
* @default ''
|
|
715
728
|
*/
|
|
716
729
|
"activeTool": PanelToolsToolId | '';
|
|
730
|
+
/**
|
|
731
|
+
* Close the tools drawer when open — used by shell keyboard shortcuts.
|
|
732
|
+
*/
|
|
733
|
+
"closeDrawer": () => Promise<void>;
|
|
717
734
|
/**
|
|
718
735
|
* Rail items rendered in the right column. Set via JS property: `el.items = [...]`. Replace the array reference to update.
|
|
719
736
|
* @default []
|
|
@@ -1838,6 +1855,11 @@ declare namespace LocalJSX {
|
|
|
1838
1855
|
* @default 'dashboard'
|
|
1839
1856
|
*/
|
|
1840
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;
|
|
1841
1863
|
}
|
|
1842
1864
|
interface DsBadge {
|
|
1843
1865
|
/**
|
|
@@ -2942,6 +2964,7 @@ declare namespace LocalJSX {
|
|
|
2942
2964
|
"gradient": boolean;
|
|
2943
2965
|
"grid": boolean;
|
|
2944
2966
|
"gradientSrc": string;
|
|
2967
|
+
"shortcutsEnabled": boolean;
|
|
2945
2968
|
}
|
|
2946
2969
|
interface DsBadgeAttributes {
|
|
2947
2970
|
"variant": BadgeVariant;
|
|
@@ -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
|
+
}
|