@parallelworks/ui 0.36.0 → 0.38.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 +16 -0
- package/dist/ai/agent/permissions.d.ts +2 -2
- package/dist/ai/agent/permissions.js +3 -1
- package/dist/ai/components/ChatSidebar.js +7 -4
- package/dist/ai/components/agent/PermissionPicker.js +1 -0
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.js +2 -2
- package/dist/ai/strings.d.ts +2 -0
- package/dist/ai/strings.js +2 -0
- package/dist/ai/ui/sidebar.d.ts +6 -0
- package/dist/ai/ui/sidebar.js +15 -1
- package/dist/components/Dropdown.d.ts +2 -0
- package/dist/components/Dropdown.js +7 -2
- package/dist/components/Input.d.ts +11 -6
- package/dist/components/Input.js +93 -74
- package/dist/components/Provider.d.ts +31 -0
- package/dist/components/Provider.js +50 -2
- package/dist/editing.d.ts +404 -0
- package/dist/editor/EditorField.js +1 -1
- package/dist/editor/Monaco.d.ts +15 -1
- package/dist/editor/Monaco.js +117 -13
- package/dist/editor/editorModel.d.ts +5 -0
- package/dist/editor/editorModel.js +23 -0
- package/dist/editor/index.d.ts +4 -0
- package/dist/editor/index.js +5 -1
- package/dist/editor/lintContext.d.ts +21 -0
- package/dist/editor/lintContext.js +136 -0
- package/dist/editor/lintOwner.d.ts +2 -0
- package/dist/editor/lintOwner.js +5 -0
- package/dist/editor/nestedText.d.ts +13 -0
- package/dist/editor/nestedText.js +15 -0
- package/dist/editor/reveal.d.ts +4 -0
- package/dist/editor/reveal.js +15 -0
- package/dist/editor/settingsYaml.d.ts +12 -0
- package/dist/editor/settingsYaml.js +41 -0
- package/dist/form/FieldWrapper.js +1 -0
- package/dist/form/Fields/DropdownField.js +1 -1
- package/dist/form/Fields/DurationField.js +1 -1
- package/dist/form/Form.js +65 -24
- 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/fieldRegistry.js +1 -1
- package/dist/form/formEditing.d.ts +25 -0
- package/dist/form/formEditing.js +20 -0
- package/dist/form/index.js +5 -5
- package/dist/graph/InputDialog.d.ts +80 -0
- package/dist/graph/InputDialog.js +2110 -0
- package/dist/graph/InputsEditor.d.ts +12 -0
- package/dist/graph/InputsEditor.js +1594 -0
- package/dist/graph/ProblemsButton.d.ts +9 -0
- package/dist/graph/ProblemsButton.js +61 -0
- package/dist/graph/ShortcutsButton.d.ts +15 -0
- package/dist/graph/ShortcutsButton.js +56 -0
- package/dist/graph/SuggestionInput.d.ts +18 -0
- package/dist/graph/SuggestionInput.js +29 -0
- package/dist/graph/ToolbarPopover.d.ts +8 -0
- package/dist/graph/ToolbarPopover.js +71 -0
- package/dist/graph/editorApi.d.ts +51 -0
- package/dist/graph/editorApi.js +8 -0
- package/dist/graph/editorChrome.d.ts +27 -0
- package/dist/graph/editorChrome.js +75 -0
- package/dist/graph/editorFields.d.ts +180 -0
- package/dist/graph/editorFields.js +394 -0
- package/dist/graph/editorPrimitives.d.ts +60 -0
- package/dist/graph/editorPrimitives.js +150 -0
- package/dist/graph/editorStrings.d.ts +695 -0
- package/dist/graph/editorStrings.js +707 -0
- package/dist/graph/expressionRefs.d.ts +12 -0
- package/dist/graph/fieldProblems.d.ts +32 -0
- package/dist/graph/fieldProblems.js +77 -0
- package/dist/graph/index.d.ts +7 -0
- package/dist/graph/index.js +6 -1
- package/dist/graph/inputLayout.d.ts +25 -0
- 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.d.ts +103 -0
- package/dist/graph/inputRefs.js +196 -0
- package/dist/graph/inputRows.d.ts +43 -0
- package/dist/graph/inputRows.js +65 -0
- package/dist/graph/records.d.ts +3 -0
- package/dist/graph/records.js +7 -0
- package/dist/graph/refusalText.d.ts +6 -0
- package/dist/graph/settingsViews.d.ts +26 -0
- package/dist/graph/settingsViews.js +62 -0
- package/dist/graph/usesInputs.d.ts +17 -0
- package/dist/graph/usesInputs.js +40 -0
- package/dist/icons/bi/BiRedo.js +16 -0
- package/dist/icons/bi/BiUndo.js +16 -0
- package/dist/icons/lu/LuBraces.js +27 -0
- package/dist/icons/lu/LuCode.js +27 -0
- package/dist/icons/lu/LuKeyboard.js +71 -0
- package/dist/icons/lu/LuNetwork.js +63 -0
- package/dist/icons/lu/LuTextCursorInput.js +45 -0
- package/dist/icons.d.ts +2 -2
- package/dist/icons.js +8 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/list/ActionToolbar.d.ts +2 -0
- package/dist/list/ActionToolbar.js +18 -5
- package/dist/list/RowContextMenu.d.ts +10 -1
- package/dist/list/RowContextMenu.js +185 -30
- 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 { FormEditingContext } from "../form/formEditing.js";
|
|
21
|
+
import { WizardPagesContext } from "../form/Wizard/wizardPages.js";
|
|
22
|
+
import { asRecord } from "./records.js";
|
|
23
|
+
import { openOnAddOf, text } from "./editorFields.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 };
|