@workbench-kit/shell-react 0.0.2-prototype.0.2.41 → 0.0.2-prototype.0.2.44

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 (39) hide show
  1. package/README.md +81 -2
  2. package/package.json +14 -10
  3. package/src/editor/workspace-reconcile.tsx +12 -7
  4. package/src/extensions/extension-enablement-controller.ts +40 -9
  5. package/src/extensions/theme-selection-protection.ts +80 -55
  6. package/src/field-remap/chrome-labels.ts +117 -0
  7. package/src/field-remap/convert-note-editor.tsx +119 -104
  8. package/src/field-remap/convert-palette.tsx +6 -0
  9. package/src/field-remap/demo.tsx +8 -2
  10. package/src/field-remap/detail-panel.tsx +535 -434
  11. package/src/field-remap/document-io.tsx +299 -0
  12. package/src/field-remap/drag-payload.ts +48 -0
  13. package/src/field-remap/flow-adapter.ts +216 -88
  14. package/src/field-remap/flow-ops.ts +150 -0
  15. package/src/field-remap/flow.tsx +1227 -272
  16. package/src/field-remap/index.ts +2 -0
  17. package/src/field-remap/io-class-browse.tsx +34 -22
  18. package/src/field-remap/keyboard.ts +16 -0
  19. package/src/field-remap/modal-detail.tsx +34 -0
  20. package/src/field-remap/panel.tsx +110 -14
  21. package/src/field-remap/transform-options-editor.tsx +68 -48
  22. package/src/field-remap/view.css +107 -189
  23. package/src/index.ts +7 -0
  24. package/src/keybinding-management-settings.ts +4 -0
  25. package/src/management/keybinding-overrides-storage.ts +48 -23
  26. package/src/management/keybinding-settings-view.tsx +31 -0
  27. package/src/management/keybinding-settings.tsx +4 -12
  28. package/src/management/use-keybinding-management.ts +72 -22
  29. package/src/shell/appearance-catalog.ts +453 -0
  30. package/src/shell/appearance-controller.ts +331 -0
  31. package/src/shell/appearance-presentation.ts +269 -0
  32. package/src/shell/provider.tsx +157 -19
  33. package/src/shell/settings.tsx +282 -153
  34. package/src/shell/shell.tsx +88 -18
  35. package/src/workbench/appearance-storage.ts +2 -2
  36. package/src/workbench/command-host-controller.tsx +223 -0
  37. package/src/workbench/command-host.tsx +100 -150
  38. package/src/workbench/keybinding-bridge.ts +84 -38
  39. package/src/workbench/shell-command-registration.ts +27 -2
