@dou-ui/form-builder 0.0.0-stage → 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/builder/canvas.js +695 -0
- package/dist/builder/canvas.js.map +1 -0
- package/dist/builder/completion.js +49 -0
- package/dist/builder/completion.js.map +1 -0
- package/dist/builder/condition-editor.js +6 -0
- package/dist/builder/condition-editor.js.map +1 -0
- package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js +410 -0
- package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/condition.js +159 -0
- package/dist/builder/condition.js.map +1 -0
- package/dist/builder/context.js +477 -0
- package/dist/builder/context.js.map +1 -0
- package/dist/builder/core/commands.js +373 -0
- package/dist/builder/core/commands.js.map +1 -0
- package/dist/builder/core/history.js +76 -0
- package/dist/builder/core/history.js.map +1 -0
- package/dist/builder/core/transaction.js +298 -0
- package/dist/builder/core/transaction.js.map +1 -0
- package/dist/builder/drag.js +466 -0
- package/dist/builder/drag.js.map +1 -0
- package/dist/builder/expression-box.js +19 -0
- package/dist/builder/expression-box.js.map +1 -0
- package/dist/builder/expression-input.js +6 -0
- package/dist/builder/expression-input.js.map +1 -0
- package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js +117 -0
- package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/extension.js +47 -0
- package/dist/builder/extension.js.map +1 -0
- package/dist/builder/form-builder.js +6 -0
- package/dist/builder/form-builder.js.map +1 -0
- package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js +450 -0
- package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/index.js +22 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/inspector.js +6 -0
- package/dist/builder/inspector.js.map +1 -0
- package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js +442 -0
- package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/json-dialog.js +6 -0
- package/dist/builder/json-dialog.js.map +1 -0
- package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js +281 -0
- package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/locale.js +1271 -0
- package/dist/builder/locale.js.map +1 -0
- package/dist/builder/logic-fields.js +18 -0
- package/dist/builder/logic-fields.js.map +1 -0
- package/dist/builder/logic.js +332 -0
- package/dist/builder/logic.js.map +1 -0
- package/dist/builder/menu.js +92 -0
- package/dist/builder/menu.js.map +1 -0
- package/dist/builder/outline-row.js +6 -0
- package/dist/builder/outline-row.js.map +1 -0
- package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js +88 -0
- package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/outline.js +6 -0
- package/dist/builder/outline.js.map +1 -0
- package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js +111 -0
- package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/palette.js +6 -0
- package/dist/builder/palette.js.map +1 -0
- package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js +96 -0
- package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/preview.js +6 -0
- package/dist/builder/preview.js.map +1 -0
- package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js +111 -0
- package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/settings-binding.js +57 -0
- package/dist/builder/settings-binding.js.map +1 -0
- package/dist/builder/settings.js +487 -0
- package/dist/builder/settings.js.map +1 -0
- package/dist/builder/span-field.js +104 -0
- package/dist/builder/span-field.js.map +1 -0
- package/dist/code/expression-input.js +6 -0
- package/dist/code/expression-input.js.map +1 -0
- package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js +88 -0
- package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/extension.js +202 -0
- package/dist/code/extension.js.map +1 -0
- package/dist/code/index.js +19 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/language.js +43 -0
- package/dist/code/language.js.map +1 -0
- package/dist/collaboration/index.js +382 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/order.js +88 -0
- package/dist/collaboration/order.js.map +1 -0
- package/dist/core/check.js +314 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/document.js +112 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/expression.js +854 -0
- package/dist/core/expression.js.map +1 -0
- package/dist/core/formulas.js +242 -0
- package/dist/core/formulas.js.map +1 -0
- package/dist/core/rules.js +83 -0
- package/dist/core/rules.js.map +1 -0
- package/dist/core/values.js +80 -0
- package/dist/core/values.js.map +1 -0
- package/dist/fields/builtin.js +167 -0
- package/dist/fields/builtin.js.map +1 -0
- package/dist/fields/components.js +155 -0
- package/dist/fields/components.js.map +1 -0
- package/dist/fields/define.js +44 -0
- package/dist/fields/define.js.map +1 -0
- package/dist/fields/format.js +125 -0
- package/dist/fields/format.js.map +1 -0
- package/dist/fields/options.js +54 -0
- package/dist/fields/options.js.map +1 -0
- package/dist/index.js +20 -0
- package/dist/locale.js +161 -0
- package/dist/locale.js.map +1 -0
- package/dist/renderer/containers.js +430 -0
- package/dist/renderer/containers.js.map +1 -0
- package/dist/renderer/context.js +21 -0
- package/dist/renderer/context.js.map +1 -0
- package/dist/renderer/form-renderer.js +6 -0
- package/dist/renderer/form-renderer.js.map +1 -0
- package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js +272 -0
- package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/renderer/index.js +19 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/node-view.js +385 -0
- package/dist/renderer/node-view.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +27 -0
- package/dist/types/builder/canvas.d.ts +5 -0
- package/dist/types/builder/completion.d.ts +35 -0
- package/dist/types/builder/condition-editor.vue.d.ts +17 -0
- package/dist/types/builder/condition.d.ts +37 -0
- package/dist/types/builder/context.d.ts +149 -0
- package/dist/types/builder/core/commands.d.ts +74 -0
- package/dist/types/builder/core/history.d.ts +23 -0
- package/dist/types/builder/core/transaction.d.ts +108 -0
- package/dist/types/builder/drag.d.ts +52 -0
- package/dist/types/builder/expression-box.d.ts +8 -0
- package/dist/types/builder/expression-input.vue.d.ts +29 -0
- package/dist/types/builder/extension.d.ts +149 -0
- package/dist/types/builder/form-builder.vue.d.ts +24 -0
- package/dist/types/builder/index.d.ts +76 -0
- package/dist/types/builder/inspector.vue.d.ts +3 -0
- package/dist/types/builder/json-dialog.vue.d.ts +10 -0
- package/dist/types/builder/locale.d.ts +195 -0
- package/dist/types/builder/logic-fields.d.ts +2 -0
- package/dist/types/builder/logic.d.ts +57 -0
- package/dist/types/builder/menu.d.ts +23 -0
- package/dist/types/builder/outline-row.vue.d.ts +19 -0
- package/dist/types/builder/outline.vue.d.ts +3 -0
- package/dist/types/builder/palette.vue.d.ts +3 -0
- package/dist/types/builder/preview.vue.d.ts +3 -0
- package/dist/types/builder/settings-binding.d.ts +23 -0
- package/dist/types/builder/settings.d.ts +49 -0
- package/dist/types/builder/span-field.d.ts +4 -0
- package/dist/types/builder/types.d.ts +59 -0
- package/dist/types/code/expression-input.vue.d.ts +23 -0
- package/dist/types/code/extension.d.ts +45 -0
- package/dist/types/code/index.d.ts +12 -0
- package/dist/types/code/language.d.ts +6 -0
- package/dist/types/collaboration/index.d.ts +64 -0
- package/dist/types/collaboration/order.d.ts +20 -0
- package/dist/types/core/check.d.ts +68 -0
- package/dist/types/core/document.d.ts +36 -0
- package/dist/types/core/expression.d.ts +175 -0
- package/dist/types/core/formulas.d.ts +44 -0
- package/dist/types/core/rules.d.ts +23 -0
- package/dist/types/core/types.d.ts +211 -0
- package/dist/types/core/values.d.ts +22 -0
- package/dist/types/fields/builtin.d.ts +9 -0
- package/dist/types/fields/components.d.ts +177 -0
- package/dist/types/fields/define.d.ts +93 -0
- package/dist/types/fields/format.d.ts +76 -0
- package/dist/types/fields/options.d.ts +11 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/locale.d.ts +45 -0
- package/dist/types/renderer/containers.d.ts +20 -0
- package/dist/types/renderer/context.d.ts +186 -0
- package/dist/types/renderer/form-renderer.vue.d.ts +25 -0
- package/dist/types/renderer/index.d.ts +81 -0
- package/dist/types/renderer/node-view.d.ts +56 -0
- package/package.json +88 -4
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { formatMessage } from "../locale.js";
|
|
2
|
+
import { canContainFormNode, formNodePlaces } from "./core/transaction.js";
|
|
3
|
+
import { dropPlaces, topmost } from "./core/commands.js";
|
|
4
|
+
import { nodeTitle } from "./settings.js";
|
|
5
|
+
import { holdPointer } from "dou-ui";
|
|
6
|
+
// src/builder/drag.ts
|
|
7
|
+
/** How far a mouse moves before a press becomes a drag, in px. */
|
|
8
|
+
var DRAG_DISTANCE = 4;
|
|
9
|
+
/** How long a finger rests on a node before it is picked up, in ms. */
|
|
10
|
+
var HOLD_DELAY = 300;
|
|
11
|
+
/** How far a finger may move while resting, in px; further is a scroll. */
|
|
12
|
+
var HOLD_SLOP = 8;
|
|
13
|
+
/** The band at the top and bottom of the canvas that scrolls it while dragging, in px. */
|
|
14
|
+
var EDGE = 48;
|
|
15
|
+
/** The fastest the canvas scrolls while dragging, in px per frame. */
|
|
16
|
+
var EDGE_SPEED = 18;
|
|
17
|
+
var contains = (box, x, y) => x >= box.left && x <= box.right && y >= box.top && y <= box.bottom;
|
|
18
|
+
function createDragController(state, scroller, layer) {
|
|
19
|
+
let holders = [];
|
|
20
|
+
let source = null;
|
|
21
|
+
let dragged = [];
|
|
22
|
+
let target = null;
|
|
23
|
+
let pointer = {
|
|
24
|
+
x: 0,
|
|
25
|
+
y: 0
|
|
26
|
+
};
|
|
27
|
+
let frame = 0;
|
|
28
|
+
let measuredAt = 0;
|
|
29
|
+
let swallow = false;
|
|
30
|
+
let cleanup = null;
|
|
31
|
+
let places = null;
|
|
32
|
+
let placeAt = 0;
|
|
33
|
+
const root = () => scroller();
|
|
34
|
+
const contentPoint = (x, y) => {
|
|
35
|
+
const el = root();
|
|
36
|
+
const rect = el.getBoundingClientRect();
|
|
37
|
+
return {
|
|
38
|
+
x: x - rect.left + el.scrollLeft,
|
|
39
|
+
y: y - rect.top + el.scrollTop
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/** Read every grid and node of the canvas once; moving the pointer then only does math. */
|
|
43
|
+
function measure() {
|
|
44
|
+
const el = root();
|
|
45
|
+
if (!el) return;
|
|
46
|
+
const base = el.getBoundingClientRect();
|
|
47
|
+
const box = (node) => {
|
|
48
|
+
const rect = node.getBoundingClientRect();
|
|
49
|
+
const left = rect.left - base.left + el.scrollLeft;
|
|
50
|
+
const top = rect.top - base.top + el.scrollTop;
|
|
51
|
+
return {
|
|
52
|
+
left,
|
|
53
|
+
top,
|
|
54
|
+
right: left + rect.width,
|
|
55
|
+
bottom: top + rect.height
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const hidden = new Set(dragged.map((node) => node.id));
|
|
59
|
+
holders = [];
|
|
60
|
+
for (const grid of el.querySelectorAll("[data-holder]")) {
|
|
61
|
+
const owner = grid.closest("[data-node]");
|
|
62
|
+
let inside = false;
|
|
63
|
+
for (let up = owner; up;) {
|
|
64
|
+
if (hidden.has(up.dataset.node)) inside = true;
|
|
65
|
+
up = up.parentElement?.closest("[data-node]") ?? null;
|
|
66
|
+
}
|
|
67
|
+
if (inside) continue;
|
|
68
|
+
const children = [];
|
|
69
|
+
for (const child of grid.children) if (child instanceof HTMLElement && child.dataset.node) children.push({
|
|
70
|
+
id: child.dataset.node,
|
|
71
|
+
...box(child)
|
|
72
|
+
});
|
|
73
|
+
holders.push({
|
|
74
|
+
id: grid.dataset.holder || null,
|
|
75
|
+
children,
|
|
76
|
+
...box(grid)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
measuredAt = performance.now();
|
|
80
|
+
}
|
|
81
|
+
/** The place under a point: the innermost grid that takes the nodes, and the gap nearest. */
|
|
82
|
+
function hit(x, y) {
|
|
83
|
+
const map = formNodePlaces(state.document);
|
|
84
|
+
const probe = dragged.length ? dragged : [{
|
|
85
|
+
id: "",
|
|
86
|
+
type: source.type
|
|
87
|
+
}];
|
|
88
|
+
const fits = (holder) => {
|
|
89
|
+
const node = holder.id === null ? null : map.get(holder.id)?.node;
|
|
90
|
+
if (node === void 0) return false;
|
|
91
|
+
return probe.every((each) => canContainFormNode(node, each));
|
|
92
|
+
};
|
|
93
|
+
let chosen;
|
|
94
|
+
for (const holder of holders) if (contains(holder, x, y) && fits(holder)) chosen = holder;
|
|
95
|
+
chosen ??= holders.find((holder) => holder.id === null);
|
|
96
|
+
if (!chosen) return null;
|
|
97
|
+
const children = chosen.children;
|
|
98
|
+
if (!children.length) return {
|
|
99
|
+
parent: chosen.id,
|
|
100
|
+
index: 0,
|
|
101
|
+
holder: chosen,
|
|
102
|
+
line: {
|
|
103
|
+
left: chosen.left,
|
|
104
|
+
top: chosen.top,
|
|
105
|
+
length: chosen.right - chosen.left,
|
|
106
|
+
vertical: false
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
let best = children[0];
|
|
110
|
+
let bestIndex = 0;
|
|
111
|
+
let bestDistance = Infinity;
|
|
112
|
+
children.forEach((child, index) => {
|
|
113
|
+
const dx = x < child.left ? child.left - x : x > child.right ? x - child.right : 0;
|
|
114
|
+
const dy = y < child.top ? child.top - y : y > child.bottom ? y - child.bottom : 0;
|
|
115
|
+
const distance = dx * dx + dy * dy;
|
|
116
|
+
if (distance < bestDistance) {
|
|
117
|
+
bestDistance = distance;
|
|
118
|
+
best = child;
|
|
119
|
+
bestIndex = index;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const width = chosen.right - chosen.left;
|
|
123
|
+
const beside = best.right - best.left < width - 2 && y >= best.top && y <= best.bottom;
|
|
124
|
+
let after;
|
|
125
|
+
let line;
|
|
126
|
+
if (beside) {
|
|
127
|
+
after = x > (best.left + best.right) / 2;
|
|
128
|
+
line = {
|
|
129
|
+
left: after ? best.right + 4 : best.left - 4,
|
|
130
|
+
top: best.top,
|
|
131
|
+
length: best.bottom - best.top,
|
|
132
|
+
vertical: true
|
|
133
|
+
};
|
|
134
|
+
} else {
|
|
135
|
+
after = y > (best.top + best.bottom) / 2;
|
|
136
|
+
line = {
|
|
137
|
+
left: best.left,
|
|
138
|
+
top: after ? best.bottom + 6 : best.top - 6,
|
|
139
|
+
length: best.right - best.left,
|
|
140
|
+
vertical: false
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
parent: chosen.id,
|
|
145
|
+
index: bestIndex + (after ? 1 : 0),
|
|
146
|
+
holder: chosen,
|
|
147
|
+
line
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function draw() {
|
|
151
|
+
const { indicator, holder } = layer;
|
|
152
|
+
if (indicator) {
|
|
153
|
+
indicator.hidden = !target;
|
|
154
|
+
if (target) {
|
|
155
|
+
const { left, top, length, vertical } = target.line;
|
|
156
|
+
indicator.dataset.vertical = String(vertical);
|
|
157
|
+
indicator.style.transform = `translate(${left}px, ${top}px)`;
|
|
158
|
+
indicator.style.setProperty("--dou-form-builder-drop-length", `${length}px`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (holder) {
|
|
162
|
+
holder.hidden = !target || target.parent === null;
|
|
163
|
+
if (target && target.parent !== null) {
|
|
164
|
+
const box = target.holder;
|
|
165
|
+
holder.style.transform = `translate(${box.left}px, ${box.top}px)`;
|
|
166
|
+
holder.style.width = `${box.right - box.left}px`;
|
|
167
|
+
holder.style.height = `${box.bottom - box.top}px`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function moveGhost() {
|
|
172
|
+
if (layer.ghost) layer.ghost.style.transform = `translate(${pointer.x + 12}px, ${pointer.y + 12}px)`;
|
|
173
|
+
}
|
|
174
|
+
/** Dim the nodes being dragged where they were. */
|
|
175
|
+
function markDragged(on) {
|
|
176
|
+
const el = root();
|
|
177
|
+
if (!el) return;
|
|
178
|
+
for (const node of dragged) {
|
|
179
|
+
const item = el.querySelector(`[data-node="${CSS.escape(node.id)}"]`);
|
|
180
|
+
if (!item) continue;
|
|
181
|
+
if (on) item.dataset.dragged = "";
|
|
182
|
+
else delete item.dataset.dragged;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Each frame: scroll near the edges, then find and draw the place under the pointer. */
|
|
186
|
+
function tick() {
|
|
187
|
+
frame = 0;
|
|
188
|
+
const el = root();
|
|
189
|
+
if (!el || !source) return;
|
|
190
|
+
const rect = el.getBoundingClientRect();
|
|
191
|
+
let speed = 0;
|
|
192
|
+
if (pointer.y < rect.top + EDGE) speed = -((rect.top + EDGE - pointer.y) / EDGE);
|
|
193
|
+
else if (pointer.y > rect.bottom - EDGE) speed = (pointer.y - (rect.bottom - EDGE)) / EDGE;
|
|
194
|
+
speed = Math.max(-1, Math.min(1, speed)) * EDGE_SPEED;
|
|
195
|
+
if (speed) {
|
|
196
|
+
const before = el.scrollTop;
|
|
197
|
+
el.scrollTop += speed;
|
|
198
|
+
if (el.scrollTop !== before && performance.now() - measuredAt > 200) measure();
|
|
199
|
+
}
|
|
200
|
+
const point = contentPoint(pointer.x, pointer.y);
|
|
201
|
+
target = hit(point.x, point.y);
|
|
202
|
+
draw();
|
|
203
|
+
moveGhost();
|
|
204
|
+
if (speed) frame = requestAnimationFrame(tick);
|
|
205
|
+
}
|
|
206
|
+
const schedule = () => {
|
|
207
|
+
if (!frame) frame = requestAnimationFrame(tick);
|
|
208
|
+
};
|
|
209
|
+
function begin(next, label) {
|
|
210
|
+
source = next;
|
|
211
|
+
dragged = next.kind === "nodes" ? next.ids.map((id) => formNodePlaces(state.document).get(id)?.node).filter((node) => !!node) : [];
|
|
212
|
+
measure();
|
|
213
|
+
markDragged(true);
|
|
214
|
+
if (layer.ghost) {
|
|
215
|
+
layer.ghost.hidden = false;
|
|
216
|
+
layer.ghost.textContent = label;
|
|
217
|
+
}
|
|
218
|
+
root()?.classList.add("is-dragging");
|
|
219
|
+
document.documentElement.classList.add("dou-form-builder-dragging");
|
|
220
|
+
schedule();
|
|
221
|
+
}
|
|
222
|
+
function end(drop) {
|
|
223
|
+
cancelAnimationFrame(frame);
|
|
224
|
+
frame = 0;
|
|
225
|
+
markDragged(false);
|
|
226
|
+
const was = source;
|
|
227
|
+
const place = target;
|
|
228
|
+
source = null;
|
|
229
|
+
target = null;
|
|
230
|
+
dragged = [];
|
|
231
|
+
holders = [];
|
|
232
|
+
draw();
|
|
233
|
+
if (layer.ghost) layer.ghost.hidden = true;
|
|
234
|
+
root()?.classList.remove("is-dragging");
|
|
235
|
+
document.documentElement.classList.remove("dou-form-builder-dragging");
|
|
236
|
+
if (!drop || !was || !place) return;
|
|
237
|
+
if (was.kind === "type") state.add(was.type, {
|
|
238
|
+
parent: place.parent,
|
|
239
|
+
index: place.index
|
|
240
|
+
});
|
|
241
|
+
else state.moveTo({
|
|
242
|
+
parent: place.parent,
|
|
243
|
+
index: place.index
|
|
244
|
+
}, was.ids);
|
|
245
|
+
}
|
|
246
|
+
/** Follow a pointer after a press until it is let go. */
|
|
247
|
+
function track(event, options) {
|
|
248
|
+
cleanup?.();
|
|
249
|
+
const startX = event.clientX;
|
|
250
|
+
const startY = event.clientY;
|
|
251
|
+
const touch = event.pointerType !== "mouse";
|
|
252
|
+
const hold = touch ? holdPointer(event, root()) : null;
|
|
253
|
+
let started = false;
|
|
254
|
+
let timer = 0;
|
|
255
|
+
const launch = () => {
|
|
256
|
+
const picked = options.start();
|
|
257
|
+
if (!picked) return stopAll(false);
|
|
258
|
+
started = true;
|
|
259
|
+
hold?.grab();
|
|
260
|
+
begin(...picked);
|
|
261
|
+
};
|
|
262
|
+
if (touch && options.touchHold) timer = window.setTimeout(launch, HOLD_DELAY);
|
|
263
|
+
const onMove = (e) => {
|
|
264
|
+
if (e.pointerId !== event.pointerId) return;
|
|
265
|
+
pointer = {
|
|
266
|
+
x: e.clientX,
|
|
267
|
+
y: e.clientY
|
|
268
|
+
};
|
|
269
|
+
const distance = Math.hypot(e.clientX - startX, e.clientY - startY);
|
|
270
|
+
if (!started) {
|
|
271
|
+
if (touch) {
|
|
272
|
+
if (distance > HOLD_SLOP) stopAll(false);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (distance < DRAG_DISTANCE) return;
|
|
276
|
+
launch();
|
|
277
|
+
}
|
|
278
|
+
schedule();
|
|
279
|
+
};
|
|
280
|
+
const onUp = (e) => {
|
|
281
|
+
if (e.pointerId !== event.pointerId) return;
|
|
282
|
+
stopAll(started);
|
|
283
|
+
};
|
|
284
|
+
const onCancel = (e) => {
|
|
285
|
+
if (e.pointerId === event.pointerId) stopAll(false);
|
|
286
|
+
};
|
|
287
|
+
const onKey = (e) => {
|
|
288
|
+
if (e.key !== "Escape" || !started) return;
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
e.stopPropagation();
|
|
291
|
+
stopAll(false);
|
|
292
|
+
};
|
|
293
|
+
function stopAll(drop) {
|
|
294
|
+
window.clearTimeout(timer);
|
|
295
|
+
window.removeEventListener("pointermove", onMove);
|
|
296
|
+
window.removeEventListener("pointerup", onUp);
|
|
297
|
+
window.removeEventListener("pointercancel", onCancel);
|
|
298
|
+
window.removeEventListener("keydown", onKey, true);
|
|
299
|
+
hold?.release();
|
|
300
|
+
cleanup = null;
|
|
301
|
+
if (started) {
|
|
302
|
+
swallow = true;
|
|
303
|
+
setTimeout(() => swallow = false);
|
|
304
|
+
end(drop);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
pointer = {
|
|
308
|
+
x: startX,
|
|
309
|
+
y: startY
|
|
310
|
+
};
|
|
311
|
+
window.addEventListener("pointermove", onMove);
|
|
312
|
+
window.addEventListener("pointerup", onUp);
|
|
313
|
+
window.addEventListener("pointercancel", onCancel);
|
|
314
|
+
window.addEventListener("keydown", onKey, true);
|
|
315
|
+
cleanup = () => stopAll(false);
|
|
316
|
+
}
|
|
317
|
+
function pressNode(event, id) {
|
|
318
|
+
if (state.readonly || event.button !== 0 || event.shiftKey || event.metaKey || event.ctrlKey) return;
|
|
319
|
+
track(event, {
|
|
320
|
+
touchHold: true,
|
|
321
|
+
start: () => {
|
|
322
|
+
if (!state.selected.value.has(id)) state.select(id);
|
|
323
|
+
const ids = topmost(state.document, state.selection);
|
|
324
|
+
if (!ids.length) return null;
|
|
325
|
+
const first = formNodePlaces(state.document).get(ids[0])?.node;
|
|
326
|
+
const label = ids.length > 1 ? `${first ? titleOf(state, first) : ""} +${ids.length - 1}` : first ? titleOf(state, first) : "";
|
|
327
|
+
return [{
|
|
328
|
+
kind: "nodes",
|
|
329
|
+
ids
|
|
330
|
+
}, label];
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
function pressType(event, type, label) {
|
|
335
|
+
if (state.readonly || event.button !== 0) return;
|
|
336
|
+
if (event.pointerType === "touch") return;
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
track(event, {
|
|
339
|
+
touchHold: false,
|
|
340
|
+
start: () => [{
|
|
341
|
+
kind: "type",
|
|
342
|
+
type
|
|
343
|
+
}, label]
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
function showPlace(prefix = "") {
|
|
347
|
+
const place = places?.[placeAt];
|
|
348
|
+
if (!place) return;
|
|
349
|
+
measure();
|
|
350
|
+
const holder = holders.find((each) => each.id === place.parent);
|
|
351
|
+
if (!holder) {
|
|
352
|
+
target = null;
|
|
353
|
+
return draw();
|
|
354
|
+
}
|
|
355
|
+
const child = holder.children[place.index];
|
|
356
|
+
const prev = holder.children[place.index - 1];
|
|
357
|
+
const line = child ? {
|
|
358
|
+
left: child.left,
|
|
359
|
+
top: child.top - 6,
|
|
360
|
+
length: child.right - child.left,
|
|
361
|
+
vertical: false
|
|
362
|
+
} : prev ? {
|
|
363
|
+
left: prev.left,
|
|
364
|
+
top: prev.bottom + 6,
|
|
365
|
+
length: prev.right - prev.left,
|
|
366
|
+
vertical: false
|
|
367
|
+
} : {
|
|
368
|
+
left: holder.left,
|
|
369
|
+
top: holder.top,
|
|
370
|
+
length: holder.right - holder.left,
|
|
371
|
+
vertical: false
|
|
372
|
+
};
|
|
373
|
+
target = {
|
|
374
|
+
...place,
|
|
375
|
+
holder,
|
|
376
|
+
line
|
|
377
|
+
};
|
|
378
|
+
draw();
|
|
379
|
+
const el = root();
|
|
380
|
+
if (el) {
|
|
381
|
+
const top = line.top - el.scrollTop;
|
|
382
|
+
if (top < EDGE || top > el.clientHeight - EDGE) el.scrollTop = line.top - el.clientHeight / 2;
|
|
383
|
+
}
|
|
384
|
+
state.announce(prefix + describe(place));
|
|
385
|
+
}
|
|
386
|
+
function describe(place) {
|
|
387
|
+
const messages = state.messages;
|
|
388
|
+
const holder = place.parent === null ? null : formNodePlaces(state.document).get(place.parent)?.node;
|
|
389
|
+
const where = holder ? titleOf(state, holder) : messages.drag.top;
|
|
390
|
+
return formatMessage(messages.drag.place, {
|
|
391
|
+
where,
|
|
392
|
+
index: place.index + 1
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function pickUp() {
|
|
396
|
+
if (state.readonly) return false;
|
|
397
|
+
const ids = topmost(state.document, state.selection);
|
|
398
|
+
if (!ids.length) return false;
|
|
399
|
+
const list = dropPlaces(state.document, ids, state.shownPage);
|
|
400
|
+
if (!list.length) return false;
|
|
401
|
+
const own = formNodePlaces(state.document).get(ids[0]);
|
|
402
|
+
places = list;
|
|
403
|
+
placeAt = Math.max(0, list.findIndex((place) => place.parent === (own.parent?.id ?? null) && place.index === own.index));
|
|
404
|
+
source = {
|
|
405
|
+
kind: "nodes",
|
|
406
|
+
ids
|
|
407
|
+
};
|
|
408
|
+
dragged = ids.map((id) => formNodePlaces(state.document).get(id).node);
|
|
409
|
+
markDragged(true);
|
|
410
|
+
root()?.classList.add("is-dragging");
|
|
411
|
+
showPlace(state.messages.drag.picked);
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
function keydown(event) {
|
|
415
|
+
if (!places) return false;
|
|
416
|
+
const key = event.key;
|
|
417
|
+
if (key === "ArrowUp" || key === "ArrowLeft" || key === "ArrowDown" || key === "ArrowRight") {
|
|
418
|
+
placeAt = Math.min(Math.max(placeAt + (key === "ArrowUp" || key === "ArrowLeft" ? -1 : 1), 0), places.length - 1);
|
|
419
|
+
showPlace();
|
|
420
|
+
} else if (key === "Home" || key === "End") {
|
|
421
|
+
placeAt = key === "Home" ? 0 : places.length - 1;
|
|
422
|
+
showPlace();
|
|
423
|
+
} else if (key === "Enter" || key === " ") {
|
|
424
|
+
const place = places[placeAt];
|
|
425
|
+
const ids = source.ids;
|
|
426
|
+
places = null;
|
|
427
|
+
finishKeyboard();
|
|
428
|
+
state.moveTo(place, ids);
|
|
429
|
+
} else if (key === "Escape" || key === "Tab") {
|
|
430
|
+
places = null;
|
|
431
|
+
finishKeyboard();
|
|
432
|
+
state.announce(state.messages.drag.cancelled);
|
|
433
|
+
if (key === "Tab") return false;
|
|
434
|
+
} else return true;
|
|
435
|
+
event.preventDefault();
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
function finishKeyboard() {
|
|
439
|
+
markDragged(false);
|
|
440
|
+
source = null;
|
|
441
|
+
target = null;
|
|
442
|
+
dragged = [];
|
|
443
|
+
holders = [];
|
|
444
|
+
draw();
|
|
445
|
+
root()?.classList.remove("is-dragging");
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
pressNode,
|
|
449
|
+
pressType,
|
|
450
|
+
swallowClick: () => swallow,
|
|
451
|
+
pickUp,
|
|
452
|
+
keydown,
|
|
453
|
+
get carrying() {
|
|
454
|
+
return places !== null;
|
|
455
|
+
},
|
|
456
|
+
stop() {
|
|
457
|
+
cleanup?.();
|
|
458
|
+
places = null;
|
|
459
|
+
finishKeyboard();
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
var titleOf = (state, node) => nodeTitle(node, state.fields, state.messages);
|
|
464
|
+
export { createDragController };
|
|
465
|
+
|
|
466
|
+
//# sourceMappingURL=drag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drag.js","names":[],"sources":["../../src/builder/drag.ts"],"sourcesContent":["import { holdPointer } from 'dou-ui'\nimport type { PointerHold } from 'dou-ui'\nimport type { FormNode } from '../core/types'\nimport { formatMessage } from '../locale'\nimport type { FormFieldType } from '../fields/define'\nimport type { BuilderState } from './context'\nimport type { FormBuilderInsertPlace } from './core/commands'\nimport { dropPlaces, topmost } from './core/commands'\nimport { canContainFormNode, formNodePlaces } from './core/transaction'\nimport { nodeTitle } from './settings'\n\n/** How far a mouse moves before a press becomes a drag, in px. */\nconst DRAG_DISTANCE = 4\n/** How long a finger rests on a node before it is picked up, in ms. */\nconst HOLD_DELAY = 300\n/** How far a finger may move while resting, in px; further is a scroll. */\nconst HOLD_SLOP = 8\n/** The band at the top and bottom of the canvas that scrolls it while dragging, in px. */\nconst EDGE = 48\n/** The fastest the canvas scrolls while dragging, in px per frame. */\nconst EDGE_SPEED = 18\n\n/** A rectangle in the coordinates of the canvas's content, so scrolling does not change it. */\ninterface Box {\n left: number\n top: number\n right: number\n bottom: number\n}\n\ninterface HolderBox extends Box {\n /** The container whose children the grid holds; `null` for the top of the form. */\n id: string | null\n /** Its children on the canvas, in order. */\n children: ChildBox[]\n}\n\ninterface ChildBox extends Box {\n id: string\n}\n\n/** Where a drop goes, and how to draw it. */\nexport interface DropTarget extends FormBuilderInsertPlace {\n /** The line between nodes, in the canvas's content coordinates. */\n line: { left: number; top: number; length: number; vertical: boolean }\n /** The grid the node goes into. */\n holder: Box\n}\n\n/** What is being dragged: nodes of the canvas, or a new node of a type from the palette. */\nexport type DragSource = { kind: 'nodes'; ids: string[] } | { kind: 'type'; type: string }\n\n/** The parts the canvas draws for a drag; the controller moves them without rendering. */\nexport interface DragLayer {\n indicator: HTMLElement | null\n holder: HTMLElement | null\n ghost: HTMLElement | null\n}\n\nexport interface DragController {\n /** A press on a node of the canvas: becomes a drag after a move (mouse) or a rest (touch). */\n pressNode: (event: PointerEvent, id: string) => void\n /** A press on an entry of the palette. */\n pressType: (event: PointerEvent, type: string, label: string) => void\n /** Whether the click that ends a drag should be ignored. */\n swallowClick: () => boolean\n /** Pick up the selected nodes to move them with the keyboard. */\n pickUp: () => boolean\n /** Keys while nodes are picked up; returns whether the key was used. */\n keydown: (event: KeyboardEvent) => boolean\n /** Whether nodes are picked up with the keyboard. */\n readonly carrying: boolean\n stop: () => void\n}\n\nconst contains = (box: Box, x: number, y: number) =>\n x >= box.left && x <= box.right && y >= box.top && y <= box.bottom\n\nexport function createDragController(\n state: BuilderState,\n scroller: () => HTMLElement | null,\n layer: DragLayer,\n): DragController {\n let holders: HolderBox[] = []\n let source: DragSource | null = null\n let dragged: FormNode[] = []\n let target: DropTarget | null = null\n let pointer = { x: 0, y: 0 }\n let frame = 0\n let measuredAt = 0\n let swallow = false\n let cleanup: (() => void) | null = null\n // Keyboard: the places in reading order and the one shown.\n let places: FormBuilderInsertPlace[] | null = null\n let placeAt = 0\n\n const root = () => scroller()\n const contentPoint = (x: number, y: number) => {\n const el = root()!\n const rect = el.getBoundingClientRect()\n return { x: x - rect.left + el.scrollLeft, y: y - rect.top + el.scrollTop }\n }\n\n /** Read every grid and node of the canvas once; moving the pointer then only does math. */\n function measure() {\n const el = root()\n if (!el) return\n const base = el.getBoundingClientRect()\n const box = (node: Element): Box => {\n const rect = node.getBoundingClientRect()\n const left = rect.left - base.left + el.scrollLeft\n const top = rect.top - base.top + el.scrollTop\n return { left, top, right: left + rect.width, bottom: top + rect.height }\n }\n const hidden = new Set(dragged.map((node) => node.id))\n holders = []\n for (const grid of el.querySelectorAll<HTMLElement>('[data-holder]')) {\n // Grids inside the dragged nodes take no drops.\n const owner = grid.closest<HTMLElement>('[data-node]')\n let inside = false\n for (let up: HTMLElement | null = owner; up;) {\n if (hidden.has(up.dataset.node!)) inside = true\n up = up.parentElement?.closest<HTMLElement>('[data-node]') ?? null\n }\n if (inside) continue\n const children: ChildBox[] = []\n for (const child of grid.children)\n if (child instanceof HTMLElement && child.dataset.node)\n children.push({ id: child.dataset.node, ...box(child) })\n holders.push({ id: grid.dataset.holder || null, children, ...box(grid) })\n }\n measuredAt = performance.now()\n }\n\n /** The place under a point: the innermost grid that takes the nodes, and the gap nearest. */\n function hit(x: number, y: number): DropTarget | null {\n const map = formNodePlaces(state.document)\n const probe: FormNode[] = dragged.length\n ? dragged\n : [{ id: '', type: (source as { type: string }).type } as FormNode]\n const fits = (holder: HolderBox) => {\n const node = holder.id === null ? null : map.get(holder.id)?.node\n if (node === undefined) return false\n return probe.every((each) => canContainFormNode(node, each))\n }\n // Grids come in document order, so the last one around the point is the innermost.\n let chosen: HolderBox | undefined\n for (const holder of holders) if (contains(holder, x, y) && fits(holder)) chosen = holder\n chosen ??= holders.find((holder) => holder.id === null)\n if (!chosen) return null\n const children = chosen.children\n if (!children.length)\n return {\n parent: chosen.id,\n index: 0,\n holder: chosen,\n line: {\n left: chosen.left,\n top: chosen.top,\n length: chosen.right - chosen.left,\n vertical: false,\n },\n }\n // The child nearest the point, then which side of it.\n let best = children[0]!\n let bestIndex = 0\n let bestDistance = Infinity\n children.forEach((child, index) => {\n const dx = x < child.left ? child.left - x : x > child.right ? x - child.right : 0\n const dy = y < child.top ? child.top - y : y > child.bottom ? y - child.bottom : 0\n const distance = dx * dx + dy * dy\n if (distance < bestDistance) {\n bestDistance = distance\n best = child\n bestIndex = index\n }\n })\n const width = chosen.right - chosen.left\n const beside = best.right - best.left < width - 2 && y >= best.top && y <= best.bottom\n let after: boolean\n let line: DropTarget['line']\n if (beside) {\n after = x > (best.left + best.right) / 2\n line = {\n left: after ? best.right + 4 : best.left - 4,\n top: best.top,\n length: best.bottom - best.top,\n vertical: true,\n }\n } else {\n after = y > (best.top + best.bottom) / 2\n line = {\n left: best.left,\n top: after ? best.bottom + 6 : best.top - 6,\n length: best.right - best.left,\n vertical: false,\n }\n }\n return { parent: chosen.id, index: bestIndex + (after ? 1 : 0), holder: chosen, line }\n }\n\n function draw() {\n const { indicator, holder } = layer\n if (indicator) {\n indicator.hidden = !target\n if (target) {\n const { left, top, length, vertical } = target.line\n indicator.dataset.vertical = String(vertical)\n indicator.style.transform = `translate(${left}px, ${top}px)`\n indicator.style.setProperty('--dou-form-builder-drop-length', `${length}px`)\n }\n }\n if (holder) {\n // The top of the form shows no tint: the line is enough there.\n holder.hidden = !target || target.parent === null\n if (target && target.parent !== null) {\n const box = target.holder\n holder.style.transform = `translate(${box.left}px, ${box.top}px)`\n holder.style.width = `${box.right - box.left}px`\n holder.style.height = `${box.bottom - box.top}px`\n }\n }\n }\n\n function moveGhost() {\n if (layer.ghost)\n layer.ghost.style.transform = `translate(${pointer.x + 12}px, ${pointer.y + 12}px)`\n }\n\n /** Dim the nodes being dragged where they were. */\n function markDragged(on: boolean) {\n const el = root()\n if (!el) return\n // A data attribute, not a class: rendering the node keeps it.\n for (const node of dragged) {\n const item = el.querySelector<HTMLElement>(`[data-node=\"${CSS.escape(node.id)}\"]`)\n if (!item) continue\n if (on) item.dataset.dragged = ''\n else delete item.dataset.dragged\n }\n }\n\n /** Each frame: scroll near the edges, then find and draw the place under the pointer. */\n function tick() {\n frame = 0\n const el = root()\n if (!el || !source) return\n const rect = el.getBoundingClientRect()\n let speed = 0\n if (pointer.y < rect.top + EDGE) speed = -((rect.top + EDGE - pointer.y) / EDGE)\n else if (pointer.y > rect.bottom - EDGE) speed = (pointer.y - (rect.bottom - EDGE)) / EDGE\n speed = Math.max(-1, Math.min(1, speed)) * EDGE_SPEED\n if (speed) {\n const before = el.scrollTop\n el.scrollTop += speed\n // Fields drawn while scrolling change the layout: read it again now and then.\n if (el.scrollTop !== before && performance.now() - measuredAt > 200) measure()\n }\n const point = contentPoint(pointer.x, pointer.y)\n target = hit(point.x, point.y)\n draw()\n moveGhost()\n if (speed) frame = requestAnimationFrame(tick)\n }\n\n const schedule = () => {\n if (!frame) frame = requestAnimationFrame(tick)\n }\n\n function begin(next: DragSource, label: string) {\n source = next\n dragged =\n next.kind === 'nodes'\n ? next.ids\n .map((id) => formNodePlaces(state.document).get(id)?.node)\n .filter((node): node is FormNode => !!node)\n : []\n measure()\n markDragged(true)\n if (layer.ghost) {\n layer.ghost.hidden = false\n layer.ghost.textContent = label\n }\n root()?.classList.add('is-dragging')\n document.documentElement.classList.add('dou-form-builder-dragging')\n schedule()\n }\n\n function end(drop: boolean) {\n cancelAnimationFrame(frame)\n frame = 0\n markDragged(false)\n const was = source\n const place = target\n source = null\n target = null\n dragged = []\n holders = []\n draw()\n if (layer.ghost) layer.ghost.hidden = true\n root()?.classList.remove('is-dragging')\n document.documentElement.classList.remove('dou-form-builder-dragging')\n if (!drop || !was || !place) return\n if (was.kind === 'type') state.add(was.type, { parent: place.parent, index: place.index })\n else state.moveTo({ parent: place.parent, index: place.index }, was.ids)\n }\n\n /** Follow a pointer after a press until it is let go. */\n function track(\n event: PointerEvent,\n options: { touchHold: boolean; start: () => [DragSource, string] | null },\n ) {\n cleanup?.()\n const startX = event.clientX\n const startY = event.clientY\n const touch = event.pointerType !== 'mouse'\n const hold: PointerHold | null = touch ? holdPointer(event, root()) : null\n let started = false\n let timer = 0\n\n const launch = () => {\n const picked = options.start()\n if (!picked) return stopAll(false)\n started = true\n hold?.grab()\n begin(...picked)\n }\n if (touch && options.touchHold) timer = window.setTimeout(launch, HOLD_DELAY)\n\n const onMove = (e: PointerEvent) => {\n if (e.pointerId !== event.pointerId) return\n pointer = { x: e.clientX, y: e.clientY }\n const distance = Math.hypot(e.clientX - startX, e.clientY - startY)\n if (!started) {\n if (touch) {\n // A finger that moves before the rest is over scrolls the page instead.\n if (distance > HOLD_SLOP) stopAll(false)\n return\n }\n if (distance < DRAG_DISTANCE) return\n launch()\n }\n schedule()\n }\n const onUp = (e: PointerEvent) => {\n if (e.pointerId !== event.pointerId) return\n stopAll(started)\n }\n const onCancel = (e: PointerEvent) => {\n if (e.pointerId === event.pointerId) stopAll(false)\n }\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || !started) return\n e.preventDefault()\n e.stopPropagation()\n stopAll(false)\n }\n function stopAll(drop: boolean) {\n window.clearTimeout(timer)\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onCancel)\n window.removeEventListener('keydown', onKey, true)\n hold?.release()\n cleanup = null\n if (started) {\n // The click that ends the drag is not a press on what is under it.\n swallow = true\n setTimeout(() => (swallow = false))\n end(drop)\n }\n }\n pointer = { x: startX, y: startY }\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onCancel)\n window.addEventListener('keydown', onKey, true)\n cleanup = () => stopAll(false)\n }\n\n function pressNode(event: PointerEvent, id: string) {\n if (state.readonly || event.button !== 0 || event.shiftKey || event.metaKey || event.ctrlKey)\n return\n track(event, {\n touchHold: true,\n start: () => {\n // Dragging an unselected node moves it alone; a selected one takes the selection along.\n if (!state.selected.value.has(id)) state.select(id)\n const ids = topmost(state.document, state.selection)\n if (!ids.length) return null\n const map = formNodePlaces(state.document)\n const first = map.get(ids[0]!)?.node\n const label =\n ids.length > 1\n ? `${first ? titleOf(state, first) : ''} +${ids.length - 1}`\n : first\n ? titleOf(state, first)\n : ''\n return [{ kind: 'nodes', ids }, label]\n },\n })\n }\n\n function pressType(event: PointerEvent, type: string, label: string) {\n if (state.readonly || event.button !== 0) return\n // On touch the palette scrolls and a tap adds: only a mouse or a pen drags from it.\n if (event.pointerType === 'touch') return\n // No text selection while dragging from the palette; the click still adds.\n event.preventDefault()\n track(event, { touchHold: false, start: () => [{ kind: 'type', type }, label] })\n }\n\n // ---- Keyboard ----\n\n function showPlace(prefix = '') {\n const place = places?.[placeAt]\n if (!place) return\n measure()\n const holder = holders.find((each) => each.id === place.parent)\n if (!holder) {\n target = null\n return draw()\n }\n const child = holder.children[place.index]\n const prev = holder.children[place.index - 1]\n const line: DropTarget['line'] = child\n ? { left: child.left, top: child.top - 6, length: child.right - child.left, vertical: false }\n : prev\n ? { left: prev.left, top: prev.bottom + 6, length: prev.right - prev.left, vertical: false }\n : {\n left: holder.left,\n top: holder.top,\n length: holder.right - holder.left,\n vertical: false,\n }\n target = { ...place, holder, line }\n draw()\n const el = root()\n if (el) {\n const top = line.top - el.scrollTop\n if (top < EDGE || top > el.clientHeight - EDGE) el.scrollTop = line.top - el.clientHeight / 2\n }\n state.announce(prefix + describe(place))\n }\n\n function describe(place: FormBuilderInsertPlace) {\n const messages = state.messages\n const holder =\n place.parent === null ? null : formNodePlaces(state.document).get(place.parent)?.node\n const where = holder ? titleOf(state, holder) : messages.drag.top\n return formatMessage(messages.drag.place, { where, index: place.index + 1 })\n }\n\n function pickUp() {\n if (state.readonly) return false\n const ids = topmost(state.document, state.selection)\n if (!ids.length) return false\n const list = dropPlaces(state.document, ids, state.shownPage)\n if (!list.length) return false\n const own = formNodePlaces(state.document).get(ids[0]!)!\n places = list\n placeAt = Math.max(\n 0,\n list.findIndex(\n (place) => place.parent === (own.parent?.id ?? null) && place.index === own.index,\n ),\n )\n source = { kind: 'nodes', ids }\n dragged = ids.map((id) => formNodePlaces(state.document).get(id)!.node)\n markDragged(true)\n root()?.classList.add('is-dragging')\n showPlace(state.messages.drag.picked)\n return true\n }\n\n function keydown(event: KeyboardEvent) {\n if (!places) return false\n const key = event.key\n if (key === 'ArrowUp' || key === 'ArrowLeft' || key === 'ArrowDown' || key === 'ArrowRight') {\n const delta = key === 'ArrowUp' || key === 'ArrowLeft' ? -1 : 1\n placeAt = Math.min(Math.max(placeAt + delta, 0), places.length - 1)\n showPlace()\n } else if (key === 'Home' || key === 'End') {\n placeAt = key === 'Home' ? 0 : places.length - 1\n showPlace()\n } else if (key === 'Enter' || key === ' ') {\n const place = places[placeAt]!\n const ids = (source as { ids: string[] }).ids\n places = null\n finishKeyboard()\n state.moveTo(place, ids)\n } else if (key === 'Escape' || key === 'Tab') {\n places = null\n finishKeyboard()\n state.announce(state.messages.drag.cancelled)\n if (key === 'Tab') return false\n } else return true\n event.preventDefault()\n return true\n }\n\n function finishKeyboard() {\n markDragged(false)\n source = null\n target = null\n dragged = []\n holders = []\n draw()\n root()?.classList.remove('is-dragging')\n }\n\n return {\n pressNode,\n pressType,\n swallowClick: () => swallow,\n pickUp,\n keydown,\n get carrying() {\n return places !== null\n },\n stop() {\n cleanup?.()\n places = null\n finishKeyboard()\n },\n }\n}\n\nconst titleOf = (state: BuilderState, node: FormNode) =>\n nodeTitle(node, state.fields as Map<string, FormFieldType>, state.messages)\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useBuilderState } from "./context.js";
|
|
2
|
+
import expression_input_default from "./expression-input.js";
|
|
3
|
+
import { defineComponent, h } from "vue";
|
|
4
|
+
// src/builder/expression-box.ts
|
|
5
|
+
var ExpressionBox = defineComponent({
|
|
6
|
+
name: "DouFormBuilderExpressionBox",
|
|
7
|
+
inheritAttrs: false,
|
|
8
|
+
setup(_, { attrs }) {
|
|
9
|
+
const state = useBuilderState();
|
|
10
|
+
return () => h(state.extensions().expressionInput ?? expression_input_default, state.readonly ? {
|
|
11
|
+
...attrs,
|
|
12
|
+
readonly: true,
|
|
13
|
+
placeholder: "—"
|
|
14
|
+
} : attrs);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
export { ExpressionBox as default };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=expression-box.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expression-box.js","names":["ExpressionInput"],"sources":["../../src/builder/expression-box.ts"],"sourcesContent":["import { defineComponent, h } from 'vue'\nimport { useBuilderState } from './context'\nimport ExpressionInput from './expression-input.vue'\n\nconst ExpressionBox = defineComponent({\n name: 'DouFormBuilderExpressionBox',\n inheritAttrs: false,\n setup(_, { attrs }) {\n const state = useBuilderState()\n return () =>\n h(\n state.extensions().expressionInput ?? ExpressionInput,\n // Read-only, an example in the box would read as a value: an empty one shows a dash.\n state.readonly ? { ...attrs, readonly: true, placeholder: '—' } : attrs,\n )\n },\n})\n\n/**\n * The text box for an expression: an extension's `expressionInput`, else the built-in one.\n * It is read-only while the builder is. Everything given to it goes on to that box, so it\n * takes what the built-in one takes.\n */\nexport default ExpressionBox as unknown as typeof ExpressionInput\n"],"mappings":";;;;AAIA,IAAM,gBAAgB,gBAAgB;CACpC,MAAM;CACN,cAAc;CACd,MAAM,GAAG,EAAE,SAAS;EAClB,MAAM,QAAQ,gBAAgB;EAC9B,aACE,EACE,MAAM,WAAW,CAAC,CAAC,mBAAmBA,0BAEtC,MAAM,WAAW;GAAE,GAAG;GAAO,UAAU;GAAM,aAAa;EAAI,IAAI,KACpE;CACJ;AACF,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import expression_input_vue_vue_type_script_setup_true_lang_default from "./expression-input.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/builder/expression-input.vue
|
|
3
|
+
var expression_input_default = expression_input_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { expression_input_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=expression-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expression-input.js","names":["_sfc_main"],"sources":["../../src/builder/expression-input.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, useId, useTemplateRef } from 'vue'\nimport { DouAutocomplete } from 'dou-ui'\nimport type { AutocompleteOption } from 'dou-ui'\nimport { builtinFunctions, checkExpression } from '../core/expression'\nimport { formatMessage } from '../locale'\nimport { completeExpression } from './completion'\nimport type { Completion } from './completion'\nimport { useBuilderState } from './context'\n\n/**\n * Expression text with completions of field paths and functions, and the\n * first problem of the expression shown under it as it is typed.\n */\ndefineOptions({ name: 'DouFormBuilderExpression', inheritAttrs: false })\n\ninterface Name {\n path: string\n label?: string\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string | null\n /** The paths `values` can read. */\n names?: Name[]\n /** The paths `item` can read; left out where there is no list item. */\n itemNames?: Name[]\n /** Whether the expression can read `value`, as a check can. */\n readsValue?: boolean\n placeholder?: string\n disabled?: boolean\n /** Shown and selectable, but not changed; also when the form around is read-only. */\n readonly?: boolean\n }>(),\n { modelValue: '', names: () => [], itemNames: undefined, placeholder: undefined },\n)\nconst emit = defineEmits<{ 'update:modelValue': [value: string] }>()\n\nconst state = useBuilderState()\nconst root = useTemplateRef<HTMLElement>('root')\nconst errorId = `dou-form-builder-expression-${useId()}`\n\nconst text = computed({\n get: () => props.modelValue ?? '',\n set: (value: string) => emit('update:modelValue', value),\n})\n\nconst functions = computed(() => [\n ...new Set([...Object.keys(builtinFunctions), ...state.functionNames.value]),\n])\n\nconst problem = computed(() => {\n const source = text.value\n if (!source.trim()) return undefined\n const found = checkExpression(source, {\n fields: props.names.map((name) => name.path),\n itemFields: props.itemNames ? props.itemNames.map((name) => name.path) : [],\n functions: state.functionNames.value,\n })[0]\n if (!found) return undefined\n const words = state.messages.expression.problems\n return formatMessage(words[found.code] ?? found.message, { text: found.text })\n})\n\nconst input = () => root.value?.querySelector('textarea') ?? null\n\n/** The completions of the last fetch, by the text each one makes. */\nlet offered = new Map<string, Completion>()\n\nfunction fetch(query: string): AutocompleteOption[] {\n const element = input()\n const caret = element?.selectionStart ?? query.length\n const words = state.messages.expression\n const list = completeExpression(\n query,\n caret,\n {\n names: props.names,\n itemNames: props.itemNames,\n value: props.readsValue,\n functions: functions.value,\n },\n { values: words.values, item: words.item, value: words.value },\n )\n offered = new Map(list.map((completion) => [completion.text, completion]))\n return list.map((completion) => ({\n value: completion.text,\n label: completion.label,\n description: completion.description,\n }))\n}\n\nasync function onSelect(option: AutocompleteOption) {\n const completion = offered.get(option.value)\n await nextTick()\n const element = input()\n if (!completion || !element) return\n element.focus()\n element.setSelectionRange(completion.caret, completion.caret)\n}\n</script>\n\n<template>\n <div ref=\"root\" class=\"dou-form-builder__expression\">\n <DouAutocomplete\n v-model=\"text\"\n v-bind=\"$attrs\"\n :fetch=\"fetch\"\n :debounce=\"0\"\n :highlight=\"false\"\n multiline\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :invalid=\"!!problem\"\n :placeholder=\"placeholder\"\n :aria-invalid=\"problem ? 'true' : undefined\"\n :aria-describedby=\"problem ? errorId : undefined\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n class=\"dou-form-builder__expression-input\"\n @select=\"onSelect\"\n />\n <div v-if=\"problem\" :id=\"errorId\" class=\"dou-form-builder__expression-error\">\n {{ problem }}\n </div>\n </div>\n</template>\n"],"mappings":""}
|