@n-uf/placement 26.11.11 → 26.11.13
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/CHANGELOG.md +56 -0
- package/README.md +158 -0
- package/dist/desk.d.ts +2 -1
- package/dist/panels.d.ts +3 -2
- package/dist/stack.d.ts +351 -0
- package/dist/stack.js +491 -0
- package/dist/stack.js.map +1 -0
- package/dist/target-D3PiLg44.d.ts +17 -0
- package/dist/{targets-B_U2Zf7x.d.ts → targets-B6eLC-52.d.ts} +2 -15
- package/dist/targets.d.ts +2 -1
- package/package.json +5 -1
package/dist/stack.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clampInto,
|
|
3
|
+
rect
|
|
4
|
+
} from "./chunk-QMTNB2C3.js";
|
|
5
|
+
|
|
6
|
+
// src/stack/model.ts
|
|
7
|
+
var DEFAULT_RAIL = 30;
|
|
8
|
+
var DEFAULT_MENU = 28;
|
|
9
|
+
var finite = (x, fallback) => Number.isFinite(x) ? x : fallback;
|
|
10
|
+
function limitsOf(input) {
|
|
11
|
+
const min = Math.max(0, finite(input.cross.min, 0));
|
|
12
|
+
const max = Number.isNaN(input.cross.max) ? Infinity : input.cross.max;
|
|
13
|
+
return { min, max: Math.max(min, max), rail: Math.max(0, finite(input.cross.rail ?? DEFAULT_RAIL, DEFAULT_RAIL)) };
|
|
14
|
+
}
|
|
15
|
+
var menuMain = (input) => Math.max(0, finite(input.menu?.main ?? DEFAULT_MENU, DEFAULT_MENU));
|
|
16
|
+
var formSetsCross = (f) => f.setsCross ?? f.kind === "open";
|
|
17
|
+
function withItem(input, item, at) {
|
|
18
|
+
const k = Math.min(Math.max(0, Math.floor(at)), input.items.length);
|
|
19
|
+
return { ...input, items: [...input.items.slice(0, k), item, ...input.items.slice(k)] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/stack/layout.ts
|
|
23
|
+
var EPS = 1e-6;
|
|
24
|
+
var isMenu = (f) => f.kind === "menu";
|
|
25
|
+
var finite2 = (x, fallback = 0) => Number.isFinite(x) ? x : fallback;
|
|
26
|
+
var mainOf = (f, which) => isMenu(f) ? 0 : Math.max(0, finite2(f.main[which]));
|
|
27
|
+
var crossMin = (f) => finite2(f.cross.min ?? f.cross.natural);
|
|
28
|
+
function victim(items, at) {
|
|
29
|
+
let best = -1;
|
|
30
|
+
for (let i = 0; i < items.length; i++) {
|
|
31
|
+
if (at[i] + 1 >= items[i].forms.length) continue;
|
|
32
|
+
if (best < 0) {
|
|
33
|
+
best = i;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const a = items[i];
|
|
37
|
+
const b = items[best];
|
|
38
|
+
const ka = a.kept ? 1 : 0;
|
|
39
|
+
const kb = b.kept ? 1 : 0;
|
|
40
|
+
if (ka !== kb ? ka < kb : a.used !== b.used ? a.used < b.used : true) best = i;
|
|
41
|
+
}
|
|
42
|
+
return best;
|
|
43
|
+
}
|
|
44
|
+
function fitStack(input) {
|
|
45
|
+
const length = Math.max(0, finite2(input.length));
|
|
46
|
+
const lim = limitsOf(input);
|
|
47
|
+
const items = input.items.filter((it) => it.forms.length > 0);
|
|
48
|
+
const start = items.map((it) => Math.min(Math.max(0, Math.floor(finite2(it.from ?? 0))), it.forms.length - 1));
|
|
49
|
+
const at = [...start];
|
|
50
|
+
const form = (i) => items[i].forms[at[i]];
|
|
51
|
+
const idx = items.map((_, i) => i);
|
|
52
|
+
const firstStep = [];
|
|
53
|
+
const menuOrder = [];
|
|
54
|
+
const step = (i) => {
|
|
55
|
+
if (!firstStep.includes(i)) firstStep.push(i);
|
|
56
|
+
at[i]++;
|
|
57
|
+
if (isMenu(form(i)) && !menuOrder.includes(i)) menuOrder.push(i);
|
|
58
|
+
};
|
|
59
|
+
for (const i of idx) if (isMenu(form(i))) menuOrder.push(i);
|
|
60
|
+
const crossOf = () => {
|
|
61
|
+
const setters = idx.filter((i) => !isMenu(form(i)) && formSetsCross(form(i)));
|
|
62
|
+
if (setters.length === 0) return lim.rail;
|
|
63
|
+
const want = Math.max(...setters.map((i) => finite2(form(i).cross.natural)));
|
|
64
|
+
return Math.min(Math.max(want, lim.min), lim.max);
|
|
65
|
+
};
|
|
66
|
+
const inMenu = () => idx.some((i) => isMenu(form(i)));
|
|
67
|
+
const minSum = () => idx.reduce((s, i) => s + mainOf(form(i), "min"), 0) + (inMenu() ? menuMain(input) : 0);
|
|
68
|
+
const canStep = (i) => at[i] + 1 < items[i].forms.length;
|
|
69
|
+
const tooWide = (i, cross2) => !isMenu(form(i)) && !formSetsCross(form(i)) && crossMin(form(i)) > cross2 + EPS;
|
|
70
|
+
let cross = crossOf();
|
|
71
|
+
let overflow = 0;
|
|
72
|
+
const guard = items.reduce((s, it) => s + it.forms.length, 0) + 1;
|
|
73
|
+
for (let n = 0; n <= guard; n++) {
|
|
74
|
+
cross = crossOf();
|
|
75
|
+
let stepped = false;
|
|
76
|
+
for (const i of idx) {
|
|
77
|
+
if (tooWide(i, cross) && canStep(i) && !isMenu(items[i].forms[at[i] + 1])) {
|
|
78
|
+
step(i);
|
|
79
|
+
stepped = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (stepped) continue;
|
|
83
|
+
const over = minSum() - length;
|
|
84
|
+
if (over <= EPS) break;
|
|
85
|
+
const v = victim(items, at);
|
|
86
|
+
if (v < 0) {
|
|
87
|
+
overflow = over;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
const intoMenu = isMenu(items[v].forms[at[v] + 1]);
|
|
91
|
+
const gain = mainOf(form(v), "min") - (inMenu() ? 0 : menuMain(input));
|
|
92
|
+
if (intoMenu && gain < 0 && idx.filter(canStep).length === 1) {
|
|
93
|
+
overflow = over;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
step(v);
|
|
97
|
+
}
|
|
98
|
+
cross = crossOf();
|
|
99
|
+
const menuSize = inMenu() ? menuMain(input) : 0;
|
|
100
|
+
const avail = Math.max(0, length - menuSize);
|
|
101
|
+
const drawn = idx.filter((i) => !isMenu(form(i)));
|
|
102
|
+
const min = (i) => mainOf(form(i), "min");
|
|
103
|
+
const nat = (i) => Math.max(min(i), mainOf(form(i), "natural"));
|
|
104
|
+
const sumMin = drawn.reduce((s, i) => s + min(i), 0);
|
|
105
|
+
const sumNat = drawn.reduce((s, i) => s + nat(i), 0);
|
|
106
|
+
const size = /* @__PURE__ */ new Map();
|
|
107
|
+
if (sumNat <= avail + EPS) {
|
|
108
|
+
const growers = drawn.filter((i) => form(i).main.grows);
|
|
109
|
+
const spare = Math.max(0, avail - sumNat);
|
|
110
|
+
const weight = growers.reduce((s, i) => s + nat(i), 0);
|
|
111
|
+
for (const i of drawn) {
|
|
112
|
+
const share = growers.includes(i) ? weight > EPS ? nat(i) / weight : 1 / growers.length : 0;
|
|
113
|
+
size.set(i, nat(i) + spare * share);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
const t = avail > sumMin && sumNat > sumMin ? (avail - sumMin) / (sumNat - sumMin) : 0;
|
|
117
|
+
for (const i of drawn) size.set(i, min(i) + (nat(i) - min(i)) * t);
|
|
118
|
+
}
|
|
119
|
+
const rectAt = (from, main) => input.axis === "column" ? { x: 0, y: from, width: cross, height: main } : { x: from, y: 0, width: main, height: cross };
|
|
120
|
+
const rects = /* @__PURE__ */ new Map();
|
|
121
|
+
let cursor = 0;
|
|
122
|
+
for (const i of drawn) {
|
|
123
|
+
const s = size.get(i);
|
|
124
|
+
rects.set(i, rectAt(cursor, s));
|
|
125
|
+
cursor += s;
|
|
126
|
+
}
|
|
127
|
+
const menuRect = rectAt(Math.max(cursor, length - menuSize), menuSize);
|
|
128
|
+
const inside = idx.filter((i) => isMenu(form(i)));
|
|
129
|
+
const menu = inside.length > 0 ? { rect: menuRect, items: inside.map((i) => items[i].id) } : null;
|
|
130
|
+
const placed = idx.map((i) => ({ id: items[i].id, form: at[i], kind: form(i).kind, rect: isMenu(form(i)) ? menuRect : rects.get(i) }));
|
|
131
|
+
const notes = [];
|
|
132
|
+
for (const it of input.items) if (it.forms.length === 0) notes.push({ code: "no-forms", id: it.id });
|
|
133
|
+
for (const i of firstStep) if (at[i] > start[i]) notes.push({ code: "form-down", id: items[i].id, from: start[i], to: at[i] });
|
|
134
|
+
for (const i of menuOrder) if (isMenu(form(i))) notes.push({ code: "to-menu", id: items[i].id });
|
|
135
|
+
for (const i of idx) if (tooWide(i, cross)) notes.push({ code: "too-wide", id: items[i].id });
|
|
136
|
+
if (overflow > EPS) notes.push({ code: "overflow", over: overflow });
|
|
137
|
+
return { axis: input.axis, length, cross, items: placed, menu, notes };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/stack/targets.ts
|
|
141
|
+
function bounds(input, fit) {
|
|
142
|
+
const column = input.axis === "column";
|
|
143
|
+
const start = (r) => column ? r.y : r.x;
|
|
144
|
+
const size = (r) => column ? r.height : r.width;
|
|
145
|
+
const placed = new Map(fit.items.map((p) => [p.id, p]));
|
|
146
|
+
const b = [0];
|
|
147
|
+
let end = 0;
|
|
148
|
+
for (const it of input.items) {
|
|
149
|
+
const p = placed.get(it.id);
|
|
150
|
+
if (!p || p.kind === "menu") {
|
|
151
|
+
b.push(end);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
b.push(start(p.rect) + size(p.rect) / 2);
|
|
155
|
+
end = start(p.rect) + size(p.rect);
|
|
156
|
+
}
|
|
157
|
+
b.push(fit.length);
|
|
158
|
+
for (let i = 1; i < b.length; i++) b[i] = Math.min(Math.max(b[i], b[i - 1]), fit.length);
|
|
159
|
+
return b;
|
|
160
|
+
}
|
|
161
|
+
function stackTargets(input, dragged) {
|
|
162
|
+
if (dragged.forms.length === 0) return [];
|
|
163
|
+
const rest = { ...input, items: input.items.filter((it) => it.id !== dragged.id) };
|
|
164
|
+
const base = fitStack(rest);
|
|
165
|
+
const b = bounds(rest, base);
|
|
166
|
+
const column = rest.axis === "column";
|
|
167
|
+
const newest = rest.items.reduce((m, it) => Math.max(m, it.used), dragged.used) + 1;
|
|
168
|
+
const item = { ...dragged, used: newest };
|
|
169
|
+
const out = [];
|
|
170
|
+
for (let k = 0; k <= rest.items.length; k++) {
|
|
171
|
+
const solved = withItem(rest, item, k);
|
|
172
|
+
const fit = fitStack(solved);
|
|
173
|
+
const slot = fit.items.find((p) => p.id === dragged.id).rect;
|
|
174
|
+
const from = b[k];
|
|
175
|
+
const to = b[k + 1];
|
|
176
|
+
const across = Math.max(base.cross, fit.cross);
|
|
177
|
+
out.push({
|
|
178
|
+
id: `gap:${k}`,
|
|
179
|
+
priority: 2,
|
|
180
|
+
item: dragged.id,
|
|
181
|
+
place: { kind: "gap", at: k },
|
|
182
|
+
hit: column ? rect(0, from, across, to - from) : rect(from, 0, to - from, across),
|
|
183
|
+
slot,
|
|
184
|
+
marker: column ? rect(slot.x, slot.y, slot.width, 0) : rect(slot.x, slot.y, 0, slot.height),
|
|
185
|
+
input: solved,
|
|
186
|
+
fit
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/stack/store.ts
|
|
193
|
+
var createStack = (order) => ({ order: [...order], kept: [], closed: [], used: [...order], forms: {} });
|
|
194
|
+
var isForm = (f) => typeof f === "number" && Number.isInteger(f) && f >= 1;
|
|
195
|
+
var uniq = (ids, order) => [...new Set(ids)].filter((id) => order.includes(id));
|
|
196
|
+
var uniqLast = (ids, order) => ids.filter((id, i) => order.includes(id) && ids.lastIndexOf(id) === i);
|
|
197
|
+
var noForms = () => /* @__PURE__ */ Object.create(null);
|
|
198
|
+
function storeStack(state) {
|
|
199
|
+
const forms = noForms();
|
|
200
|
+
for (const id of state.order) {
|
|
201
|
+
const f = state.forms[id];
|
|
202
|
+
if (id !== "__proto__" && Object.hasOwn(state.forms, id) && isForm(f)) forms[id] = f;
|
|
203
|
+
}
|
|
204
|
+
return { v: 1, order: [...state.order], kept: uniq(state.kept, state.order), closed: uniq(state.closed, state.order), used: uniqLast(state.used, state.order), forms };
|
|
205
|
+
}
|
|
206
|
+
var isObject = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
|
|
207
|
+
function readStack(data, known) {
|
|
208
|
+
try {
|
|
209
|
+
return read(data, known);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
return { ok: false, error: `unreadable: ${e instanceof Error ? e.message : typeof e}` };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
var versionWords = (v) => typeof v === "number" ? String(v) : typeof v === "string" ? JSON.stringify(v.length > 24 ? `${v.slice(0, 24)}\u2026` : v) : typeof v;
|
|
215
|
+
function read(data, known) {
|
|
216
|
+
if (!isObject(data)) return { ok: false, error: "not an object" };
|
|
217
|
+
if (data.v !== 1) return { ok: false, error: `unknown version ${versionWords(data.v)} (this reads version 1)` };
|
|
218
|
+
if (!Array.isArray(data.order)) return { ok: false, error: "order is missing" };
|
|
219
|
+
const dropped = [];
|
|
220
|
+
const drop = (what) => {
|
|
221
|
+
if (!dropped.includes(what)) dropped.push(what);
|
|
222
|
+
};
|
|
223
|
+
for (const k of Object.keys(data)) if (!["v", "order", "kept", "closed", "used", "forms"].includes(k)) drop(k);
|
|
224
|
+
const order = [];
|
|
225
|
+
for (const x of data.order) {
|
|
226
|
+
if (typeof x !== "string") drop(`a ${typeof x} where an id goes`);
|
|
227
|
+
else if (known && !known.includes(x) || order.includes(x)) drop(x);
|
|
228
|
+
else order.push(x);
|
|
229
|
+
}
|
|
230
|
+
const list = (key) => {
|
|
231
|
+
const out = [];
|
|
232
|
+
const x = data[key];
|
|
233
|
+
if (x === void 0) return out;
|
|
234
|
+
if (!Array.isArray(x)) {
|
|
235
|
+
drop(key);
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
for (const id of x) {
|
|
239
|
+
if (typeof id === "string" && order.includes(id)) {
|
|
240
|
+
if (key === "used" && out.includes(id)) out.splice(out.indexOf(id), 1);
|
|
241
|
+
if (!out.includes(id)) out.push(id);
|
|
242
|
+
} else drop(typeof id === "string" ? `${key}:${id}` : `a ${typeof id} in ${key}`);
|
|
243
|
+
}
|
|
244
|
+
return out;
|
|
245
|
+
};
|
|
246
|
+
const kept = list("kept");
|
|
247
|
+
const closed = list("closed");
|
|
248
|
+
const used = list("used");
|
|
249
|
+
const forms = noForms();
|
|
250
|
+
if (data.forms !== void 0) {
|
|
251
|
+
if (!isObject(data.forms)) drop("forms");
|
|
252
|
+
else {
|
|
253
|
+
for (const id of Object.keys(data.forms)) {
|
|
254
|
+
const f = data.forms[id];
|
|
255
|
+
if (id === "__proto__" || !Object.hasOwn(data.forms, id) || !order.includes(id) || !isForm(f)) drop(`forms:${id}`);
|
|
256
|
+
else forms[id] = f;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { ok: true, state: { order, kept, closed, used, forms }, dropped };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/stack/floats.ts
|
|
264
|
+
var EPS2 = 1e-3;
|
|
265
|
+
var finite3 = (x, fallback) => Number.isFinite(x) ? x : fallback;
|
|
266
|
+
var overlaps = (a, b) => Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x) > EPS2 && Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y) > EPS2;
|
|
267
|
+
var shared = (a, b) => Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
|
|
268
|
+
var clean = (r, at) => rect(finite3(r.x, at.x), finite3(r.y, at.y), Math.max(0, finite3(r.width, 0)), Math.max(0, finite3(r.height, 0)));
|
|
269
|
+
var WORK = 3e6;
|
|
270
|
+
var COVERED_PLACES = 2048;
|
|
271
|
+
var Nearest = class {
|
|
272
|
+
h = [];
|
|
273
|
+
less(a, b) {
|
|
274
|
+
return a[2] !== b[2] ? a[2] < b[2] : a[0] !== b[0] ? a[0] < b[0] : a[1] < b[1];
|
|
275
|
+
}
|
|
276
|
+
get size() {
|
|
277
|
+
return this.h.length;
|
|
278
|
+
}
|
|
279
|
+
push(t) {
|
|
280
|
+
const h = this.h;
|
|
281
|
+
let i = h.push(t) - 1;
|
|
282
|
+
while (i > 0) {
|
|
283
|
+
const p = i - 1 >> 1;
|
|
284
|
+
if (!this.less(h[i], h[p])) break;
|
|
285
|
+
[h[i], h[p]] = [h[p], h[i]];
|
|
286
|
+
i = p;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
pop() {
|
|
290
|
+
const h = this.h;
|
|
291
|
+
const top = h[0];
|
|
292
|
+
const last = h.pop();
|
|
293
|
+
if (h.length > 0) {
|
|
294
|
+
h[0] = last;
|
|
295
|
+
let i = 0;
|
|
296
|
+
for (; ; ) {
|
|
297
|
+
const l = 2 * i + 1;
|
|
298
|
+
const r = l + 1;
|
|
299
|
+
let m = i;
|
|
300
|
+
if (l < h.length && this.less(h[l], h[m])) m = l;
|
|
301
|
+
if (r < h.length && this.less(h[r], h[m])) m = r;
|
|
302
|
+
if (m === i) break;
|
|
303
|
+
[h[i], h[m]] = [h[m], h[i]];
|
|
304
|
+
i = m;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return top;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
function* byDistance(xs, ys, fx, fy) {
|
|
311
|
+
const sx = [...xs].sort((a, b) => Math.abs(a - fx) - Math.abs(b - fx) || a - b);
|
|
312
|
+
const sy = [...ys].sort((a, b) => Math.abs(a - fy) - Math.abs(b - fy) || a - b);
|
|
313
|
+
const q = new Nearest();
|
|
314
|
+
const d = (i, j) => Math.hypot(sx[i] - fx, sy[j] - fy);
|
|
315
|
+
if (sy.length > 0) for (let i = 0; i < sx.length; i++) q.push([i, 0, d(i, 0)]);
|
|
316
|
+
while (q.size > 0) {
|
|
317
|
+
const [i, j, far] = q.pop();
|
|
318
|
+
yield { x: sx[i], y: sy[j], far };
|
|
319
|
+
if (j + 1 < sy.length) q.push([i, j + 1, d(i, j + 1)]);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function fitFloat(want, stage, avoid = []) {
|
|
323
|
+
const area = clean(stage, rect(0, 0, 0, 0));
|
|
324
|
+
const r = clean(want, area);
|
|
325
|
+
const inside = clampInto(r, area);
|
|
326
|
+
const clamped = inside.x !== want.x || inside.y !== want.y;
|
|
327
|
+
const reach = rect(area.x, area.y, Math.max(area.width, r.width), Math.max(area.height, r.height));
|
|
328
|
+
const near = [];
|
|
329
|
+
avoid.forEach((a, index) => {
|
|
330
|
+
const at = clean(a, area);
|
|
331
|
+
if (at.width > EPS2 && at.height > EPS2 && overlaps(at, reach)) near.push({ at, index });
|
|
332
|
+
});
|
|
333
|
+
const under = (cand) => near.filter((n) => overlaps(cand, n.at)).map((n) => n.index);
|
|
334
|
+
const blocked = (cand) => near.some((n) => overlaps(cand, n.at));
|
|
335
|
+
if (!blocked(inside)) return { rect: inside, clamped, snapped: false, covers: [], searchCapped: false };
|
|
336
|
+
const lo = (v, from, to) => Math.min(Math.max(v, from), Math.max(from, to));
|
|
337
|
+
const xs = [inside.x, area.x, area.x + area.width - r.width];
|
|
338
|
+
const ys = [inside.y, area.y, area.y + area.height - r.height];
|
|
339
|
+
for (const { at } of near) {
|
|
340
|
+
xs.push(at.x - r.width, at.x + at.width);
|
|
341
|
+
ys.push(at.y - r.height, at.y + at.height);
|
|
342
|
+
}
|
|
343
|
+
const places = (list, from, size, extent) => [...new Set(list.map((v) => lo(v, from, from + extent - size)))];
|
|
344
|
+
const px = places(xs, area.x, r.width, area.width);
|
|
345
|
+
const py = places(ys, area.y, r.height, area.height);
|
|
346
|
+
const ylo = area.y;
|
|
347
|
+
const yhi = Math.max(area.y, area.y + area.height - r.height);
|
|
348
|
+
const byX = [...px].sort((a, b) => Math.abs(a - inside.x) - Math.abs(b - inside.x) || a - b);
|
|
349
|
+
let clear = null;
|
|
350
|
+
let work = 0;
|
|
351
|
+
let searchCapped = false;
|
|
352
|
+
for (const x of byX) {
|
|
353
|
+
const dx = Math.abs(x - inside.x);
|
|
354
|
+
if (clear && dx >= clear.far) break;
|
|
355
|
+
work += near.length + 1;
|
|
356
|
+
if (work > WORK) {
|
|
357
|
+
searchCapped = true;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
const stretches = [];
|
|
361
|
+
for (const { at } of near) {
|
|
362
|
+
if (Math.min(x + r.width, at.x + at.width) - Math.max(x, at.x) > EPS2) stretches.push([at.y - r.height, at.y + at.height]);
|
|
363
|
+
}
|
|
364
|
+
stretches.sort((a, b) => a[0] - b[0]);
|
|
365
|
+
let cursor = ylo;
|
|
366
|
+
let bestY = null;
|
|
367
|
+
const gap = (from, to) => {
|
|
368
|
+
if (from > to) return;
|
|
369
|
+
const y = Math.min(Math.max(inside.y, from), to);
|
|
370
|
+
if (bestY === null || Math.abs(y - inside.y) < Math.abs(bestY - inside.y)) bestY = y;
|
|
371
|
+
};
|
|
372
|
+
for (const [a, b] of stretches) {
|
|
373
|
+
gap(cursor, Math.min(a, yhi));
|
|
374
|
+
cursor = Math.max(cursor, b);
|
|
375
|
+
if (cursor > yhi) break;
|
|
376
|
+
}
|
|
377
|
+
if (cursor <= yhi) gap(cursor, yhi);
|
|
378
|
+
if (bestY === null) continue;
|
|
379
|
+
const far = Math.hypot(dx, bestY - inside.y);
|
|
380
|
+
if (!clear || far < clear.far) clear = { x, y: bestY, far };
|
|
381
|
+
}
|
|
382
|
+
if (clear) return { rect: rect(clear.x, clear.y, r.width, r.height), clamped, snapped: clear.far > EPS2, covers: [], searchCapped };
|
|
383
|
+
let best = null;
|
|
384
|
+
let tried = 0;
|
|
385
|
+
for (const p of byDistance(px, py, inside.x, inside.y)) {
|
|
386
|
+
if (tried++ >= COVERED_PLACES) break;
|
|
387
|
+
const cand = rect(p.x, p.y, r.width, r.height);
|
|
388
|
+
const left = near.reduce((s, n) => s + shared(cand, n.at), 0);
|
|
389
|
+
if (!best || left < best.left - EPS2) best = { rect: cand, left, far: p.far, hits: under(cand) };
|
|
390
|
+
}
|
|
391
|
+
const out = best ?? { rect: inside, far: 0, hits: under(inside) };
|
|
392
|
+
return { rect: out.rect, clamped, snapped: out.far > EPS2, covers: out.hits, searchCapped };
|
|
393
|
+
}
|
|
394
|
+
var PLANE = rect(-1e9, -1e9, 2e9, 2e9);
|
|
395
|
+
function stackFloatTarget(input) {
|
|
396
|
+
const { stage, size } = input;
|
|
397
|
+
const avoid = input.avoid ?? [];
|
|
398
|
+
const w = Math.max(0, finite3(size.width, 0));
|
|
399
|
+
const h = Math.max(0, finite3(size.height, 0));
|
|
400
|
+
return {
|
|
401
|
+
// Not "float": `./desk` has a target of that id, and a consumer may hold both.
|
|
402
|
+
id: `float:${input.id}`,
|
|
403
|
+
kind: "float",
|
|
404
|
+
priority: 3,
|
|
405
|
+
free: true,
|
|
406
|
+
hit: PLANE,
|
|
407
|
+
label: input.label ?? "Float here",
|
|
408
|
+
item: input.id,
|
|
409
|
+
make(point, grab) {
|
|
410
|
+
const g = grab ?? { x: w / 2, y: h / 2 };
|
|
411
|
+
const fit = fitFloat(rect(point.x - g.x, point.y - g.y, w, h), stage, avoid);
|
|
412
|
+
return { rect: fit.rect, fit };
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
var isSize = (n) => typeof n === "number" && Number.isFinite(n) && n > 0;
|
|
417
|
+
var isAt = (n) => typeof n === "number" && Number.isFinite(n);
|
|
418
|
+
function storeFloats(floats) {
|
|
419
|
+
const last = /* @__PURE__ */ new Map();
|
|
420
|
+
floats.forEach((f, i) => {
|
|
421
|
+
const r = f.rect;
|
|
422
|
+
if (isAt(r.x) && isAt(r.y) && isSize(r.width) && isSize(r.height)) last.set(f.id, i);
|
|
423
|
+
});
|
|
424
|
+
const out = [];
|
|
425
|
+
floats.forEach((f, i) => {
|
|
426
|
+
if (last.get(f.id) === i) out.push({ id: f.id, x: f.rect.x, y: f.rect.y, width: f.rect.width, height: f.rect.height });
|
|
427
|
+
});
|
|
428
|
+
return { v: 1, floats: out };
|
|
429
|
+
}
|
|
430
|
+
var isObject2 = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
|
|
431
|
+
var FIELDS = ["id", "x", "y", "width", "height"];
|
|
432
|
+
function readFloats(data, known) {
|
|
433
|
+
try {
|
|
434
|
+
return read2(data, known);
|
|
435
|
+
} catch (e) {
|
|
436
|
+
return { ok: false, error: `unreadable: ${e instanceof Error ? e.message : typeof e}` };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function read2(data, known) {
|
|
440
|
+
if (!isObject2(data)) return { ok: false, error: "not an object" };
|
|
441
|
+
if (data.v !== 1) return { ok: false, error: `unknown version ${versionWords(data.v)} (this reads version 1)` };
|
|
442
|
+
if (!Array.isArray(data.floats)) return { ok: false, error: "floats is missing" };
|
|
443
|
+
const dropped = [];
|
|
444
|
+
const drop = (what) => {
|
|
445
|
+
if (!dropped.includes(what)) dropped.push(what);
|
|
446
|
+
};
|
|
447
|
+
for (const k of Object.keys(data)) if (k !== "v" && k !== "floats") drop(k);
|
|
448
|
+
const floats = [];
|
|
449
|
+
for (const x of data.floats) {
|
|
450
|
+
if (!isObject2(x)) {
|
|
451
|
+
drop(`a ${x === null ? "null" : Array.isArray(x) ? "list" : typeof x} where a float goes`);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
const id = x.id;
|
|
455
|
+
if (typeof id !== "string") {
|
|
456
|
+
drop("a float with no id");
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (known && !known.includes(id) || !isAt(x.x) || !isAt(x.y) || !isSize(x.width) || !isSize(x.height)) {
|
|
460
|
+
drop(`float:${id}`);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
for (const k of Object.keys(x)) {
|
|
464
|
+
if (k === "pinned") drop(`pinned:${id}`);
|
|
465
|
+
else if (!FIELDS.includes(k)) drop(`float:${id}:${k}`);
|
|
466
|
+
}
|
|
467
|
+
const before = floats.findIndex((f) => f.id === id);
|
|
468
|
+
if (before >= 0) {
|
|
469
|
+
floats.splice(before, 1);
|
|
470
|
+
drop(`float:${id}`);
|
|
471
|
+
}
|
|
472
|
+
floats.push({ id, rect: rect(x.x, x.y, x.width, x.height) });
|
|
473
|
+
}
|
|
474
|
+
return { ok: true, floats, dropped };
|
|
475
|
+
}
|
|
476
|
+
export {
|
|
477
|
+
DEFAULT_MENU,
|
|
478
|
+
DEFAULT_RAIL,
|
|
479
|
+
createStack,
|
|
480
|
+
fitFloat,
|
|
481
|
+
fitStack,
|
|
482
|
+
formSetsCross,
|
|
483
|
+
readFloats,
|
|
484
|
+
readStack,
|
|
485
|
+
stackFloatTarget,
|
|
486
|
+
stackTargets,
|
|
487
|
+
storeFloats,
|
|
488
|
+
storeStack,
|
|
489
|
+
withItem
|
|
490
|
+
};
|
|
491
|
+
//# sourceMappingURL=stack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stack/model.ts","../src/stack/layout.ts","../src/stack/targets.ts","../src/stack/store.ts","../src/stack/floats.ts"],"sourcesContent":["/**\n * The edge stack's shapes. A stack is the items along one edge of a stage: a column (vertical, a side region) or a\n * row (horizontal, a bar). An item is a panel or a control group; it declares its forms, largest first, and the\n * solver (`fitStack`) picks one per item. Plain data in and out: no words, no time, no DOM.\n *\n * The form kinds are structural labels the app draws by; the solver reads only `menu` (a form with no room of its own)\n * and, for the cross size, `open`. Panel and group kinds may be mixed on one item.\n */\nimport type { Rect } from \"../geometry\";\n\n/** `\"column\"` runs the stack down (its main size is a height, its cross size a width); `\"row\"` runs it across. */\nexport type StackAxis = \"column\" | \"row\";\n\n/** How a panel draws: open (header and body), a header only, a rail icon, or an entry in the » menu. */\nexport type PanelFormKind = \"open\" | \"header\" | \"rail\" | \"menu\";\n/** How a control group draws: a row of words, the words stacked, icons only, or an entry in the » menu. */\nexport type GroupFormKind = \"row\" | \"stacked\" | \"icons\" | \"menu\";\nexport type StackFormKind = PanelFormKind | GroupFormKind;\n\n/** Along the stack: `min` is the least it can be drawn, `natural` what it asks for; `grows` takes spare length. */\nexport interface MainSize {\n readonly min: number;\n readonly natural: number;\n readonly grows?: boolean;\n}\n\n/** One way to draw an item. A `menu` form takes no room in the stack (the » entry stands for it). */\nexport interface StackForm {\n readonly kind: StackFormKind;\n readonly main: MainSize;\n /**\n * Across the stack: `natural` is what this form asks for (it sets the stack's cross size when `setsCross`); `min`\n * is the least it can be drawn across (default `natural`). A form that doesn't set the cross size takes its next\n * form only when its `min` is over the stack's cross size, so a header stays in a column an open panel made\n * narrow when it has a small `min`.\n */\n readonly cross: { readonly natural: number; readonly min?: number };\n /**\n * Whether this form sets the stack's cross size (the column's width, the bar's height). Default: true for a\n * panel's `open`, false for everything else (a group never sets a column's width).\n */\n readonly setsCross?: boolean;\n}\n\nexport interface StackItem {\n readonly id: string;\n /** Largest first. The solver starts at `from` and steps down one at a time. An item with none is left out. */\n readonly forms: readonly StackForm[];\n /** How recently it was used: the smaller, the longer ago, the sooner it gives way. */\n readonly used: number;\n /** Kept: gives way after every item that isn't. */\n readonly kept?: boolean;\n /**\n * The first form the item may take, an index into `forms` (default 0; a panel folded by hand starts at its header).\n * The solver never goes above it. `form` in the result and in the notes are indexes into `forms`, not into what is\n * left after `from`.\n */\n readonly from?: number;\n}\n\n/** The cross size a stack may take. With no form setting it, the stack is a `rail` wide (default {@link DEFAULT_RAIL}). */\nexport interface CrossLimits {\n readonly min: number;\n readonly max: number;\n readonly rail?: number;\n}\n\nexport interface StackInput {\n readonly axis: StackAxis;\n /** The stack's length along the axis. */\n readonly length: number;\n readonly cross: CrossLimits;\n /** In order, from the start of the stack. */\n readonly items: readonly StackItem[];\n /** The » menu entry's size along the stack, taken only while an item is in the menu (default {@link DEFAULT_MENU}). */\n readonly menu?: { readonly main: number };\n}\n\n/** The width of a stack nothing open holds, px. */\nexport const DEFAULT_RAIL = 30;\n/** The » menu entry's size along the stack, px. */\nexport const DEFAULT_MENU = 28;\n\nconst finite = (x: number, fallback: number): number => (Number.isFinite(x) ? x : fallback);\n\n/**\n * The cross limits with a non-number replaced: `min` and `rail` that aren't finite become 0 and the default rail, a\n * `max` that is NaN becomes no limit (Infinity is one already), and `max` is never under `min`.\n */\nexport function limitsOf(input: StackInput): { readonly min: number; readonly max: number; readonly rail: number } {\n const min = Math.max(0, finite(input.cross.min, 0));\n const max = Number.isNaN(input.cross.max) ? Infinity : input.cross.max;\n return { min, max: Math.max(min, max), rail: Math.max(0, finite(input.cross.rail ?? DEFAULT_RAIL, DEFAULT_RAIL)) };\n}\nexport const menuMain = (input: StackInput): number => Math.max(0, finite(input.menu?.main ?? DEFAULT_MENU, DEFAULT_MENU));\n\n/** Whether a form sets the stack's cross size: its own say, else true for a panel's `open`. */\nexport const formSetsCross = (f: StackForm): boolean => f.setsCross ?? f.kind === \"open\";\n\n/** `input` with `item` inserted at index `at` (clamped to the items' range). */\nexport function withItem(input: StackInput, item: StackItem, at: number): StackInput {\n const k = Math.min(Math.max(0, Math.floor(at)), input.items.length);\n return { ...input, items: [...input.items.slice(0, k), item, ...input.items.slice(k)] };\n}\n\n/** An item drawn: the form it took, its kind and its rect (stack-local px). One in the menu has the menu entry's rect. */\nexport interface PlacedStackItem {\n readonly id: string;\n /** Index into the item's `forms`. */\n readonly form: number;\n readonly kind: StackFormKind;\n readonly rect: Rect;\n}\n\nexport interface StackMenu {\n /** The » entry's rect. */\n readonly rect: Rect;\n /** The items in it, in the stack's order: exactly the items whose kind is `menu`. */\n readonly items: readonly string[];\n}\n\n/**\n * Why a stack looks as it does:\n * - `form-down`: an item took a smaller form than the one it started in (`from`, an index into its forms), by length\n * or by cross size.\n * - `to-menu`: an item is in the » menu.\n * - `too-wide`: an item's form doesn't set the cross size and its `cross.min` is over the stack's. Cross size never\n * puts an item in the menu, so it keeps its last drawn form and the app clips it.\n * - `no-forms`: an item with no forms, left out of `items`.\n * - `overflow`: no item can give more way and the stack is still `over` px too long (forms that don't end in a `menu`\n * form can't give more; neither can a stack where going into the menu would only make it longer).\n */\nexport type StackNote =\n | { readonly code: \"form-down\"; readonly id: string; readonly from: number; readonly to: number }\n | { readonly code: \"to-menu\"; readonly id: string }\n | { readonly code: \"too-wide\"; readonly id: string }\n | { readonly code: \"no-forms\"; readonly id: string }\n | { readonly code: \"overflow\"; readonly over: number };\nexport type StackNoteCode = StackNote[\"code\"];\n\nexport interface StackFit {\n readonly axis: StackAxis;\n readonly length: number;\n /** The stack's size across: the widest `open` (clamped to the limits), or the rail. */\n readonly cross: number;\n /** One per input item that has forms, in order. */\n readonly items: readonly PlacedStackItem[];\n /** The » entry, or null while no item is in the menu. */\n readonly menu: StackMenu | null;\n /** `no-forms` first, then `form-down` per item in the order they gave way, `to-menu`, `too-wide`, and `overflow`. */\n readonly notes: readonly StackNote[];\n}\n","/**\n * `fitStack`: which form each item of a stack takes, and where it goes.\n *\n * 1. Every item starts in its largest form it may take (`forms[from]`, default the first).\n * 2. The stack's cross size is the largest `cross.natural` among the items whose form sets it (an open panel's, by\n * default), clamped to the limits; with none, the rail width.\n * 3. Cross-fit: an item whose form doesn't set the cross size and whose `cross.min` is over it steps down until it\n * fits (a group never sets a column's width, so a group alone becomes a rail of icons). Cross size never steps\n * an item into the menu: when only the menu is left it keeps its last drawn form and a `too-wide` note says so.\n * 4. Give-way: while the smallest sizes (`main.min`) don't fit the length, the item used longest ago steps down one\n * form; unkept before kept, the later in the stack first on a tie. The same item keeps giving way until its forms\n * end, then the next does. A `menu` form takes no room; the » entry does, while any item is in it (and an item\n * doesn't go into the menu when that would only make the stack longer and nothing else can give way).\n * Steps 2 to 4 repeat until nothing changes: a panel folding can narrow the column, which asks more of the groups.\n * 5. The room the items leave goes to the forms that grow, shared by their natural sizes. With none, the items keep\n * their natural sizes from the start of the stack. Between the minimum and natural sizes every item shrinks by the\n * same share of what it can give.\n *\n * The solver has no open or closed state: it lays out the items it is given, and a consumer closes a panel by not\n * passing it (or by starting it at a later form with `from`). An item with no forms is left out (`no-forms`). Pure:\n * same input, same fit.\n */\nimport type { Rect } from \"../geometry\";\nimport { formSetsCross, limitsOf, menuMain, type PlacedStackItem, type StackFit, type StackForm, type StackInput, type StackItem, type StackMenu, type StackNote } from \"./model\";\n\nconst EPS = 1e-6;\nconst isMenu = (f: StackForm): boolean => f.kind === \"menu\";\nconst finite = (x: number, fallback = 0): number => (Number.isFinite(x) ? x : fallback);\nconst mainOf = (f: StackForm, which: \"min\" | \"natural\"): number => (isMenu(f) ? 0 : Math.max(0, finite(f.main[which])));\n/** The least a form can be drawn across the stack. */\nconst crossMin = (f: StackForm): number => finite(f.cross.min ?? f.cross.natural);\n\n/** The item that gives way next: unkept before kept, used longest ago, the later in the stack on a tie. */\nfunction victim(items: readonly StackItem[], at: readonly number[]): number {\n let best = -1;\n for (let i = 0; i < items.length; i++) {\n if (at[i]! + 1 >= items[i]!.forms.length) continue;\n if (best < 0) {\n best = i;\n continue;\n }\n const a = items[i]!;\n const b = items[best]!;\n const ka = a.kept ? 1 : 0;\n const kb = b.kept ? 1 : 0;\n if (ka !== kb ? ka < kb : a.used !== b.used ? a.used < b.used : true) best = i;\n }\n return best;\n}\n\nexport function fitStack(input: StackInput): StackFit {\n const length = Math.max(0, finite(input.length));\n const lim = limitsOf(input);\n const items = input.items.filter((it) => it.forms.length > 0);\n const start = items.map((it) => Math.min(Math.max(0, Math.floor(finite(it.from ?? 0))), it.forms.length - 1));\n const at = [...start];\n const form = (i: number): StackForm => items[i]!.forms[at[i]!]!;\n const idx = items.map((_, i) => i);\n\n const firstStep: number[] = [];\n const menuOrder: number[] = [];\n const step = (i: number): void => {\n if (!firstStep.includes(i)) firstStep.push(i);\n at[i]!++;\n if (isMenu(form(i)) && !menuOrder.includes(i)) menuOrder.push(i);\n };\n // An item that starts in the menu is in it from the start.\n for (const i of idx) if (isMenu(form(i))) menuOrder.push(i);\n\n const crossOf = (): number => {\n const setters = idx.filter((i) => !isMenu(form(i)) && formSetsCross(form(i)));\n if (setters.length === 0) return lim.rail;\n const want = Math.max(...setters.map((i) => finite(form(i).cross.natural)));\n return Math.min(Math.max(want, lim.min), lim.max);\n };\n const inMenu = (): boolean => idx.some((i) => isMenu(form(i)));\n const minSum = (): number => idx.reduce((s, i) => s + mainOf(form(i), \"min\"), 0) + (inMenu() ? menuMain(input) : 0);\n const canStep = (i: number): boolean => at[i]! + 1 < items[i]!.forms.length;\n const tooWide = (i: number, cross: number): boolean => !isMenu(form(i)) && !formSetsCross(form(i)) && crossMin(form(i)) > cross + EPS;\n\n let cross = crossOf();\n let overflow = 0;\n const guard = items.reduce((s, it) => s + it.forms.length, 0) + 1;\n for (let n = 0; n <= guard; n++) {\n cross = crossOf();\n // Too wide for the stack: step down, but never into the menu (that is the length's call).\n let stepped = false;\n for (const i of idx) {\n if (tooWide(i, cross) && canStep(i) && !isMenu(items[i]!.forms[at[i]! + 1]!)) {\n step(i);\n stepped = true;\n }\n }\n if (stepped) continue;\n const over = minSum() - length;\n if (over <= EPS) break;\n const v = victim(items, at);\n if (v < 0) {\n overflow = over;\n break;\n }\n // Into the menu only when that helps, or when something else can still give way.\n const intoMenu = isMenu(items[v]!.forms[at[v]! + 1]!);\n const gain = mainOf(form(v), \"min\") - (inMenu() ? 0 : menuMain(input));\n if (intoMenu && gain < 0 && idx.filter(canStep).length === 1) {\n overflow = over;\n break;\n }\n step(v);\n }\n cross = crossOf();\n\n // Sizes along the stack.\n const menuSize = inMenu() ? menuMain(input) : 0;\n const avail = Math.max(0, length - menuSize);\n const drawn = idx.filter((i) => !isMenu(form(i)));\n const min = (i: number): number => mainOf(form(i), \"min\");\n const nat = (i: number): number => Math.max(min(i), mainOf(form(i), \"natural\"));\n const sumMin = drawn.reduce((s, i) => s + min(i), 0);\n const sumNat = drawn.reduce((s, i) => s + nat(i), 0);\n const size = new Map<number, number>();\n if (sumNat <= avail + EPS) {\n const growers = drawn.filter((i) => form(i).main.grows);\n const spare = Math.max(0, avail - sumNat);\n const weight = growers.reduce((s, i) => s + nat(i), 0);\n for (const i of drawn) {\n const share = growers.includes(i) ? (weight > EPS ? nat(i) / weight : 1 / growers.length) : 0;\n size.set(i, nat(i) + spare * share);\n }\n } else {\n const t = avail > sumMin && sumNat > sumMin ? (avail - sumMin) / (sumNat - sumMin) : 0;\n for (const i of drawn) size.set(i, min(i) + (nat(i) - min(i)) * t);\n }\n\n const rectAt = (from: number, main: number): Rect => (input.axis === \"column\" ? { x: 0, y: from, width: cross, height: main } : { x: from, y: 0, width: main, height: cross });\n const rects = new Map<number, Rect>();\n let cursor = 0;\n for (const i of drawn) {\n const s = size.get(i)!;\n rects.set(i, rectAt(cursor, s));\n cursor += s;\n }\n const menuRect = rectAt(Math.max(cursor, length - menuSize), menuSize);\n const inside = idx.filter((i) => isMenu(form(i)));\n const menu: StackMenu | null = inside.length > 0 ? { rect: menuRect, items: inside.map((i) => items[i]!.id) } : null;\n\n const placed: PlacedStackItem[] = idx.map((i) => ({ id: items[i]!.id, form: at[i]!, kind: form(i).kind, rect: isMenu(form(i)) ? menuRect : rects.get(i)! }));\n\n const notes: StackNote[] = [];\n for (const it of input.items) if (it.forms.length === 0) notes.push({ code: \"no-forms\", id: it.id });\n for (const i of firstStep) if (at[i]! > start[i]!) notes.push({ code: \"form-down\", id: items[i]!.id, from: start[i]!, to: at[i]! });\n for (const i of menuOrder) if (isMenu(form(i))) notes.push({ code: \"to-menu\", id: items[i]!.id });\n for (const i of idx) if (tooWide(i, cross)) notes.push({ code: \"too-wide\", id: items[i]!.id });\n if (overflow > EPS) notes.push({ code: \"overflow\", over: overflow });\n return { axis: input.axis, length, cross, items: placed, menu, notes };\n}\n","/**\n * The drop targets for dragging an item into a stack, in the shape @n-uf/motion/drag takes. `stackTargets(input,\n * dragged)` gives one target for every gap of the stack without the dragged item (an item of the input with its id\n * is lifted out first, so a reorder within a stack works): before each item and after the last, so an empty stack has\n * one. An item with no forms has no place of its own, but its gaps count.\n *\n * - `id` `gap:<k>`, priority 2, `place` `{ kind: \"gap\", at: k }` (k counts the input's items, the dragged one lifted\n * out, including any with no forms; it is not an index into `fit.items`, which leaves those out: look the dragged\n * item up there by its id, as `slot` already does).\n * - `hit`: the gap's zone along the axis, the stack split at its items' middles (an item in the menu has no place of\n * its own: the gaps next to it split the stretch where the stack's drawn items end; with every item in the menu only\n * the last gap, \"at the end\", has any length, and the others are a point at the start). Across, it is the larger of\n * the stack's current size and the size the drop gives it, so the pointer over the preview is over the target.\n * - `fit`: the full `fitStack` of the stack with the dragged item inserted at k, and `input` the stack that was\n * solved. The preview is `slot`, the dragged item's rect in `fit`: exactly the rect it gets after the drop, because\n * the drop is that same solve. `marker` is the line where its leading edge lands: a rect with no thickness across\n * the stack's cross size (the app draws it with one).\n *\n * The dragged item counts as just used: it is newer than every item in the stack, so the others give way before it.\n * A group lands in the form that fits; a panel that would set a wider column makes room by that solve too. A dragged\n * item with no forms has no targets.\n */\nimport { rect, type Rect } from \"../geometry\";\nimport type { Target } from \"../targets/target\";\nimport { fitStack } from \"./layout\";\nimport { withItem, type StackFit, type StackInput, type StackItem } from \"./model\";\n\nexport type StackPlace = { readonly kind: \"gap\"; readonly at: number };\n\nexport interface StackTarget extends Target {\n readonly priority: 2;\n /** The item dragged. */\n readonly item: string;\n readonly place: StackPlace;\n /** The line where the dragged item's leading edge lands. */\n readonly marker: Rect;\n /** The stack with the dragged item inserted: what a drop here makes. */\n readonly input: StackInput;\n /** Its fit: the preview and the drop are both its rect for the dragged item. */\n readonly fit: StackFit;\n}\n\n/** The gap boundaries along the axis: `b[0]` is the start, `b[k]` the middle of item k - 1, `b[n + 1]` the length. */\nfunction bounds(input: StackInput, fit: StackFit): number[] {\n const column = input.axis === \"column\";\n const start = (r: Rect): number => (column ? r.y : r.x);\n const size = (r: Rect): number => (column ? r.height : r.width);\n const placed = new Map(fit.items.map((p) => [p.id, p]));\n const b = [0];\n // Where the drawn items have ended so far: an item in the menu (or with no forms) sits there.\n let end = 0;\n for (const it of input.items) {\n const p = placed.get(it.id);\n if (!p || p.kind === \"menu\") {\n b.push(end);\n continue;\n }\n b.push(start(p.rect) + size(p.rect) / 2);\n end = start(p.rect) + size(p.rect);\n }\n b.push(fit.length);\n // Monotone, whatever the sizes did (a stack that overflows).\n for (let i = 1; i < b.length; i++) b[i] = Math.min(Math.max(b[i]!, b[i - 1]!), fit.length);\n return b;\n}\n\nexport function stackTargets(input: StackInput, dragged: StackItem): StackTarget[] {\n if (dragged.forms.length === 0) return [];\n const rest: StackInput = { ...input, items: input.items.filter((it) => it.id !== dragged.id) };\n const base = fitStack(rest);\n const b = bounds(rest, base);\n const column = rest.axis === \"column\";\n const newest = rest.items.reduce((m, it) => Math.max(m, it.used), dragged.used) + 1;\n const item: StackItem = { ...dragged, used: newest };\n const out: StackTarget[] = [];\n for (let k = 0; k <= rest.items.length; k++) {\n const solved = withItem(rest, item, k);\n const fit = fitStack(solved);\n const slot = fit.items.find((p) => p.id === dragged.id)!.rect;\n const from = b[k]!;\n const to = b[k + 1]!;\n const across = Math.max(base.cross, fit.cross);\n out.push({\n id: `gap:${k}`,\n priority: 2,\n item: dragged.id,\n place: { kind: \"gap\", at: k },\n hit: column ? rect(0, from, across, to - from) : rect(from, 0, to - from, across),\n slot,\n marker: column ? rect(slot.x, slot.y, slot.width, 0) : rect(slot.x, slot.y, 0, slot.height),\n input: solved,\n fit,\n });\n }\n return out;\n}\n","/**\n * The stack's stored form: `storeStack(state)` gives plain JSON-safe data (`{ v: 1, order, kept, closed, used, forms\n * }`); `readStack(data, known?)` reads parsed data back and never throws. Anything but an object of version 1 is\n * `{ ok: false, error }`. Inside one it is lenient, as the panels' reader is: an id that isn't a string, is repeated\n * or (with `known`) isn't one the consumer has is dropped and listed in `dropped`; a `kept`, `closed`, `used` or\n * `forms` entry for an id that isn't in `order` is dropped; a form that isn't a whole number from 1 up is dropped;\n * the key `__proto__` in `forms` is dropped (the other keys, `constructor` and `prototype` included, are ids like any\n * other: `forms` is an object with no prototype, written and read); a field this version doesn't know is skipped and\n * reported as `\"<field>\"`. An item the consumer has that the stored order lacks is no error: the consumer places it.\n *\n * - `kept`: items kept open (they give way last).\n * - `closed`: items the user closed; their place in `order` is kept for when they open.\n * - `used`: the use order, oldest to newest, so a reload restores who gives way first (an item's `used` is its index in\n * it; one the list lacks is older than all). An id listed twice counts at its last place: the newest use wins.\n * - `forms`: the user's chosen form per item, the index of the largest form it may take (`StackItem.from`). 0, the\n * first, is not stored.\n */\n\nexport interface StackState {\n /** The items' order along the stack. */\n readonly order: readonly string[];\n /** Items kept open: they give way last. */\n readonly kept: readonly string[];\n /** Items the user closed. */\n readonly closed: readonly string[];\n /** The use order, oldest first. */\n readonly used: readonly string[];\n /** The form index the user chose per item (only those that chose one; absent means the first). */\n readonly forms: Readonly<Record<string, number>>;\n}\n\nexport interface StoredStack {\n readonly v: 1;\n readonly order: string[];\n readonly kept: string[];\n readonly closed: string[];\n readonly used: string[];\n readonly forms: Record<string, number>;\n}\n\nexport type StackRead = { readonly ok: true; readonly state: StackState; readonly dropped: readonly string[] } | { readonly ok: false; readonly error: string };\n\n/** A state with these items in this order, none kept or closed, used in that order, every form the first. */\nexport const createStack = (order: readonly string[]): StackState => ({ order: [...order], kept: [], closed: [], used: [...order], forms: {} });\n\nconst isForm = (f: unknown): f is number => typeof f === \"number\" && Number.isInteger(f) && f >= 1;\n/** Ids that are in `order`, each once, at the first place it was listed. */\nconst uniq = (ids: readonly string[], order: readonly string[]): string[] => [...new Set(ids)].filter((id) => order.includes(id));\n/** Ids that are in `order`, each once, at the last place it was listed (the newest use wins). */\nconst uniqLast = (ids: readonly string[], order: readonly string[]): string[] => ids.filter((id, i) => order.includes(id) && ids.lastIndexOf(id) === i);\n/** Forms live in an object with no prototype, so no key (`constructor`, `prototype`) is more than a key; only `__proto__` is refused. */\nconst noForms = (): Record<string, number> => Object.create(null) as Record<string, number>;\n\nexport function storeStack(state: StackState): StoredStack {\n const forms = noForms();\n for (const id of state.order) {\n const f = state.forms[id];\n if (id !== \"__proto__\" && Object.hasOwn(state.forms, id) && isForm(f)) forms[id] = f;\n }\n return { v: 1, order: [...state.order], kept: uniq(state.kept, state.order), closed: uniq(state.closed, state.order), used: uniqLast(state.used, state.order), forms };\n}\n\nconst isObject = (x: unknown): x is Record<string, unknown> => typeof x === \"object\" && x !== null && !Array.isArray(x);\n\nexport function readStack(data: unknown, known?: readonly string[]): StackRead {\n try {\n return read(data, known);\n } catch (e) {\n // A getter that throws, a proxy: still no throw.\n return { ok: false, error: `unreadable: ${e instanceof Error ? e.message : typeof e}` };\n }\n}\n\n/** A stored version in words for an error (a string is quoted and cut); shared with the floats' reader, not exported from the entry. */\nexport const versionWords = (v: unknown): string => (typeof v === \"number\" ? String(v) : typeof v === \"string\" ? JSON.stringify(v.length > 24 ? `${v.slice(0, 24)}…` : v) : typeof v);\n\nfunction read(data: unknown, known?: readonly string[]): StackRead {\n if (!isObject(data)) return { ok: false, error: \"not an object\" };\n if (data.v !== 1) return { ok: false, error: `unknown version ${versionWords(data.v)} (this reads version 1)` };\n if (!Array.isArray(data.order)) return { ok: false, error: \"order is missing\" };\n const dropped: string[] = [];\n const drop = (what: string): void => {\n if (!dropped.includes(what)) dropped.push(what);\n };\n for (const k of Object.keys(data)) if (![\"v\", \"order\", \"kept\", \"closed\", \"used\", \"forms\"].includes(k)) drop(k);\n const order: string[] = [];\n for (const x of data.order as unknown[]) {\n if (typeof x !== \"string\") drop(`a ${typeof x} where an id goes`);\n else if ((known && !known.includes(x)) || order.includes(x)) drop(x);\n else order.push(x);\n }\n // A list of ids that are in `order`, each once: at the first place it was listed, or for `used` the last (the newest use wins).\n const list = (key: \"kept\" | \"closed\" | \"used\"): string[] => {\n const out: string[] = [];\n const x = data[key];\n if (x === undefined) return out;\n if (!Array.isArray(x)) {\n drop(key);\n return out;\n }\n for (const id of x as unknown[]) {\n if (typeof id === \"string\" && order.includes(id)) {\n if (key === \"used\" && out.includes(id)) out.splice(out.indexOf(id), 1);\n if (!out.includes(id)) out.push(id);\n } else drop(typeof id === \"string\" ? `${key}:${id}` : `a ${typeof id} in ${key}`);\n }\n return out;\n };\n const kept = list(\"kept\");\n const closed = list(\"closed\");\n const used = list(\"used\");\n const forms = noForms();\n if (data.forms !== undefined) {\n if (!isObject(data.forms)) drop(\"forms\");\n else {\n for (const id of Object.keys(data.forms)) {\n const f = data.forms[id];\n if (id === \"__proto__\" || !Object.hasOwn(data.forms, id) || !order.includes(id) || !isForm(f)) drop(`forms:${id}`);\n else forms[id] = f;\n }\n }\n }\n return { ok: true, state: { order, kept, closed, used, forms }, dropped };\n}\n","/**\n * Floats for the edge stack: an item that isn't docked lies above the pages in the stage's own px (view space), so the\n * pages scrolling under it never move it. A float has no pinned flag: there is one kind of float, and it is in view\n * space. Pure, like the rest of `./stack`.\n *\n * - `fitFloat(rect, stage, avoid)`: the rect a float gets. It is moved (never resized) to lie inside the stage, then\n * beside whatever docked content it would cover: the nearest place inside the stage that is clear of every `avoid`\n * rect (the docked items' rects, in the same px). One larger than the stage keeps its top-left corner on the\n * stage's; if no clear place exists the float stays where it covers the least and `covers` says which rects.\n * A clear place is found whenever one exists: the x's are tried nearest first and, for each, the blocked stretches of\n * y are merged and skipped, so no clear place is missed however many places lie between. Cost (measured in a test\n * run on a laptop): about 100 docked rects 0.1 ms, 300 a few ms even when no place is clear (6 ms median, 15 ms\n * worst), 1,000 about 17 ms; a safety net stops the clear search after 3 million rect tests and says so in\n * `searchCapped`; a stage with no clear place looks at the 2,048 nearest places for the least covered.\n * - `stackFloatTarget(input)`: a drop target in the shape `@n-uf/motion/drag` takes, free (no room is made for it),\n * `kind` `\"float\"`, id `float:<item>`, whose `hit` covers the whole plane, so a pointer past the stage's edge still\n * hits it (a float drop never cancels), at priority 3 so a docked stack's gaps win where they are. `make(point,\n * grab)` is the one answer for the preview and the drop: the float's rect with the pointer holding it at `grab`,\n * clamped and clear of the docked content.\n * - `storeFloats` / `readFloats`: the stored form, `{ v: 1, floats: [{ id, x, y, width, height }] }`, in stacking\n * order (the last is on top). It writes no `pinned`; one an old store has is read, ignored and listed.\n */\nimport { clampInto, rect, type Point, type Rect, type Size } from \"../geometry\";\nimport type { Target } from \"../targets/target\";\nimport { versionWords } from \"./store\";\n\n/** A float: the item's id and its rect in view space (the stage's px). */\nexport interface StackFloat {\n readonly id: string;\n readonly rect: Rect;\n}\n\nexport interface FloatFit {\n /** The float's rect: inside the stage and clear of the docked content where a clear place exists. */\n readonly rect: Rect;\n /** It was moved to lie inside the stage. */\n readonly clamped: boolean;\n /** It was moved beside the docked content it would have covered. */\n readonly snapped: boolean;\n /**\n * Indexes into `avoid` of the rects it still covers. Empty whenever a clear place was found, which is whenever one\n * exists (unless `searchCapped`); not empty only when the stage has no clear place for the float, and then it stays\n * where it covers the least among the 2,048 nearest candidate places.\n */\n readonly covers: readonly number[];\n /** The safety net on the clear search was hit (thousands of rects): the place found may not be the nearest, or a clear place may exist that was not found. Normally false. */\n readonly searchCapped: boolean;\n}\n\nconst EPS = 1e-3;\nconst finite = (x: number, fallback: number): number => (Number.isFinite(x) ? x : fallback);\n\n/** Whether two rects share an area (touching edges don't). */\nconst overlaps = (a: Rect, b: Rect): boolean => Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x) > EPS && Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y) > EPS;\nconst shared = (a: Rect, b: Rect): number =>\n Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));\n\n/** `r` with a non-number replaced and no negative size. */\nconst clean = (r: Rect, at: Rect): Rect => rect(finite(r.x, at.x), finite(r.y, at.y), Math.max(0, finite(r.width, 0)), Math.max(0, finite(r.height, 0)));\n\n/** A safety net on the clear search, in rect tests (only thousands of rects reach it), and the places the least-covered search looks at. */\nconst WORK = 3_000_000;\nconst COVERED_PLACES = 2048;\n\ntype Triple = [number, number, number];\n\n/** A binary min-heap of `[index, index, distance]` triples, by distance then indexes (a fixed order). */\nclass Nearest {\n private readonly h: Triple[] = [];\n private less(a: Triple, b: Triple): boolean {\n return a[2] !== b[2] ? a[2] < b[2] : a[0] !== b[0] ? a[0] < b[0] : a[1] < b[1];\n }\n get size(): number {\n return this.h.length;\n }\n push(t: Triple): void {\n const h = this.h;\n let i = h.push(t) - 1;\n while (i > 0) {\n const p = (i - 1) >> 1;\n if (!this.less(h[i]!, h[p]!)) break;\n [h[i], h[p]] = [h[p]!, h[i]!];\n i = p;\n }\n }\n pop(): Triple {\n const h = this.h;\n const top = h[0]!;\n const last = h.pop()!;\n if (h.length > 0) {\n h[0] = last;\n let i = 0;\n for (;;) {\n const l = 2 * i + 1;\n const r = l + 1;\n let m = i;\n if (l < h.length && this.less(h[l]!, h[m]!)) m = l;\n if (r < h.length && this.less(h[r]!, h[m]!)) m = r;\n if (m === i) break;\n [h[i], h[m]] = [h[m]!, h[i]!];\n i = m;\n }\n }\n return top;\n }\n}\n\n/** The places `xs` crossed with `ys`, nearest to `(fx, fy)` first (Euclidean), without building the cross product. */\nfunction* byDistance(xs: readonly number[], ys: readonly number[], fx: number, fy: number): Generator<{ x: number; y: number; far: number }> {\n const sx = [...xs].sort((a, b) => Math.abs(a - fx) - Math.abs(b - fx) || a - b);\n const sy = [...ys].sort((a, b) => Math.abs(a - fy) - Math.abs(b - fy) || a - b);\n const q = new Nearest();\n const d = (i: number, j: number): number => Math.hypot(sx[i]! - fx, sy[j]! - fy);\n // Each x starts at its nearest y; popping a place pushes the same x's next y.\n if (sy.length > 0) for (let i = 0; i < sx.length; i++) q.push([i, 0, d(i, 0)]);\n while (q.size > 0) {\n const [i, j, far] = q.pop();\n yield { x: sx[i]!, y: sy[j]!, far };\n if (j + 1 < sy.length) q.push([i, j + 1, d(i, j + 1)]);\n }\n}\n\nexport function fitFloat(want: Rect, stage: Rect, avoid: readonly Rect[] = []): FloatFit {\n const area = clean(stage, rect(0, 0, 0, 0));\n const r = clean(want, area);\n const inside = clampInto(r, area);\n // Compared with what was asked, so a position that wasn't a number (and was replaced) counts as moved.\n const clamped = inside.x !== want.x || inside.y !== want.y;\n // Only rects that can touch the float wherever the stage lets it be: its reach.\n const reach = rect(area.x, area.y, Math.max(area.width, r.width), Math.max(area.height, r.height));\n const near: { at: Rect; index: number }[] = [];\n avoid.forEach((a, index) => {\n const at = clean(a, area);\n if (at.width > EPS && at.height > EPS && overlaps(at, reach)) near.push({ at, index });\n });\n const under = (cand: Rect): number[] => near.filter((n) => overlaps(cand, n.at)).map((n) => n.index);\n const blocked = (cand: Rect): boolean => near.some((n) => overlaps(cand, n.at));\n if (!blocked(inside)) return { rect: inside, clamped, snapped: false, covers: [], searchCapped: false };\n\n // The places to look at: every x that puts the float against an edge of the stage or of a rect (or where it is),\n // crossed with every y that does; a clear place exists if one does at all, as a float slid against edges.\n const lo = (v: number, from: number, to: number): number => Math.min(Math.max(v, from), Math.max(from, to));\n const xs = [inside.x, area.x, area.x + area.width - r.width];\n const ys = [inside.y, area.y, area.y + area.height - r.height];\n for (const { at } of near) {\n xs.push(at.x - r.width, at.x + at.width);\n ys.push(at.y - r.height, at.y + at.height);\n }\n const places = (list: number[], from: number, size: number, extent: number): number[] => [...new Set(list.map((v) => lo(v, from, from + extent - size)))];\n const px = places(xs, area.x, r.width, area.width);\n const py = places(ys, area.y, r.height, area.height);\n // Nearest clear place: the x's nearest first; for each, the rects that overlap that x block open stretches of y, and\n // the nearest clear y is the wanted one if it is clear, else the end of the stretch it is in (the stretches are\n // merged, so a whole blocked run is skipped at once). A later x can't beat a clear place at least as far as its x\n // alone, so the search stops there. `WORK` bounds the rect tests as a safety net.\n const ylo = area.y;\n const yhi = Math.max(area.y, area.y + area.height - r.height);\n const byX = [...px].sort((a, b) => Math.abs(a - inside.x) - Math.abs(b - inside.x) || a - b);\n let clear: { x: number; y: number; far: number } | null = null;\n let work = 0;\n let searchCapped = false;\n for (const x of byX) {\n const dx = Math.abs(x - inside.x);\n if (clear && dx >= clear.far) break;\n work += near.length + 1;\n if (work > WORK) {\n searchCapped = true;\n break;\n }\n const stretches: [number, number][] = [];\n for (const { at } of near) {\n if (Math.min(x + r.width, at.x + at.width) - Math.max(x, at.x) > EPS) stretches.push([at.y - r.height, at.y + at.height]);\n }\n stretches.sort((a, b) => a[0] - b[0]);\n // The clear y's are the closed gaps between the merged open stretches, within the stage.\n let cursor = ylo;\n let bestY: number | null = null;\n const gap = (from: number, to: number): void => {\n if (from > to) return;\n const y = Math.min(Math.max(inside.y, from), to);\n if (bestY === null || Math.abs(y - inside.y) < Math.abs(bestY - inside.y)) bestY = y;\n };\n for (const [a, b] of stretches) {\n gap(cursor, Math.min(a, yhi));\n cursor = Math.max(cursor, b);\n if (cursor > yhi) break;\n }\n if (cursor <= yhi) gap(cursor, yhi);\n if (bestY === null) continue;\n const far = Math.hypot(dx, bestY - inside.y);\n if (!clear || far < clear.far) clear = { x, y: bestY, far };\n }\n if (clear) return { rect: rect(clear.x, clear.y, r.width, r.height), clamped, snapped: clear.far > EPS, covers: [], searchCapped };\n // None is clear: the nearest places that cover the least.\n let best: { rect: Rect; left: number; far: number; hits: number[] } | null = null;\n let tried = 0;\n for (const p of byDistance(px, py, inside.x, inside.y)) {\n if (tried++ >= COVERED_PLACES) break;\n const cand = rect(p.x, p.y, r.width, r.height);\n const left = near.reduce((s, n) => s + shared(cand, n.at), 0);\n if (!best || left < best.left - EPS) best = { rect: cand, left, far: p.far, hits: under(cand) };\n }\n const out = best ?? { rect: inside, far: 0, hits: under(inside) };\n return { rect: out.rect, clamped, snapped: out.far > EPS, covers: out.hits, searchCapped };\n}\n\n// --- the drop target ---------------------------------------------------------------------------------------------\n\n/** What a float drop makes: the same answer for the preview and for the release. */\nexport interface FloatDrop {\n /** The float's rect in view space. */\n readonly rect: Rect;\n readonly fit: FloatFit;\n}\n\nexport interface StackFloatTargetInput {\n /** The item dragged. */\n readonly id: string;\n /** The stage, in view space. */\n readonly stage: Rect;\n /** The float's size: it is never resized. */\n readonly size: Size;\n /** The docked content to stay clear of: the docked items' rects (and the » entries'), in view space. */\n readonly avoid?: readonly Rect[];\n /** What the target is called (default \"Float here\"). */\n readonly label?: string;\n}\n\nexport interface FloatTarget extends Target {\n /** What kind of place it is, for a consumer that holds targets of several kinds. */\n readonly kind: \"float\";\n readonly priority: 3;\n readonly free: true;\n /** The item dragged. */\n readonly item: string;\n /**\n * The drop with the pointer at `point` holding the float at `grab` (its offset from the float's top-left corner,\n * default the middle): `rect` is the preview and the landing, clamped into the stage and clear of the docked content.\n */\n make(point: Point, grab?: Point): FloatDrop;\n}\n\n/** A zone that covers any pointer the page can see: large and finite (an infinite rect has no far edge to compare). */\nconst PLANE = rect(-1e9, -1e9, 2e9, 2e9);\n\nexport function stackFloatTarget(input: StackFloatTargetInput): FloatTarget {\n const { stage, size } = input;\n const avoid = input.avoid ?? [];\n const w = Math.max(0, finite(size.width, 0));\n const h = Math.max(0, finite(size.height, 0));\n return {\n // Not \"float\": `./desk` has a target of that id, and a consumer may hold both.\n id: `float:${input.id}`,\n kind: \"float\",\n priority: 3,\n free: true,\n hit: PLANE,\n label: input.label ?? \"Float here\",\n item: input.id,\n make(point, grab) {\n const g = grab ?? { x: w / 2, y: h / 2 };\n const fit = fitFloat(rect(point.x - g.x, point.y - g.y, w, h), stage, avoid);\n return { rect: fit.rect, fit };\n },\n };\n}\n\n// --- the stored form ---------------------------------------------------------------------------------------------\n\nexport interface StoredFloats {\n readonly v: 1;\n readonly floats: { id: string; x: number; y: number; width: number; height: number }[];\n}\n\nexport type FloatsRead = { readonly ok: true; readonly floats: StackFloat[]; readonly dropped: readonly string[] } | { readonly ok: false; readonly error: string };\n\nconst isSize = (n: unknown): n is number => typeof n === \"number\" && Number.isFinite(n) && n > 0;\nconst isAt = (n: unknown): n is number => typeof n === \"number\" && Number.isFinite(n);\n\n/**\n * The floats as plain JSON-safe data, in the order given (the stacking order, the last on top). Never a `pinned`.\n * What it can't read back is left out, silently: a float whose position isn't a finite number or whose size isn't over\n * 0 (a zero-size float), and an earlier entry of a repeated id (the last readable entry of an id is the one on top,\n * so it stays, at its own place in the order).\n */\nexport function storeFloats(floats: readonly StackFloat[]): StoredFloats {\n const last = new Map<string, number>();\n floats.forEach((f, i) => {\n const r = f.rect;\n if (isAt(r.x) && isAt(r.y) && isSize(r.width) && isSize(r.height)) last.set(f.id, i);\n });\n const out: StoredFloats[\"floats\"] = [];\n floats.forEach((f, i) => {\n if (last.get(f.id) === i) out.push({ id: f.id, x: f.rect.x, y: f.rect.y, width: f.rect.width, height: f.rect.height });\n });\n return { v: 1, floats: out };\n}\n\nconst isObject = (x: unknown): x is Record<string, unknown> => typeof x === \"object\" && x !== null && !Array.isArray(x);\nconst FIELDS = [\"id\", \"x\", \"y\", \"width\", \"height\"];\n\n/**\n * Reads parsed data back; never throws. Anything but an object of version 1 with a `floats` array is `{ ok: false,\n * error }`. Inside one it is lenient: a float that isn't an object, has no string id, isn't one the\n * consumer has (with `known`) or has a position or size that isn't a finite number (a size over 0) is dropped and\n * listed in `dropped` as `float:<id>` (or `a <type> where a float goes`); a top-level field this version doesn't\n * know is listed as `\"<field>\"`, and so is a float's own (`float:<id>:<field>`) while the float itself is kept.\n * A `pinned` key (an old store's flag, whatever its value) is ignored: the float loads as a plain float, in view\n * space as stored, and `pinned:<id>` is listed. A repeated id keeps its last readable entry (the list is in stacking\n * order, the last on top); the earlier ones are dropped and listed as `float:<id>`.\n */\nexport function readFloats(data: unknown, known?: readonly string[]): FloatsRead {\n try {\n return read(data, known);\n } catch (e) {\n // A getter that throws, a proxy: still no throw.\n return { ok: false, error: `unreadable: ${e instanceof Error ? e.message : typeof e}` };\n }\n}\n\nfunction read(data: unknown, known?: readonly string[]): FloatsRead {\n if (!isObject(data)) return { ok: false, error: \"not an object\" };\n if (data.v !== 1) return { ok: false, error: `unknown version ${versionWords(data.v)} (this reads version 1)` };\n if (!Array.isArray(data.floats)) return { ok: false, error: \"floats is missing\" };\n const dropped: string[] = [];\n const drop = (what: string): void => {\n if (!dropped.includes(what)) dropped.push(what);\n };\n for (const k of Object.keys(data)) if (k !== \"v\" && k !== \"floats\") drop(k);\n const floats: StackFloat[] = [];\n for (const x of data.floats as unknown[]) {\n if (!isObject(x)) {\n drop(`a ${x === null ? \"null\" : Array.isArray(x) ? \"list\" : typeof x} where a float goes`);\n continue;\n }\n const id = x.id;\n if (typeof id !== \"string\") {\n drop(\"a float with no id\");\n continue;\n }\n if ((known && !known.includes(id)) || !isAt(x.x) || !isAt(x.y) || !isSize(x.width) || !isSize(x.height)) {\n drop(`float:${id}`);\n continue;\n }\n for (const k of Object.keys(x)) {\n if (k === \"pinned\") drop(`pinned:${id}`);\n else if (!FIELDS.includes(k)) drop(`float:${id}:${k}`);\n }\n // A repeated id: the last readable entry is the one on top (the list is in stacking order), the earlier is dropped.\n const before = floats.findIndex((f) => f.id === id);\n if (before >= 0) {\n floats.splice(before, 1);\n drop(`float:${id}`);\n }\n floats.push({ id, rect: rect(x.x, x.y, x.width, x.height) });\n }\n return { ok: true, floats, dropped };\n}\n"],"mappings":"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","names":["finite","cross","EPS","finite","isObject","read"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { R as Rect } from './geometry-mL-GTRti.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A drop target in the shape @n-uf/motion/drag takes: an id, a priority (1 most precise: a rail or a tab strip;
|
|
5
|
+
* 2 a slot; 3 a broad area such as floating anywhere), the zone the pointer hits, and optionally where the item
|
|
6
|
+
* lands, a label and whether it lands freely (no room is made for it).
|
|
7
|
+
*/
|
|
8
|
+
interface Target {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly priority: 1 | 2 | 3;
|
|
11
|
+
readonly hit: Rect;
|
|
12
|
+
readonly slot?: Rect;
|
|
13
|
+
readonly label?: string;
|
|
14
|
+
readonly free?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type { Target as T };
|