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

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 CHANGED
@@ -120,6 +120,7 @@ import {
120
120
  FieldRemapFlowMapper,
121
121
  createJsonataValueTransform,
122
122
  } from '@workbench-kit/shell-react/field-remap';
123
+ import type { FieldRemapDocument } from '@workbench-kit/field-remap';
123
124
  import '@workbench-kit/shell-react/field-remap/view.css';
124
125
 
125
126
  // Uncontrolled demo:
@@ -133,6 +134,44 @@ import '@workbench-kit/shell-react/field-remap/view.css';
133
134
  for Flow-only embeds and custom bundler setups. The full barrel
134
135
  `import { FieldRemapPanel } from '@workbench-kit/shell-react'` stays supported.
135
136
 
137
+ ### n→m operators in direct Flow embeds
138
+
139
+ `FieldRemapFlowMapper` keeps durable edges and document-v2 combine/split operators
140
+ controlled. A host that enables operator authoring owns both arrays and commits each
141
+ complete next operator array through the same persistence/history boundary:
142
+
143
+ ```tsx
144
+ const [document, setDocument] = useState(initialDocument);
145
+ const commitDocumentChange = (next: FieldRemapDocument) => {
146
+ setDocument(next);
147
+ hostHistory.record(next);
148
+ persist(next);
149
+ };
150
+
151
+ <FieldRemapFlowMapper
152
+ sources={sources}
153
+ targets={targets}
154
+ transforms={registry}
155
+ edges={document.edges}
156
+ onEdgesChange={(next) => commitDocumentChange({ ...document, edges: next })}
157
+ operators={document.operators ?? []}
158
+ onOperatorsChange={(next) => commitDocumentChange({ ...document, operators: next })}
159
+ />;
160
+ ```
161
+
162
+ The presence of `onOperatorsChange` is the operator-authoring capability signal: it
163
+ enables the existing Add combine / Add split actions and routes operator wiring, detail,
164
+ and deletion mutations through that callback. Omitting the callback is intentional
165
+ inspect-only projection; supplied operators can still render and be selected, but operator
166
+ mutation chrome is absent. Do not infer writability from operator count, sample, chrome, or
167
+ labels. `readOnly` suppresses Flow authoring even when mutation callbacks are present.
168
+
169
+ `FieldRemapPanel` already supplies this wiring for its fully uncontrolled composite
170
+ `{ edges, operators }` state. If either durable channel is controlled, its existing
171
+ composite `historyOwner` contract applies as described below; consumers do not add a second
172
+ operator state layer merely to enable Panel authoring. Direct operator inventory drag/drop
173
+ and double-click placement are deferred to [#219](https://github.com/NewChoBo/workbench-kit/issues/219).
174
+
136
175
  ### Semantic history ownership
137
176
 
138
177
  `FieldRemapPanel` keeps a private composite `{ edges, operators }` undo/redo stack only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-kit/shell-react",
3
- "version": "0.0.2-prototype.0.2.43",
3
+ "version": "0.0.2-prototype.0.2.45",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -16,6 +16,7 @@
16
16
  "./field-remap": "./src/field-remap/index.ts",
17
17
  "./field-remap/view.css": "./src/field-remap/view.css",
18
18
  "./host-shell": "./src/shell/host-shell.tsx",
19
+ "./keybinding-management-settings": "./src/keybinding-management-settings.ts",
19
20
  "./layout-storage": "./src/workbench/layout-storage.ts",
20
21
  "./provider": "./src/shell/provider.tsx",
21
22
  "./shell": "./src/shell/shell.tsx"
@@ -32,14 +33,14 @@
32
33
  "@radix-ui/react-slot": "^1.3.0",
33
34
  "@xyflow/react": "^12.11.2",
34
35
  "jsonata": "^2.2.0",
35
- "@workbench-kit/platform": "0.0.2-prototype.0.2.43",
36
- "@workbench-kit/field-remap": "0.0.2-prototype.0.2.43",
37
- "@workbench-kit/tokens": "0.0.2-prototype.0.2.43",
38
- "@workbench-kit/react": "0.0.2-prototype.0.2.43",
39
- "@workbench-kit/workbench-core": "0.0.2-prototype.0.2.43",
40
- "@workbench-kit/workbench-extension-sdk": "0.0.2-prototype.0.2.43",
41
- "@workbench-kit/workspace": "0.0.2-prototype.0.2.43",
42
- "@workbench-kit/workbench-config": "0.0.2-prototype.0.2.43"
36
+ "@workbench-kit/platform": "0.0.2-prototype.0.2.45",
37
+ "@workbench-kit/field-remap": "0.0.2-prototype.0.2.45",
38
+ "@workbench-kit/tokens": "0.0.2-prototype.0.2.45",
39
+ "@workbench-kit/react": "0.0.2-prototype.0.2.45",
40
+ "@workbench-kit/workbench-core": "0.0.2-prototype.0.2.45",
41
+ "@workbench-kit/workbench-extension-sdk": "0.0.2-prototype.0.2.45",
42
+ "@workbench-kit/workspace": "0.0.2-prototype.0.2.45",
43
+ "@workbench-kit/workbench-config": "0.0.2-prototype.0.2.45"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "react": "^19.0.0",
@@ -1,19 +1,24 @@
1
1
  import { useEffect } from 'react';
2
+ import type { EditorService } from '@workbench-kit/workbench-core';
2
3
 
3
4
  import { createWorkspaceFileAvailabilityChecker } from './workspace-file-availability.js';
4
- import { useEditorService } from './use-editor.js';
5
- import { useWorkbench } from '../shell/provider.js';
6
5
  import {
7
6
  isWorkspaceResourceService,
8
7
  useWorkspaceResourceState,
9
8
  } from '../workbench/workspace-view-state.js';
10
9
 
10
+ interface EditorWorkspaceReconcilerProps {
11
+ readonly editorService: EditorService;
12
+ readonly workspaceHostService?: unknown;
13
+ }
14
+
11
15
  /** Component-only module so Vite Fast Refresh can accept this boundary. */
12
- export function EditorWorkspaceReconciler(): null {
13
- const { workspaceHostPort } = useWorkbench();
14
- const editorService = useEditorService();
15
- const workspaceService = isWorkspaceResourceService(workspaceHostPort?.service)
16
- ? workspaceHostPort.service
16
+ export function EditorWorkspaceReconciler({
17
+ editorService,
18
+ workspaceHostService,
19
+ }: EditorWorkspaceReconcilerProps): null {
20
+ const workspaceService = isWorkspaceResourceService(workspaceHostService)
21
+ ? workspaceHostService
17
22
  : undefined;
18
23
  const workspaceState = useWorkspaceResourceState(workspaceService);
19
24
 
@@ -43,6 +43,31 @@ export interface FieldRemapChromeLabels {
43
43
  /** Additive selection-detail Modal copy. Omitted legacy label objects use English defaults. */
44
44
  readonly detailModalTitle?: string;
45
45
  readonly closeDetailModal?: string;
46
+ /** Additive current-v2 document import/export chrome copy. */
47
+ readonly exportDocumentJson?: string;
48
+ readonly copyDocumentJson?: string;
49
+ readonly exportDocumentTitle?: string;
50
+ readonly closeDocumentExport?: string;
51
+ readonly exportDocumentDescription?: string;
52
+ readonly exportDocumentLabel?: string;
53
+ readonly importDocumentJson?: string;
54
+ readonly importDocumentTitle?: string;
55
+ readonly closeDocumentImport?: string;
56
+ readonly importDocumentDescription?: string;
57
+ readonly importDocumentLabel?: string;
58
+ readonly importDocumentPlaceholder?: string;
59
+ readonly applyDocumentImport?: string;
60
+ readonly cancelDocumentImport?: string;
61
+ readonly documentCopied?: string;
62
+ readonly documentCopyFailed?: string;
63
+ readonly documentImportUnavailable?: string;
64
+ readonly documentImportInvalidJson?: string;
65
+ readonly documentImportUnsupportedVersion?: string;
66
+ readonly documentImportInvalidDocument?: string;
67
+ readonly documentImportDuplicateId?: string;
68
+ readonly documentImportIncompatibleSource?: string;
69
+ readonly documentImportIncompatibleTarget?: string;
70
+ readonly documentImportUnavailableTransform?: string;
46
71
  /** Additive Flow preview copy. Omitted legacy label objects use English defaults. */
47
72
  readonly previewTitle?: string;
48
73
  readonly previewLoading?: string;
@@ -86,6 +111,32 @@ export const defaultFieldRemapChromeLabels = {
86
111
  readOnlyEmptyDetailDescription: 'Select a mapping to inspect its details.',
87
112
  detailModalTitle: 'Mapping details',
88
113
  closeDetailModal: 'Close details',
114
+ exportDocumentJson: 'Export JSON',
115
+ copyDocumentJson: 'Copy JSON',
116
+ exportDocumentTitle: 'Export mapping document',
117
+ closeDocumentExport: 'Close export',
118
+ exportDocumentDescription:
119
+ 'Copy the current version 2 mapping document or select the JSON manually.',
120
+ exportDocumentLabel: 'Current mapping document JSON',
121
+ importDocumentJson: 'Import JSON',
122
+ importDocumentTitle: 'Import mapping document',
123
+ closeDocumentImport: 'Close import',
124
+ importDocumentDescription:
125
+ 'Paste a current version 2 mapping document. A valid import replaces the complete mapping in one step.',
126
+ importDocumentLabel: 'Mapping document JSON',
127
+ importDocumentPlaceholder: 'Paste mapping document JSON',
128
+ applyDocumentImport: 'Validate and import',
129
+ cancelDocumentImport: 'Cancel',
130
+ documentCopied: 'Mapping document copied.',
131
+ documentCopyFailed: 'The mapping document could not be copied.',
132
+ documentImportUnavailable: 'Import is unavailable for this mapping.',
133
+ documentImportInvalidJson: 'Enter valid mapping document JSON.',
134
+ documentImportUnsupportedVersion: 'This mapping document version is not supported.',
135
+ documentImportInvalidDocument: 'This mapping document is invalid.',
136
+ documentImportDuplicateId: 'This mapping document contains duplicate identities.',
137
+ documentImportIncompatibleSource: 'This mapping document uses an unavailable source field.',
138
+ documentImportIncompatibleTarget: 'This mapping document uses an unavailable target field.',
139
+ documentImportUnavailableTransform: 'This mapping document uses an unavailable convert.',
89
140
  previewTitle: 'Sample preview',
90
141
  previewLoading: 'Updating preview…',
91
142
  previewError: 'Preview failed',
@@ -128,6 +179,30 @@ export const fieldRemapChromeLabelKeys = {
128
179
  readOnlyEmptyDetailDescription: 'fieldRemap.readOnlyEmptyDetailDescription',
129
180
  detailModalTitle: 'fieldRemap.detailModalTitle',
130
181
  closeDetailModal: 'fieldRemap.closeDetailModal',
182
+ exportDocumentJson: 'fieldRemap.exportDocumentJson',
183
+ copyDocumentJson: 'fieldRemap.copyDocumentJson',
184
+ exportDocumentTitle: 'fieldRemap.exportDocumentTitle',
185
+ closeDocumentExport: 'fieldRemap.closeDocumentExport',
186
+ exportDocumentDescription: 'fieldRemap.exportDocumentDescription',
187
+ exportDocumentLabel: 'fieldRemap.exportDocumentLabel',
188
+ importDocumentJson: 'fieldRemap.importDocumentJson',
189
+ importDocumentTitle: 'fieldRemap.importDocumentTitle',
190
+ closeDocumentImport: 'fieldRemap.closeDocumentImport',
191
+ importDocumentDescription: 'fieldRemap.importDocumentDescription',
192
+ importDocumentLabel: 'fieldRemap.importDocumentLabel',
193
+ importDocumentPlaceholder: 'fieldRemap.importDocumentPlaceholder',
194
+ applyDocumentImport: 'fieldRemap.applyDocumentImport',
195
+ cancelDocumentImport: 'fieldRemap.cancelDocumentImport',
196
+ documentCopied: 'fieldRemap.documentCopied',
197
+ documentCopyFailed: 'fieldRemap.documentCopyFailed',
198
+ documentImportUnavailable: 'fieldRemap.documentImportUnavailable',
199
+ documentImportInvalidJson: 'fieldRemap.documentImportInvalidJson',
200
+ documentImportUnsupportedVersion: 'fieldRemap.documentImportUnsupportedVersion',
201
+ documentImportInvalidDocument: 'fieldRemap.documentImportInvalidDocument',
202
+ documentImportDuplicateId: 'fieldRemap.documentImportDuplicateId',
203
+ documentImportIncompatibleSource: 'fieldRemap.documentImportIncompatibleSource',
204
+ documentImportIncompatibleTarget: 'fieldRemap.documentImportIncompatibleTarget',
205
+ documentImportUnavailableTransform: 'fieldRemap.documentImportUnavailableTransform',
131
206
  previewTitle: 'fieldRemap.previewTitle',
132
207
  previewLoading: 'fieldRemap.previewLoading',
133
208
  previewError: 'fieldRemap.previewError',
@@ -180,6 +255,30 @@ export function resolveFieldRemapChromeLabels(
180
255
  readOnlyEmptyDetailDescription: resolve('readOnlyEmptyDetailDescription'),
181
256
  detailModalTitle: resolve('detailModalTitle'),
182
257
  closeDetailModal: resolve('closeDetailModal'),
258
+ exportDocumentJson: resolve('exportDocumentJson'),
259
+ copyDocumentJson: resolve('copyDocumentJson'),
260
+ exportDocumentTitle: resolve('exportDocumentTitle'),
261
+ closeDocumentExport: resolve('closeDocumentExport'),
262
+ exportDocumentDescription: resolve('exportDocumentDescription'),
263
+ exportDocumentLabel: resolve('exportDocumentLabel'),
264
+ importDocumentJson: resolve('importDocumentJson'),
265
+ importDocumentTitle: resolve('importDocumentTitle'),
266
+ closeDocumentImport: resolve('closeDocumentImport'),
267
+ importDocumentDescription: resolve('importDocumentDescription'),
268
+ importDocumentLabel: resolve('importDocumentLabel'),
269
+ importDocumentPlaceholder: resolve('importDocumentPlaceholder'),
270
+ applyDocumentImport: resolve('applyDocumentImport'),
271
+ cancelDocumentImport: resolve('cancelDocumentImport'),
272
+ documentCopied: resolve('documentCopied'),
273
+ documentCopyFailed: resolve('documentCopyFailed'),
274
+ documentImportUnavailable: resolve('documentImportUnavailable'),
275
+ documentImportInvalidJson: resolve('documentImportInvalidJson'),
276
+ documentImportUnsupportedVersion: resolve('documentImportUnsupportedVersion'),
277
+ documentImportInvalidDocument: resolve('documentImportInvalidDocument'),
278
+ documentImportDuplicateId: resolve('documentImportDuplicateId'),
279
+ documentImportIncompatibleSource: resolve('documentImportIncompatibleSource'),
280
+ documentImportIncompatibleTarget: resolve('documentImportIncompatibleTarget'),
281
+ documentImportUnavailableTransform: resolve('documentImportUnavailableTransform'),
183
282
  previewTitle: resolve('previewTitle'),
184
283
  previewLoading: resolve('previewLoading'),
185
284
  previewError: resolve('previewError'),
@@ -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,
@@ -46,6 +53,7 @@ export function ConvertNoteEditor({
46
53
  onSelectionChange,
47
54
  readOnly = false,
48
55
  }: ConvertNoteEditorProps): JSX.Element | null {
56
+ const transformSelectId = useId();
49
57
  const chain = edge.transformIds ?? [];
50
58
  const transformId = chain[stepIndex];
51
59
 
@@ -91,115 +99,116 @@ export function ConvertNoteEditor({
91
99
  data-testid="field-remap-convert-note"
92
100
  aria-label="Convert note editor"
93
101
  >
94
- <div className="workbench-field-remap-convert-note__header">
95
- <div>
96
- <p className="workbench-field-remap-convert-note__eyebrow">Convert</p>
97
- <h4 data-testid="field-remap-convert-note-title">{stepLabel}</h4>
98
- <code
99
- className="workbench-field-remap-convert-note__binding"
100
- data-testid="field-remap-convert-note-binding"
101
- >
102
- {edge.sourceFieldId} → {edge.targetSlotId}
103
- </code>
104
- <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">
105
138
  Step {stepIndex + 1} of {chain.length} on this binding
106
- </p>
107
- </div>
108
- <div className="workbench-field-remap-convert-note__actions">
109
- <Button
110
- compact
111
- type="button"
112
- data-testid="field-remap-convert-note-back"
113
- onClick={() => onSelectionChange({ kind: 'edge', edgeId: edge.id })}
114
- >
115
- Binding
116
- </Button>
117
- <Button
118
- compact
119
- type="button"
120
- data-testid="field-remap-convert-note-clear"
121
- onClick={() => onSelectionChange(null)}
122
- >
123
- Close
124
- </Button>
125
- </div>
126
- </div>
139
+ </WorkbenchPropertyHint>
140
+ </WorkbenchPropertySection>
141
+
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>
127
172
 
128
- <section
129
- className="workbench-field-remap-convert-note__section"
130
- aria-labelledby="field-remap-convert-note-registry"
131
- data-testid="field-remap-step-settings"
132
- >
133
- <h5 id="field-remap-convert-note-registry">Transform</h5>
134
- <label className="workbench-field-remap-convert-note__field">
135
- <span>Registry id</span>
136
- <select
137
- aria-label="Convert transform id"
138
- data-testid="field-remap-step-id"
139
- value={transformId}
173
+ <WorkbenchPropertySection title="Options">
174
+ <TransformOptionsEditor
175
+ fields={optionFields}
176
+ value={optionValue}
140
177
  disabled={readOnly}
141
- onChange={(event) => {
142
- applyEdge(
143
- setTransformStepIdOnEdge(edge, stepIndex, event.target.value, {
144
- registry: transforms,
145
- sourceType: portTypes.sourceType,
146
- targetType: portTypes.targetType,
147
- }),
148
- );
178
+ onChange={(nextOptions) => {
179
+ applyEdge(replaceTransformStepOptionsOnEdge(edge, stepIndex, nextOptions));
149
180
  }}
150
- >
151
- {!replaceCatalog.some((item) => item.id === transformId) ? (
152
- <option value={transformId}>{transformId}</option>
153
- ) : null}
154
- {replaceCatalog.map((item) => (
155
- <option key={item.id} value={item.id}>
156
- {item.label}
157
- </option>
158
- ))}
159
- </select>
160
- </label>
161
- </section>
181
+ />
182
+ </WorkbenchPropertySection>
162
183
 
163
- <section
164
- className="workbench-field-remap-convert-note__section"
165
- aria-labelledby="field-remap-convert-note-options"
166
- >
167
- <h5 id="field-remap-convert-note-options">Options</h5>
168
- <TransformOptionsEditor
169
- fields={optionFields}
170
- value={optionValue}
171
- disabled={readOnly}
172
- onChange={(nextOptions) => {
173
- applyEdge(replaceTransformStepOptionsOnEdge(edge, stepIndex, nextOptions));
174
- }}
175
- />
176
- </section>
177
-
178
- {!readOnly ? (
179
- <div className="workbench-field-remap-convert-note__footer">
180
- <Button
181
- compact
182
- type="button"
183
- data-testid="field-remap-convert-note-remove"
184
- onClick={() => {
185
- const next = removeTransformStepFromEdge(edge, stepIndex);
186
- applyEdge(next);
187
- const nextLen = next.transformIds?.length ?? 0;
188
- if (nextLen === 0) {
189
- onSelectionChange({ kind: 'edge', edgeId: edge.id });
190
- } else {
191
- onSelectionChange({
192
- kind: 'transformStep',
193
- edgeId: edge.id,
194
- stepIndex: Math.min(stepIndex, nextLen - 1),
195
- });
196
- }
197
- }}
198
- >
199
- Remove convert
200
- </Button>
201
- </div>
202
- ) : null}
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>
203
212
  </aside>
204
213
  );
205
214
  }