@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.
- package/README.md +81 -2
- package/package.json +14 -10
- package/src/editor/workspace-reconcile.tsx +12 -7
- package/src/extensions/extension-enablement-controller.ts +40 -9
- package/src/extensions/theme-selection-protection.ts +80 -55
- package/src/field-remap/chrome-labels.ts +117 -0
- package/src/field-remap/convert-note-editor.tsx +119 -104
- package/src/field-remap/convert-palette.tsx +6 -0
- package/src/field-remap/demo.tsx +8 -2
- package/src/field-remap/detail-panel.tsx +535 -434
- package/src/field-remap/document-io.tsx +299 -0
- package/src/field-remap/drag-payload.ts +48 -0
- package/src/field-remap/flow-adapter.ts +216 -88
- package/src/field-remap/flow-ops.ts +150 -0
- package/src/field-remap/flow.tsx +1227 -272
- package/src/field-remap/index.ts +2 -0
- package/src/field-remap/io-class-browse.tsx +34 -22
- package/src/field-remap/keyboard.ts +16 -0
- package/src/field-remap/modal-detail.tsx +34 -0
- package/src/field-remap/panel.tsx +110 -14
- package/src/field-remap/transform-options-editor.tsx +68 -48
- package/src/field-remap/view.css +107 -189
- package/src/index.ts +7 -0
- package/src/keybinding-management-settings.ts +4 -0
- package/src/management/keybinding-overrides-storage.ts +48 -23
- package/src/management/keybinding-settings-view.tsx +31 -0
- package/src/management/keybinding-settings.tsx +4 -12
- package/src/management/use-keybinding-management.ts +72 -22
- package/src/shell/appearance-catalog.ts +453 -0
- package/src/shell/appearance-controller.ts +331 -0
- package/src/shell/appearance-presentation.ts +269 -0
- package/src/shell/provider.tsx +157 -19
- package/src/shell/settings.tsx +282 -153
- package/src/shell/shell.tsx +88 -18
- package/src/workbench/appearance-storage.ts +2 -2
- package/src/workbench/command-host-controller.tsx +223 -0
- package/src/workbench/command-host.tsx +100 -150
- package/src/workbench/keybinding-bridge.ts +84 -38
- package/src/workbench/shell-command-registration.ts +27 -2
package/src/field-remap/index.ts
CHANGED
|
@@ -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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
72
|
+
</Badge>
|
|
70
73
|
) : null}
|
|
71
74
|
{node.hidden === true ? (
|
|
72
|
-
<
|
|
75
|
+
<Badge
|
|
76
|
+
className="workbench-field-remap-io-browse__badge"
|
|
77
|
+
title="hidden"
|
|
78
|
+
variant="muted"
|
|
79
|
+
>
|
|
73
80
|
{labels?.hiddenBadge ?? 'Hidden'}
|
|
74
|
-
</
|
|
81
|
+
</Badge>
|
|
75
82
|
) : null}
|
|
76
83
|
</span>
|
|
77
|
-
</
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
<
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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,16 +5,23 @@ 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 {
|
|
11
12
|
createBuiltinValueTransformRegistry,
|
|
13
|
+
createFieldRemapDocument,
|
|
14
|
+
deserializeFieldRemapImport,
|
|
15
|
+
FieldRemapImportAdmissionError,
|
|
12
16
|
findParentChildMappingConflicts,
|
|
13
17
|
projectSourceFields,
|
|
14
18
|
projectTargetSlots,
|
|
15
19
|
pruneMappingEdgesForShapes,
|
|
20
|
+
serializeFieldRemapDocument,
|
|
16
21
|
sourceFieldsFromPlainObject,
|
|
17
22
|
targetSlotsFromPlainObject,
|
|
23
|
+
UnsupportedFieldRemapDocumentVersionError,
|
|
24
|
+
type FieldRemapDocument,
|
|
18
25
|
type MappingEdge,
|
|
19
26
|
type MappingOperator,
|
|
20
27
|
type SourceField,
|
|
@@ -50,6 +57,8 @@ import {
|
|
|
50
57
|
} from './shape-io-editor.js';
|
|
51
58
|
import { createFieldRemapPreviewController } from './preview-controller.js';
|
|
52
59
|
import type { FieldRemapPreviewState } from './preview.js';
|
|
60
|
+
import { isFieldRemapEditableShortcutTarget } from './keyboard.js';
|
|
61
|
+
import { FieldRemapDocumentIo, type FieldRemapDocumentImportActionResult } from './document-io.js';
|
|
53
62
|
import './view.css';
|
|
54
63
|
|
|
55
64
|
export type { FieldRemapHistorySnapshot } from './history.js';
|
|
@@ -80,6 +89,8 @@ export interface FieldRemapPanelProps {
|
|
|
80
89
|
readonly sample?: FieldRemapSampleId | FieldRemapSampleDefinition | undefined;
|
|
81
90
|
/** Optional host-owned transform registry (defaults to builtins + JSONata). */
|
|
82
91
|
readonly transforms?: ValueTransformRegistry | undefined;
|
|
92
|
+
/** View-only authoring guard forwarded to Flow; this is not an authorization boundary. */
|
|
93
|
+
readonly readOnly?: boolean | undefined;
|
|
83
94
|
/**
|
|
84
95
|
* When true (default), show paste-JSON / type editors for host-owned shapes.
|
|
85
96
|
* Prefer {@link FieldRemapPanelProps.ioChrome} for browse-first hosts; when
|
|
@@ -107,12 +118,17 @@ export interface FieldRemapPanelProps {
|
|
|
107
118
|
/** Optional controlled n→m operators (document v2). */
|
|
108
119
|
readonly operators?: readonly MappingOperator[] | undefined;
|
|
109
120
|
readonly onOperatorsChange?: ((operators: readonly MappingOperator[]) => void) | undefined;
|
|
121
|
+
/**
|
|
122
|
+
* Atomic whole-document replacement proposal for controlled or mixed-control imports.
|
|
123
|
+
* Incremental edge/operator callbacks remain unchanged.
|
|
124
|
+
*/
|
|
125
|
+
readonly onDocumentReplace?: ((document: FieldRemapDocument) => void) | undefined;
|
|
110
126
|
/**
|
|
111
127
|
* Composite history owner for controlled or mixed-control mapping state. When either durable
|
|
112
128
|
* channel is controlled, the Panel never creates a partial private history.
|
|
113
129
|
*/
|
|
114
130
|
readonly historyOwner?: FieldRemapHistoryOwner | undefined;
|
|
115
|
-
/** Imperative semantic undo/redo actions for host chrome.
|
|
131
|
+
/** Imperative semantic undo/redo actions for host chrome. */
|
|
116
132
|
readonly historyActionsRef?: Ref<FieldRemapHistoryActions | null> | undefined;
|
|
117
133
|
/** Reports the active history owner's current undo/redo availability. */
|
|
118
134
|
readonly onHistoryAvailabilityChange?:
|
|
@@ -136,6 +152,7 @@ export interface FieldRemapPanelProps {
|
|
|
136
152
|
readonly showBindingsList?: FieldRemapFlowMapperProps['showBindingsList'];
|
|
137
153
|
readonly showConvertPalette?: FieldRemapFlowMapperProps['showConvertPalette'];
|
|
138
154
|
readonly emptyDetail?: FieldRemapFlowMapperProps['emptyDetail'];
|
|
155
|
+
readonly detailPresentation?: FieldRemapFlowMapperProps['detailPresentation'];
|
|
139
156
|
/** Show the controller-owned preview snapshot in the nested Flow rail. */
|
|
140
157
|
readonly showFlowPreview?: boolean;
|
|
141
158
|
/** Forwarded to {@link FieldRemapFlowMapper} Controls MiniMap toggle. */
|
|
@@ -249,6 +266,7 @@ function createFieldRemapPreviewSignature(): (value: unknown) => string {
|
|
|
249
266
|
export function FieldRemapPanel({
|
|
250
267
|
sample: sampleProp,
|
|
251
268
|
transforms: transformsProp,
|
|
269
|
+
readOnly = false,
|
|
252
270
|
editableShapes = true,
|
|
253
271
|
ioChrome: ioChromeProp,
|
|
254
272
|
includeHidden: includeHiddenProp,
|
|
@@ -258,6 +276,7 @@ export function FieldRemapPanel({
|
|
|
258
276
|
onEdgesChange,
|
|
259
277
|
operators: operatorsProp,
|
|
260
278
|
onOperatorsChange,
|
|
279
|
+
onDocumentReplace,
|
|
261
280
|
historyOwner,
|
|
262
281
|
historyActionsRef,
|
|
263
282
|
onHistoryAvailabilityChange,
|
|
@@ -273,6 +292,7 @@ export function FieldRemapPanel({
|
|
|
273
292
|
showBindingsList,
|
|
274
293
|
showConvertPalette,
|
|
275
294
|
emptyDetail,
|
|
295
|
+
detailPresentation,
|
|
276
296
|
showFlowPreview,
|
|
277
297
|
onShowMinimapChange,
|
|
278
298
|
onPaneContextMenu,
|
|
@@ -282,7 +302,8 @@ export function FieldRemapPanel({
|
|
|
282
302
|
labels,
|
|
283
303
|
t,
|
|
284
304
|
}: FieldRemapPanelProps): JSX.Element {
|
|
285
|
-
const
|
|
305
|
+
const configuredIoChrome = resolveFieldRemapIoChrome(ioChromeProp, editableShapes);
|
|
306
|
+
const ioChrome = readOnly && configuredIoChrome === 'edit' ? 'browse' : configuredIoChrome;
|
|
286
307
|
const [uncontrolledIncludeHidden, setUncontrolledIncludeHidden] = useState(false);
|
|
287
308
|
const includeHiddenControlled = includeHiddenProp !== undefined;
|
|
288
309
|
const includeHidden = includeHiddenControlled ? includeHiddenProp : uncontrolledIncludeHidden;
|
|
@@ -429,6 +450,40 @@ export function FieldRemapPanel({
|
|
|
429
450
|
applyHistorySnapshot(next);
|
|
430
451
|
};
|
|
431
452
|
|
|
453
|
+
const documentImportAvailable =
|
|
454
|
+
!readOnly && (historyOwnedByPanel || onDocumentReplace !== undefined);
|
|
455
|
+
|
|
456
|
+
const importDocumentText = (text: string): FieldRemapDocumentImportActionResult => {
|
|
457
|
+
let document: FieldRemapDocument;
|
|
458
|
+
try {
|
|
459
|
+
document = deserializeFieldRemapImport(text, {
|
|
460
|
+
sources: sourceFields,
|
|
461
|
+
targets: targetSlots,
|
|
462
|
+
transforms: registry,
|
|
463
|
+
});
|
|
464
|
+
} catch (error) {
|
|
465
|
+
if (error instanceof UnsupportedFieldRemapDocumentVersionError) {
|
|
466
|
+
return { status: 'rejected', code: 'unsupported-version' };
|
|
467
|
+
}
|
|
468
|
+
if (error instanceof FieldRemapImportAdmissionError) {
|
|
469
|
+
return { status: 'rejected', code: error.code };
|
|
470
|
+
}
|
|
471
|
+
return { status: 'rejected', code: 'invalid-document' };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (historyOwnedByPanel) {
|
|
475
|
+
recordSemanticSnapshot(
|
|
476
|
+
createFieldRemapHistorySnapshot(document.edges, document.operators ?? []),
|
|
477
|
+
);
|
|
478
|
+
return { status: 'accepted' };
|
|
479
|
+
}
|
|
480
|
+
if (!onDocumentReplace) {
|
|
481
|
+
return { status: 'rejected', code: 'invalid-document' };
|
|
482
|
+
}
|
|
483
|
+
onDocumentReplace(document);
|
|
484
|
+
return { status: 'accepted' };
|
|
485
|
+
};
|
|
486
|
+
|
|
432
487
|
const commitFlowEdges = (next: readonly MappingEdge[]) => {
|
|
433
488
|
if (includeHidden) {
|
|
434
489
|
recordSemanticSnapshot(createFieldRemapHistorySnapshot(next, operators));
|
|
@@ -503,6 +558,41 @@ export function FieldRemapPanel({
|
|
|
503
558
|
: (historyOwner?.canRedo ?? false),
|
|
504
559
|
};
|
|
505
560
|
|
|
561
|
+
const handleHistoryKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
562
|
+
if (readOnly) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (
|
|
566
|
+
event.defaultPrevented ||
|
|
567
|
+
event.altKey ||
|
|
568
|
+
(!event.ctrlKey && !event.metaKey) ||
|
|
569
|
+
isFieldRemapEditableShortcutTarget(event.target)
|
|
570
|
+
) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const key = event.key.toLowerCase();
|
|
575
|
+
const requestsUndo = key === 'z' && !event.shiftKey;
|
|
576
|
+
const requestsRedo =
|
|
577
|
+
(key === 'z' && event.shiftKey) ||
|
|
578
|
+
(key === 'y' && event.ctrlKey && !event.metaKey && !event.shiftKey);
|
|
579
|
+
if (
|
|
580
|
+
(!requestsUndo && !requestsRedo) ||
|
|
581
|
+
(requestsUndo && !historyAvailability.canUndo) ||
|
|
582
|
+
(requestsRedo && !historyAvailability.canRedo)
|
|
583
|
+
) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
event.preventDefault();
|
|
588
|
+
event.stopPropagation();
|
|
589
|
+
if (requestsUndo) {
|
|
590
|
+
undo();
|
|
591
|
+
} else {
|
|
592
|
+
redo();
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
506
596
|
useImperativeHandle(historyActionsRef, () => ({ ...historyAvailability, undo, redo }), [
|
|
507
597
|
historyAvailability.canRedo,
|
|
508
598
|
historyAvailability.canUndo,
|
|
@@ -644,10 +734,25 @@ export function FieldRemapPanel({
|
|
|
644
734
|
data-testid="field-remap-demo"
|
|
645
735
|
data-sample-id={sample.id}
|
|
646
736
|
data-edges-mode={edgesControlled ? 'controlled' : 'uncontrolled'}
|
|
737
|
+
data-read-only={readOnly ? 'true' : 'false'}
|
|
738
|
+
onKeyDown={handleHistoryKeyDown}
|
|
647
739
|
>
|
|
648
740
|
<header className="workbench-field-remap-demo__header">
|
|
649
741
|
<h2 className="workbench-field-remap-demo__title">{sample.title}</h2>
|
|
650
742
|
<p className="workbench-field-remap-demo__intro">{sample.description}</p>
|
|
743
|
+
<FieldRemapDocumentIo
|
|
744
|
+
getDocumentJson={() =>
|
|
745
|
+
serializeFieldRemapDocument(
|
|
746
|
+
createFieldRemapDocument(edges, {
|
|
747
|
+
...(operators.length > 0 ? { operators } : {}),
|
|
748
|
+
}),
|
|
749
|
+
)
|
|
750
|
+
}
|
|
751
|
+
importAvailable={documentImportAvailable}
|
|
752
|
+
labels={labels}
|
|
753
|
+
t={t}
|
|
754
|
+
onImportText={importDocumentText}
|
|
755
|
+
/>
|
|
651
756
|
</header>
|
|
652
757
|
|
|
653
758
|
{ioChrome === 'edit' ? (
|
|
@@ -691,9 +796,11 @@ export function FieldRemapPanel({
|
|
|
691
796
|
) : null}
|
|
692
797
|
|
|
693
798
|
<FieldRemapFlowMapper
|
|
799
|
+
readOnly={readOnly}
|
|
694
800
|
sources={flowSources}
|
|
695
801
|
targets={flowTargets}
|
|
696
802
|
edges={flowEdges}
|
|
803
|
+
parentChildConflicts={conflicts}
|
|
697
804
|
transforms={registry}
|
|
698
805
|
onEdgesChange={commitFlowEdges}
|
|
699
806
|
operators={operators}
|
|
@@ -708,6 +815,7 @@ export function FieldRemapPanel({
|
|
|
708
815
|
showBindingsList={showBindingsList}
|
|
709
816
|
showConvertPalette={showConvertPalette}
|
|
710
817
|
emptyDetail={emptyDetail}
|
|
818
|
+
detailPresentation={detailPresentation}
|
|
711
819
|
{...(showFlowPreview ? { preview, showPreview: true } : {})}
|
|
712
820
|
onShowMinimapChange={onShowMinimapChange}
|
|
713
821
|
includeHidden={includeHidden}
|
|
@@ -720,18 +828,6 @@ export function FieldRemapPanel({
|
|
|
720
828
|
t={t}
|
|
721
829
|
/>
|
|
722
830
|
|
|
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
831
|
{preview.status === 'error' ? (
|
|
736
832
|
<p className="workbench-field-remap-demo__error" role="alert">
|
|
737
833
|
{preview.message}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { useEffect, useId, useState, type JSX } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Checkbox,
|
|
5
|
+
NumberInput,
|
|
6
|
+
TextInput,
|
|
7
|
+
WorkbenchPropertyRow,
|
|
8
|
+
WorkbenchPropertyStack,
|
|
9
|
+
} from '@workbench-kit/react/primitives';
|
|
3
10
|
import type { TransformOptionField } from '@workbench-kit/field-remap';
|
|
4
11
|
|
|
5
12
|
export interface TransformOptionsEditorProps {
|
|
@@ -128,12 +135,14 @@ function StringMapEditor({
|
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
function JsonOptionEditor({
|
|
138
|
+
controlId,
|
|
131
139
|
field,
|
|
132
140
|
value,
|
|
133
141
|
disabled,
|
|
134
142
|
onChange,
|
|
135
143
|
testId,
|
|
136
144
|
}: {
|
|
145
|
+
readonly controlId: string;
|
|
137
146
|
readonly field: TransformOptionField;
|
|
138
147
|
readonly value: unknown;
|
|
139
148
|
readonly disabled: boolean;
|
|
@@ -153,6 +162,7 @@ function JsonOptionEditor({
|
|
|
153
162
|
return (
|
|
154
163
|
<div className="workbench-field-remap-options__json">
|
|
155
164
|
<textarea
|
|
165
|
+
id={controlId}
|
|
156
166
|
aria-label={field.label}
|
|
157
167
|
data-testid={testId}
|
|
158
168
|
disabled={disabled}
|
|
@@ -225,65 +235,71 @@ export function TransformOptionsEditor({
|
|
|
225
235
|
};
|
|
226
236
|
|
|
227
237
|
return (
|
|
228
|
-
<
|
|
238
|
+
<WorkbenchPropertyStack
|
|
239
|
+
className="workbench-field-remap-options"
|
|
240
|
+
data-field-remap-shortcuts="ignore"
|
|
241
|
+
data-testid={`${testIdPrefix}-editor`}
|
|
242
|
+
gap="sm"
|
|
243
|
+
>
|
|
229
244
|
{fields.map((field) => {
|
|
230
245
|
const controlId = `${baseId}-${field.key}`;
|
|
231
246
|
const testId = `${testIdPrefix}-${field.key}`;
|
|
232
247
|
const current = value[field.key];
|
|
233
248
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
key={field.key}
|
|
237
|
-
|
|
238
|
-
htmlFor={field.kind === 'boolean' ? controlId : undefined}
|
|
239
|
-
>
|
|
240
|
-
<span className="workbench-field-remap-options__label">{field.label}</span>
|
|
241
|
-
{field.kind === 'string' ? (
|
|
242
|
-
<input
|
|
249
|
+
if (field.kind === 'string') {
|
|
250
|
+
return (
|
|
251
|
+
<WorkbenchPropertyRow key={field.key} htmlFor={controlId} label={field.label}>
|
|
252
|
+
<TextInput
|
|
243
253
|
id={controlId}
|
|
244
|
-
|
|
254
|
+
controlWidth="full"
|
|
245
255
|
data-testid={testId}
|
|
246
256
|
disabled={disabled}
|
|
247
257
|
aria-label={field.label}
|
|
248
258
|
value={
|
|
249
259
|
typeof current === 'string' ? current : current == null ? '' : String(current)
|
|
250
260
|
}
|
|
251
|
-
|
|
261
|
+
onValueChange={(next) => setKey(field.key, next)}
|
|
252
262
|
/>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
263
|
+
</WorkbenchPropertyRow>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (field.kind === 'number') {
|
|
268
|
+
return (
|
|
269
|
+
<WorkbenchPropertyRow key={field.key} htmlFor={controlId} label={field.label}>
|
|
270
|
+
<NumberInput
|
|
256
271
|
id={controlId}
|
|
257
|
-
|
|
272
|
+
controlWidth="full"
|
|
258
273
|
data-testid={testId}
|
|
259
274
|
disabled={disabled}
|
|
260
275
|
aria-label={field.label}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
setKey(field.key, undefined);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
const parsed = Number(raw);
|
|
269
|
-
if (Number.isFinite(parsed)) {
|
|
270
|
-
setKey(field.key, parsed);
|
|
271
|
-
}
|
|
272
|
-
}}
|
|
276
|
+
nullable
|
|
277
|
+
value={typeof current === 'number' ? current : undefined}
|
|
278
|
+
onEmptyValue={() => setKey(field.key, undefined)}
|
|
279
|
+
onValueChange={(next) => setKey(field.key, next)}
|
|
273
280
|
/>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
281
|
+
</WorkbenchPropertyRow>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (field.kind === 'boolean') {
|
|
286
|
+
return (
|
|
287
|
+
<WorkbenchPropertyRow key={field.key} htmlFor={controlId} label={field.label}>
|
|
288
|
+
<Checkbox
|
|
277
289
|
id={controlId}
|
|
278
|
-
type="checkbox"
|
|
279
290
|
data-testid={testId}
|
|
280
291
|
disabled={disabled}
|
|
281
292
|
aria-label={field.label}
|
|
282
293
|
checked={Boolean(current)}
|
|
283
|
-
|
|
294
|
+
onCheckedChange={(next) => setKey(field.key, next)}
|
|
284
295
|
/>
|
|
285
|
-
|
|
286
|
-
|
|
296
|
+
</WorkbenchPropertyRow>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (field.kind === 'stringMap') {
|
|
301
|
+
return (
|
|
302
|
+
<WorkbenchPropertyRow key={field.key} label={field.label}>
|
|
287
303
|
<StringMapEditor
|
|
288
304
|
field={field}
|
|
289
305
|
value={current}
|
|
@@ -293,19 +309,23 @@ export function TransformOptionsEditor({
|
|
|
293
309
|
setKey(field.key, Object.keys(next).length > 0 ? next : undefined)
|
|
294
310
|
}
|
|
295
311
|
/>
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
312
|
+
</WorkbenchPropertyRow>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<WorkbenchPropertyRow key={field.key} htmlFor={controlId} label={field.label}>
|
|
318
|
+
<JsonOptionEditor
|
|
319
|
+
controlId={controlId}
|
|
320
|
+
field={field}
|
|
321
|
+
value={current}
|
|
322
|
+
disabled={disabled}
|
|
323
|
+
testId={testId}
|
|
324
|
+
onChange={(next) => setKey(field.key, next)}
|
|
325
|
+
/>
|
|
326
|
+
</WorkbenchPropertyRow>
|
|
307
327
|
);
|
|
308
328
|
})}
|
|
309
|
-
</
|
|
329
|
+
</WorkbenchPropertyStack>
|
|
310
330
|
);
|
|
311
331
|
}
|