@parallelworks/ui 0.37.0 → 0.39.0
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 +1 -1
- package/dist/ai/components/ChatInput.js +8 -23
- package/dist/ai/components/ChatSidebar.js +7 -4
- package/dist/ai/components/chatDraft.d.ts +7 -0
- package/dist/ai/components/chatDraft.js +66 -0
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.js +2 -2
- package/dist/ai/ui/sidebar.d.ts +6 -0
- package/dist/ai/ui/sidebar.js +15 -1
- package/dist/editing.d.ts +2 -0
- package/dist/editor/Monaco.js +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/editor/lintContext.js +15 -3
- package/dist/form/FieldWrapper.js +1 -0
- package/dist/form/Form.js +64 -23
- package/dist/form/Wizard/WizardContainer.js +66 -6
- package/dist/form/Wizard/WizardStepContent.js +23 -10
- package/dist/form/Wizard/wizardPages.d.ts +12 -0
- package/dist/form/Wizard/wizardPages.js +5 -0
- package/dist/form/formEditing.d.ts +25 -0
- package/dist/form/formEditing.js +20 -0
- package/dist/graph/DependencyGraph.d.ts +8 -1
- package/dist/graph/DependencyGraph.js +433 -204
- package/dist/graph/GraphEditor.d.ts +27 -0
- package/dist/graph/GraphEditor.js +1543 -0
- package/dist/graph/GraphEditorDialogs.d.ts +87 -0
- package/dist/graph/GraphEditorDialogs.js +1808 -0
- package/dist/graph/InputDialog.js +1568 -54
- package/dist/graph/InputsEditor.d.ts +12 -0
- package/dist/graph/InputsEditor.js +1594 -0
- package/dist/graph/JobSummary.d.ts +3 -0
- package/dist/graph/JobSummary.js +34 -21
- package/dist/graph/MatrixGroup.d.ts +2 -1
- package/dist/graph/MatrixGroup.js +10 -3
- package/dist/graph/ShortcutsButton.js +56 -0
- package/dist/graph/SuggestionInput.js +29 -0
- package/dist/graph/WorkflowSettingsDialog.d.ts +21 -0
- package/dist/graph/WorkflowSettingsDialog.js +670 -0
- package/dist/graph/editorApi.js +8 -0
- package/dist/graph/editorChrome.js +75 -0
- package/dist/graph/editorFields.js +535 -0
- package/dist/graph/editorPrimitives.js +95 -4
- package/dist/graph/editorRows.d.ts +58 -0
- package/dist/graph/editorRows.js +460 -0
- package/dist/graph/editorStrings.d.ts +1 -0
- package/dist/graph/editorStrings.js +10 -1
- package/dist/graph/expressionRefs.js +39 -0
- package/dist/graph/fieldProblems.js +111 -0
- package/dist/graph/graphEditorState.d.ts +191 -0
- package/dist/graph/graphEditorState.js +74 -0
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +6 -2
- package/dist/graph/inputLayout.js +337 -0
- package/dist/graph/inputPages.d.ts +33 -0
- package/dist/graph/inputPages.js +186 -0
- package/dist/graph/inputRefs.js +388 -0
- package/dist/graph/inputRows.d.ts +43 -0
- package/dist/graph/inputRows.js +65 -0
- package/dist/graph/refusalText.js +13 -0
- package/dist/graph/settingsViews.js +62 -0
- package/dist/graph/stepWith.d.ts +38 -0
- package/dist/graph/stepWith.js +536 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1594 @@
|
|
|
1
|
+
import { useWorkflowEditing } from "../components/Provider.js";
|
|
2
|
+
import { BiSolidTrashAlt } from "../icons/bi/BiSolidTrashAlt.js";
|
|
3
|
+
import { BsArrowsCollapse } from "../icons/bs/BsArrowsCollapse.js";
|
|
4
|
+
import { BsDash } from "../icons/bs/BsDash.js";
|
|
5
|
+
import { BsFiles } from "../icons/bs/BsFiles.js";
|
|
6
|
+
import { BsThreeDots } from "../icons/bs/BsThreeDots.js";
|
|
7
|
+
import { FaArrowDown } from "../icons/fa/FaArrowDown.js";
|
|
8
|
+
import { FaArrowUp } from "../icons/fa/FaArrowUp.js";
|
|
9
|
+
import { FaEdit } from "../icons/fa/FaEdit.js";
|
|
10
|
+
import { FaPlus } from "../icons/fa/FaPlus.js";
|
|
11
|
+
import { GoPlus } from "../icons/go/GoPlus.js";
|
|
12
|
+
import { HiDuplicate } from "../icons/hi/HiDuplicate.js";
|
|
13
|
+
import { MdDragHandle } from "../icons/md/MdDragHandle.js";
|
|
14
|
+
import { TbAlertTriangle } from "../icons/tb/TbAlertTriangle.js";
|
|
15
|
+
import { TOOLTIP_ID } from "../components/Tooltip.js";
|
|
16
|
+
import { IconButton } from "../components/IconButton.js";
|
|
17
|
+
import { useRowMenu } from "../list/RowContextMenu.js";
|
|
18
|
+
import { MarqueeBox, createStore, reclaimFocus, returningFocus, trackDrag, useFocusAfterDialog, usePressOutside, useStore } from "./editorPrimitives.js";
|
|
19
|
+
import { useGraphEditorStrings, useInputsEditorStrings } from "./editorStrings.js";
|
|
20
|
+
import { asRecord } from "./records.js";
|
|
21
|
+
import { openOnAddOf, text } from "./editorFields.js";
|
|
22
|
+
import { FormEditingContext } from "../form/formEditing.js";
|
|
23
|
+
import { WizardPagesContext } from "../form/Wizard/wizardPages.js";
|
|
24
|
+
import { dropLayoutEdits, isWizard, resizeLayout, withInputLayouts } from "./inputLayout.js";
|
|
25
|
+
import { INPUT_TYPE_GROUPS, InputDialog, TypeBadge, allNames, newInputDefinition, withCreatedInputs } from "./InputDialog.js";
|
|
26
|
+
import { overlaps, typing } from "./editorApi.js";
|
|
27
|
+
import { ALT_KEY, MOD_KEY } from "./ShortcutsButton.js";
|
|
28
|
+
import { AddChip, BarDivider, DragLabel, EditorBar } from "./editorChrome.js";
|
|
29
|
+
import { addPageEdit, splitIntoPagesEdits, unsplitPagesEdits, wizardGroupDefinition } from "./inputPages.js";
|
|
30
|
+
import { inputCell, linesIn, rowsOf, snapSplit } from "./inputRows.js";
|
|
31
|
+
import cx from "classnames";
|
|
32
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
33
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
34
|
+
//#region src/graph/InputsEditor.tsx
|
|
35
|
+
var createFormStore = () => createStore({
|
|
36
|
+
drag: null,
|
|
37
|
+
edge: null,
|
|
38
|
+
resizing: false,
|
|
39
|
+
dialog: null,
|
|
40
|
+
hovered: null,
|
|
41
|
+
problems: [],
|
|
42
|
+
selection: [],
|
|
43
|
+
cursor: null,
|
|
44
|
+
cursorCopy: null,
|
|
45
|
+
open: [],
|
|
46
|
+
marquee: null
|
|
47
|
+
});
|
|
48
|
+
var ApiContext = createContext(null);
|
|
49
|
+
var InputsContext = createContext(void 0);
|
|
50
|
+
var EMPTY_STORE = createFormStore();
|
|
51
|
+
function useUi(store, select) {
|
|
52
|
+
return useStore(store, select);
|
|
53
|
+
}
|
|
54
|
+
function containerAt(editing, inputs, parent) {
|
|
55
|
+
let map = asRecord(inputs);
|
|
56
|
+
for (const name of parent) {
|
|
57
|
+
const definition = asRecord(map[name]);
|
|
58
|
+
const key = editing.inputChildrenKey(definition["type"]);
|
|
59
|
+
map = key ? asRecord(definition[key]) : {};
|
|
60
|
+
}
|
|
61
|
+
return map;
|
|
62
|
+
}
|
|
63
|
+
function namesIn(container) {
|
|
64
|
+
return Object.keys(container).filter((name) => name !== "$meta");
|
|
65
|
+
}
|
|
66
|
+
var key = (path) => JSON.stringify(path);
|
|
67
|
+
function samePath(a, b) {
|
|
68
|
+
return a.length === b.length && a.every((name, i) => b[i] === name);
|
|
69
|
+
}
|
|
70
|
+
function isInside(path, ancestor) {
|
|
71
|
+
return path.length >= ancestor.length && ancestor.every((name, i) => path[i] === name);
|
|
72
|
+
}
|
|
73
|
+
var below = (path, ancestor) => path.length > ancestor.length && isInside(path, ancestor);
|
|
74
|
+
var pathOf = (rowKey) => JSON.parse(rowKey);
|
|
75
|
+
function outermost(paths) {
|
|
76
|
+
return paths.filter((path) => !paths.some((other) => below(path, other)));
|
|
77
|
+
}
|
|
78
|
+
function contains(rect, box) {
|
|
79
|
+
return rect.left <= box.left && rect.right >= box.right && rect.top <= box.top && rect.bottom >= box.bottom;
|
|
80
|
+
}
|
|
81
|
+
var CHROME = "[data-input-chrome], [data-input-add], [role=\"menu\"], [role=\"dialog\"]";
|
|
82
|
+
var CONTROLS = "input, textarea, select, button, a[href], label, [contenteditable=\"true\"], .monaco-editor, [role=\"button\"], [role=\"checkbox\"], [role=\"combobox\"], [role=\"listbox\"], [role=\"option\"], [role=\"radio\"], [role=\"slider\"], [role=\"switch\"], [role=\"tab\"]";
|
|
83
|
+
var KEYED_WIDGETS = ".monaco-editor, [role=\"combobox\"], [role=\"listbox\"], [role=\"option\"], [role=\"radio\"], [role=\"slider\"], [role=\"switch\"], [role=\"tab\"]";
|
|
84
|
+
function rowsIn(root) {
|
|
85
|
+
return [...root.querySelectorAll("[data-input-path]")].map((el) => ({
|
|
86
|
+
path: pathOf(el.dataset["inputPath"] ?? "[]"),
|
|
87
|
+
copy: el.dataset["inputInstance"] ?? "",
|
|
88
|
+
rect: el.getBoundingClientRect()
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The inputs a box picks: the ones it touches in the innermost group holding it, each taken
|
|
93
|
+
* whole, so a box works inside a group as well as across the form.
|
|
94
|
+
*/
|
|
95
|
+
function inputsInBox(root, box) {
|
|
96
|
+
const touched = rowsIn(root).filter(({ rect }) => overlaps(rect, box));
|
|
97
|
+
const holder = touched.filter(({ path, rect }) => contains(rect, box) && touched.some((other) => below(other.path, path))).reduce((deepest, { path }) => deepest && deepest.length >= path.length ? deepest : path, null);
|
|
98
|
+
return outermost((holder ? touched.filter(({ path }) => below(path, holder)) : touched).map(({ path }) => path));
|
|
99
|
+
}
|
|
100
|
+
function ChromeButton({ label, onClick, children }) {
|
|
101
|
+
return /* @__PURE__ */ jsx("button", {
|
|
102
|
+
type: "button",
|
|
103
|
+
"aria-label": label,
|
|
104
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
105
|
+
"data-tooltip-content": label,
|
|
106
|
+
className: "flex h-5 w-5 cursor-pointer items-center justify-center rounded hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app)",
|
|
107
|
+
onClick: (e) => {
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
onClick(e);
|
|
110
|
+
},
|
|
111
|
+
children
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** An input's row: its field, with a toolbar of its type, drag grip and actions on hover. */
|
|
115
|
+
function InputRow({ path, hidden = false, instance, children }) {
|
|
116
|
+
const api = useContext(ApiContext);
|
|
117
|
+
const inputs = useContext(InputsContext);
|
|
118
|
+
const editing = useWorkflowEditing();
|
|
119
|
+
const t = useInputsEditorStrings();
|
|
120
|
+
const g = useGraphEditorStrings();
|
|
121
|
+
const rowKey = key(path);
|
|
122
|
+
const copy = instance ?? rowKey;
|
|
123
|
+
const active = useUi(api?.store ?? EMPTY_STORE, (state) => state.open.includes(copy) && !state.drag && !state.resizing);
|
|
124
|
+
const selected = useUi(api?.store ?? EMPTY_STORE, (state) => state.selection.includes(rowKey));
|
|
125
|
+
const problems = useUi(api?.store ?? EMPTY_STORE, (state) => state.problems.filter((problem) => problem.input && key(problem.input) === rowKey).map((problem) => `${problem.line}\n${problem.message}`).join("\n\n"));
|
|
126
|
+
if (!api) return children;
|
|
127
|
+
const found = problems ? problems.split("\n\n") : [];
|
|
128
|
+
const definition = api.definition(path);
|
|
129
|
+
const name = path.at(-1) ?? "";
|
|
130
|
+
const parent = path.slice(0, -1);
|
|
131
|
+
const index = namesIn(containerAt(editing, inputs, parent)).indexOf(name);
|
|
132
|
+
const childKey = hidden ? editing.inputChildrenKey(definition["type"]) : void 0;
|
|
133
|
+
const outline = childKey && childKey !== "template" ? namesIn(asRecord(definition[childKey])) : null;
|
|
134
|
+
const flagged = hidden && definition["hidden"] !== void 0 && definition["hidden"] !== false;
|
|
135
|
+
const actions = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
136
|
+
/* @__PURE__ */ jsx("button", {
|
|
137
|
+
type: "button",
|
|
138
|
+
"aria-label": t.inputActions,
|
|
139
|
+
"data-drag-handle": true,
|
|
140
|
+
className: "flex h-5 w-5 cursor-grab items-center justify-center rounded hover:bg-(--theme-muted-panel-bg)",
|
|
141
|
+
onPointerDown: (e) => api.startDrag(e, path, name),
|
|
142
|
+
onClick: (e) => {
|
|
143
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
144
|
+
api.openMenu(r.left, r.bottom, path);
|
|
145
|
+
},
|
|
146
|
+
children: /* @__PURE__ */ jsx(MdDragHandle, { className: "h-3.5 w-3.5" })
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsx("span", {
|
|
149
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
150
|
+
"data-tooltip-content": name,
|
|
151
|
+
children: /* @__PURE__ */ jsx(TypeBadge, { type: text(definition["type"]) })
|
|
152
|
+
}),
|
|
153
|
+
(definition["type"] === "group" || definition["type"] === "list") && /* @__PURE__ */ jsx(PageActions, {
|
|
154
|
+
api,
|
|
155
|
+
parent: path,
|
|
156
|
+
list: definition[editing.inputChildrenKey(definition["type"]) ?? ""]
|
|
157
|
+
}),
|
|
158
|
+
/* @__PURE__ */ jsx(ChromeButton, {
|
|
159
|
+
label: t.editInput,
|
|
160
|
+
onClick: () => api.edit(path),
|
|
161
|
+
children: /* @__PURE__ */ jsx(FaEdit, { className: "h-3 w-3" })
|
|
162
|
+
}),
|
|
163
|
+
/* @__PURE__ */ jsx(ChromeButton, {
|
|
164
|
+
label: t.addInputBelow,
|
|
165
|
+
onClick: (e) => {
|
|
166
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
167
|
+
api.openTypeMenu(r.right, r.bottom, parent, index + 1);
|
|
168
|
+
},
|
|
169
|
+
children: /* @__PURE__ */ jsx(FaPlus, { className: "h-3 w-3" })
|
|
170
|
+
}),
|
|
171
|
+
/* @__PURE__ */ jsx(ChromeButton, {
|
|
172
|
+
label: t.deleteInput,
|
|
173
|
+
onClick: () => api.remove(path),
|
|
174
|
+
children: /* @__PURE__ */ jsx(BsDash, { className: "h-3 w-3" })
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ jsx(ChromeButton, {
|
|
177
|
+
label: t.inputActions,
|
|
178
|
+
onClick: (e) => {
|
|
179
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
180
|
+
api.openMenu(r.right, r.bottom, path);
|
|
181
|
+
},
|
|
182
|
+
children: /* @__PURE__ */ jsx(BsThreeDots, { className: "h-3 w-3" })
|
|
183
|
+
})
|
|
184
|
+
] });
|
|
185
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
186
|
+
role: "none",
|
|
187
|
+
"data-input-path": rowKey,
|
|
188
|
+
"data-input-instance": copy,
|
|
189
|
+
"data-input-index": index,
|
|
190
|
+
...hidden ? { "data-input-hidden": "" } : {},
|
|
191
|
+
...selected ? { "data-selected": "" } : {},
|
|
192
|
+
className: cx("relative w-full", hidden && "mb-[15px]", found.length > 0 && "rounded-md ring-2 ring-(--theme-error)", selected && "isolate before:pointer-events-none before:absolute before:-inset-x-2 before:-inset-y-1 before:-z-10 before:rounded-lg before:bg-(--theme-element)/15 before:ring-2 before:ring-(--theme-element) before:content-['']"),
|
|
193
|
+
onPointerOver: (e) => {
|
|
194
|
+
e.stopPropagation();
|
|
195
|
+
api.hover(copy);
|
|
196
|
+
},
|
|
197
|
+
onContextMenu: (e) => {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
e.stopPropagation();
|
|
200
|
+
api.openMenu(e.clientX, e.clientY, path);
|
|
201
|
+
},
|
|
202
|
+
children: [
|
|
203
|
+
hidden ? /* @__PURE__ */ jsxs("div", {
|
|
204
|
+
className: "flex items-center gap-1.5 text-[11px] leading-4 theme-muted-text",
|
|
205
|
+
children: [
|
|
206
|
+
/* @__PURE__ */ jsx("span", {
|
|
207
|
+
className: "font-mono",
|
|
208
|
+
children: name
|
|
209
|
+
}),
|
|
210
|
+
flagged && /* @__PURE__ */ jsx("span", {
|
|
211
|
+
className: "italic",
|
|
212
|
+
children: t.hidden
|
|
213
|
+
}),
|
|
214
|
+
/* @__PURE__ */ jsx("div", {
|
|
215
|
+
"data-input-chrome": true,
|
|
216
|
+
className: "ml-auto flex items-center gap-0.5",
|
|
217
|
+
children: actions
|
|
218
|
+
})
|
|
219
|
+
]
|
|
220
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
221
|
+
className: cx("flex justify-end overflow-hidden transition-[height] duration-150 focus-within:h-7", active ? "h-7" : "h-0"),
|
|
222
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
223
|
+
"data-input-chrome": true,
|
|
224
|
+
className: "flex h-6 items-center gap-0.5 rounded-md border theme-border bg-(--theme-panel-bg) px-1 py-0.5 text-[11px] leading-4 theme-muted-text shadow-sm",
|
|
225
|
+
children: actions
|
|
226
|
+
})
|
|
227
|
+
}),
|
|
228
|
+
found.length > 0 && /* @__PURE__ */ jsx("button", {
|
|
229
|
+
type: "button",
|
|
230
|
+
"data-input-chrome": true,
|
|
231
|
+
"aria-label": g.problemCount(found.length),
|
|
232
|
+
"data-tooltip-id": "global-tooltip",
|
|
233
|
+
"data-tooltip-content": found.map((problem) => problem.slice(problem.indexOf("\n") + 1)).join("\n"),
|
|
234
|
+
className: "absolute -left-2 -top-2 z-10 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-(--theme-panel-bg) text-(--theme-error) shadow-sm",
|
|
235
|
+
onClick: () => api.reveal(Number.parseInt(found[0] ?? "0", 10)),
|
|
236
|
+
children: /* @__PURE__ */ jsx(TbAlertTriangle, { className: "h-3.5 w-3.5" })
|
|
237
|
+
}),
|
|
238
|
+
children,
|
|
239
|
+
outline && /* @__PURE__ */ jsxs("div", {
|
|
240
|
+
className: "ml-2 border-l theme-border pl-3",
|
|
241
|
+
children: [outline.map((child) => /* @__PURE__ */ jsx(InputRow, {
|
|
242
|
+
path: [...path, child],
|
|
243
|
+
hidden: true
|
|
244
|
+
}, child)), /* @__PURE__ */ jsx(AddInput, { parent: path })]
|
|
245
|
+
})
|
|
246
|
+
]
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* A list's page commands: split it into pages, or, once it is a wizard, add a page or put its
|
|
251
|
+
* inputs back. `bar` is the editor's own bar, for the form; otherwise a group's or list's row.
|
|
252
|
+
*/
|
|
253
|
+
function PageActions({ api, parent, list, bar = false }) {
|
|
254
|
+
const t = useInputsEditorStrings();
|
|
255
|
+
if (!isWizard(list)) return bar ? /* @__PURE__ */ jsx(IconButton, {
|
|
256
|
+
icon: /* @__PURE__ */ jsx(BsFiles, { className: "h-4 w-4" }),
|
|
257
|
+
label: t.splitIntoPages,
|
|
258
|
+
size: "sm",
|
|
259
|
+
variant: "ghost",
|
|
260
|
+
onClick: () => api.splitIntoPages(parent)
|
|
261
|
+
}) : /* @__PURE__ */ jsx(ChromeButton, {
|
|
262
|
+
label: t.splitIntoPages,
|
|
263
|
+
onClick: () => api.splitIntoPages(parent),
|
|
264
|
+
children: /* @__PURE__ */ jsx(BsFiles, { className: "h-3 w-3" })
|
|
265
|
+
});
|
|
266
|
+
return bar ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AddChip, {
|
|
267
|
+
icon: /* @__PURE__ */ jsx(FaPlus, { className: "h-3 w-3" }),
|
|
268
|
+
label: t.page,
|
|
269
|
+
hint: t.addPage,
|
|
270
|
+
onClick: () => api.addPage(parent)
|
|
271
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
272
|
+
icon: /* @__PURE__ */ jsx(BsArrowsCollapse, { className: "h-4 w-4" }),
|
|
273
|
+
label: t.unsplitPages,
|
|
274
|
+
size: "sm",
|
|
275
|
+
variant: "ghost",
|
|
276
|
+
onClick: () => api.unsplitPages(parent)
|
|
277
|
+
})] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ChromeButton, {
|
|
278
|
+
label: t.addPage,
|
|
279
|
+
onClick: () => api.addPage(parent),
|
|
280
|
+
children: /* @__PURE__ */ jsx(GoPlus, { className: "h-3 w-3" })
|
|
281
|
+
}), /* @__PURE__ */ jsx(ChromeButton, {
|
|
282
|
+
label: t.unsplitPages,
|
|
283
|
+
onClick: () => api.unsplitPages(parent),
|
|
284
|
+
children: /* @__PURE__ */ jsx(BsArrowsCollapse, { className: "h-3 w-3" })
|
|
285
|
+
})] });
|
|
286
|
+
}
|
|
287
|
+
function pagesIn(list) {
|
|
288
|
+
const record = asRecord(list);
|
|
289
|
+
return namesIn(record).filter((name) => asRecord(record[name])["type"] === "step");
|
|
290
|
+
}
|
|
291
|
+
/** The bar's page of the form's wizard, turned by typing its number or with the arrow keys. */
|
|
292
|
+
function PageNumber({ count, page, onChange }) {
|
|
293
|
+
const t = useInputsEditorStrings();
|
|
294
|
+
const shown = Math.min(Math.max(page, 0), Math.max(count - 1, 0));
|
|
295
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
296
|
+
className: "flex h-7 items-center gap-1 px-1 text-xs theme-muted-text",
|
|
297
|
+
children: [
|
|
298
|
+
t.page,
|
|
299
|
+
/* @__PURE__ */ jsx("input", {
|
|
300
|
+
type: "number",
|
|
301
|
+
min: 1,
|
|
302
|
+
max: count,
|
|
303
|
+
value: shown + 1,
|
|
304
|
+
"aria-label": t.pageNumber,
|
|
305
|
+
className: "h-6 w-10 rounded border theme-border bg-(--theme-input-bg) px-1 text-center text-(--theme-input) text-xs",
|
|
306
|
+
onChange: (e) => {
|
|
307
|
+
const typed = Number(e.target.value);
|
|
308
|
+
if (Number.isInteger(typed) && typed >= 1 && typed <= count) onChange(typed - 1);
|
|
309
|
+
}
|
|
310
|
+
}),
|
|
311
|
+
/* @__PURE__ */ jsxs("span", { children: ["/ ", count] })
|
|
312
|
+
]
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
var ADD_INDENT = [
|
|
316
|
+
"",
|
|
317
|
+
"ml-4",
|
|
318
|
+
"ml-8",
|
|
319
|
+
"ml-12"
|
|
320
|
+
];
|
|
321
|
+
/** The add button that ends each list of inputs, naming the list it adds to. */
|
|
322
|
+
function AddInput({ parent }) {
|
|
323
|
+
const api = useContext(ApiContext);
|
|
324
|
+
const t = useInputsEditorStrings();
|
|
325
|
+
if (!api) return null;
|
|
326
|
+
const holder = parent.length > 0 ? api.definition(parent) : {};
|
|
327
|
+
const name = text(holder["label"]) || text(holder["title"]) || (parent.at(-1) ?? "");
|
|
328
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
329
|
+
type: "button",
|
|
330
|
+
"data-input-add": key(parent),
|
|
331
|
+
className: cx("mb-[15px] flex cursor-pointer items-center gap-1.5 text-[13px] theme-muted-text hover:text-(--theme-link)", ADD_INDENT[Math.min(parent.length, ADD_INDENT.length - 1)]),
|
|
332
|
+
onClick: (e) => {
|
|
333
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
334
|
+
api.openTypeMenu(r.left, r.bottom, parent, Number.MAX_SAFE_INTEGER);
|
|
335
|
+
},
|
|
336
|
+
children: [/* @__PURE__ */ jsx(FaPlus, { className: "h-3 w-3" }), parent.length === 0 ? t.addInputHere : t.addFieldTo(name)]
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
var EDITING = {
|
|
340
|
+
parent: [],
|
|
341
|
+
Row: InputRow,
|
|
342
|
+
Add: AddInput
|
|
343
|
+
};
|
|
344
|
+
function typeSearch(labels) {
|
|
345
|
+
return {
|
|
346
|
+
placeholder: labels.searchTypes,
|
|
347
|
+
empty: labels.noMatchingTypes
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function typeMenu(labels, pick) {
|
|
351
|
+
const types = labels.types;
|
|
352
|
+
const item = (type) => ({
|
|
353
|
+
kind: "action",
|
|
354
|
+
label: types[type] ?? type,
|
|
355
|
+
keywords: type,
|
|
356
|
+
onSelect: () => pick(type)
|
|
357
|
+
});
|
|
358
|
+
return [...INPUT_TYPE_GROUPS.map(([group, list]) => ({
|
|
359
|
+
kind: "submenu",
|
|
360
|
+
label: labels.typeGroups[group],
|
|
361
|
+
items: list.map(item)
|
|
362
|
+
}))];
|
|
363
|
+
}
|
|
364
|
+
var extent = (rows) => ({
|
|
365
|
+
left: Math.min(...rows.map((row) => row.rect.left)),
|
|
366
|
+
right: Math.max(...rows.map((row) => row.rect.right))
|
|
367
|
+
});
|
|
368
|
+
/** Gaps between lines: above each line, after a list's shown inputs, and at each list's end. */
|
|
369
|
+
function lineGaps(root, lines) {
|
|
370
|
+
const gaps = [];
|
|
371
|
+
const lists = /* @__PURE__ */ new Map();
|
|
372
|
+
for (const line of lines) {
|
|
373
|
+
const rows = rowsOf(line);
|
|
374
|
+
if (rows.length === 0) continue;
|
|
375
|
+
gaps.push({
|
|
376
|
+
...aboveGap(line),
|
|
377
|
+
...rows.every((row) => row.trailing) ? { trailing: true } : {}
|
|
378
|
+
});
|
|
379
|
+
const list = lists.get(key(line.parent)) ?? {
|
|
380
|
+
parent: line.parent,
|
|
381
|
+
rows: []
|
|
382
|
+
};
|
|
383
|
+
list.rows.push(...rows);
|
|
384
|
+
lists.set(key(line.parent), list);
|
|
385
|
+
}
|
|
386
|
+
for (const { parent, rows } of lists.values()) {
|
|
387
|
+
const shown = rows.filter((row) => !row.trailing);
|
|
388
|
+
if (shown.length > 0 && shown.length < rows.length) gaps.push({
|
|
389
|
+
parent,
|
|
390
|
+
index: Math.max(...shown.map((row) => row.index)) + 1,
|
|
391
|
+
...extent(shown),
|
|
392
|
+
y: Math.max(...shown.map((row) => row.rect.bottom))
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
for (const { parent, rows } of lists.values()) gaps.push({
|
|
396
|
+
parent,
|
|
397
|
+
index: Math.max(...rows.map((row) => row.index)) + 1,
|
|
398
|
+
...extent(rows),
|
|
399
|
+
y: Math.max(...rows.map((row) => row.rect.bottom))
|
|
400
|
+
});
|
|
401
|
+
for (const el of root.querySelectorAll("[data-input-add]")) {
|
|
402
|
+
const parent = JSON.parse(el.dataset["inputAdd"] ?? "[]");
|
|
403
|
+
if (!lists.has(key(parent))) {
|
|
404
|
+
const rect = el.getBoundingClientRect();
|
|
405
|
+
gaps.push({
|
|
406
|
+
parent,
|
|
407
|
+
index: 0,
|
|
408
|
+
left: rect.left,
|
|
409
|
+
right: rect.right,
|
|
410
|
+
y: rect.top
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return gaps;
|
|
415
|
+
}
|
|
416
|
+
var into = (parent, mover) => mover.paths?.some((path) => isInside(parent, path)) ?? false;
|
|
417
|
+
function nearestLineGap(gaps, client, mover) {
|
|
418
|
+
let best = null;
|
|
419
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
420
|
+
for (const gap of gaps) {
|
|
421
|
+
if (into(gap.parent, mover) || gap.trailing && mover.shown) continue;
|
|
422
|
+
if (client.x < gap.left - 24 || client.x > gap.right + 24) continue;
|
|
423
|
+
const distance = Math.abs(client.y - gap.y);
|
|
424
|
+
if (distance < bestDistance || distance === bestDistance && best !== null && gap.parent.length > best.parent.length) {
|
|
425
|
+
best = gap;
|
|
426
|
+
bestDistance = distance;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return best;
|
|
430
|
+
}
|
|
431
|
+
/** A column of its own beside `column`, marked by a bar down that side. */
|
|
432
|
+
function besideGap(line, column, side) {
|
|
433
|
+
const { rows } = column;
|
|
434
|
+
const own = extent(rows);
|
|
435
|
+
const i = line.columns.indexOf(column);
|
|
436
|
+
const before = line.columns[i - 1];
|
|
437
|
+
const after = line.columns[i + 1];
|
|
438
|
+
let x = side === "left" ? own.left - 4 : own.right + 4;
|
|
439
|
+
if (side === "left" && before) x = (extent(before.rows).right + own.left) / 2;
|
|
440
|
+
else if (side === "right" && after) x = (own.right + extent(after.rows).left) / 2;
|
|
441
|
+
const top = Math.min(...rows.map((row) => row.rect.top));
|
|
442
|
+
return {
|
|
443
|
+
parent: line.parent,
|
|
444
|
+
index: side === "left" ? Math.min(...rows.map((row) => row.index)) : Math.max(...rows.map((row) => row.index)) + 1,
|
|
445
|
+
...own,
|
|
446
|
+
y: top,
|
|
447
|
+
beside: {
|
|
448
|
+
path: rows[0].path,
|
|
449
|
+
side,
|
|
450
|
+
x,
|
|
451
|
+
top,
|
|
452
|
+
bottom: Math.max(...rows.map((row) => row.rect.bottom))
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
/** Above or below `row` in its column, marked by a bar across the column. */
|
|
457
|
+
function stackGap(line, column, row, side) {
|
|
458
|
+
return {
|
|
459
|
+
parent: line.parent,
|
|
460
|
+
index: side === "above" ? row.index : row.index + 1,
|
|
461
|
+
...extent(column.rows),
|
|
462
|
+
y: side === "above" ? row.rect.top : row.rect.bottom,
|
|
463
|
+
stack: {
|
|
464
|
+
path: row.path,
|
|
465
|
+
side
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function aboveGap(line) {
|
|
470
|
+
const rows = rowsOf(line);
|
|
471
|
+
return {
|
|
472
|
+
parent: line.parent,
|
|
473
|
+
index: Math.min(...rows.map((row) => row.index)),
|
|
474
|
+
...extent(rows),
|
|
475
|
+
y: Math.min(...rows.map((row) => row.rect.top))
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function belowGap(line) {
|
|
479
|
+
const rows = rowsOf(line);
|
|
480
|
+
return {
|
|
481
|
+
parent: line.parent,
|
|
482
|
+
index: Math.max(...rows.map((row) => row.index)) + 1,
|
|
483
|
+
...extent(rows),
|
|
484
|
+
y: Math.max(...rows.map((row) => row.rect.bottom))
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/** A column narrower than its list stacks a drop on its inputs' halves instead of starting a line. */
|
|
488
|
+
function stacks(line, column) {
|
|
489
|
+
const width = column.cell?.style.getPropertyValue(WIDTH_VAR) ?? "";
|
|
490
|
+
return line.columns.length > 1 || column.rows.length > 1 || width !== "" && width !== "100%";
|
|
491
|
+
}
|
|
492
|
+
/** Where a group's own inputs start, below its title; null for an input holding none drawn. */
|
|
493
|
+
function bodyTop(root, lines, row) {
|
|
494
|
+
const tops = lines.filter((line) => samePath(line.parent, row.path)).flatMap((line) => rowsOf(line).map((child) => child.rect));
|
|
495
|
+
for (const el of root.querySelectorAll("[data-input-add]")) if (el.dataset["inputAdd"] === key(row.path)) tops.push(el.getBoundingClientRect());
|
|
496
|
+
const drawn = tops.filter((rect) => rect.height > 0).map((rect) => rect.top);
|
|
497
|
+
return drawn.length > 0 ? Math.min(...drawn) : null;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Where a drop at `client` lands: on an input's left or right quarter, in a column beside its
|
|
501
|
+
* own; over its middle, above or below it by which half, in its column or as a line of its own.
|
|
502
|
+
*/
|
|
503
|
+
function dropGap(root, client, mover) {
|
|
504
|
+
const lines = linesIn(root);
|
|
505
|
+
const gaps = lineGaps(root, lines);
|
|
506
|
+
const moving = (path) => mover.paths?.some((other) => isInside(path, other)) ?? false;
|
|
507
|
+
const under = (rect) => client.x >= rect.left && client.x <= rect.right && client.y >= rect.top && client.y <= rect.bottom;
|
|
508
|
+
let hit = null;
|
|
509
|
+
for (const line of lines) for (const column of line.columns) for (const row of column.rows) if (!moving(row.path) && under(row.rect) && (!hit || row.path.length > hit.row.path.length)) hit = {
|
|
510
|
+
line,
|
|
511
|
+
column,
|
|
512
|
+
row
|
|
513
|
+
};
|
|
514
|
+
if (hit) {
|
|
515
|
+
const { line, column, row } = hit;
|
|
516
|
+
const body = bodyTop(root, lines, row);
|
|
517
|
+
if (body !== null && client.y >= body) return nearestLineGap(gaps.filter((gap) => samePath(gap.parent, row.path)), client, mover);
|
|
518
|
+
if (row.trailing && mover.shown) return nearestLineGap(gaps.filter((gap) => samePath(gap.parent, line.parent)), client, mover);
|
|
519
|
+
const { rect } = row;
|
|
520
|
+
const quarter = rect.width / 4;
|
|
521
|
+
const shares = !mover.hidden && !row.hidden;
|
|
522
|
+
if (shares && client.x < rect.left + quarter) return besideGap(line, column, "left");
|
|
523
|
+
if (shares && client.x > rect.right - quarter) return besideGap(line, column, "right");
|
|
524
|
+
const above = client.y < (rect.top + (body ?? rect.bottom)) / 2;
|
|
525
|
+
if (shares && stacks(line, column)) return stackGap(line, column, row, above ? "above" : "below");
|
|
526
|
+
return above ? aboveGap(line) : belowGap(line);
|
|
527
|
+
}
|
|
528
|
+
if (!mover.hidden) for (const line of lines) {
|
|
529
|
+
if (into(line.parent, mover)) continue;
|
|
530
|
+
for (let i = 1; i < line.columns.length; i++) {
|
|
531
|
+
const before = line.columns[i - 1];
|
|
532
|
+
const after = line.columns[i];
|
|
533
|
+
if (!before || !after) continue;
|
|
534
|
+
const left = extent(before.rows).right;
|
|
535
|
+
const right = extent(after.rows).left;
|
|
536
|
+
const rows = [...before.rows, ...after.rows];
|
|
537
|
+
if (client.x <= left || client.x >= right || client.y < Math.min(...rows.map((row) => row.rect.top)) || client.y > Math.max(...rows.map((row) => row.rect.bottom))) continue;
|
|
538
|
+
if (before.rows.some((row) => !moving(row.path))) return besideGap(line, before, "right");
|
|
539
|
+
if (after.rows.some((row) => !moving(row.path))) return besideGap(line, after, "left");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return nearestLineGap(gaps, client, mover);
|
|
543
|
+
}
|
|
544
|
+
var WIDTH_VAR = "--input-width";
|
|
545
|
+
/** The cells two neighbouring columns sit in, by their heads, and the row of cells holding them. */
|
|
546
|
+
function cellsAt(root, edge) {
|
|
547
|
+
const cellOf = (path, copy) => {
|
|
548
|
+
const el = [...root.querySelectorAll("[data-input-path]")].find((el) => el.dataset["inputPath"] === key(path) && el.dataset["inputInstance"] === copy);
|
|
549
|
+
return el ? inputCell(el) : null;
|
|
550
|
+
};
|
|
551
|
+
const left = cellOf(edge.left, edge.copies[0]);
|
|
552
|
+
const right = cellOf(edge.right, edge.copies[1]);
|
|
553
|
+
const flow = left?.parentElement;
|
|
554
|
+
return left && right && flow && right.parentElement === flow ? {
|
|
555
|
+
left,
|
|
556
|
+
right,
|
|
557
|
+
flow
|
|
558
|
+
} : null;
|
|
559
|
+
}
|
|
560
|
+
function edgeOf(root, before, after) {
|
|
561
|
+
const [leftHead] = before.rows;
|
|
562
|
+
const [rightHead] = after.rows;
|
|
563
|
+
const copies = [leftHead?.instance ?? "", rightHead?.instance ?? ""];
|
|
564
|
+
const cells = leftHead && rightHead && cellsAt(root, {
|
|
565
|
+
left: leftHead.path,
|
|
566
|
+
right: rightHead.path,
|
|
567
|
+
copies
|
|
568
|
+
});
|
|
569
|
+
const width = cells ? cells.flow.getBoundingClientRect().width : 0;
|
|
570
|
+
if (!leftHead || !rightHead || !cells || width <= 0) return null;
|
|
571
|
+
const share = (cell) => Math.floor(cell.getBoundingClientRect().width / width * 100);
|
|
572
|
+
const rows = [...before.rows, ...after.rows];
|
|
573
|
+
return {
|
|
574
|
+
left: leftHead.path,
|
|
575
|
+
right: rightHead.path,
|
|
576
|
+
copies,
|
|
577
|
+
x: cells.left.getBoundingClientRect().right,
|
|
578
|
+
top: Math.min(...rows.map((row) => row.rect.top)),
|
|
579
|
+
bottom: Math.max(...rows.map((row) => row.rect.bottom)),
|
|
580
|
+
split: [share(cells.left), share(cells.right)]
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
/** The edge between columns on a line that the pointer is over, the innermost list's first. */
|
|
584
|
+
function edgeAt(root, x, y) {
|
|
585
|
+
let found = null;
|
|
586
|
+
for (const { parent, columns } of linesIn(root)) for (let i = 1; i < columns.length; i++) {
|
|
587
|
+
const before = columns[i - 1];
|
|
588
|
+
const after = columns[i];
|
|
589
|
+
if (!before || !after) continue;
|
|
590
|
+
const rows = [...before.rows, ...after.rows];
|
|
591
|
+
if (x < extent(before.rows).right - 2 || x > extent(after.rows).left + 2 || y < Math.min(...rows.map((row) => row.rect.top)) || y > Math.max(...rows.map((row) => row.rect.bottom))) continue;
|
|
592
|
+
if (!found || parent.length > found.depth) found = {
|
|
593
|
+
depth: parent.length,
|
|
594
|
+
before,
|
|
595
|
+
after
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
return found ? edgeOf(root, found.before, found.after) : null;
|
|
599
|
+
}
|
|
600
|
+
/** The edge between the same two columns, by their heads, if they still share a line. */
|
|
601
|
+
function edgeBetween(root, left, right) {
|
|
602
|
+
for (const { columns } of linesIn(root)) {
|
|
603
|
+
const i = columns.findIndex((column) => samePath(column.rows[0]?.path ?? [], left));
|
|
604
|
+
const before = columns[i];
|
|
605
|
+
const after = columns[i + 1];
|
|
606
|
+
if (before && after && samePath(after.rows[0]?.path ?? [], right)) return edgeOf(root, before, after);
|
|
607
|
+
}
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
var sameEdge = (a, b) => a === b || a !== null && b !== null && samePath(a.left, b.left) && samePath(a.right, b.right) && Math.round(a.x) === Math.round(b.x);
|
|
611
|
+
var DELETE_KEYS = /* @__PURE__ */ new Set(["Delete", "Backspace"]);
|
|
612
|
+
var ARROWS = {
|
|
613
|
+
ArrowUp: "up",
|
|
614
|
+
ArrowDown: "down",
|
|
615
|
+
ArrowLeft: "left",
|
|
616
|
+
ArrowRight: "right"
|
|
617
|
+
};
|
|
618
|
+
/** The input an arrow key reaches from `from` as the form draws them: the nearest one starting
|
|
619
|
+
* above or below it, or beside it, those in line with it first. */
|
|
620
|
+
function neighbour(rows, from, direction) {
|
|
621
|
+
const r = from.rect;
|
|
622
|
+
const middle = (rect) => ({
|
|
623
|
+
x: (rect.left + rect.right) / 2,
|
|
624
|
+
y: (rect.top + rect.bottom) / 2
|
|
625
|
+
});
|
|
626
|
+
const distance = (rect) => Math.hypot(middle(rect).x - middle(r).x, middle(rect).y - middle(r).y);
|
|
627
|
+
const others = rows.filter((row) => row !== from);
|
|
628
|
+
const vertical = direction === "up" || direction === "down";
|
|
629
|
+
const ahead = others.filter(({ rect }) => direction === "down" ? rect.top > r.top + 1 : direction === "up" ? rect.top < r.top - 1 : direction === "right" ? rect.left >= r.right - 1 : rect.right <= r.left + 1);
|
|
630
|
+
const inLine = ahead.filter(({ rect }) => vertical ? Math.min(rect.right, r.right) > Math.max(rect.left, r.left) : Math.min(rect.bottom, r.bottom) > Math.max(rect.top, r.top));
|
|
631
|
+
const gap = ({ rect }) => direction === "down" ? rect.top - r.top : direction === "up" ? r.top - rect.top : direction === "right" ? rect.left - r.right : r.left - rect.right;
|
|
632
|
+
return (inLine.length > 0 ? inLine : ahead).reduce((best, row) => !best || gap(row) < gap(best) || gap(row) === gap(best) && distance(row.rect) < distance(best.rect) ? row : best, void 0);
|
|
633
|
+
}
|
|
634
|
+
function undoKeys(e, editor) {
|
|
635
|
+
const pressed = e.key.toLowerCase();
|
|
636
|
+
if (pressed === "z" && !e.shiftKey) {
|
|
637
|
+
e.preventDefault();
|
|
638
|
+
editor.onUndo();
|
|
639
|
+
} else if (pressed === "z" && e.shiftKey || pressed === "y") {
|
|
640
|
+
e.preventDefault();
|
|
641
|
+
editor.onRedo();
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function formShortcuts(g) {
|
|
645
|
+
const key = g.shortcutKeys;
|
|
646
|
+
const does = g.shortcutDoes;
|
|
647
|
+
return [{
|
|
648
|
+
title: g.shortcutGroups.select,
|
|
649
|
+
shortcuts: [
|
|
650
|
+
{
|
|
651
|
+
combos: [[key.shift, key.click]],
|
|
652
|
+
does: does.selectInput
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
combos: [[key.shift, key.drag]],
|
|
656
|
+
does: does.boxInputs
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
combos: [[
|
|
660
|
+
"←",
|
|
661
|
+
"↑",
|
|
662
|
+
"↓",
|
|
663
|
+
"→"
|
|
664
|
+
]],
|
|
665
|
+
does: does.pickInput
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
combos: [[key.shift, "↑"], [key.shift, "↓"]],
|
|
669
|
+
does: does.extendInputs
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
combos: [["Esc"]],
|
|
673
|
+
does: does.escape
|
|
674
|
+
}
|
|
675
|
+
]
|
|
676
|
+
}, {
|
|
677
|
+
title: g.shortcutGroups.change,
|
|
678
|
+
shortcuts: [
|
|
679
|
+
{
|
|
680
|
+
combos: [[key.dragHandle]],
|
|
681
|
+
does: does.moveInput
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
combos: [[key.dragEdge]],
|
|
685
|
+
does: does.resizeInputs
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
combos: [[ALT_KEY, "↑"], [ALT_KEY, "↓"]],
|
|
689
|
+
does: does.reorderInputs
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
combos: [[key.dragButton]],
|
|
693
|
+
does: does.addInput
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
combos: [["E"]],
|
|
697
|
+
does: does.editInput
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
combos: [[key.delete]],
|
|
701
|
+
does: does.removeInputs
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
combos: [[key.rightClick]],
|
|
705
|
+
does: does.inputMenu
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
combos: [[MOD_KEY, "Z"], [
|
|
709
|
+
key.shift,
|
|
710
|
+
MOD_KEY,
|
|
711
|
+
"Z"
|
|
712
|
+
]],
|
|
713
|
+
does: does.undo
|
|
714
|
+
}
|
|
715
|
+
]
|
|
716
|
+
}];
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Turns the input form rendered in `children` into an editor of the
|
|
720
|
+
* workflow's `on.execute.inputs`, with edits sent through `editor`.
|
|
721
|
+
*/
|
|
722
|
+
function InputsFormEditor({ editor, inputs, children }) {
|
|
723
|
+
const t = useInputsEditorStrings();
|
|
724
|
+
const g = useGraphEditorStrings();
|
|
725
|
+
const editing = useWorkflowEditing();
|
|
726
|
+
const [store] = useState(createFormStore);
|
|
727
|
+
const [pages, setPages] = useState({});
|
|
728
|
+
const [counts, setCounts] = useState({});
|
|
729
|
+
const setPage = useCallback((wizard, index) => setPages((now) => ({
|
|
730
|
+
...now,
|
|
731
|
+
[wizard]: index
|
|
732
|
+
})), []);
|
|
733
|
+
const setCount = useCallback((wizard, count) => setCounts((now) => now[wizard] === count ? now : {
|
|
734
|
+
...now,
|
|
735
|
+
[wizard]: count
|
|
736
|
+
}), []);
|
|
737
|
+
const wizardPages = useMemo(() => ({
|
|
738
|
+
page: (wizard) => Math.min(pages[wizard] ?? 0, Math.max((counts[wizard] ?? 1) - 1, 0)),
|
|
739
|
+
setPage,
|
|
740
|
+
count: (wizard) => counts[wizard],
|
|
741
|
+
setCount
|
|
742
|
+
}), [
|
|
743
|
+
pages,
|
|
744
|
+
counts,
|
|
745
|
+
setPage,
|
|
746
|
+
setCount
|
|
747
|
+
]);
|
|
748
|
+
const containerRef = useRef(null);
|
|
749
|
+
const latest = useRef({
|
|
750
|
+
editor,
|
|
751
|
+
inputs
|
|
752
|
+
});
|
|
753
|
+
latest.current = {
|
|
754
|
+
editor,
|
|
755
|
+
inputs
|
|
756
|
+
};
|
|
757
|
+
const rowMenu = useRowMenu();
|
|
758
|
+
const openMenu = useMemo(() => returningFocus(rowMenu.openMenu, () => containerRef.current), [rowMenu.openMenu]);
|
|
759
|
+
const contextMenu = rowMenu.contextMenu;
|
|
760
|
+
useFocusAfterDialog(store, () => containerRef.current);
|
|
761
|
+
usePressOutside(() => containerRef.current, () => {
|
|
762
|
+
if (store.get().selection.length > 0) store.set({ selection: [] });
|
|
763
|
+
});
|
|
764
|
+
const api = useMemo(() => {
|
|
765
|
+
const send = (edit) => {
|
|
766
|
+
const result = latest.current.editor.onEdit(withInputLayouts(editing, latest.current.inputs, edit));
|
|
767
|
+
reclaimFocus(containerRef.current);
|
|
768
|
+
return result;
|
|
769
|
+
};
|
|
770
|
+
const rowElement = (rowKey) => [...containerRef.current?.querySelectorAll("[data-input-path]") ?? []].find((el) => el.dataset["inputPath"] === rowKey);
|
|
771
|
+
const definition = (path) => path.length === 0 ? asRecord(latest.current.inputs) : asRecord(containerAt(editing, latest.current.inputs, path.slice(0, -1))[path.at(-1) ?? ""]);
|
|
772
|
+
const indexOf = (path) => namesIn(containerAt(editing, latest.current.inputs, path.slice(0, -1))).indexOf(path.at(-1) ?? "");
|
|
773
|
+
const create = (parent, index, type, placement) => {
|
|
774
|
+
const { editor, inputs } = latest.current;
|
|
775
|
+
const wizardAbove = parent.slice(0, -1);
|
|
776
|
+
if (type === "step" && parent.length > 0 && definition(parent)["type"] === "step" && isWizard(containerAt(editing, inputs, wizardAbove))) {
|
|
777
|
+
addPage(wizardAbove);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const container = containerAt(editing, inputs, parent);
|
|
781
|
+
if (type !== "step" && isWizard(container)) {
|
|
782
|
+
const pages = namesIn(container);
|
|
783
|
+
const page = pages[Math.max(Math.min(index, pages.length) - 1, 0)];
|
|
784
|
+
if (page !== void 0) {
|
|
785
|
+
const into = [...parent, page];
|
|
786
|
+
create(into, namesIn(containerAt(editing, inputs, into)).length, type, placement);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
const ownWizard = type === "step" && !isWizard(container) ? wizardGroupDefinition(t.stepsGroup, t.stepTitle(1)) : void 0;
|
|
791
|
+
if (editor.openOnAdd === false) {
|
|
792
|
+
const name = nextInputName(editing, inputs);
|
|
793
|
+
const add = {
|
|
794
|
+
type: "addInput",
|
|
795
|
+
parent,
|
|
796
|
+
index: Math.min(index, namesIn(container).length),
|
|
797
|
+
name,
|
|
798
|
+
definition: ownWizard ?? newInputDefinition(type)
|
|
799
|
+
};
|
|
800
|
+
send(placeNewInput(editing, inputs, add, parent, name, placement));
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
store.set({ dialog: {
|
|
804
|
+
kind: "create",
|
|
805
|
+
parent,
|
|
806
|
+
index,
|
|
807
|
+
type: ownWizard ? "group" : type,
|
|
808
|
+
...ownWizard ? { definition: ownWizard } : {},
|
|
809
|
+
...placement ? { placement } : {}
|
|
810
|
+
} });
|
|
811
|
+
};
|
|
812
|
+
const listAt = (parent) => containerAt(editing, latest.current.inputs, parent);
|
|
813
|
+
const childKeyAt = (parent) => parent.length > 0 ? editing.inputChildrenKey(definition(parent)["type"]) : void 0;
|
|
814
|
+
const sendAll = ([only, ...more]) => {
|
|
815
|
+
if (only) send(more.length > 0 ? {
|
|
816
|
+
type: "batch",
|
|
817
|
+
edits: [only, ...more]
|
|
818
|
+
} : only);
|
|
819
|
+
};
|
|
820
|
+
const splitIntoPages = (parent) => sendAll(splitIntoPagesEdits(editing, listAt(parent), parent, childKeyAt(parent), t.stepTitle(1), parent.length === 0 ? t.submitLabel : void 0));
|
|
821
|
+
const addPage = (parent) => {
|
|
822
|
+
const list = listAt(parent);
|
|
823
|
+
send(addPageEdit(editing, list, parent, t.stepTitle));
|
|
824
|
+
setPages((now) => ({
|
|
825
|
+
...now,
|
|
826
|
+
[key(parent)]: Number.MAX_SAFE_INTEGER
|
|
827
|
+
}));
|
|
828
|
+
};
|
|
829
|
+
const unsplitPages = (parent) => sendAll(unsplitPagesEdits(listAt(parent), parent, childKeyAt(parent)));
|
|
830
|
+
const openTypeMenu = (x, y, parent, index, placement) => openMenu(x, y, typeMenu(t, (type) => create(parent, index, type, placement)), void 0, typeSearch(t));
|
|
831
|
+
const edit = (path) => store.set({ dialog: {
|
|
832
|
+
kind: "edit",
|
|
833
|
+
path
|
|
834
|
+
} });
|
|
835
|
+
const remove = (path) => send({
|
|
836
|
+
type: "deleteInput",
|
|
837
|
+
path
|
|
838
|
+
});
|
|
839
|
+
const inFormOrder = (paths) => {
|
|
840
|
+
const order = rowsIn(containerRef.current ?? document.body).map((row) => key(row.path));
|
|
841
|
+
return [...paths].sort((a, b) => order.indexOf(key(a)) - order.indexOf(key(b)));
|
|
842
|
+
};
|
|
843
|
+
const staysPut = (paths, gap) => {
|
|
844
|
+
if (!paths.every((path) => samePath(path.slice(0, -1), gap.parent))) return false;
|
|
845
|
+
const at = paths.map(indexOf).sort((a, b) => a - b);
|
|
846
|
+
const first = at[0] ?? 0;
|
|
847
|
+
const last = at.at(-1) ?? 0;
|
|
848
|
+
return at.every((index, i) => index === first + i) && gap.index >= first && gap.index <= last + 1;
|
|
849
|
+
};
|
|
850
|
+
const dropOf = (gap) => ({
|
|
851
|
+
parent: gap.parent,
|
|
852
|
+
index: gap.index,
|
|
853
|
+
...gap.beside ? { beside: {
|
|
854
|
+
path: gap.beside.path,
|
|
855
|
+
side: gap.beside.side
|
|
856
|
+
} } : {},
|
|
857
|
+
...gap.stack ? { stack: gap.stack } : {}
|
|
858
|
+
});
|
|
859
|
+
const placementOf = (gap) => gap.beside || gap.stack ? { drop: dropOf(gap) } : void 0;
|
|
860
|
+
const followSelection = (moved, parent) => store.set({ selection: store.get().selection.map((rowKey) => {
|
|
861
|
+
const path = pathOf(rowKey);
|
|
862
|
+
const from = moved.find((other) => isInside(path, other));
|
|
863
|
+
return from ? key([...parent, ...path.slice(from.length - 1)]) : rowKey;
|
|
864
|
+
}) });
|
|
865
|
+
const removeSelection = () => {
|
|
866
|
+
const edits = inFormOrder(outermost(store.get().selection.map(pathOf))).map((path) => ({
|
|
867
|
+
type: "deleteInput",
|
|
868
|
+
path
|
|
869
|
+
}));
|
|
870
|
+
const [only] = edits;
|
|
871
|
+
if (!only) return false;
|
|
872
|
+
if (send(edits.length === 1 ? only : {
|
|
873
|
+
type: "batch",
|
|
874
|
+
edits
|
|
875
|
+
})) store.set({ selection: [] });
|
|
876
|
+
return true;
|
|
877
|
+
};
|
|
878
|
+
const moveSelection = (delta) => {
|
|
879
|
+
const paths = inFormOrder(outermost(store.get().selection.map(pathOf)));
|
|
880
|
+
const [first] = paths;
|
|
881
|
+
if (!first) return false;
|
|
882
|
+
const parent = first.slice(0, -1);
|
|
883
|
+
if (!paths.every((path) => samePath(path.slice(0, -1), parent))) return false;
|
|
884
|
+
const at = paths.map(indexOf).sort((a, b) => a - b);
|
|
885
|
+
const count = namesIn(containerAt(editing, latest.current.inputs, parent)).length;
|
|
886
|
+
const index = delta < 0 ? (at[0] ?? 0) - 1 : (at.at(-1) ?? 0) + 2;
|
|
887
|
+
if (index < 0 || index > count) return false;
|
|
888
|
+
send({
|
|
889
|
+
type: "moveInputs",
|
|
890
|
+
paths,
|
|
891
|
+
parent,
|
|
892
|
+
index
|
|
893
|
+
});
|
|
894
|
+
return true;
|
|
895
|
+
};
|
|
896
|
+
const startDrag = (e, path, label, how = {}) => {
|
|
897
|
+
if (e.button !== 0) return;
|
|
898
|
+
e.stopPropagation();
|
|
899
|
+
const selected = store.get().selection.map(pathOf);
|
|
900
|
+
const paths = path ? selected.length > 1 && selected.some((other) => samePath(other, path)) ? inFormOrder(outermost(selected)) : [path] : null;
|
|
901
|
+
const shown = paths && paths.length > 1 ? paths.map((other) => other.at(-1)).join(", ") : label;
|
|
902
|
+
const hiddenPaths = (paths ?? []).filter((path) => containerRef.current?.querySelector(`[data-input-path='${key(path)}']`)?.hasAttribute("data-input-hidden"));
|
|
903
|
+
const mover = {
|
|
904
|
+
paths,
|
|
905
|
+
hidden: hiddenPaths.length > 0,
|
|
906
|
+
shown: !paths || hiddenPaths.length < paths.length
|
|
907
|
+
};
|
|
908
|
+
const onEnd = (client, dragged) => {
|
|
909
|
+
const gap = store.get().drag?.gap ?? null;
|
|
910
|
+
store.set({ drag: null });
|
|
911
|
+
if (!dragged) {
|
|
912
|
+
how.onClick?.();
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
if (!gap) return;
|
|
916
|
+
if (!paths) {
|
|
917
|
+
openTypeMenu(client.x, client.y, gap.parent, gap.index, placementOf(gap));
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
containerRef.current?.focus({ preventScroll: true });
|
|
921
|
+
const drop = dropOf(gap);
|
|
922
|
+
const move = staysPut(paths, gap) ? {
|
|
923
|
+
type: "batch",
|
|
924
|
+
edits: []
|
|
925
|
+
} : {
|
|
926
|
+
type: "moveInputs",
|
|
927
|
+
paths,
|
|
928
|
+
parent: gap.parent,
|
|
929
|
+
index: gap.index
|
|
930
|
+
};
|
|
931
|
+
const edits = [move, ...dropLayoutEdits(editing, latest.current.inputs, paths, drop, move)];
|
|
932
|
+
const [only] = edits;
|
|
933
|
+
if (only && send(edits.length === 1 ? only : {
|
|
934
|
+
type: "batch",
|
|
935
|
+
edits
|
|
936
|
+
})) followSelection(paths, gap.parent);
|
|
937
|
+
};
|
|
938
|
+
trackDrag({
|
|
939
|
+
x: e.clientX,
|
|
940
|
+
y: e.clientY
|
|
941
|
+
}, {
|
|
942
|
+
escape: true,
|
|
943
|
+
swallowClick: how.swallowClick ?? "drag",
|
|
944
|
+
...how.holdText ? { holdText: how.holdText } : {},
|
|
945
|
+
onMove: (client) => {
|
|
946
|
+
const root = containerRef.current;
|
|
947
|
+
const found = root ? dropGap(root, client, mover) : null;
|
|
948
|
+
const pages = paths?.every((path) => definition(path)["type"] === "step") ?? false;
|
|
949
|
+
const gap = found && (pages || !isWizard(containerAt(editing, latest.current.inputs, found.parent))) ? found : null;
|
|
950
|
+
store.set({ drag: {
|
|
951
|
+
paths,
|
|
952
|
+
label: shown,
|
|
953
|
+
client,
|
|
954
|
+
gap
|
|
955
|
+
} });
|
|
956
|
+
},
|
|
957
|
+
onEnd,
|
|
958
|
+
onCancel: () => store.set({ drag: null })
|
|
959
|
+
});
|
|
960
|
+
};
|
|
961
|
+
const menuFor = (path) => {
|
|
962
|
+
const parent = path.slice(0, -1);
|
|
963
|
+
const index = indexOf(path);
|
|
964
|
+
const count = namesIn(containerAt(editing, latest.current.inputs, parent)).length;
|
|
965
|
+
return [
|
|
966
|
+
{
|
|
967
|
+
kind: "action",
|
|
968
|
+
label: t.editInput,
|
|
969
|
+
icon: /* @__PURE__ */ jsx(FaEdit, {}),
|
|
970
|
+
onSelect: () => edit(path)
|
|
971
|
+
},
|
|
972
|
+
{
|
|
973
|
+
kind: "submenu",
|
|
974
|
+
label: t.addInputBelow,
|
|
975
|
+
icon: /* @__PURE__ */ jsx(FaPlus, {}),
|
|
976
|
+
items: typeMenu(t, (type) => create(parent, index + 1, type)),
|
|
977
|
+
search: typeSearch(t)
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
kind: "action",
|
|
981
|
+
label: t.duplicate,
|
|
982
|
+
icon: /* @__PURE__ */ jsx(HiDuplicate, {}),
|
|
983
|
+
onSelect: () => send({
|
|
984
|
+
type: "duplicateInput",
|
|
985
|
+
path
|
|
986
|
+
})
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
kind: "action",
|
|
990
|
+
label: t.moveUp,
|
|
991
|
+
icon: /* @__PURE__ */ jsx(FaArrowUp, {}),
|
|
992
|
+
disabled: index <= 0,
|
|
993
|
+
onSelect: () => send({
|
|
994
|
+
type: "moveInputs",
|
|
995
|
+
paths: [path],
|
|
996
|
+
parent,
|
|
997
|
+
index: index - 1
|
|
998
|
+
})
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
kind: "action",
|
|
1002
|
+
label: t.moveDown,
|
|
1003
|
+
icon: /* @__PURE__ */ jsx(FaArrowDown, {}),
|
|
1004
|
+
disabled: index >= count - 1,
|
|
1005
|
+
onSelect: () => send({
|
|
1006
|
+
type: "moveInputs",
|
|
1007
|
+
paths: [path],
|
|
1008
|
+
parent,
|
|
1009
|
+
index: index + 2
|
|
1010
|
+
})
|
|
1011
|
+
},
|
|
1012
|
+
{ kind: "divider" },
|
|
1013
|
+
{
|
|
1014
|
+
kind: "action",
|
|
1015
|
+
label: t.deleteInput,
|
|
1016
|
+
icon: /* @__PURE__ */ jsx(BiSolidTrashAlt, {}),
|
|
1017
|
+
destructive: true,
|
|
1018
|
+
onSelect: () => remove(path)
|
|
1019
|
+
}
|
|
1020
|
+
];
|
|
1021
|
+
};
|
|
1022
|
+
const resize = (edge, split) => {
|
|
1023
|
+
const edit = resizeLayout(editing, latest.current.inputs, edge.left, edge.right, split);
|
|
1024
|
+
return edit ? Boolean(send(edit)) : false;
|
|
1025
|
+
};
|
|
1026
|
+
const startResize = (e, edge) => {
|
|
1027
|
+
const root = containerRef.current;
|
|
1028
|
+
const cells = root && cellsAt(root, edge);
|
|
1029
|
+
if (e.button !== 0 || !cells) return;
|
|
1030
|
+
e.stopPropagation();
|
|
1031
|
+
const from = cells.left.getBoundingClientRect().left;
|
|
1032
|
+
const width = cells.flow.getBoundingClientRect().width;
|
|
1033
|
+
const total = edge.split[0] + edge.split[1];
|
|
1034
|
+
const grid = cells.flow.parentElement?.hasAttribute("data-layout-grid");
|
|
1035
|
+
const oldColumns = cells.flow.style.gridTemplateColumns;
|
|
1036
|
+
const drawn = [cells.left, cells.right].map((cell) => cell.style.getPropertyValue(WIDTH_VAR));
|
|
1037
|
+
const children = [...cells.flow.children];
|
|
1038
|
+
const weights = children.map((cell) => Math.max(1, cell.getBoundingClientRect().width));
|
|
1039
|
+
const leftIndex = children.indexOf(cells.left);
|
|
1040
|
+
const rightIndex = children.indexOf(cells.right);
|
|
1041
|
+
const combined = (weights[leftIndex] ?? 0) + (weights[rightIndex] ?? 0);
|
|
1042
|
+
const show = (split) => {
|
|
1043
|
+
if (grid) {
|
|
1044
|
+
weights[leftIndex] = combined * split[0] / total;
|
|
1045
|
+
weights[rightIndex] = combined * split[1] / total;
|
|
1046
|
+
cells.flow.style.gridTemplateColumns = weights.map((weight) => `minmax(0, ${weight}fr)`).join(" ");
|
|
1047
|
+
} else {
|
|
1048
|
+
cells.left.style.setProperty(WIDTH_VAR, `${split[0]}%`);
|
|
1049
|
+
cells.right.style.setProperty(WIDTH_VAR, `${split[1]}%`);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
const restore = () => {
|
|
1053
|
+
cells.flow.style.gridTemplateColumns = oldColumns;
|
|
1054
|
+
cells.left.style.setProperty(WIDTH_VAR, drawn[0] ?? "");
|
|
1055
|
+
cells.right.style.setProperty(WIDTH_VAR, drawn[1] ?? "");
|
|
1056
|
+
};
|
|
1057
|
+
let split = edge.split;
|
|
1058
|
+
store.set({ resizing: true });
|
|
1059
|
+
trackDrag({
|
|
1060
|
+
x: e.clientX,
|
|
1061
|
+
y: e.clientY
|
|
1062
|
+
}, {
|
|
1063
|
+
escape: true,
|
|
1064
|
+
holdText: "press",
|
|
1065
|
+
swallowClick: "always",
|
|
1066
|
+
onMove: (client) => {
|
|
1067
|
+
split = snapSplit(total, (client.x - from) / width * 100);
|
|
1068
|
+
show(split);
|
|
1069
|
+
store.set({ edge: {
|
|
1070
|
+
...edge,
|
|
1071
|
+
x: cells.left.getBoundingClientRect().right,
|
|
1072
|
+
split
|
|
1073
|
+
} });
|
|
1074
|
+
},
|
|
1075
|
+
onEnd: () => {
|
|
1076
|
+
store.set({ resizing: false });
|
|
1077
|
+
root.focus({ preventScroll: true });
|
|
1078
|
+
restore();
|
|
1079
|
+
resize(edge, split);
|
|
1080
|
+
},
|
|
1081
|
+
onCancel: () => {
|
|
1082
|
+
store.set({ resizing: false });
|
|
1083
|
+
root.focus({ preventScroll: true });
|
|
1084
|
+
restore();
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
};
|
|
1088
|
+
return {
|
|
1089
|
+
store,
|
|
1090
|
+
definition,
|
|
1091
|
+
edit,
|
|
1092
|
+
remove,
|
|
1093
|
+
openMenu: (x, y, path) => openMenu(x, y, menuFor(path)),
|
|
1094
|
+
openTypeMenu,
|
|
1095
|
+
startDrag,
|
|
1096
|
+
startResize,
|
|
1097
|
+
nudgeEdge: (edge, delta) => {
|
|
1098
|
+
resize(edge, snapSplit(edge.split[0] + edge.split[1], edge.split[0] + delta));
|
|
1099
|
+
},
|
|
1100
|
+
reveal: (line) => latest.current.editor.onReveal?.({ line }),
|
|
1101
|
+
removeSelection,
|
|
1102
|
+
moveSelection,
|
|
1103
|
+
navigate: (direction, extend) => {
|
|
1104
|
+
const root = containerRef.current;
|
|
1105
|
+
const rows = root ? rowsIn(root) : [];
|
|
1106
|
+
const { cursor, cursorCopy, selection } = store.get();
|
|
1107
|
+
const at = cursor ?? selection.at(-1);
|
|
1108
|
+
const from = rows.find((row) => row.copy === cursorCopy && key(row.path) === at) ?? rows.find((row) => key(row.path) === at);
|
|
1109
|
+
const to = from ? neighbour(rows.filter((row) => row === from || !samePath(row.path, from.path)), from, direction) : direction === "up" || direction === "left" ? rows.at(-1) : rows[0];
|
|
1110
|
+
if (!to) return false;
|
|
1111
|
+
const reached = key(to.path);
|
|
1112
|
+
const visited = from ? [key(from.path), reached] : [reached];
|
|
1113
|
+
store.set({
|
|
1114
|
+
cursor: reached,
|
|
1115
|
+
cursorCopy: to.copy,
|
|
1116
|
+
selection: extend ? [.../* @__PURE__ */ new Set([...selection, ...visited])] : [reached]
|
|
1117
|
+
});
|
|
1118
|
+
rowElement(reached)?.scrollIntoView?.({ block: "nearest" });
|
|
1119
|
+
return true;
|
|
1120
|
+
},
|
|
1121
|
+
hover: (instance) => {
|
|
1122
|
+
const root = containerRef.current;
|
|
1123
|
+
if (!root || store.get().hovered === instance) return;
|
|
1124
|
+
const heads = linesIn(root).find(({ columns }) => columns.length > 1 && columns.some((column) => column.rows.some((row) => row.instance === instance)))?.columns.flatMap((column) => column.rows[0] ? [column.rows[0].instance] : []);
|
|
1125
|
+
store.set({
|
|
1126
|
+
hovered: instance,
|
|
1127
|
+
open: [.../* @__PURE__ */ new Set([instance, ...heads ?? []])]
|
|
1128
|
+
});
|
|
1129
|
+
},
|
|
1130
|
+
splitIntoPages,
|
|
1131
|
+
addPage,
|
|
1132
|
+
unsplitPages,
|
|
1133
|
+
editSelection: () => {
|
|
1134
|
+
const [only, ...more] = store.get().selection;
|
|
1135
|
+
if (only === void 0 || more.length > 0) return false;
|
|
1136
|
+
edit(pathOf(only));
|
|
1137
|
+
return true;
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
}, [
|
|
1141
|
+
store,
|
|
1142
|
+
openMenu,
|
|
1143
|
+
t,
|
|
1144
|
+
editing
|
|
1145
|
+
]);
|
|
1146
|
+
const problems = editor.problems;
|
|
1147
|
+
useEffect(() => {
|
|
1148
|
+
store.set({ problems: problems ?? [] });
|
|
1149
|
+
}, [store, problems]);
|
|
1150
|
+
useEffect(() => {
|
|
1151
|
+
const { edge, resizing } = store.get();
|
|
1152
|
+
const root = containerRef.current;
|
|
1153
|
+
if (edge && !resizing && root && inputs) store.set({ edge: edgeBetween(root, edge.left, edge.right) });
|
|
1154
|
+
}, [store, inputs]);
|
|
1155
|
+
useEffect(() => {
|
|
1156
|
+
const { selection } = store.get();
|
|
1157
|
+
const kept = selection.filter((rowKey) => {
|
|
1158
|
+
const path = pathOf(rowKey);
|
|
1159
|
+
return Object.hasOwn(containerAt(editing, inputs, path.slice(0, -1)), path.at(-1) ?? "");
|
|
1160
|
+
});
|
|
1161
|
+
if (kept.length !== selection.length) store.set({ selection: kept });
|
|
1162
|
+
}, [
|
|
1163
|
+
store,
|
|
1164
|
+
inputs,
|
|
1165
|
+
editing
|
|
1166
|
+
]);
|
|
1167
|
+
useEffect(() => {
|
|
1168
|
+
const container = containerRef.current;
|
|
1169
|
+
if (!container) return;
|
|
1170
|
+
const onDown = (e) => {
|
|
1171
|
+
if (e.button !== 0 || !(e.target instanceof Element) || e.target.closest(CHROME)) return;
|
|
1172
|
+
const row = e.target.closest("[data-input-path]");
|
|
1173
|
+
const start = {
|
|
1174
|
+
x: e.clientX,
|
|
1175
|
+
y: e.clientY
|
|
1176
|
+
};
|
|
1177
|
+
if (!e.shiftKey) {
|
|
1178
|
+
const rowKey = row?.dataset["inputPath"];
|
|
1179
|
+
if (!rowKey) {
|
|
1180
|
+
trackDrag(start, { onEnd: (_, dragged) => {
|
|
1181
|
+
if (!dragged) store.set({ selection: [] });
|
|
1182
|
+
} });
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const selected = store.get().selection.includes(rowKey);
|
|
1186
|
+
const title = e.target.closest("[data-field-label]");
|
|
1187
|
+
const ownTitle = title !== null && title.closest("[data-input-path]") === row;
|
|
1188
|
+
if (!ownTitle && e.target.closest(CONTROLS)) {
|
|
1189
|
+
if (!selected) store.set({ selection: [] });
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const path = pathOf(rowKey);
|
|
1193
|
+
const name = path.at(-1) ?? "";
|
|
1194
|
+
if (ownTitle && title.tagName !== "LABEL") {
|
|
1195
|
+
api.startDrag(e, path, name, { holdText: "drag" });
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
e.preventDefault();
|
|
1199
|
+
container.focus({ preventScroll: true });
|
|
1200
|
+
if (!selected) store.set({
|
|
1201
|
+
selection: [rowKey],
|
|
1202
|
+
cursor: rowKey,
|
|
1203
|
+
cursorCopy: row?.dataset["inputInstance"] ?? null
|
|
1204
|
+
});
|
|
1205
|
+
api.startDrag(e, path, name, {
|
|
1206
|
+
swallowClick: "always",
|
|
1207
|
+
holdText: "drag"
|
|
1208
|
+
});
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
e.preventDefault();
|
|
1212
|
+
e.stopPropagation();
|
|
1213
|
+
container.focus({ preventScroll: true });
|
|
1214
|
+
const base = store.get().selection;
|
|
1215
|
+
trackDrag(start, {
|
|
1216
|
+
holdText: "press",
|
|
1217
|
+
swallowClick: "always",
|
|
1218
|
+
onMove: (client) => {
|
|
1219
|
+
const box = {
|
|
1220
|
+
left: Math.min(start.x, client.x),
|
|
1221
|
+
right: Math.max(start.x, client.x),
|
|
1222
|
+
top: Math.min(start.y, client.y),
|
|
1223
|
+
bottom: Math.max(start.y, client.y)
|
|
1224
|
+
};
|
|
1225
|
+
store.set({
|
|
1226
|
+
selection: [.../* @__PURE__ */ new Set([...base, ...inputsInBox(container, box).map(key)])],
|
|
1227
|
+
marquee: box
|
|
1228
|
+
});
|
|
1229
|
+
},
|
|
1230
|
+
onEnd: (_, dragged) => {
|
|
1231
|
+
store.set({ marquee: null });
|
|
1232
|
+
const rowKey = row?.dataset["inputPath"];
|
|
1233
|
+
if (!dragged && rowKey) store.set({
|
|
1234
|
+
selection: base.includes(rowKey) ? base.filter((other) => other !== rowKey) : [...base, rowKey],
|
|
1235
|
+
cursor: rowKey,
|
|
1236
|
+
cursorCopy: row?.dataset["inputInstance"] ?? null
|
|
1237
|
+
});
|
|
1238
|
+
},
|
|
1239
|
+
onCancel: () => store.set({ marquee: null })
|
|
1240
|
+
});
|
|
1241
|
+
};
|
|
1242
|
+
container.addEventListener("pointerdown", onDown, true);
|
|
1243
|
+
return () => container.removeEventListener("pointerdown", onDown, true);
|
|
1244
|
+
}, [api, store]);
|
|
1245
|
+
useEffect(() => {
|
|
1246
|
+
const container = containerRef.current;
|
|
1247
|
+
if (!container) return;
|
|
1248
|
+
const onKey = (e) => {
|
|
1249
|
+
if (typing(e.target) || e.target instanceof Element && e.target.closest(KEYED_WIDGETS)) return;
|
|
1250
|
+
const arrow = ARROWS[e.key];
|
|
1251
|
+
if (e.metaKey || e.ctrlKey) undoKeys(e, latest.current.editor);
|
|
1252
|
+
else if (DELETE_KEYS.has(e.key) && !e.altKey && api.removeSelection()) e.preventDefault();
|
|
1253
|
+
else if (e.key.toLowerCase() === "e" && !e.altKey && api.editSelection()) e.preventDefault();
|
|
1254
|
+
else if (e.altKey && (e.key === "ArrowUp" || e.key === "ArrowDown") && api.moveSelection(e.key === "ArrowUp" ? -1 : 1)) e.preventDefault();
|
|
1255
|
+
else if (!e.altKey && arrow && api.navigate(arrow, e.shiftKey)) e.preventDefault();
|
|
1256
|
+
else if (e.key === "Escape" && !store.get().drag) store.set({ selection: [] });
|
|
1257
|
+
};
|
|
1258
|
+
container.addEventListener("keydown", onKey);
|
|
1259
|
+
return () => container.removeEventListener("keydown", onKey);
|
|
1260
|
+
}, [api, store]);
|
|
1261
|
+
const registerShow = editor.registerShow;
|
|
1262
|
+
useEffect(() => {
|
|
1263
|
+
if (!registerShow) return;
|
|
1264
|
+
return registerShow((problem) => {
|
|
1265
|
+
const key = problem.input ? JSON.stringify(problem.input) : null;
|
|
1266
|
+
const row = [...containerRef.current?.querySelectorAll("[data-input-path]") ?? []].find((el) => el.dataset["inputPath"] === key);
|
|
1267
|
+
if (!row) return false;
|
|
1268
|
+
const still = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1269
|
+
row.scrollIntoView?.({
|
|
1270
|
+
block: "center",
|
|
1271
|
+
behavior: still ? "auto" : "smooth"
|
|
1272
|
+
});
|
|
1273
|
+
if (!still) row.animate?.([{ boxShadow: "0 0 0 4px color-mix(in srgb, var(--theme-element) 45%, transparent)" }, { boxShadow: "0 0 0 0 transparent" }], {
|
|
1274
|
+
duration: 1200,
|
|
1275
|
+
easing: "ease-out"
|
|
1276
|
+
});
|
|
1277
|
+
if (problem.input) store.set({ dialog: {
|
|
1278
|
+
kind: "edit",
|
|
1279
|
+
path: problem.input
|
|
1280
|
+
} });
|
|
1281
|
+
return true;
|
|
1282
|
+
});
|
|
1283
|
+
}, [registerShow, store]);
|
|
1284
|
+
const empty = namesIn(asRecord(inputs)).length === 0;
|
|
1285
|
+
return /* @__PURE__ */ jsx(ApiContext.Provider, {
|
|
1286
|
+
value: api,
|
|
1287
|
+
children: /* @__PURE__ */ jsx(InputsContext.Provider, {
|
|
1288
|
+
value: inputs,
|
|
1289
|
+
children: /* @__PURE__ */ jsxs(FormEditingContext.Provider, {
|
|
1290
|
+
value: EDITING,
|
|
1291
|
+
children: [
|
|
1292
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1293
|
+
ref: containerRef,
|
|
1294
|
+
tabIndex: 0,
|
|
1295
|
+
className: "relative outline-none",
|
|
1296
|
+
onPointerMove: (e) => {
|
|
1297
|
+
const state = store.get();
|
|
1298
|
+
const root = containerRef.current;
|
|
1299
|
+
if (!root || state.drag || state.marquee || state.resizing || resizerFocused(root)) return;
|
|
1300
|
+
const edge = root.querySelector("[data-input-cell], [data-layout-grid]") ? edgeAt(root, e.clientX, e.clientY) : null;
|
|
1301
|
+
if (!sameEdge(edge, state.edge)) store.set({ edge });
|
|
1302
|
+
},
|
|
1303
|
+
onPointerLeave: () => {
|
|
1304
|
+
const root = containerRef.current;
|
|
1305
|
+
const keep = store.get().resizing || root !== null && resizerFocused(root);
|
|
1306
|
+
store.set({
|
|
1307
|
+
hovered: null,
|
|
1308
|
+
open: [],
|
|
1309
|
+
...keep ? {} : { edge: null }
|
|
1310
|
+
});
|
|
1311
|
+
},
|
|
1312
|
+
children: [
|
|
1313
|
+
empty && /* @__PURE__ */ jsx("div", {
|
|
1314
|
+
className: "mb-3 text-sm theme-muted-text",
|
|
1315
|
+
children: t.noInputs
|
|
1316
|
+
}),
|
|
1317
|
+
/* @__PURE__ */ jsx(WizardPagesContext.Provider, {
|
|
1318
|
+
value: wizardPages,
|
|
1319
|
+
children
|
|
1320
|
+
}),
|
|
1321
|
+
/* @__PURE__ */ jsx(DropIndicator, {
|
|
1322
|
+
store,
|
|
1323
|
+
container: containerRef
|
|
1324
|
+
}),
|
|
1325
|
+
/* @__PURE__ */ jsx(RowResizer, {
|
|
1326
|
+
api,
|
|
1327
|
+
container: containerRef
|
|
1328
|
+
}),
|
|
1329
|
+
/* @__PURE__ */ jsx(Marquee, {
|
|
1330
|
+
store,
|
|
1331
|
+
container: containerRef
|
|
1332
|
+
}),
|
|
1333
|
+
/* @__PURE__ */ jsx("div", {
|
|
1334
|
+
"data-input-chrome": true,
|
|
1335
|
+
className: "sticky bottom-2 z-10 mt-2 flex justify-end",
|
|
1336
|
+
children: /* @__PURE__ */ jsxs(EditorBar, {
|
|
1337
|
+
editor,
|
|
1338
|
+
groups: formShortcuts(g),
|
|
1339
|
+
children: [
|
|
1340
|
+
/* @__PURE__ */ jsx(AddChip, {
|
|
1341
|
+
icon: /* @__PURE__ */ jsx(FaPlus, { className: "h-3 w-3" }),
|
|
1342
|
+
label: t.addInput,
|
|
1343
|
+
hint: t.addInputHint,
|
|
1344
|
+
onPointerDown: (e) => api.startDrag(e, null, t.addInput),
|
|
1345
|
+
onClick: (e) => {
|
|
1346
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
1347
|
+
const form = isWizard(inputs) ? pagesIn(inputs) : [];
|
|
1348
|
+
const last = form.at(-1);
|
|
1349
|
+
if (last) wizardPages.setPage("[]", (wizardPages.count("[]") ?? form.length) - 1);
|
|
1350
|
+
api.openTypeMenu(r.left, r.bottom, last ? [last] : [], Number.MAX_SAFE_INTEGER);
|
|
1351
|
+
}
|
|
1352
|
+
}),
|
|
1353
|
+
/* @__PURE__ */ jsx(PageActions, {
|
|
1354
|
+
api,
|
|
1355
|
+
parent: [],
|
|
1356
|
+
list: inputs,
|
|
1357
|
+
bar: true
|
|
1358
|
+
}),
|
|
1359
|
+
isWizard(inputs) && /* @__PURE__ */ jsx(PageNumber, {
|
|
1360
|
+
count: wizardPages.count("[]") ?? pagesIn(inputs).length,
|
|
1361
|
+
page: wizardPages.page("[]"),
|
|
1362
|
+
onChange: (index) => wizardPages.setPage("[]", index)
|
|
1363
|
+
}),
|
|
1364
|
+
editor.onOpenSettings && /* @__PURE__ */ jsx(BarDivider, {})
|
|
1365
|
+
]
|
|
1366
|
+
})
|
|
1367
|
+
})
|
|
1368
|
+
]
|
|
1369
|
+
}),
|
|
1370
|
+
/* @__PURE__ */ jsx(Dialogs, {
|
|
1371
|
+
api,
|
|
1372
|
+
inputs,
|
|
1373
|
+
editor
|
|
1374
|
+
}),
|
|
1375
|
+
contextMenu
|
|
1376
|
+
]
|
|
1377
|
+
})
|
|
1378
|
+
})
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
function DropIndicator({ store, container }) {
|
|
1382
|
+
const drag = useUi(store, (state) => state.drag);
|
|
1383
|
+
const root = container.current;
|
|
1384
|
+
if (!drag || !root) return null;
|
|
1385
|
+
const rect = root.getBoundingClientRect();
|
|
1386
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [drag.gap?.beside ? /* @__PURE__ */ jsx("div", {
|
|
1387
|
+
className: "pointer-events-none absolute z-20 w-1 rounded-full bg-(--theme-element)",
|
|
1388
|
+
style: {
|
|
1389
|
+
left: drag.gap.beside.x - rect.left - 2,
|
|
1390
|
+
top: drag.gap.beside.top - rect.top,
|
|
1391
|
+
height: drag.gap.beside.bottom - drag.gap.beside.top
|
|
1392
|
+
}
|
|
1393
|
+
}) : drag.gap && /* @__PURE__ */ jsx("div", {
|
|
1394
|
+
className: "pointer-events-none absolute z-20 h-1 rounded-full bg-(--theme-element)",
|
|
1395
|
+
style: {
|
|
1396
|
+
left: drag.gap.left - rect.left,
|
|
1397
|
+
top: drag.gap.y - rect.top - 2,
|
|
1398
|
+
width: drag.gap.right - drag.gap.left
|
|
1399
|
+
}
|
|
1400
|
+
}), /* @__PURE__ */ jsx(DragLabel, {
|
|
1401
|
+
at: {
|
|
1402
|
+
x: drag.client.x - rect.left,
|
|
1403
|
+
y: drag.client.y - rect.top
|
|
1404
|
+
},
|
|
1405
|
+
text: drag.label
|
|
1406
|
+
})] });
|
|
1407
|
+
}
|
|
1408
|
+
var resizerFocused = (root) => document.activeElement instanceof HTMLElement && root.contains(document.activeElement) && document.activeElement.dataset["inputResizer"] !== void 0;
|
|
1409
|
+
/** The handle on the edge between two inputs sharing a line, which moves width between them. */
|
|
1410
|
+
function RowResizer({ api, container }) {
|
|
1411
|
+
const t = useInputsEditorStrings();
|
|
1412
|
+
const edge = useUi(api.store, (state) => state.edge);
|
|
1413
|
+
const root = container.current;
|
|
1414
|
+
if (!edge || !root) return null;
|
|
1415
|
+
const rect = root.getBoundingClientRect();
|
|
1416
|
+
const total = edge.split[0] + edge.split[1];
|
|
1417
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1418
|
+
role: "separator",
|
|
1419
|
+
"aria-orientation": "vertical",
|
|
1420
|
+
"aria-label": t.resizeInputs,
|
|
1421
|
+
"aria-valuenow": edge.split[0],
|
|
1422
|
+
"aria-valuemin": Math.min(10, total / 2),
|
|
1423
|
+
"aria-valuemax": total - Math.min(10, total / 2),
|
|
1424
|
+
tabIndex: 0,
|
|
1425
|
+
"data-input-chrome": true,
|
|
1426
|
+
"data-input-resizer": true,
|
|
1427
|
+
className: "group/edge absolute z-20 flex w-3 -translate-x-1/2 cursor-col-resize touch-none justify-center outline-none",
|
|
1428
|
+
style: {
|
|
1429
|
+
left: edge.x - rect.left,
|
|
1430
|
+
top: edge.top - rect.top,
|
|
1431
|
+
height: edge.bottom - edge.top
|
|
1432
|
+
},
|
|
1433
|
+
onPointerDown: (e) => api.startResize(e, edge),
|
|
1434
|
+
onKeyDown: (e) => {
|
|
1435
|
+
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
|
|
1436
|
+
e.preventDefault();
|
|
1437
|
+
e.stopPropagation();
|
|
1438
|
+
api.nudgeEdge(edge, e.key === "ArrowLeft" ? -5 : 5);
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1441
|
+
onBlur: () => {
|
|
1442
|
+
if (!api.store.get().resizing) api.store.set({ edge: null });
|
|
1443
|
+
},
|
|
1444
|
+
children: /* @__PURE__ */ jsx("div", { className: "h-full w-0.5 rounded-full bg-(--theme-element) opacity-50 group-hover/edge:opacity-100 group-focus-visible/edge:opacity-100" })
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
function Marquee({ store, container }) {
|
|
1448
|
+
const box = useUi(store, (state) => state.marquee);
|
|
1449
|
+
const root = container.current;
|
|
1450
|
+
if (!box || !root) return null;
|
|
1451
|
+
const rect = root.getBoundingClientRect();
|
|
1452
|
+
return /* @__PURE__ */ jsx(MarqueeBox, {
|
|
1453
|
+
className: "pointer-events-none z-20",
|
|
1454
|
+
box: {
|
|
1455
|
+
left: box.left - rect.left,
|
|
1456
|
+
top: box.top - rect.top,
|
|
1457
|
+
right: box.right - rect.left,
|
|
1458
|
+
bottom: box.bottom - rect.top
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
function Dialogs({ api, inputs, editor }) {
|
|
1463
|
+
const editing = useWorkflowEditing();
|
|
1464
|
+
const dialog = useUi(api.store, (state) => state.dialog);
|
|
1465
|
+
const close = () => api.store.set({ dialog: null });
|
|
1466
|
+
if (!dialog) return null;
|
|
1467
|
+
const save = (edit, created, home) => {
|
|
1468
|
+
const merged = withCreatedInputs(editing, created, home, edit);
|
|
1469
|
+
if (merged) editor.onEdit(withInputLayouts(editing, inputs, merged));
|
|
1470
|
+
};
|
|
1471
|
+
if (dialog.kind === "create") {
|
|
1472
|
+
const siblings = namesIn(containerAt(editing, inputs, dialog.parent));
|
|
1473
|
+
const index = Math.min(dialog.index, siblings.length);
|
|
1474
|
+
const home = homeFor(editing, inputs, dialog.parent, index);
|
|
1475
|
+
const placement = dialog.placement;
|
|
1476
|
+
return /* @__PURE__ */ jsx(InputDialog, {
|
|
1477
|
+
name: nextInputName(editing, inputs),
|
|
1478
|
+
definition: dialog.definition ?? newInputDefinition(dialog.type),
|
|
1479
|
+
isNew: true,
|
|
1480
|
+
siblings,
|
|
1481
|
+
allowStep: isWizard(containerAt(editing, inputs, dialog.parent)),
|
|
1482
|
+
inputs,
|
|
1483
|
+
home,
|
|
1484
|
+
onClose: close,
|
|
1485
|
+
view: editor.settingsView,
|
|
1486
|
+
onViewChange: editor.onSettingsViewChange,
|
|
1487
|
+
openOnAdd: openOnAddOf(editor),
|
|
1488
|
+
yaml: { onSave: (name, text, created) => save(placeNewInput(editing, inputs, {
|
|
1489
|
+
type: "batch",
|
|
1490
|
+
edits: [{
|
|
1491
|
+
type: "addInput",
|
|
1492
|
+
parent: dialog.parent,
|
|
1493
|
+
index: samePath(home.parent, dialog.parent) ? index + created.length : index,
|
|
1494
|
+
name,
|
|
1495
|
+
definition: asRecord(editing.loadYaml(text))
|
|
1496
|
+
}, {
|
|
1497
|
+
type: "setInputYaml",
|
|
1498
|
+
path: [...dialog.parent, name],
|
|
1499
|
+
yaml: text
|
|
1500
|
+
}]
|
|
1501
|
+
}, dialog.parent, name, placement), created, home) },
|
|
1502
|
+
onSave: (name, definition, _patch, created) => {
|
|
1503
|
+
const add = {
|
|
1504
|
+
type: "addInput",
|
|
1505
|
+
parent: dialog.parent,
|
|
1506
|
+
index: samePath(home.parent, dialog.parent) ? index + created.length : index,
|
|
1507
|
+
name,
|
|
1508
|
+
definition
|
|
1509
|
+
};
|
|
1510
|
+
save(placeNewInput(editing, inputs, add, dialog.parent, name, placement), created, home);
|
|
1511
|
+
}
|
|
1512
|
+
}, `create:${key(dialog.parent)}:${dialog.index}`);
|
|
1513
|
+
}
|
|
1514
|
+
const { path } = dialog;
|
|
1515
|
+
const name = path.at(-1) ?? "";
|
|
1516
|
+
const container = containerAt(editing, inputs, path.slice(0, -1));
|
|
1517
|
+
if (!Object.hasOwn(container, name)) return null;
|
|
1518
|
+
const home = homeFor(editing, inputs, path.slice(0, -1), namesIn(container).indexOf(name));
|
|
1519
|
+
return /* @__PURE__ */ jsx(InputDialog, {
|
|
1520
|
+
name,
|
|
1521
|
+
definition: asRecord(container[name]),
|
|
1522
|
+
isNew: false,
|
|
1523
|
+
siblings: namesIn(container).filter((other) => other !== name),
|
|
1524
|
+
allowStep: isWizard(container),
|
|
1525
|
+
inputs,
|
|
1526
|
+
home,
|
|
1527
|
+
onClose: close,
|
|
1528
|
+
onDelete: () => editor.onEdit(withInputLayouts(editing, inputs, {
|
|
1529
|
+
type: "deleteInput",
|
|
1530
|
+
path
|
|
1531
|
+
})),
|
|
1532
|
+
view: editor.settingsView,
|
|
1533
|
+
onViewChange: editor.onSettingsViewChange,
|
|
1534
|
+
openOnAdd: openOnAddOf(editor),
|
|
1535
|
+
yaml: {
|
|
1536
|
+
source: editor.readSource?.(),
|
|
1537
|
+
path,
|
|
1538
|
+
onSave: (next, text, created) => {
|
|
1539
|
+
const at = [...path.slice(0, -1), next];
|
|
1540
|
+
save(next === name ? {
|
|
1541
|
+
type: "setInputYaml",
|
|
1542
|
+
path,
|
|
1543
|
+
yaml: text
|
|
1544
|
+
} : {
|
|
1545
|
+
type: "batch",
|
|
1546
|
+
edits: [{
|
|
1547
|
+
type: "updateInput",
|
|
1548
|
+
path,
|
|
1549
|
+
name: next
|
|
1550
|
+
}, {
|
|
1551
|
+
type: "setInputYaml",
|
|
1552
|
+
path: at,
|
|
1553
|
+
yaml: text
|
|
1554
|
+
}]
|
|
1555
|
+
}, created, home);
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
onSave: (next, _definition, patch, created) => save({
|
|
1559
|
+
type: "updateInput",
|
|
1560
|
+
path,
|
|
1561
|
+
...next !== name ? { name: next } : {},
|
|
1562
|
+
...patch
|
|
1563
|
+
}, created, home)
|
|
1564
|
+
}, `edit:${key(path)}`);
|
|
1565
|
+
}
|
|
1566
|
+
function homeFor(editing, inputs, container, index) {
|
|
1567
|
+
const [top, next] = container;
|
|
1568
|
+
if (top === void 0) return {
|
|
1569
|
+
parent: [],
|
|
1570
|
+
index
|
|
1571
|
+
};
|
|
1572
|
+
if (text(asRecord(asRecord(inputs)[top])["type"]) === "step") {
|
|
1573
|
+
const fields = namesIn(containerAt(editing, inputs, [top]));
|
|
1574
|
+
return {
|
|
1575
|
+
parent: [top],
|
|
1576
|
+
index: next === void 0 ? index : fields.indexOf(next)
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
return {
|
|
1580
|
+
parent: [],
|
|
1581
|
+
index: namesIn(asRecord(inputs)).indexOf(top)
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function nextInputName(editing, inputs) {
|
|
1585
|
+
return editing.newInputName(allNames(editing, asRecord(inputs), /* @__PURE__ */ new Set()));
|
|
1586
|
+
}
|
|
1587
|
+
function placeNewInput(editing, inputs, edit, parent, name, placement) {
|
|
1588
|
+
return placement ? {
|
|
1589
|
+
type: "batch",
|
|
1590
|
+
edits: [edit, ...dropLayoutEdits(editing, inputs, [[...parent, name]], placement.drop, edit)]
|
|
1591
|
+
} : edit;
|
|
1592
|
+
}
|
|
1593
|
+
//#endregion
|
|
1594
|
+
export { InputsFormEditor };
|