@workbench-kit/shell-react 0.0.2-prototype.0.2.40 → 0.0.2-prototype.0.2.43

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 (35) hide show
  1. package/README.md +42 -2
  2. package/package.json +13 -10
  3. package/src/extensions/extension-enablement-controller.ts +40 -9
  4. package/src/extensions/theme-selection-protection.ts +80 -55
  5. package/src/field-remap/chrome-labels.ts +18 -0
  6. package/src/field-remap/convert-note-editor.tsx +30 -24
  7. package/src/field-remap/convert-palette.tsx +6 -0
  8. package/src/field-remap/demo.tsx +8 -2
  9. package/src/field-remap/detail-panel.tsx +225 -192
  10. package/src/field-remap/drag-payload.ts +48 -0
  11. package/src/field-remap/flow-adapter.ts +216 -88
  12. package/src/field-remap/flow-ops.ts +150 -0
  13. package/src/field-remap/flow.tsx +1055 -162
  14. package/src/field-remap/index.ts +2 -0
  15. package/src/field-remap/io-class-browse.tsx +34 -22
  16. package/src/field-remap/keyboard.ts +16 -0
  17. package/src/field-remap/modal-detail.tsx +34 -0
  18. package/src/field-remap/panel.tsx +50 -14
  19. package/src/field-remap/transform-options-editor.tsx +5 -1
  20. package/src/field-remap/view.css +61 -42
  21. package/src/index.ts +7 -0
  22. package/src/management/keybinding-overrides-storage.ts +48 -23
  23. package/src/management/keybinding-settings.tsx +10 -1
  24. package/src/management/use-keybinding-management.ts +69 -19
  25. package/src/shell/appearance-catalog.ts +453 -0
  26. package/src/shell/appearance-controller.ts +331 -0
  27. package/src/shell/appearance-presentation.ts +269 -0
  28. package/src/shell/provider.tsx +147 -18
  29. package/src/shell/settings.tsx +282 -153
  30. package/src/shell/shell.tsx +88 -18
  31. package/src/workbench/appearance-storage.ts +2 -2
  32. package/src/workbench/command-host-controller.tsx +223 -0
  33. package/src/workbench/command-host.tsx +100 -150
  34. package/src/workbench/keybinding-bridge.ts +84 -38
  35. package/src/workbench/shell-command-registration.ts +27 -2
@@ -16,6 +16,8 @@ export {
16
16
  } from './panel.js';
17
17
  export {
18
18
  FieldRemapFlowMapper,
19
+ type FieldRemapConnectionFeedback,
20
+ type FieldRemapConnectionFeedbackReason,
19
21
  type FieldRemapFlowActions,
20
22
  type FieldRemapFlowMapperProps,
21
23
  } from './flow.js';
@@ -5,6 +5,8 @@ import {
5
5
  type SourceField,
6
6
  type TargetSlot,
7
7
  } from '@workbench-kit/field-remap';
8
+ import { SideBarRow } from '@workbench-kit/react/layout';
9
+ import { Badge } from '@workbench-kit/react/primitives';
8
10
 
9
11
  export type FieldRemapIoChrome = 'browse' | 'edit' | 'none';
10
12
 
