@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.
@@ -0,0 +1,299 @@
1
+ import { useEffect, useId, useMemo, useRef, useState, type FormEvent, type JSX } from 'react';
2
+ import { Modal } from '@workbench-kit/react/modal';
3
+ import { Button, TextArea } from '@workbench-kit/react/primitives';
4
+ import { WorkbenchModalPortal } from '@workbench-kit/react/workbench/modal-portal';
5
+ import type { FieldRemapImportFailureCode } from '@workbench-kit/field-remap';
6
+
7
+ import {
8
+ resolveFieldRemapChromeLabels,
9
+ type FieldRemapChromeLabels,
10
+ type FieldRemapTranslate,
11
+ } from './chrome-labels.js';
12
+
13
+ export type FieldRemapDocumentImportActionResult =
14
+ | { readonly status: 'accepted' }
15
+ | {
16
+ readonly status: 'rejected';
17
+ readonly code: FieldRemapImportFailureCode;
18
+ };
19
+
20
+ interface FieldRemapDocumentIoProps {
21
+ readonly getDocumentJson: () => string;
22
+ readonly importAvailable: boolean;
23
+ readonly labels?: Partial<FieldRemapChromeLabels> | undefined;
24
+ readonly t?: FieldRemapTranslate | undefined;
25
+ readonly onImportText: (text: string) => FieldRemapDocumentImportActionResult;
26
+ }
27
+
28
+ type DocumentIoStatus =
29
+ | { readonly kind: 'success'; readonly message: string }
30
+ | { readonly kind: 'error'; readonly message: string };
31
+
32
+ function importFailureMessage(
33
+ code: FieldRemapImportFailureCode,
34
+ labels: FieldRemapChromeLabels,
35
+ ): string {
36
+ switch (code) {
37
+ case 'invalid-json':
38
+ return labels.documentImportInvalidJson ?? '';
39
+ case 'unsupported-version':
40
+ return labels.documentImportUnsupportedVersion ?? '';
41
+ case 'duplicate-id':
42
+ return labels.documentImportDuplicateId ?? '';
43
+ case 'incompatible-source':
44
+ return labels.documentImportIncompatibleSource ?? '';
45
+ case 'incompatible-target':
46
+ return labels.documentImportIncompatibleTarget ?? '';
47
+ case 'unavailable-transform':
48
+ return labels.documentImportUnavailableTransform ?? '';
49
+ case 'invalid-document':
50
+ return labels.documentImportInvalidDocument ?? '';
51
+ }
52
+ }
53
+
54
+ export function FieldRemapDocumentIo({
55
+ getDocumentJson,
56
+ importAvailable,
57
+ labels: labelOverrides,
58
+ t,
59
+ onImportText,
60
+ }: FieldRemapDocumentIoProps): JSX.Element {
61
+ const labels = useMemo(
62
+ () => resolveFieldRemapChromeLabels(labelOverrides, t),
63
+ [labelOverrides, t],
64
+ );
65
+ const [exportOpen, setExportOpen] = useState(false);
66
+ const [exportText, setExportText] = useState('');
67
+ const [importOpen, setImportOpen] = useState(false);
68
+ const [draft, setDraft] = useState('');
69
+ const [importError, setImportError] = useState<string | null>(null);
70
+ const [status, setStatus] = useState<DocumentIoStatus | null>(null);
71
+ const instanceId = useId();
72
+ const exportTextId = `field-remap-document-export-text-${instanceId}`;
73
+ const importTextId = `field-remap-document-import-text-${instanceId}`;
74
+ const importErrorId = `field-remap-document-import-error-${instanceId}`;
75
+ const exportTextareaRef = useRef<HTMLTextAreaElement>(null);
76
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
77
+
78
+ useEffect(() => {
79
+ if (!importAvailable && importOpen) {
80
+ setImportOpen(false);
81
+ setDraft('');
82
+ setImportError(null);
83
+ }
84
+ }, [importAvailable, importOpen]);
85
+
86
+ const closeImport = () => {
87
+ setImportOpen(false);
88
+ setDraft('');
89
+ setImportError(null);
90
+ };
91
+
92
+ const closeExport = () => {
93
+ setExportOpen(false);
94
+ setExportText('');
95
+ setStatus(null);
96
+ };
97
+
98
+ const openExport = () => {
99
+ try {
100
+ setExportText(getDocumentJson());
101
+ setStatus(null);
102
+ setExportOpen(true);
103
+ } catch {
104
+ setStatus({ kind: 'error', message: labels.documentCopyFailed ?? '' });
105
+ }
106
+ };
107
+
108
+ const copyDocument = async () => {
109
+ try {
110
+ const clipboard = globalThis.navigator?.clipboard;
111
+ if (!clipboard || typeof clipboard.writeText !== 'function') {
112
+ throw new Error('Clipboard write is unavailable.');
113
+ }
114
+ await clipboard.writeText(exportText);
115
+ setStatus({ kind: 'success', message: labels.documentCopied ?? '' });
116
+ } catch {
117
+ setStatus({ kind: 'error', message: labels.documentCopyFailed ?? '' });
118
+ }
119
+ };
120
+
121
+ const openImport = () => {
122
+ if (!importAvailable) {
123
+ return;
124
+ }
125
+ setDraft('');
126
+ setImportError(null);
127
+ setStatus(null);
128
+ setImportOpen(true);
129
+ };
130
+
131
+ const applyImport = (event: FormEvent<HTMLFormElement>) => {
132
+ event.preventDefault();
133
+ const result = onImportText(draft);
134
+ if (result.status === 'rejected') {
135
+ setImportError(importFailureMessage(result.code, labels));
136
+ queueMicrotask(() => textareaRef.current?.focus());
137
+ return;
138
+ }
139
+ closeImport();
140
+ };
141
+
142
+ return (
143
+ <div className="workbench-field-remap-document-io" data-testid="field-remap-document-io">
144
+ <div className="workbench-field-remap-document-io__actions">
145
+ <Button
146
+ compact
147
+ type="button"
148
+ data-testid="field-remap-export-document"
149
+ onClick={openExport}
150
+ >
151
+ {labels.exportDocumentJson}
152
+ </Button>
153
+ <Button
154
+ compact
155
+ type="button"
156
+ data-testid="field-remap-import-document"
157
+ disabled={!importAvailable}
158
+ onClick={openImport}
159
+ >
160
+ {labels.importDocumentJson}
161
+ </Button>
162
+ </div>
163
+ {!importAvailable ? (
164
+ <span className="workbench-field-remap-document-io__availability">
165
+ {labels.documentImportUnavailable}
166
+ </span>
167
+ ) : null}
168
+ {status && !exportOpen ? (
169
+ <span
170
+ className="workbench-field-remap-document-io__status"
171
+ data-status={status.kind}
172
+ role={status.kind === 'error' ? 'alert' : 'status'}
173
+ >
174
+ {status.message}
175
+ </span>
176
+ ) : null}
177
+
178
+ {exportOpen ? (
179
+ <WorkbenchModalPortal>
180
+ <Modal
181
+ bodyClassName="workbench-field-remap-document-export__body"
182
+ bodyLayout="stack"
183
+ bodyPadding="lg"
184
+ bodyScroll="auto"
185
+ className="workbench-field-remap-document-export"
186
+ closeLabel={labels.closeDocumentExport}
187
+ footer={
188
+ <>
189
+ <Button
190
+ type="button"
191
+ data-testid="field-remap-copy-document"
192
+ onClick={() => void copyDocument()}
193
+ >
194
+ {labels.copyDocumentJson}
195
+ </Button>
196
+ <Button type="button" onClick={closeExport}>
197
+ {labels.closeDocumentExport}
198
+ </Button>
199
+ </>
200
+ }
201
+ initialFocusRef={exportTextareaRef}
202
+ title={labels.exportDocumentTitle}
203
+ onClose={closeExport}
204
+ >
205
+ <p className="workbench-field-remap-document-export__description">
206
+ {labels.exportDocumentDescription}
207
+ </p>
208
+ <label className="workbench-field-remap-document-export__label" htmlFor={exportTextId}>
209
+ {labels.exportDocumentLabel}
210
+ </label>
211
+ <TextArea
212
+ ref={exportTextareaRef}
213
+ id={exportTextId}
214
+ controlWidth="full"
215
+ data-testid="field-remap-document-export-text"
216
+ monospace
217
+ readOnly
218
+ rows={12}
219
+ value={exportText}
220
+ onFocus={(event) => event.currentTarget.select()}
221
+ />
222
+ {status ? (
223
+ <p
224
+ className="workbench-field-remap-document-io__status"
225
+ data-status={status.kind}
226
+ role={status.kind === 'error' ? 'alert' : 'status'}
227
+ >
228
+ {status.message}
229
+ </p>
230
+ ) : null}
231
+ </Modal>
232
+ </WorkbenchModalPortal>
233
+ ) : null}
234
+
235
+ {importOpen ? (
236
+ <WorkbenchModalPortal>
237
+ <Modal
238
+ bodyClassName="workbench-field-remap-document-import__body"
239
+ bodyLayout="stack"
240
+ bodyPadding="lg"
241
+ bodyScroll="auto"
242
+ className="workbench-field-remap-document-import"
243
+ closeLabel={labels.closeDocumentImport}
244
+ footer={
245
+ <>
246
+ <Button type="button" onClick={closeImport}>
247
+ {labels.cancelDocumentImport}
248
+ </Button>
249
+ <Button
250
+ type="submit"
251
+ variant="primary"
252
+ data-testid="field-remap-apply-document-import"
253
+ >
254
+ {labels.applyDocumentImport}
255
+ </Button>
256
+ </>
257
+ }
258
+ initialFocusRef={textareaRef}
259
+ title={labels.importDocumentTitle}
260
+ onClose={closeImport}
261
+ onSubmit={applyImport}
262
+ >
263
+ <p className="workbench-field-remap-document-import__description">
264
+ {labels.importDocumentDescription}
265
+ </p>
266
+ <label className="workbench-field-remap-document-import__label" htmlFor={importTextId}>
267
+ {labels.importDocumentLabel}
268
+ </label>
269
+ <TextArea
270
+ ref={textareaRef}
271
+ id={importTextId}
272
+ aria-describedby={importError ? importErrorId : undefined}
273
+ aria-invalid={importError ? true : undefined}
274
+ controlWidth="full"
275
+ data-testid="field-remap-document-import-text"
276
+ monospace
277
+ placeholder={labels.importDocumentPlaceholder}
278
+ rows={12}
279
+ value={draft}
280
+ onChange={(event) => {
281
+ setDraft(event.currentTarget.value);
282
+ setImportError(null);
283
+ }}
284
+ />
285
+ {importError ? (
286
+ <p
287
+ id={importErrorId}
288
+ className="workbench-field-remap-document-import__error"
289
+ role="alert"
290
+ >
291
+ {importError}
292
+ </p>
293
+ ) : null}
294
+ </Modal>
295
+ </WorkbenchModalPortal>
296
+ ) : null}
297
+ </div>
298
+ );
299
+ }
@@ -377,8 +377,10 @@ const DEFAULT_FIT_VIEW_OPTIONS = { padding: 0.12, maxZoom: 1.15 } as const;
377
377
  * to the React Flow store (avoids update loops with controlled nodes).