@@ -1,5 +1,12 @@
1
- import { useMemo, type JSX } from 'react';
2
- import { Button } from '@workbench-kit/react/primitives';
1
+ import { useId, useMemo, type JSX } from 'react';
2
+ import {
3
+ Button,
4
+ Select,
5
+ WorkbenchPropertyHint,
6
+ WorkbenchPropertyRow,
7
+ WorkbenchPropertySection,
8
+ WorkbenchPropertyStack,
9
+ } from '@workbench-kit/react/primitives';
3
10
  import {
4
11
  optionFieldsForStep,
5
12
  resolveOptionSteps,
@@ -28,6 +35,7 @@ export interface ConvertNoteEditorProps {
28
35
  readonly onEdgesChange: (edges: readonly MappingEdge[]) => void;
29
36
  readonly edges: readonly MappingEdge[];
30
37
  readonly onSelectionChange: (next: FieldRemapSelection) => void;
38
+ readonly readOnly?: boolean | undefined;
31
39
  }
32
40
 
33
41
  /**
@@ -43,7 +51,9 @@ export function ConvertNoteEditor({
43
51
  edges,
44
52
  onEdgesChange,
45
53
  onSelectionChange,
54
+ readOnly = false,
46
55
  }: ConvertNoteEditorProps): JSX.Element | null {
56
+ const transformSelectId = useId();
47
57
  const chain = edge.transformIds ?? [];
48
58
  const transformId = chain[stepIndex];
49
59
 
@@ -77,7 +87,7 @@ export function ConvertNoteEditor({
77
87
  const stepLabel = definition?.label ?? transformId;
78
88
 
79
89
  const applyEdge = (next: MappingEdge | null) => {
80
- if (!next) {
90
+ if (readOnly || !next) {
81
91
  return;
82
92
  }
83
93
  onEdgesChange(edges.map((item) => (item.id === edge.id ? next : item)));
@@ -89,111 +99,116 @@ export function ConvertNoteEditor({
89
99
  data-testid="field-remap-convert-note"
90
100
  aria-label="Convert note editor"
91
101
  >
92
- <div className="workbench-field-remap-convert-note__header">
93
- <div>
94
- <p className="workbench-field-remap-convert-note__eyebrow">Convert</p>
95
- <h4 data-testid="field-remap-convert-note-title">{stepLabel}</h4>
96
- <code
97
- className="workbench-field-remap-convert-note__binding"
98
- data-testid="field-remap-convert-note-binding"
99
- >
100
- {edge.sourceFieldId} → {edge.targetSlotId}
101
- </code>
102
- <p className="workbench-field-remap-convert-note__muted">
102
+ <WorkbenchPropertyStack gap="sm">
103
+ <WorkbenchPropertySection
104
+ title="Convert"
105
+ actions={
106
+ <div className="workbench-field-remap-convert-note__actions">
107
+ <Button
108
+ compact
109
+ type="button"
110
+ data-testid="field-remap-convert-note-back"
111
+ onClick={() => onSelectionChange({ kind: 'edge', edgeId: edge.id })}
112
+ >
113
+ Binding
114
+ </Button>
115
+ <Button
116
+ compact
117
+ type="button"
118
+ data-testid="field-remap-convert-note-clear"
119
+ onClick={() => onSelectionChange(null)}
120
+ >
121
+ Close
122
+ </Button>
123
+ </div>
124
+ }
125
+ >
126
+ <WorkbenchPropertyRow label="Transform">
127
+ <strong data-testid="field-remap-convert-note-title">{stepLabel}</strong>
128
+ </WorkbenchPropertyRow>
129
+ <WorkbenchPropertyRow label="Binding">
130
+ <code
131
+ className="workbench-field-remap-convert-note__binding"
132
+ data-testid="field-remap-convert-note-binding"
133
+ >
134
+ {edge.sourceFieldId} → {edge.targetSlotId}
135
+ </code>
136
+ </WorkbenchPropertyRow>
137
+ <WorkbenchPropertyHint className="workbench-field-remap-convert-note__muted">
103
138
  Step {stepIndex + 1} of {chain.length} on this binding
104
- </p>
105
- </div>
106
- <div className="workbench-field-remap-convert-note__actions">
107
- <Button
108
- compact
109
- type="button"
110
- data-testid="field-remap-convert-note-back"
111
- onClick={() => onSelectionChange({ kind: 'edge', edgeId: edge.id })}
112
- >
113
- Binding
114
- </Button>
115
- <Button
116
- compact
117
- type="button"
118
- data-testid="field-remap-convert-note-clear"
119
- onClick={() => onSelectionChange(null)}
120
- >
121
- Close
122
- </Button>
123
- </div>
124
- </div>
139
+ </WorkbenchPropertyHint>
140
+ </WorkbenchPropertySection>
125
141
 
126
- <section
127
- className="workbench-field-remap-convert-note__section"
128
- aria-labelledby="field-remap-convert-note-registry"
129
- data-testid="field-remap-step-settings"
130
- >
131
- <h5 id="field-remap-convert-note-registry">Transform</h5>
132
- <label className="workbench-field-remap-convert-note__field">
133
- <span>Registry id</span>
134
- <select
135
- aria-label="Convert transform id"
136
- data-testid="field-remap-step-id"
137
- value={transformId}
138
- onChange={(event) => {
139
- applyEdge(
140
- setTransformStepIdOnEdge(edge, stepIndex, event.target.value, {
141
- registry: transforms,
142
- sourceType: portTypes.sourceType,
143
- targetType: portTypes.targetType,
144
- }),
145
- );
146
- }}
147
- >
148
- {!replaceCatalog.some((item) => item.id === transformId) ? (
149
- <option value={transformId}>{transformId}</option>
150
- ) : null}
151
- {replaceCatalog.map((item) => (
152
- <option key={item.id} value={item.id}>
153
- {item.label}
154
- </option>
155
- ))}
156
- </select>
157
- </label>
158
- </section>
142
+ <WorkbenchPropertySection title="Transform" data-testid="field-remap-step-settings">
143
+ <WorkbenchPropertyRow htmlFor={transformSelectId} label="Registry id">
144
+ <Select
145
+ id={transformSelectId}
146
+ aria-label="Convert transform id"
147
+ controlWidth="full"
148
+ data-testid="field-remap-step-id"
149
+ value={transformId}
150
+ disabled={readOnly}
151
+ onValueChange={(nextTransformId) => {
152
+ applyEdge(
153
+ setTransformStepIdOnEdge(edge, stepIndex, nextTransformId, {
154
+ registry: transforms,
155
+ sourceType: portTypes.sourceType,
156
+ targetType: portTypes.targetType,
157
+ }),
158
+ );
159
+ }}
160
+ >
161
+ {!replaceCatalog.some((item) => item.id === transformId) ? (
162
+ <option value={transformId}>{transformId}</option>
163
+ ) : null}
164
+ {replaceCatalog.map((item) => (
165
+ <option key={item.id} value={item.id}>
166
+ {item.label}
167
+ </option>
168
+ ))}
169
+ </Select>
170
+ </WorkbenchPropertyRow>
171
+ </WorkbenchPropertySection>
159
172
 
160
- <section
161
- className="workbench-field-remap-convert-note__section"
162
- aria-labelledby="field-remap-convert-note-options"
163
- >
164
- <h5 id="field-remap-convert-note-options">Options</h5>
165
- <TransformOptionsEditor
166
- fields={optionFields}
167
- value={optionValue}
168
- onChange={(nextOptions) => {
169
- applyEdge(replaceTransformStepOptionsOnEdge(edge, stepIndex, nextOptions));
170
- }}
171
- />
172
- </section>
173
+ <WorkbenchPropertySection title="Options">
174
+ <TransformOptionsEditor
175
+ fields={optionFields}
176
+ value={optionValue}
177
+ disabled={readOnly}
178
+ onChange={(nextOptions) => {
179
+ applyEdge(replaceTransformStepOptionsOnEdge(edge, stepIndex, nextOptions));
180
+ }}
181
+ />
182
+ </WorkbenchPropertySection>
173
183
 
174
- <div className="workbench-field-remap-convert-note__footer">
175
- <Button
176
- compact
177
- type="button"
178
- data-testid="field-remap-convert-note-remove"
179
- onClick={() => {
180
- const next = removeTransformStepFromEdge(edge, stepIndex);
181
- applyEdge(next);
182
- const nextLen = next.transformIds?.length ?? 0;
183
- if (nextLen === 0) {
184
- onSelectionChange({ kind: 'edge', edgeId: edge.id });
185
- } else {
186
- onSelectionChange({
187
- kind: 'transformStep',
188
- edgeId: edge.id,
189
- stepIndex: Math.min(stepIndex, nextLen - 1),
190
- });
191
- }
192
- }}
193
- >
194
- Remove convert
195
- </Button>
196
- </div>
184
+ {!readOnly ? (
185
+ <WorkbenchPropertySection title="Actions">
186
+ <WorkbenchPropertyRow label="Convert step">
187
+ <Button
188
+ compact
189
+ type="button"
190
+ data-testid="field-remap-convert-note-remove"
191
+ onClick={() => {
192
+ const next = removeTransformStepFromEdge(edge, stepIndex);
193
+ applyEdge(next);
194
+ const nextLen = next.transformIds?.length ?? 0;
195
+ if (nextLen === 0) {
196
+ onSelectionChange({ kind: 'edge', edgeId: edge.id });
197
+ } else {
198
+ onSelectionChange({
199
+ kind: 'transformStep',
200
+ edgeId: edge.id,
201
+ stepIndex: Math.min(stepIndex, nextLen - 1),
202
+ });
203
+ }
204
+ }}
205
+ >
206
+ Remove convert
207
+ </Button>
208
+ </WorkbenchPropertyRow>
209
+ </WorkbenchPropertySection>
210
+ ) : null}
211
+ </WorkbenchPropertyStack>
197
212
  </aside>
198
213
  );
199
214
  }
@@ -4,6 +4,7 @@ import { ClearableTextInput, IconButton } from '@workbench-kit/react/primitives'
4
4
  import type { ValueTransformRegistry } from '@workbench-kit/field-remap';
5
5
 
6
6
  import { defaultFieldRemapChromeLabels, type FieldRemapChromeLabels } from './chrome-labels.js';
7
+ import { writeFieldRemapTransformDragData } from './drag-payload.js';
7
8
 
8
9
  export interface FieldRemapConvertPaletteProps {
9
10
  readonly transforms: ValueTransformRegistry;
@@ -207,6 +208,7 @@ export function FieldRemapConvertPalette({
207
208
  key={definition.id}
208
209
  aria-selected={selected}
209
210
  data-testid={`field-remap-palette-item-${definition.id}`}
211
+ draggable
210
212
  role="option"
211
213
  selected={selected}
212
214
  tabIndex={definition.id === rovingTransformId ? 0 : -1}
@@ -217,6 +219,10 @@ export function FieldRemapConvertPalette({
217
219
  onSelectedTransformIdChange(definition.id);
218
220
  }}
219
221
  onDoubleClick={() => onPlaceDraft(definition.id)}
222
+ onDragStart={(event) => {
223
+ event.dataTransfer.effectAllowed = 'copy';
224
+ writeFieldRemapTransformDragData(event.dataTransfer, definition.id);
225
+ }}
220
226
  onFocus={() => setRequestedRovingTransformId(definition.id)}
221
227
  onKeyDown={(event) => handleOptionKeyDown(event, definition.id)}
222
228
  >
@@ -25,6 +25,8 @@ export interface SampleFieldRemapDemoProps {
25
25
  readonly showBindingsList?: FieldRemapFlowMapperProps['showBindingsList'];
26
26
  readonly showConvertPalette?: FieldRemapFlowMapperProps['showConvertPalette'];
27
27
  readonly emptyDetail?: FieldRemapFlowMapperProps['emptyDetail'];
28
+ readonly detailPresentation?: FieldRemapFlowMapperProps['detailPresentation'];
29
+ readonly readOnly?: FieldRemapFlowMapperProps['readOnly'];
28
30
  /** When true, show host-chrome demo controls (history + fit view). */
29
31
  readonly showHostChromeDemo?: boolean | undefined;
30
32
  /** Prefer `browse` for I/O class/field inspection demos. */
@@ -46,6 +48,8 @@ export function SampleFieldRemapDemo({
46
48
  showBindingsList,
47
49
  showConvertPalette,
48
50
  emptyDetail,
51
+ detailPresentation,
52
+ readOnly,
49
53
  showHostChromeDemo = false,
50
54
  ioChrome,
51
55
  browseSeedShapes = false,
@@ -89,7 +93,7 @@ export function SampleFieldRemapDemo({
89
93
  compact
90
94
  type="button"
91
95
  data-testid="field-remap-undo"
92
- disabled={!historyAvailability.canUndo}
96
+ disabled={readOnly || !historyAvailability.canUndo}
93
97
  onClick={() => historyActionsRef.current?.undo()}
94
98
  >
95
99
  Undo
@@ -98,7 +102,7 @@ export function SampleFieldRemapDemo({
98
102
  compact
99
103
  type="button"
100
104
  data-testid="field-remap-redo"
101
- disabled={!historyAvailability.canRedo}
105
+ disabled={readOnly || !historyAvailability.canRedo}
102
106
  onClick={() => historyActionsRef.current?.redo()}
103
107
  >
104
108
  Redo
@@ -119,6 +123,8 @@ export function SampleFieldRemapDemo({
119
123
  showBindingsList={showBindingsList}
120
124
  showConvertPalette={showConvertPalette}
121
125
  emptyDetail={emptyDetail}
126
+ detailPresentation={detailPresentation}
127
+ readOnly={readOnly}
122
128
  onShowMinimapChange={setShowMinimap}
123
129
  ioChrome={ioChrome}
124
130
  editableShapes={ioChrome === 'browse' ? false : undefined}