@@ -56,25 +58,30 @@ function FieldTree({
56
58
  <ul className="workbench-field-remap-io-browse__fields">
57
59
  {nodes.map((node) => (
58
60
  <li key={node.id}>
59
- <div className="workbench-field-remap-io-browse__row">
61
+ <SideBarRow className="workbench-field-remap-io-browse__row" data-static="true">
60
62
  <code className="workbench-field-remap-io-browse__path">{node.path ?? node.label}</code>
61
63
  <span className="workbench-field-remap-io-browse__meta">
62
64
  {node.dataType ? <span>{node.dataType}</span> : null}
63
65
  {node.classRef ? (
64
- <span
66
+ <Badge
65
67
  className="workbench-field-remap-io-browse__badge"
66
68
  title={labels?.classRefTitle ?? 'classRef'}
69
+ variant="muted"
67
70
  >
68
71
  {formatClassRef(node.classRef)}
69
- </span>
72
+ </Badge>
70
73
  ) : null}
71
74
  {node.hidden === true ? (
72
- <span className="workbench-field-remap-io-browse__badge" title="hidden">
75
+ <Badge
76
+ className="workbench-field-remap-io-browse__badge"
77
+ title="hidden"
78
+ variant="muted"
79
+ >
73
80
  {labels?.hiddenBadge ?? 'Hidden'}
74
- </span>
81
+ </Badge>
75
82
  ) : null}
76
83
  </span>
77
- </div>
84
+ </SideBarRow>
78
85
  {node.children?.length ? (
79
86
  <FieldTree emptyLabel={emptyLabel} labels={labels} nodes={node.children} />
80
87
  ) : null}
@@ -101,22 +108,27 @@ function PortSection({
101
108
  {nodes.length === 0 ? (
102
109
  <p className="workbench-field-remap-io-browse__empty">{emptyLabel}</p>
103
110
  ) : (
104
- nodes.map((node) => (
105
- <div className="workbench-field-remap-io-browse__port" key={node.id}>
106
- <p className="workbench-field-remap-io-browse__port-title">
107
- <span className="workbench-field-remap-io-browse__port-id">{node.label}</span>
108
- <span className="workbench-field-remap-io-browse__port-meta">
109
- {node.classRef ? formatClassRef(node.classRef) : node.id}
110
- {node.hidden === true ? ` · ${labels?.hiddenBadge ?? 'Hidden'}` : ''}
111
- </span>
112
- </p>
113
- <FieldTree
114
- emptyLabel={emptyLabel}
115
- labels={labels}
116
- nodes={node.children?.length ? node.children : [node]}
117
- />
118
- </div>
119
- ))
111
+ <ul className="workbench-field-remap-io-browse__ports">
112
+ {nodes.map((node) => (
113
+ <li className="workbench-field-remap-io-browse__port" key={node.id}>
114
+ <SideBarRow
115
+ className="workbench-field-remap-io-browse__port-title"
116
+ data-static="true"
117
+ >
118
+ <span className="workbench-field-remap-io-browse__port-id">{node.label}</span>
119
+ <span className="workbench-field-remap-io-browse__port-meta">
120
+ {node.classRef ? formatClassRef(node.classRef) : node.id}
121
+ {node.hidden === true ? ` · ${labels?.hiddenBadge ?? 'Hidden'}` : ''}
122
+ </span>
123
+ </SideBarRow>
124
+ <FieldTree
125
+ emptyLabel={emptyLabel}
126
+ labels={labels}
127
+ nodes={node.children?.length ? node.children : [node]}
128
+ />
129
+ </li>
130
+ ))}
131
+ </ul>
120
132
  )}
121
133
  </section>
122
134
  );
@@ -0,0 +1,16 @@
1
+ const EDITABLE_SHORTCUT_TARGET = [
2
+ 'input',
3
+ 'textarea',
4
+ 'select',
5
+ '[contenteditable]:not([contenteditable="false"])',
6
+ '[role="textbox"]',
7
+ '[data-field-remap-shortcuts="ignore"]',
8
+ ].join(', ');
9
+
10
+ export function isFieldRemapEditableShortcutTarget(target: EventTarget | null): boolean {
11
+ return (
12
+ typeof Element !== 'undefined' &&
13
+ target instanceof Element &&
14
+ target.closest(EDITABLE_SHORTCUT_TARGET) !== null
15
+ );
16
+ }
@@ -0,0 +1,34 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ import { Modal } from '@workbench-kit/react/modal';
3
+ import { WorkbenchModalPortal } from '@workbench-kit/react/workbench/modal-portal';
4
+
5
+ export interface FieldRemapModalDetailProps {
6
+ readonly children: ReactNode;
7
+ readonly closeLabel: string;
8
+ readonly onClose: () => void;
9
+ readonly title: string;
10
+ }
11
+
12
+ export function FieldRemapModalDetail({
13
+ children,
14
+ closeLabel,
15
+ onClose,
16
+ title,
17
+ }: FieldRemapModalDetailProps): JSX.Element {
18
+ return (
19
+ <WorkbenchModalPortal>
20
+ <Modal
21
+ bodyClassName="workbench-field-remap-detail-modal__body"
22
+ bodyPadding="none"
23
+ bodyScroll="auto"
24
+ className="workbench-field-remap-detail-modal"
25
+ closeLabel={closeLabel}
26
+ closeOnEscape={false}
27
+ title={title}
28
+ onClose={onClose}
29
+ >
30
+ {children}
31
+ </Modal>
32
+ </WorkbenchModalPortal>
33
+ );
34
+ }
@@ -5,6 +5,7 @@ import {
5
5
  useRef,
6
6
  useState,
7
7
  type JSX,
8
+ type KeyboardEvent,
8
9
  type Ref,
9
10
  } from 'react';
10
11
  import {
@@ -50,6 +51,7 @@ import {
50
51
  } from './shape-io-editor.js';
51
52
  import { createFieldRemapPreviewController } from './preview-controller.js';
52
53
  import type { FieldRemapPreviewState } from './preview.js';
54
+ import { isFieldRemapEditableShortcutTarget } from './keyboard.js';
53
55
  import './view.css';
54
56
 
55
57
  export type { FieldRemapHistorySnapshot } from './history.js';
@@ -80,6 +82,8 @@ export interface FieldRemapPanelProps {
80
82
  readonly sample?: FieldRemapSampleId | FieldRemapSampleDefinition | undefined;
81
83
  /** Optional host-owned transform registry (defaults to builtins + JSONata). */
82
84
  readonly transforms?: ValueTransformRegistry | undefined;
85
+ /** View-only authoring guard forwarded to Flow; this is not an authorization boundary. */
86
+ readonly readOnly?: boolean | undefined;
83
87
  /**
84
88
  * When true (default), show paste-JSON / type editors for host-owned shapes.
85
89
  * Prefer {@link FieldRemapPanelProps.ioChrome} for browse-first hosts; when
@@ -112,7 +116,7 @@ export interface FieldRemapPanelProps {
112
116
  * channel is controlled, the Panel never creates a partial private history.
113
117
  */
114
118
  readonly historyOwner?: FieldRemapHistoryOwner | undefined;
115
- /** Imperative semantic undo/redo actions for host chrome. Keyboard routing remains host-owned. */
119
+ /** Imperative semantic undo/redo actions for host chrome. */
116
120
  readonly historyActionsRef?: Ref<FieldRemapHistoryActions | null> | undefined;
117
121
  /** Reports the active history owner's current undo/redo availability. */
118
122
  readonly onHistoryAvailabilityChange?:
@@ -136,6 +140,7 @@ export interface FieldRemapPanelProps {
136
140
  readonly showBindingsList?: FieldRemapFlowMapperProps['showBindingsList'];
137
141
  readonly showConvertPalette?: FieldRemapFlowMapperProps['showConvertPalette'];
138
142
  readonly emptyDetail?: FieldRemapFlowMapperProps['emptyDetail'];
143
+ readonly detailPresentation?: FieldRemapFlowMapperProps['detailPresentation'];
139
144
  /** Show the controller-owned preview snapshot in the nested Flow rail. */
140
145
  readonly showFlowPreview?: boolean;
141
146
  /** Forwarded to {@link FieldRemapFlowMapper} Controls MiniMap toggle. */
@@ -249,6 +254,7 @@ function createFieldRemapPreviewSignature(): (value: unknown) => string {
249
254
  export function FieldRemapPanel({
250
255
  sample: sampleProp,
251
256
  transforms: transformsProp,
257
+ readOnly = false,
252
258
  editableShapes = true,
253
259
  ioChrome: ioChromeProp,
254
260
  includeHidden: includeHiddenProp,
@@ -273,6 +279,7 @@ export function FieldRemapPanel({
273
279
  showBindingsList,
274
280
  showConvertPalette,
275
281
  emptyDetail,
282
+ detailPresentation,
276
283
  showFlowPreview,
277
284
  onShowMinimapChange,
278
285
  onPaneContextMenu,
@@ -282,7 +289,8 @@ export function FieldRemapPanel({
282
289
  labels,
283
290
  t,
284
291
  }: FieldRemapPanelProps): JSX.Element {
285
- const ioChrome = resolveFieldRemapIoChrome(ioChromeProp, editableShapes);
292
+ const configuredIoChrome = resolveFieldRemapIoChrome(ioChromeProp, editableShapes);
293
+ const ioChrome = readOnly && configuredIoChrome === 'edit' ? 'browse' : configuredIoChrome;
286
294
  const [uncontrolledIncludeHidden, setUncontrolledIncludeHidden] = useState(false);
287
295
  const includeHiddenControlled = includeHiddenProp !== undefined;
288
296
  const includeHidden = includeHiddenControlled ? includeHiddenProp : uncontrolledIncludeHidden;
@@ -503,6 +511,41 @@ export function FieldRemapPanel({
503
511
  : (historyOwner?.canRedo ?? false),
504
512
  };
505
513
 
514
+ const handleHistoryKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
515
+ if (readOnly) {
516
+ return;
517
+ }
518
+ if (
519
+ event.defaultPrevented ||
520
+ event.altKey ||
521
+ (!event.ctrlKey && !event.metaKey) ||
522
+ isFieldRemapEditableShortcutTarget(event.target)
523
+ ) {
524
+ return;
525
+ }
526
+
527
+ const key = event.key.toLowerCase();
528
+ const requestsUndo = key === 'z' && !event.shiftKey;
529
+ const requestsRedo =
530
+ (key === 'z' && event.shiftKey) ||
531
+ (key === 'y' && event.ctrlKey && !event.metaKey && !event.shiftKey);
532
+ if (
533
+ (!requestsUndo && !requestsRedo) ||
534
+ (requestsUndo && !historyAvailability.canUndo) ||
535
+ (requestsRedo && !historyAvailability.canRedo)
536
+ ) {
537
+ return;
538
+ }
539
+
540
+ event.preventDefault();
541
+ event.stopPropagation();
542
+ if (requestsUndo) {
543
+ undo();
544
+ } else {
545
+ redo();
546
+ }
547
+ };
548
+
506
549
  useImperativeHandle(historyActionsRef, () => ({ ...historyAvailability, undo, redo }), [
507
550
  historyAvailability.canRedo,
508
551
  historyAvailability.canUndo,
@@ -644,6 +687,8 @@ export function FieldRemapPanel({
644
687
  data-testid="field-remap-demo"
645
688
  data-sample-id={sample.id}
646
689
  data-edges-mode={edgesControlled ? 'controlled' : 'uncontrolled'}
690
+ data-read-only={readOnly ? 'true' : 'false'}
691
+ onKeyDown={handleHistoryKeyDown}
647
692
  >
648
693
  <header className="workbench-field-remap-demo__header">
649
694
  <h2 className="workbench-field-remap-demo__title">{sample.title}</h2>
@@ -691,9 +736,11 @@ export function FieldRemapPanel({
691
736
  ) : null}
692
737
 
693
738
  <FieldRemapFlowMapper
739
+ readOnly={readOnly}
694
740
  sources={flowSources}
695
741
  targets={flowTargets}
696
742
  edges={flowEdges}
743
+ parentChildConflicts={conflicts}
697
744
  transforms={registry}
698
745
  onEdgesChange={commitFlowEdges}
699
746
  operators={operators}
@@ -708,6 +755,7 @@ export function FieldRemapPanel({
708
755
  showBindingsList={showBindingsList}
709
756
  showConvertPalette={showConvertPalette}
710
757
  emptyDetail={emptyDetail}
758
+ detailPresentation={detailPresentation}
711
759
  {...(showFlowPreview ? { preview, showPreview: true } : {})}
712
760
  onShowMinimapChange={onShowMinimapChange}
713
761
  includeHidden={includeHidden}
@@ -720,18 +768,6 @@ export function FieldRemapPanel({
720
768
  t={t}
721
769
  />
722
770
 
723
- {conflicts.length > 0 ? (
724
- <p
725
- className="workbench-field-remap-demo__warn"
726
- role="status"
727
- data-testid="field-remap-conflicts"
728
- >
729
- Warning: parent and child fields are both mapped (
730
- {conflicts.map((item) => `${item.parentId} / ${item.childId}`).join('; ')}). Prefer one
731
- level.
732
- </p>
733
- ) : null}
734
-
735
771
  {preview.status === 'error' ? (
736
772
  <p className="workbench-field-remap-demo__error" role="alert">
737
773
  {preview.message}
@@ -225,7 +225,11 @@ export function TransformOptionsEditor({
225
225
  };
226
226
 
227
227
  return (
228
- <div className="workbench-field-remap-options" data-testid={`${testIdPrefix}-editor`}>
228
+ <div
229
+ className="workbench-field-remap-options"
230
+ data-field-remap-shortcuts="ignore"
231
+ data-testid={`${testIdPrefix}-editor`}
232
+ >
229
233
  {fields.map((field) => {
230
234
  const controlId = `${baseId}-${field.key}`;
231
235
  const testId = `${testIdPrefix}-${field.key}`;
@@ -772,6 +772,11 @@
772
772
  font: inherit;
773
773
  }
774
774
 
775
+ .workbench-field-remap-flow__binding-select.is-selected {
776
+ color: var(--vscode-textLink-foreground, var(--color-accent, #3794ff));
777
+ font-weight: 600;
778
+ }
779
+
775
780
  .workbench-field-remap-detail,
776
781
  .workbench-field-remap-convert-note {
777
782
  display: flex;
@@ -797,6 +802,15 @@
797
802
  overflow: auto;
798
803
  }
799
804
 
805
+ .workbench-field-remap-detail-modal .workbench-field-remap-detail,
806
+ .workbench-field-remap-detail-modal .workbench-field-remap-convert-note {
807
+ min-height: 0;
808
+ max-height: none;
809
+ border: 0;
810
+ border-radius: 0;
811
+ background: transparent;
812
+ }
813
+
800
814
  .workbench-field-remap-preview {
801
815
  display: flex;
802
816
  flex-direction: column;
@@ -1418,18 +1432,15 @@
1418
1432
  }
1419
1433
 
1420
1434
  .workbench-field-remap-io-browse {
1435
+ --ui-sidebar-inline-padding: var(--shell-spacing-side-bar-inline);
1436
+ --ui-sidebar-row-block-padding: var(--shell-spacing-side-bar-block);
1437
+ --ui-sidebar-tree-indent-step: 10px;
1421
1438
  display: grid;
1422
- grid-template-columns: repeat(2, minmax(0, 1fr));
1423
- gap: 1rem;
1439
+ grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
1440
+ gap: var(--shell-spacing-block);
1424
1441
  width: 100%;
1425
1442
  }
1426
1443
 
1427
- @media (max-width: 800px) {
1428
- .workbench-field-remap-io-browse {
1429
- grid-template-columns: 1fr;
1430
- }
1431
- }
1432
-
1433
1444
  .workbench-field-remap-io-browse__toggle {
1434
1445
  display: inline-flex;
1435
1446
  align-items: center;
@@ -1442,31 +1453,36 @@
1442
1453
  .workbench-field-remap-io-browse__section {
1443
1454
  display: flex;
1444
1455
  flex-direction: column;
1445
- gap: 0.65rem;
1456
+ gap: var(--shell-spacing-block);
1446
1457
  min-width: 0;
1447
1458
  }
1448
1459
 
1449
1460
  .workbench-field-remap-io-browse__section-title {
1450
1461
  margin: 0;
1451
- font-size: 0.85rem;
1462
+ padding: 0 var(--ui-sidebar-inline-padding);
1463
+ color: var(--color-text-muted);
1464
+ font-size: var(--font-size-sm);
1452
1465
  font-weight: 600;
1453
1466
  }
1454
1467
 
1468
+ .workbench-field-remap-io-browse__ports,
1469
+ .workbench-field-remap-io-browse__fields {
1470
+ margin: 0;
1471
+ padding: 0;
1472
+ list-style: none;
1473
+ }
1474
+
1455
1475
  .workbench-field-remap-io-browse__port {
1456
- display: flex;
1457
- flex-direction: column;
1458
- gap: 0.35rem;
1459
- padding: 0.5rem 0.65rem;
1460
- border: 1px solid var(--vscode-widget-border, var(--color-border, #3c3c3c));
1461
- border-radius: 4px;
1476
+ border-block-start: 1px solid var(--color-border);
1462
1477
  }
1463
1478
 
1464
1479
  .workbench-field-remap-io-browse__port-title {
1465
- display: flex;
1466
1480
  flex-wrap: wrap;
1467
- gap: 0.35rem 0.75rem;
1468
- margin: 0;
1469
- align-items: baseline;
1481
+ min-height: 26px;
1482
+ gap: 5px;
1483
+ padding: var(--ui-sidebar-row-block-padding) var(--ui-sidebar-inline-padding);
1484
+ color: var(--color-text);
1485
+ font-size: var(--font-size-sm);
1470
1486
  }
1471
1487
 
1472
1488
  .workbench-field-remap-io-browse__port-id {
@@ -1474,49 +1490,52 @@
1474
1490
  }
1475
1491
 
1476
1492
  .workbench-field-remap-io-browse__port-meta {
1477
- font-size: 0.75rem;
1478
- opacity: 0.75;
1479
- }
1480
-
1481
- .workbench-field-remap-io-browse__fields {
1482
- margin: 0;
1483
- padding-left: 1rem;
1484
- list-style: disc;
1493
+ margin-inline-start: auto;
1494
+ color: var(--color-text-muted);
1495
+ font-size: var(--font-size-xs);
1485
1496
  }
1486
1497
 
1487
1498
  .workbench-field-remap-io-browse__fields .workbench-field-remap-io-browse__fields {
1488
- margin-top: 0.25rem;
1499
+ padding-inline-start: var(--ui-sidebar-tree-indent-step);
1489
1500
  }
1490
1501
 
1491
1502
  .workbench-field-remap-io-browse__row {
1492
- display: flex;
1493
1503
  flex-wrap: wrap;
1494
- gap: 0.35rem 0.75rem;
1495
- align-items: baseline;
1504
+ min-height: 26px;
1505
+ gap: 5px;
1506
+ padding: var(--ui-sidebar-row-block-padding) var(--ui-sidebar-inline-padding);
1507
+ color: var(--color-text-muted);
1508
+ font-size: var(--font-size-sm);
1509
+ }
1510
+
1511
+ .workbench-field-remap-io-browse__port-title:hover,
1512
+ .workbench-field-remap-io-browse__row:hover {
1513
+ background: transparent;
1496
1514
  }
1497
1515
 
1498
1516
  .workbench-field-remap-io-browse__path {
1499
- font-size: 0.8rem;
1517
+ color: var(--color-text);
1518
+ font-size: inherit;
1500
1519
  word-break: break-all;
1501
1520
  }
1502
1521
 
1503
1522
  .workbench-field-remap-io-browse__meta {
1504
1523
  display: inline-flex;
1505
1524
  flex-wrap: wrap;
1506
- gap: 0.35rem;
1507
- font-size: 0.75rem;
1508
- opacity: 0.8;
1525
+ align-items: center;
1526
+ gap: 5px;
1527
+ margin-inline-start: auto;
1528
+ color: var(--color-text-muted);
1529
+ font-size: var(--font-size-xs);
1509
1530
  }
1510
1531
 
1511
1532
  .workbench-field-remap-io-browse__badge {
1512
- padding: 0.05rem 0.35rem;
1513
- border-radius: 3px;
1514
- background: var(--vscode-badge-background, rgba(127, 127, 127, 0.25));
1515
- color: var(--vscode-badge-foreground, inherit);
1533
+ flex: none;
1516
1534
  }
1517
1535
 
1518
1536
  .workbench-field-remap-io-browse__empty {
1519
1537
  margin: 0;
1520
- font-size: 0.8rem;
1521
- opacity: 0.7;
1538
+ padding: var(--ui-sidebar-row-block-padding) var(--ui-sidebar-inline-padding);
1539
+ color: var(--color-text-muted);
1540
+ font-size: var(--font-size-sm);
1522
1541
  }
package/src/index.ts CHANGED
@@ -114,6 +114,8 @@ export {
114
114
  } from './field-remap/panel.js';
115
115
  export {
116
116
  FieldRemapFlowMapper,
117
+ type FieldRemapConnectionFeedback,
118
+ type FieldRemapConnectionFeedbackReason,
117
119
  type FieldRemapFlowActions,
118
120
  type FieldRemapFlowMapperProps,
119
121
  } from './field-remap/flow.js';
@@ -230,6 +232,11 @@ export {
230
232
  resolveShellCommandActivities,
231
233
  } from './workbench/command-palette.js';
232
234
  export { WorkbenchCommandHost, type WorkbenchCommandHostProps } from './workbench/command-host.js';
235
+ export {
236
+ WorkbenchCommandHostController,
237
+ type WorkbenchCommandHostControllerProps,
238
+ type WorkbenchCommandHostExecutor,
239
+ } from './workbench/command-host-controller.js';
233
240
  export { WorkbenchStartupGate, type WorkbenchStartupGateProps } from './workbench/startup-gate.js';
234
241
  export {
235
242
  normalizeKeybindingKeyFromEvent,
@@ -1,22 +1,21 @@
1
- import {
2
- parseWorkbenchKeybindingsConfig,
3
- type WorkbenchKeybindingDefinition,
4
- } from '@workbench-kit/workbench-config';
1
+ import { type WorkbenchKeybindingDefinition } from '@workbench-kit/workbench-config';
5
2
  import {
6
3
  type WorkbenchPersistenceDiagnosticOptions,
7
- type WorkbenchPersistenceReadResult,
8
4
  type WorkbenchPersistenceWriteResult,
9
5
  type WorkbenchStorageReader,
10
6
  type WorkbenchStorageWriter,
11
7
  } from '@workbench-kit/workbench-core';
12
-
13
8
  import {
14
- readLocalJsonStorage,
15
- readLocalJsonStorageResult,
16
- resolveLocalWorkbenchStorage,
17
- writeLocalJsonStorage,
18
- writeLocalJsonStorageResult,
19
- } from '../storage/local-json-storage.js';
9
+ readWorkbenchKeybindingOverridesStorageResult,
10
+ writeWorkbenchKeybindingOverridesStorageResult,
11
+ type WorkbenchKeybindingOverridesStorageReadResult,
12
+ } from '@workbench-kit/workbench-core/keybinding-overrides-storage';
13
+ import {
14
+ resolveWorkbenchShortcutPlatform,
15
+ type WorkbenchShortcutPlatform,
16
+ } from '@workbench-kit/platform';
17
+
18
+ import { resolveLocalWorkbenchStorage } from '../storage/local-json-storage.js';
20
19
 
21
20
  export const DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY = 'workbench-kit/.workbench/keybindings';
22
21
 
@@ -27,22 +26,35 @@ export function isWorkbenchKeybindingPersistenceAvailable(): boolean {
27
26
  export function readPersistedKeybindingOverrides(
28
27
  storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
29
28
  storage?: WorkbenchStorageReader,
29
+ options: ReadPersistedKeybindingOverridesOptions = {},
30
30
  ): readonly WorkbenchKeybindingDefinition[] {
31
- return readLocalJsonStorage(storageKey, parseWorkbenchKeybindingsConfig, () => [], storage);
31
+ return readPersistedKeybindingOverridesResult(storageKey, storage, options).value;
32
+ }
33
+
34
+ export interface ReadPersistedKeybindingOverridesOptions extends WorkbenchPersistenceDiagnosticOptions {
35
+ readonly platform?: WorkbenchShortcutPlatform | undefined;
36
+ }
37
+
38
+ export interface PersistedKeybindingOverridesReadResult extends WorkbenchKeybindingOverridesStorageReadResult {
39
+ readonly value: readonly WorkbenchKeybindingDefinition[];
32
40
  }
33
41
 
34
42
  export function readPersistedKeybindingOverridesResult(
35
43
  storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
36
44
  storage?: WorkbenchStorageReader,
37
- options: WorkbenchPersistenceDiagnosticOptions = {},
38
- ): WorkbenchPersistenceReadResult<readonly WorkbenchKeybindingDefinition[]> {
39
- return readLocalJsonStorageResult(
40
- storageKey,
41
- parseWorkbenchKeybindingsConfig,
42
- () => [],
43
- storage,
45
+ options: ReadPersistedKeybindingOverridesOptions = {},
46
+ ): PersistedKeybindingOverridesReadResult {
47
+ const result = readWorkbenchKeybindingOverridesStorageResult({
44
48
  options,
45
- );
49
+ platform: options.platform ?? resolveWorkbenchShortcutPlatform(),
50
+ storage: resolveLocalWorkbenchStorage(storage),
51
+ storageKey,
52
+ });
53
+
54
+ return {
55
+ ...result,
56
+ value: result.entries,
57
+ };
46
58
  }
47
59
 
48
60
  export function writePersistedKeybindingOverrides(
@@ -50,7 +62,15 @@ export function writePersistedKeybindingOverrides(
50
62
  storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
51
63
  storage?: WorkbenchStorageWriter,
52
64
  ): void {
53
- writeLocalJsonStorage(storageKey, overrides, storage, { errorMode: 'throw' });
65
+ const resolvedStorage = resolveLocalWorkbenchStorage(storage);
66
+ if (!resolvedStorage) {
67
+ return;
68
+ }
69
+
70
+ const result = writePersistedKeybindingOverridesResult(overrides, storageKey, resolvedStorage);
71
+ if (!result.committed) {
72
+ throw new Error(result.diagnostic.message);
73
+ }
54
74
  }
55
75
 
56
76
  export function writePersistedKeybindingOverridesResult(
@@ -59,5 +79,10 @@ export function writePersistedKeybindingOverridesResult(
59
79
  storage?: WorkbenchStorageWriter,
60
80
  options: WorkbenchPersistenceDiagnosticOptions = {},
61
81
  ): WorkbenchPersistenceWriteResult {
62
- return writeLocalJsonStorageResult(storageKey, overrides, storage, options);
82
+ return writeWorkbenchKeybindingOverridesStorageResult({
83
+ entries: overrides,
84
+ options,
85
+ storage: resolveLocalWorkbenchStorage(storage),
86
+ storageKey,
87
+ });
63
88
  }
@@ -3,11 +3,20 @@ import { KeybindingManagementPanel } from '@workbench-kit/react/workbench/manage
3
3
  import { useKeybindingManagementModel } from './use-keybinding-management.js';
4
4
 
5
5
  export function WorkbenchKeybindingManagementSettings() {
6
- const { entries, overrideCount, resetKeybinding, setKeybinding } = useKeybindingManagementModel();
6
+ const {
7
+ editingDisabledReason,
8
+ entries,
9
+ overrideCount,
10
+ platform,
11
+ resetKeybinding,
12
+ setKeybinding,
13
+ } = useKeybindingManagementModel();
7
14
 
8
15
  return (
9
16
  <KeybindingManagementPanel
17
+ editingDisabledReason={editingDisabledReason}
10
18
  entries={entries}
19
+ platform={platform}
11
20
  summaryLabel={`${entries.length} command${entries.length === 1 ? '' : 's'} · ${overrideCount} user override${overrideCount === 1 ? '' : 's'}`}
12
21
  onResetKeybinding={resetKeybinding}
13
22
  onSetKeybinding={setKeybinding}