378
378
  */
379
379
  function FieldRemapFlowActionsBridge({
380
+ canFitView,
380
381
  flowActionsRef,
381
382
  }: {
383
+ readonly canFitView: () => boolean;
382
384
  readonly flowActionsRef?: Ref<FieldRemapFlowActions | null> | undefined;
383
385
  }): null {
384
386
  const { fitView } = useReactFlow();
@@ -386,13 +388,16 @@ function FieldRemapFlowActionsBridge({
386
388
  flowActionsRef,
387
389
  () => ({
388
390
  fitView: (options) => {
391
+ if (!canFitView()) {
392
+ return;
393
+ }
389
394
  void fitView({
390
395
  padding: options?.padding ?? DEFAULT_FIT_VIEW_OPTIONS.padding,
391
396
  maxZoom: options?.maxZoom ?? DEFAULT_FIT_VIEW_OPTIONS.maxZoom,
392
397
  });
393
398
  },
394
399
  }),
395
- [fitView],
400
+ [canFitView, fitView],
396
401
  );
397
402
  return null;
398
403
  }
@@ -700,7 +705,9 @@ function FieldRemapFlowCanvas({
700
705
  const showAuthoringPalette = showConvertPalette && !readOnly;
701
706
  const emptyDetail = emptyDetailProp ?? (chrome === 'embed' ? 'collapse' : 'hint');
702
707
  const mapperRef = useRef<HTMLDivElement>(null);
708
+ const canvasRef = useRef<HTMLDivElement>(null);
703
709
  const restoreMapperFocusRef = useRef(false);
710
+ const [hasPositiveCanvasSize, setHasPositiveCanvasSize] = useState(false);
704
711
  const [workspaceLayout, setWorkspaceLayout] = useState<'wide' | 'medium' | 'narrow'>('wide');
705
712
  const [internalSelection, setInternalSelection] = useState<FieldRemapSelection>(null);
706
713
  const authoritativeSelection = selectionProp !== undefined ? selectionProp : internalSelection;
@@ -982,6 +989,48 @@ function FieldRemapFlowCanvas({
982
989
  return () => observer.disconnect();
983
990
  }, []);
984
991
 
992
+ const canFitView = useCallback(() => {
993
+ const bounds = canvasRef.current?.getBoundingClientRect();
994
+ return bounds !== undefined && bounds.width > 0 && bounds.height > 0;
995
+ }, []);
996
+
997
+ useEffect(() => {
998
+ const element = canvasRef.current;
999
+ if (!element) {
1000
+ return;
1001
+ }
1002
+
1003
+ const updateMountEligibility = () => {
1004
+ const bounds = element.getBoundingClientRect();
1005
+ if (bounds.width > 0 && bounds.height > 0) {
1006
+ setHasPositiveCanvasSize(true);
1007
+ }
1008
+ };
1009
+
1010
+ updateMountEligibility();
1011
+ if (typeof ResizeObserver === 'undefined') {
1012
+ return;
1013
+ }
1014
+ let pendingFrame: number | undefined;
1015
+ const scheduleMountEligibilityUpdate = () => {
1016
+ if (pendingFrame !== undefined) {
1017
+ return;
1018
+ }
1019
+ pendingFrame = requestAnimationFrame(() => {
1020
+ pendingFrame = undefined;
1021
+ updateMountEligibility();
1022
+ });
1023
+ };
1024
+ const observer = new ResizeObserver(scheduleMountEligibilityUpdate);
1025
+ observer.observe(element);
1026
+ return () => {
1027
+ observer.disconnect();
1028
+ if (pendingFrame !== undefined) {
1029
+ cancelAnimationFrame(pendingFrame);
1030
+ }
1031
+ };
1032
+ }, []);
1033
+
985
1034
  const graph = useMemo(
986
1035
  () =>
987
1036
  mappingToFlowGraph({
@@ -1837,105 +1886,61 @@ function FieldRemapFlowCanvas({
1837
1886
  ) : null}
1838
1887
  </>
1839
1888
 
1840
- <div className="workbench-field-remap-flow__canvas" data-testid="field-remap-flow">
1841
- <ReactFlow
1842
- nodes={nodes}
1843
- edges={flowEdges}
1844
- nodeTypes={nodeTypes}
1845
- edgeTypes={edgeTypes}
1846
- onNodesChange={onProjectedNodesChange}
1847
- onEdgesChange={onProjectedFlowEdgesChange}
1848
- onConnect={readOnly ? undefined : onConnect}
1849
- onConnectStart={readOnly ? undefined : onConnectStart}
1850
- onConnectEnd={readOnly ? undefined : onConnectEnd}
1851
- onEdgesDelete={readOnly ? undefined : onEdgesDelete}
1852
- onDragOver={onCanvasDragOver}
1853
- onDrop={onCanvasDrop}
1854
- onNodeClick={onNodeClick}
1855
- onEdgeClick={onEdgeClick}
1856
- onPaneContextMenu={onPaneContextMenu ? handlePaneContextMenu : undefined}
1857
- onNodeContextMenu={onNodeContextMenu ? handleNodeContextMenu : undefined}
1858
- onEdgeContextMenu={onEdgeContextMenu ? handleEdgeContextMenu : undefined}
1859
- isValidConnection={isValidConnection}
1860
- nodesDraggable={!readOnly}
1861
- nodesConnectable={!readOnly}
1862
- edgesReconnectable={!readOnly}
1863
- elementsSelectable={false}
1864
- ariaLabelConfig={flowAriaLabelConfig}
1865
- deleteKeyCode={null}
1866
- fitView
1867
- fitViewOptions={DEFAULT_FIT_VIEW_OPTIONS}
1868
- proOptions={{ hideAttribution: true }}
1869
- >
1870
- <FieldRemapFlowActionsBridge flowActionsRef={flowActionsRef} />
1871
- <Background gap={16} color="var(--xy-background-pattern-color)" />
1872
- <Controls showInteractive={false} fitViewOptions={DEFAULT_FIT_VIEW_OPTIONS}>
1873
- {onShowMinimapChange ? (
1874
- <ControlButton
1875
- aria-label={showMinimap ? chromeLabels.hideMinimap : chromeLabels.showMinimap}
1876
- className={
1877
- showMinimap
1878
- ? 'workbench-field-remap-flow__minimap-toggle is-active'
1879
- : 'workbench-field-remap-flow__minimap-toggle'
1880
- }
1881
- data-testid="field-remap-toggle-minimap"
1882
- title={showMinimap ? chromeLabels.hideMinimap : chromeLabels.showMinimap}
1883
- onClick={() => {
1884
- onShowMinimapChange(!showMinimap);
1885
- }}
1886
- >
1887
- <svg
1888
- aria-hidden="true"
1889
- fill="none"
1890
- height="16"
1891
- stroke="currentColor"
1892
- strokeLinecap="round"
1893
- strokeLinejoin="round"
1894
- strokeWidth="1.75"
1895
- viewBox="0 0 24 24"
1896
- width="16"
1889
+ <div
1890
+ ref={canvasRef}
1891
+ className="workbench-field-remap-flow__canvas"
1892
+ data-testid="field-remap-flow"
1893
+ >
1894
+ {hasPositiveCanvasSize ? (
1895
+ <ReactFlow
1896
+ nodes={nodes}
1897
+ edges={flowEdges}
1898
+ nodeTypes={nodeTypes}
1899
+ edgeTypes={edgeTypes}
1900
+ onNodesChange={onProjectedNodesChange}
1901
+ onEdgesChange={onProjectedFlowEdgesChange}
1902
+ onConnect={readOnly ? undefined : onConnect}
1903
+ onConnectStart={readOnly ? undefined : onConnectStart}
1904
+ onConnectEnd={readOnly ? undefined : onConnectEnd}
1905
+ onEdgesDelete={readOnly ? undefined : onEdgesDelete}
1906
+ onDragOver={onCanvasDragOver}
1907
+ onDrop={onCanvasDrop}
1908
+ onNodeClick={onNodeClick}
1909
+ onEdgeClick={onEdgeClick}
1910
+ onPaneContextMenu={onPaneContextMenu ? handlePaneContextMenu : undefined}
1911
+ onNodeContextMenu={onNodeContextMenu ? handleNodeContextMenu : undefined}
1912
+ onEdgeContextMenu={onEdgeContextMenu ? handleEdgeContextMenu : undefined}
1913
+ isValidConnection={isValidConnection}
1914
+ nodesDraggable={!readOnly}
1915
+ nodesConnectable={!readOnly}
1916
+ edgesReconnectable={!readOnly}
1917
+ elementsSelectable={false}
1918
+ ariaLabelConfig={flowAriaLabelConfig}
1919
+ deleteKeyCode={null}
1920
+ fitView
1921
+ fitViewOptions={DEFAULT_FIT_VIEW_OPTIONS}
1922
+ proOptions={{ hideAttribution: true }}
1923
+ >
1924
+ <FieldRemapFlowActionsBridge
1925
+ canFitView={canFitView}
1926
+ flowActionsRef={flowActionsRef}
1927
+ />
1928
+ <Background gap={16} color="var(--xy-background-pattern-color)" />
1929
+ <Controls showInteractive={false} fitViewOptions={DEFAULT_FIT_VIEW_OPTIONS}>
1930
+ {onShowMinimapChange ? (
1931
+ <ControlButton
1932
+ aria-label={showMinimap ? chromeLabels.hideMinimap : chromeLabels.showMinimap}
1933
+ className={
1934
+ showMinimap
1935
+ ? 'workbench-field-remap-flow__minimap-toggle is-active'
1936
+ : 'workbench-field-remap-flow__minimap-toggle'
1937
+ }
1938
+ data-testid="field-remap-toggle-minimap"
1939
+ title={showMinimap ? chromeLabels.hideMinimap : chromeLabels.showMinimap}
1940
+ onClick={() => {
1941
+ onShowMinimapChange(!showMinimap);
1942
+ }}
1897
1943
  >
1898
- <path d="M3 6.5 9 4l6 2.5L21 4v13.5L15 20l-6-2.5L3 20z" />
1899
- <path d="M9 4v13.5" />
1900
- <path d="M15 6.5V20" />
1901
- </svg>
1902
- </ControlButton>
1903
- ) : null}
1904
- {onIncludeHiddenChange ? (
1905
- <ControlButton
1906
- aria-label={
1907
- includeHidden ? chromeLabels.hideHiddenFields : chromeLabels.showHiddenFields
1908
- }
1909
- aria-pressed={includeHidden}
1910
- className={
1911
- includeHidden
1912
- ? 'workbench-field-remap-flow__hidden-toggle is-active'
1913
- : 'workbench-field-remap-flow__hidden-toggle'
1914
- }
1915
- data-testid="field-remap-toggle-hidden-fields"
1916
- title={
1917
- includeHidden ? chromeLabels.hideHiddenFields : chromeLabels.showHiddenFields
1918
- }
1919
- onClick={() => {
1920
- onIncludeHiddenChange(!includeHidden);
1921
- }}
1922
- >
1923
- {includeHidden ? (
1924
- <svg
1925
- aria-hidden="true"
1926
- fill="none"
1927
- height="16"
1928
- stroke="currentColor"
1929
- strokeLinecap="round"
1930
- strokeLinejoin="round"
1931
- strokeWidth="1.75"
1932
- viewBox="0 0 24 24"
1933
- width="16"
1934
- >
1935
- <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" />
1936
- <circle cx="12" cy="12" r="3" />
1937
- </svg>
1938
- ) : (
1939
1944
  <svg
1940
1945
  aria-hidden="true"
1941
1946
  fill="none"
@@ -1947,35 +1952,92 @@ function FieldRemapFlowCanvas({
1947
1952
  viewBox="0 0 24 24"
1948
1953
  width="16"
1949
1954
  >
1950
- <path d="M3 3l18 18" />
1951
- <path d="M10.6 10.6a3 3 0 0 0 4.2 4.2" />
1952
- <path d="M9.9 5.1A10.6 10.6 0 0 1 12 5c6.5 0 10 7 10 7a17.4 17.4 0 0 1-3.2 4.4" />
1953
- <path d="M6.1 6.1C3.9 7.7 2 12 2 12s3.5 7 10 7a10.4 10.4 0 0 0 4.2-.9" />
1955
+ <path d="M3 6.5 9 4l6 2.5L21 4v13.5L15 20l-6-2.5L3 20z" />
1956
+ <path d="M9 4v13.5" />
1957
+ <path d="M15 6.5V20" />
1954
1958
  </svg>
1955
- )}
1956
- </ControlButton>
1959
+ </ControlButton>
1960
+ ) : null}
1961
+ {onIncludeHiddenChange ? (
1962
+ <ControlButton
1963
+ aria-label={
1964
+ includeHidden ? chromeLabels.hideHiddenFields : chromeLabels.showHiddenFields
1965
+ }
1966
+ aria-pressed={includeHidden}
1967
+ className={
1968
+ includeHidden
1969
+ ? 'workbench-field-remap-flow__hidden-toggle is-active'
1970
+ : 'workbench-field-remap-flow__hidden-toggle'
1971
+ }
1972
+ data-testid="field-remap-toggle-hidden-fields"
1973
+ title={
1974
+ includeHidden ? chromeLabels.hideHiddenFields : chromeLabels.showHiddenFields
1975
+ }
1976
+ onClick={() => {
1977
+ onIncludeHiddenChange(!includeHidden);
1978
+ }}
1979
+ >
1980
+ {includeHidden ? (
1981
+ <svg
1982
+ aria-hidden="true"
1983
+ fill="none"
1984
+ height="16"
1985
+ stroke="currentColor"
1986
+ strokeLinecap="round"
1987
+ strokeLinejoin="round"
1988
+ strokeWidth="1.75"
1989
+ viewBox="0 0 24 24"
1990
+ width="16"
1991
+ >
1992
+ <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" />
1993
+ <circle cx="12" cy="12" r="3" />
1994
+ </svg>
1995
+ ) : (
1996
+ <svg
1997
+ aria-hidden="true"
1998
+ fill="none"
1999
+ height="16"
2000
+ stroke="currentColor"
2001
+ strokeLinecap="round"
2002
+ strokeLinejoin="round"
2003
+ strokeWidth="1.75"
2004
+ viewBox="0 0 24 24"
2005
+ width="16"
2006
+ >
2007
+ <path d="M3 3l18 18" />
2008
+ <path d="M10.6 10.6a3 3 0 0 0 4.2 4.2" />
2009
+ <path d="M9.9 5.1A10.6 10.6 0 0 1 12 5c6.5 0 10 7 10 7a17.4 17.4 0 0 1-3.2 4.4" />
2010
+ <path d="M6.1 6.1C3.9 7.7 2 12 2 12s3.5 7 10 7a10.4 10.4 0 0 0 4.2-.9" />
2011
+ </svg>
2012
+ )}
2013
+ </ControlButton>
2014
+ ) : null}
2015
+ </Controls>
2016
+ {showMinimap ? (
2017
+ <MiniMap
2018
+ pannable
2019
+ zoomable
2020
+ bgColor="var(--xy-minimap-background-color)"
2021
+ maskColor="var(--xy-minimap-mask-background-color)"
2022
+ nodeColor={(node) => {
2023
+ const kind = (node.data as FieldRemapFlowNodeData | undefined)?.kind;
2024
+ if (kind === 'source-object') {
2025
+ return 'var(--vscode-charts-blue, #3794ff)';
2026
+ }
2027
+ if (kind === 'target-object') {
2028
+ return 'var(--vscode-charts-green, #89d185)';
2029
+ }
2030
+ return 'var(--vscode-focusBorder, var(--color-accent, #3794ff))';
2031
+ }}
2032
+ nodeStrokeColor="var(--xy-minimap-node-stroke-color)"
2033
+ />
1957
2034
  ) : null}
1958
- </Controls>
1959
- {showMinimap ? (
1960
- <MiniMap
1961
- pannable
1962
- zoomable
1963
- bgColor="var(--xy-minimap-background-color)"
1964
- maskColor="var(--xy-minimap-mask-background-color)"
1965
- nodeColor={(node) => {
1966
- const kind = (node.data as FieldRemapFlowNodeData | undefined)?.kind;
1967
- if (kind === 'source-object') {
1968
- return 'var(--vscode-charts-blue, #3794ff)';
1969
- }
1970
- if (kind === 'target-object') {
1971
- return 'var(--vscode-charts-green, #89d185)';
1972
- }
1973
- return 'var(--vscode-focusBorder, var(--color-accent, #3794ff))';
1974
- }}
1975
- nodeStrokeColor="var(--xy-minimap-node-stroke-color)"
1976
- />
1977
- ) : null}
1978
- </ReactFlow>
2035
+ </ReactFlow>
2036
+ ) : (
2037
+ <p className="workbench-field-remap-demo__warn" role="status">
2038
+ Mapping canvas is waiting for available space.
2039
+ </p>
2040
+ )}
1979
2041
  </div>
1980
2042
 
1981
2043
  <div className="workbench-field-remap-flow__side-rail">