@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
@@ -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,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. Keyboard routing remains host-owned. */
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 ioChrome = resolveFieldRemapIoChrome(ioChromeProp, editableShapes);
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 { Button } from '@workbench-kit/react/primitives';
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
- <div className="workbench-field-remap-options" data-testid={`${testIdPrefix}-editor`}>
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
- return (
235
- <label
236
- key={field.key}
237
- className="workbench-field-remap-options__field"
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
- type="text"
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
- onChange={(event) => setKey(field.key, event.target.value)}
261
+ onValueChange={(next) => setKey(field.key, next)}
252
262
  />
253
- ) : null}
254
- {field.kind === 'number' ? (
255
- <input
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
- type="number"
272
+ controlWidth="full"
258
273
  data-testid={testId}
259
274
  disabled={disabled}
260
275
  aria-label={field.label}
261
- value={typeof current === 'number' ? current : ''}
262
- onChange={(event) => {
263
- const raw = event.target.value;
264
- if (raw === '') {
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
- ) : null}
275
- {field.kind === 'boolean' ? (
276
- <input
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
- onChange={(event) => setKey(field.key, event.target.checked)}
294
+ onCheckedChange={(next) => setKey(field.key, next)}
284
295
  />
285
- ) : null}
286
- {field.kind === 'stringMap' ? (
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
- ) : null}
297
- {field.kind === 'json' ? (
298
- <JsonOptionEditor
299
- field={field}
300
- value={current}
301
- disabled={disabled}
302
- testId={testId}
303
- onChange={(next) => setKey(field.key, next)}
304
- />
305
- ) : null}
306
- </label>
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
- </div>
329
+ </WorkbenchPropertyStack>
310
330
  );
311
331
  }