@weasel-js/guides 1.10.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -22
- package/dist/index.d.ts +205 -152
- package/dist/index.js +421 -340
- package/dist/index.js.map +1 -1
- package/package.json +4 -16
- package/dist/chunk-B25KFI3I.js +0 -18
- package/dist/chunk-B25KFI3I.js.map +0 -1
- package/dist/chunk-LOFNN7EQ.js +0 -45
- package/dist/chunk-LOFNN7EQ.js.map +0 -1
- package/dist/chunk-TLFMAZ6M.js +0 -31
- package/dist/chunk-TLFMAZ6M.js.map +0 -1
- package/dist/insert.d.ts +0 -30
- package/dist/insert.js +0 -58
- package/dist/insert.js.map +0 -1
- package/dist/move.d.ts +0 -42
- package/dist/move.js +0 -18
- package/dist/move.js.map +0 -1
- package/dist/pointSnapToGrid-BaVEjV23.d.ts +0 -13
- package/dist/resize.d.ts +0 -38
- package/dist/resize.js +0 -89
- package/dist/resize.js.map +0 -1
- package/dist/types-DRSPiXfk.d.ts +0 -38
package/dist/index.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { pointToGridCell } from './chunk-TLFMAZ6M.js';
|
|
2
|
-
export { gridSnapStrategy, pointToGridCell } from './chunk-TLFMAZ6M.js';
|
|
3
|
-
export { pointSnapToGrid } from './chunk-B25KFI3I.js';
|
|
4
|
-
import { guideOffsetOn } from './chunk-LOFNN7EQ.js';
|
|
5
|
-
export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy } from './chunk-LOFNN7EQ.js';
|
|
6
1
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
7
|
-
import { useLatest,
|
|
8
|
-
import {
|
|
2
|
+
import { resolveAxes, useLatest, viewportWorldRect, pxExtent, tickedSegment, measuredSegmentCommands, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, rectFromPlane, scenePoseFrame, unionAABB, createTransformOp, screenToWorld, nodeGates, resolveStrokeWidth } from '@weasel-js/core';
|
|
3
|
+
import { PATH_M, PATH_L } from '@weasel-js/geom';
|
|
9
4
|
|
|
10
5
|
// src/grid/roundToCell.ts
|
|
11
6
|
function roundToCell(value, cellSize) {
|
|
12
7
|
return Math.round(value / cellSize) * cellSize || 0;
|
|
13
8
|
}
|
|
9
|
+
function pointToGridCell(point, spacing, unitSystem, origin = { x: 0, y: 0 }) {
|
|
10
|
+
const s = resolveAxes(spacing, unitSystem);
|
|
11
|
+
return {
|
|
12
|
+
col: Math.floor((point.x - origin.x) / s.x),
|
|
13
|
+
row: Math.floor((point.y - origin.y) / s.y)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/grid/useGridCellHover.ts
|
|
14
18
|
function cellsEqual(a, b) {
|
|
15
19
|
if (a === b) return true;
|
|
16
20
|
if (a === null || b === null) return false;
|
|
@@ -64,6 +68,7 @@ var DEFAULT_SUB = {
|
|
|
64
68
|
paint: { kind: "solid", color: "rgba(255, 255, 255, 0.04)" },
|
|
65
69
|
width: 1
|
|
66
70
|
};
|
|
71
|
+
var MIN_SCREEN_SPACING = 4;
|
|
67
72
|
function createGridLayer(opts) {
|
|
68
73
|
const line = opts.style?.line ?? DEFAULT_LINE;
|
|
69
74
|
const accent = opts.style?.accent ?? DEFAULT_ACCENT;
|
|
@@ -71,11 +76,11 @@ function createGridLayer(opts) {
|
|
|
71
76
|
return {
|
|
72
77
|
id: "grid",
|
|
73
78
|
label: "Grid",
|
|
74
|
-
draw: (_data, view) => {
|
|
75
|
-
const b = opts.bounds();
|
|
76
|
-
if (b.width <= 0 || b.height <= 0) return [];
|
|
79
|
+
draw: (_data, view, dims) => {
|
|
77
80
|
const { accentEvery, subdivisions } = opts;
|
|
78
81
|
const spacing = resolveAxes(opts.spacing, opts.unitSystem);
|
|
82
|
+
const b = opts.bounds ? opts.bounds(view, dims) : onLattice(viewportWorldRect(view, dims), spacing, accentEvery && accentEvery > 0 ? accentEvery : 1);
|
|
83
|
+
if (!(b.width > 0 && b.height > 0 && Number.isFinite(b.width) && Number.isFinite(b.height))) return [];
|
|
79
84
|
const x0 = b.x;
|
|
80
85
|
const y0 = b.y;
|
|
81
86
|
const x1 = b.x + b.width;
|
|
@@ -89,55 +94,47 @@ function createGridLayer(opts) {
|
|
|
89
94
|
coords: new Float32Array([sx, sy, ex, ey]),
|
|
90
95
|
fillRule: "nonzero"
|
|
91
96
|
};
|
|
92
|
-
const
|
|
97
|
+
const across = sx === ex ? px.x : px.y;
|
|
93
98
|
children.push({
|
|
94
99
|
kind: "path",
|
|
95
100
|
path,
|
|
96
|
-
stroke: { ...s, width: resolveStrokeWidth(s.width ?? 1, 1) *
|
|
101
|
+
stroke: { ...s, width: resolveStrokeWidth(s.width ?? 1, 1) * across }
|
|
97
102
|
});
|
|
98
103
|
};
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (k %
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
for (let y = y0; y <= y1 + 1e-9; y += stepY) {
|
|
108
|
-
const k = Math.round((y - y0) / stepY);
|
|
109
|
-
if (k % subdivisions === 0) continue;
|
|
110
|
-
pushSegment(x0, y, x1, y, sub);
|
|
104
|
+
const family = (axis, step, skipEvery, s) => {
|
|
105
|
+
if (!(step * Math.abs(view.scale[axis]) >= MIN_SCREEN_SPACING)) return;
|
|
106
|
+
const [from, to] = axis === "x" ? [x0, x1] : [y0, y1];
|
|
107
|
+
for (let k = 0; from + k * step <= to + 1e-9; k++) {
|
|
108
|
+
if (skipEvery > 0 && k % skipEvery === 0) continue;
|
|
109
|
+
const at = from + k * step;
|
|
110
|
+
if (axis === "x") pushSegment(at, y0, at, y1, s);
|
|
111
|
+
else pushSegment(x0, at, x1, at, s);
|
|
111
112
|
}
|
|
113
|
+
};
|
|
114
|
+
const accentOn = accentEvery !== void 0 && accentEvery > 0;
|
|
115
|
+
for (const axis of ["x", "y"]) {
|
|
116
|
+
if (subdivisions && subdivisions > 1) family(axis, spacing[axis] / subdivisions, subdivisions, sub);
|
|
112
117
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
if (!isAccent) pushSegment(x, y0, x, y1, line);
|
|
117
|
-
idx++;
|
|
118
|
-
}
|
|
119
|
-
idx = 0;
|
|
120
|
-
for (let y = y0; y <= y1 + 1e-9; y += spacing.y) {
|
|
121
|
-
const isAccent = !!accentEvery && accentEvery > 0 && idx % accentEvery === 0;
|
|
122
|
-
if (!isAccent) pushSegment(x0, y, x1, y, line);
|
|
123
|
-
idx++;
|
|
124
|
-
}
|
|
125
|
-
if (accentEvery && accentEvery > 0) {
|
|
126
|
-
idx = 0;
|
|
127
|
-
for (let x = x0; x <= x1 + 1e-9; x += spacing.x) {
|
|
128
|
-
if (idx % accentEvery === 0) pushSegment(x, y0, x, y1, accent);
|
|
129
|
-
idx++;
|
|
130
|
-
}
|
|
131
|
-
idx = 0;
|
|
132
|
-
for (let y = y0; y <= y1 + 1e-9; y += spacing.y) {
|
|
133
|
-
if (idx % accentEvery === 0) pushSegment(x0, y, x1, y, accent);
|
|
134
|
-
idx++;
|
|
135
|
-
}
|
|
118
|
+
for (const axis of ["x", "y"]) family(axis, spacing[axis], accentOn ? accentEvery : 0, line);
|
|
119
|
+
if (accentOn) {
|
|
120
|
+
for (const axis of ["x", "y"]) family(axis, spacing[axis] * accentEvery, 0, accent);
|
|
136
121
|
}
|
|
137
122
|
return children;
|
|
138
123
|
}
|
|
139
124
|
};
|
|
140
125
|
}
|
|
126
|
+
function onLattice(r, spacing, accentEvery) {
|
|
127
|
+
const px = spacing.x * accentEvery;
|
|
128
|
+
const py = spacing.y * accentEvery;
|
|
129
|
+
const x = Math.floor(r.x / px) * px;
|
|
130
|
+
const y = Math.floor(r.y / py) * py;
|
|
131
|
+
return {
|
|
132
|
+
x,
|
|
133
|
+
y,
|
|
134
|
+
width: Math.ceil((r.x + r.width) / px) * px - x,
|
|
135
|
+
height: Math.ceil((r.y + r.height) / py) * py - y
|
|
136
|
+
};
|
|
137
|
+
}
|
|
141
138
|
var DEFAULT_FILL = { paint: { kind: "solid", color: "rgba(127,176,105,0.15)" } };
|
|
142
139
|
function createCellHighlightLayer(opts) {
|
|
143
140
|
const fill = opts.fill ?? DEFAULT_FILL;
|
|
@@ -190,24 +187,6 @@ function useGuides(initial = []) {
|
|
|
190
187
|
return { guides, getGuides, addGuide, removeGuide, clearGuides };
|
|
191
188
|
}
|
|
192
189
|
var DEFAULT_COLOR = "#3aa0ff";
|
|
193
|
-
var LABEL_FONT = { fontSize: 10 };
|
|
194
|
-
var LABEL_CHAR_W = 6;
|
|
195
|
-
var LABEL_H = 14;
|
|
196
|
-
var LABEL_PAD_X = 4;
|
|
197
|
-
function tickedSegment(x0, y0, x1, y1, t) {
|
|
198
|
-
const h = t / 2;
|
|
199
|
-
const vertical = x0 === x1;
|
|
200
|
-
const ends = t <= 0 ? [] : vertical ? [x0 - h, y0, x0 + h, y0, x1 - h, y1, x1 + h, y1] : [x0, y0 - h, x0, y0 + h, x1, y1 - h, x1, y1 + h];
|
|
201
|
-
const coords = [x0, y0, x1, y1, ...ends];
|
|
202
|
-
const commands = [];
|
|
203
|
-
for (let i = 0; i < coords.length; i += 4) commands.push(PATH_M, PATH_L);
|
|
204
|
-
return {
|
|
205
|
-
kind: "polygon",
|
|
206
|
-
commands: new Uint8Array(commands),
|
|
207
|
-
coords: new Float32Array(coords),
|
|
208
|
-
fillRule: "nonzero"
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
190
|
function createGuidesLayer(opts) {
|
|
212
191
|
const lineWidth = opts.lineWidth ?? 1;
|
|
213
192
|
const color = opts.color ?? DEFAULT_COLOR;
|
|
@@ -244,19 +223,7 @@ function createGuidesLayer(opts) {
|
|
|
244
223
|
}
|
|
245
224
|
for (const g of gaps) {
|
|
246
225
|
const [x0, y0, x1, y1] = g.axis === "x" ? [sx(g.min), sy(g.at), sx(g.max), sy(g.at)] : [sx(g.at), sy(g.min), sx(g.at), sy(g.max)];
|
|
247
|
-
out.push(
|
|
248
|
-
if (format === null) continue;
|
|
249
|
-
const text = format(g.max - g.min);
|
|
250
|
-
const w = text.length * LABEL_CHAR_W + LABEL_PAD_X * 2;
|
|
251
|
-
const left = (x0 + x1) / 2 - w / 2;
|
|
252
|
-
const top = (y0 + y1) / 2 - LABEL_H / 2;
|
|
253
|
-
out.push({ kind: "path", path: rectPath(left, top, w, LABEL_H), fill: { paint: { kind: "solid", color: gapColor } } });
|
|
254
|
-
out.push(textCommandFromRuns(
|
|
255
|
-
left + LABEL_PAD_X,
|
|
256
|
-
top + 2,
|
|
257
|
-
[{ text, fill: { paint: { kind: "solid", color: "#fff" } } }],
|
|
258
|
-
LABEL_FONT
|
|
259
|
-
));
|
|
226
|
+
out.push(...measuredSegmentCommands(x0, y0, x1, y1, ticks, gapStroke, format?.(g.max - g.min) ?? null, gapColor));
|
|
260
227
|
}
|
|
261
228
|
return out;
|
|
262
229
|
}
|
|
@@ -302,57 +269,6 @@ function deriveAlignmentGuides(targets, opts = {}) {
|
|
|
302
269
|
return [...seenX.values(), ...seenY.values()];
|
|
303
270
|
}
|
|
304
271
|
|
|
305
|
-
// src/guides/alignment/match.ts
|
|
306
|
-
var MOVE_ANCHORS = {
|
|
307
|
-
x: ["min", "center", "max"],
|
|
308
|
-
y: ["min", "center", "max"]
|
|
309
|
-
};
|
|
310
|
-
function featureOffset(b, axis, anchor) {
|
|
311
|
-
if (axis === "x") {
|
|
312
|
-
if (anchor === "min") return b.x;
|
|
313
|
-
if (anchor === "center") return b.x + b.width / 2;
|
|
314
|
-
return b.x + b.width;
|
|
315
|
-
}
|
|
316
|
-
if (anchor === "min") return b.y;
|
|
317
|
-
if (anchor === "center") return b.y + b.height / 2;
|
|
318
|
-
return b.y + b.height;
|
|
319
|
-
}
|
|
320
|
-
function bestAxis(b, axis, anchors, candidates, worldTolerance) {
|
|
321
|
-
let bestAbs = Infinity;
|
|
322
|
-
let bestDelta = 0;
|
|
323
|
-
let bestGuide = null;
|
|
324
|
-
for (const anchor of anchors) {
|
|
325
|
-
const o = featureOffset(b, axis, anchor);
|
|
326
|
-
for (const g of candidates) {
|
|
327
|
-
if (g.axis !== axis) continue;
|
|
328
|
-
const d = g.offset - o;
|
|
329
|
-
const ad = Math.abs(d);
|
|
330
|
-
if (ad <= worldTolerance && ad < bestAbs) {
|
|
331
|
-
bestAbs = ad;
|
|
332
|
-
bestDelta = d;
|
|
333
|
-
bestGuide = g;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return { delta: bestGuide ? bestDelta : 0, guide: bestGuide };
|
|
338
|
-
}
|
|
339
|
-
function matchAlignment(bounds, candidates, worldTolerance, anchors) {
|
|
340
|
-
const x = bestAxis(bounds, "x", anchors.x, candidates, worldTolerance.x);
|
|
341
|
-
const y = bestAxis(bounds, "y", anchors.y, candidates, worldTolerance.y);
|
|
342
|
-
const top = bounds.y + y.delta;
|
|
343
|
-
const left = bounds.x + x.delta;
|
|
344
|
-
return {
|
|
345
|
-
dx: x.delta,
|
|
346
|
-
dy: y.delta,
|
|
347
|
-
activeX: stretchSpan(x.guide, top, top + bounds.height),
|
|
348
|
-
activeY: stretchSpan(y.guide, left, left + bounds.width)
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
function stretchSpan(g, min, max) {
|
|
352
|
-
if (g === null || g.span === void 0) return g;
|
|
353
|
-
return { ...g, span: { min: Math.min(g.span.min, min), max: Math.max(g.span.max, max) } };
|
|
354
|
-
}
|
|
355
|
-
|
|
356
272
|
// src/guides/alignment/spacing.ts
|
|
357
273
|
var EPS2 = 1e-6;
|
|
358
274
|
var lo = (b, a) => a === "x" ? b.x : b.y;
|
|
@@ -373,9 +289,9 @@ function hitsStrip(g, b, span) {
|
|
|
373
289
|
return lo(b, g.axis) < g.max - EPS2 && hi(b, g.axis) > g.min + EPS2 && lo(b, c) < span[1] - EPS2 && hi(b, c) > span[0] + EPS2;
|
|
374
290
|
}
|
|
375
291
|
function measureGaps(targets, axis) {
|
|
376
|
-
return
|
|
292
|
+
return measureGapSpans(targets, axis).map((m) => m.gap);
|
|
377
293
|
}
|
|
378
|
-
function
|
|
294
|
+
function measureGapSpans(targets, axis) {
|
|
379
295
|
const out = [];
|
|
380
296
|
for (const a of targets) {
|
|
381
297
|
let next = null;
|
|
@@ -392,7 +308,7 @@ function measure(targets, axis) {
|
|
|
392
308
|
}
|
|
393
309
|
return out.sort((p, q) => p.gap.min - q.gap.min || p.gap.at - q.gap.at);
|
|
394
310
|
}
|
|
395
|
-
function
|
|
311
|
+
function spacingOffers(b, axis, edge, targets, refs) {
|
|
396
312
|
const bLo = lo(b, axis);
|
|
397
313
|
const bHi = hi(b, axis);
|
|
398
314
|
const size = bHi - bLo;
|
|
@@ -405,10 +321,9 @@ function bestAxis2(b, axis, edge, targets, refs, tol) {
|
|
|
405
321
|
if (lo(t, axis) >= center && (right === null || lo(t, axis) < lo(right, axis))) right = t;
|
|
406
322
|
}
|
|
407
323
|
const sizes = refs.map((g) => g.max - g.min);
|
|
408
|
-
|
|
324
|
+
const out = [];
|
|
409
325
|
const offer = (delta, gap) => {
|
|
410
|
-
if (gap
|
|
411
|
-
if (best === null || Math.abs(delta) < Math.abs(best.delta)) best = { delta, size: gap };
|
|
326
|
+
if (gap > EPS2) out.push({ delta, size: gap });
|
|
412
327
|
};
|
|
413
328
|
const L = left === null ? null : hi(left, axis);
|
|
414
329
|
const R = right === null ? null : lo(right, axis);
|
|
@@ -428,28 +343,39 @@ function bestAxis2(b, axis, edge, targets, refs, tol) {
|
|
|
428
343
|
const own = R !== null ? [R - bHi] : [];
|
|
429
344
|
for (const g of [...sizes, ...own]) if (L + g < bHi - EPS2) offer(L + g - bLo, g);
|
|
430
345
|
}
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
function bestAxis(b, axis, edge, targets, refs, tol) {
|
|
349
|
+
let best = null;
|
|
350
|
+
for (const o of spacingOffers(b, axis, edge, targets, refs)) {
|
|
351
|
+
if (Math.abs(o.delta) > tol) continue;
|
|
352
|
+
if (best === null || Math.abs(o.delta) < Math.abs(best.delta)) best = o;
|
|
353
|
+
}
|
|
431
354
|
return best;
|
|
432
355
|
}
|
|
433
|
-
function
|
|
356
|
+
function equalGaps(b, axis, size, targets, refs) {
|
|
434
357
|
const eq = (g) => Math.abs(g.max - g.min - size) < 1e-3;
|
|
435
358
|
const out = refs.filter(eq);
|
|
436
|
-
const
|
|
359
|
+
const near = targets.filter((t) => hi(t, axis) > lo(b, axis) - size - EPS2 && lo(t, axis) < hi(b, axis) + size + EPS2);
|
|
360
|
+
const own = measureGaps([...near, b], axis).filter((g) => (Math.abs(g.max - lo(b, axis)) < EPS2 || Math.abs(g.min - hi(b, axis)) < EPS2) && eq(g));
|
|
437
361
|
return [...out, ...own];
|
|
438
362
|
}
|
|
363
|
+
function referenceGaps(bounds, measured) {
|
|
364
|
+
return measured.filter((m) => !hitsStrip(m.gap, bounds, m.span)).map((m) => m.gap);
|
|
365
|
+
}
|
|
439
366
|
function matchSpacing(bounds, targets, worldTolerance, moving = { x: "both", y: "both" }) {
|
|
440
|
-
const
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
const
|
|
444
|
-
const y = moving.y === null ? null : bestAxis2(bounds, "y", moving.y, targets, refsY, worldTolerance.y);
|
|
367
|
+
const refsX = referenceGaps(bounds, measureGapSpans(targets, "x"));
|
|
368
|
+
const refsY = referenceGaps(bounds, measureGapSpans(targets, "y"));
|
|
369
|
+
const x = moving.x === null ? null : bestAxis(bounds, "x", moving.x, targets, refsX, worldTolerance.x);
|
|
370
|
+
const y = moving.y === null ? null : bestAxis(bounds, "y", moving.y, targets, refsY, worldTolerance.y);
|
|
445
371
|
const dx = x?.delta ?? 0;
|
|
446
372
|
const dy = y?.delta ?? 0;
|
|
447
373
|
const final = applyEdges(bounds, dx, dy, moving);
|
|
448
374
|
return {
|
|
449
375
|
dx,
|
|
450
376
|
dy,
|
|
451
|
-
gapsX: x === null ? [] :
|
|
452
|
-
gapsY: y === null ? [] :
|
|
377
|
+
gapsX: x === null ? [] : equalGaps(final, "x", x.size, targets, refsX),
|
|
378
|
+
gapsY: y === null ? [] : equalGaps(final, "y", y.size, targets, refsY)
|
|
453
379
|
};
|
|
454
380
|
}
|
|
455
381
|
function applyEdges(b, dx, dy, moving) {
|
|
@@ -466,223 +392,378 @@ function applyEdges(b, dx, dy, moving) {
|
|
|
466
392
|
} else if (moving.y === "max") height += dy;
|
|
467
393
|
return { x, y, width, height };
|
|
468
394
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const span = g.span && range(g.span, (v) => into(m, across(g.axis), v));
|
|
479
|
-
return { ...g, offset: guideOffsetOn(g, m), ...span ? { span } : {} };
|
|
480
|
-
}
|
|
481
|
-
function guideFromPlane(g, m) {
|
|
482
|
-
const span = g.span && range(g.span, (v) => outOf(m, across(g.axis), v));
|
|
483
|
-
return { ...g, offset: outOf(m, g.axis, g.offset), ...span ? { span } : {} };
|
|
484
|
-
}
|
|
485
|
-
function gapFromPlane(g, m) {
|
|
486
|
-
return { ...range(g, (v) => outOf(m, g.axis, v)), at: outOf(m, across(g.axis), g.at) };
|
|
487
|
-
}
|
|
488
|
-
function alignmentOnPlane(args, plane) {
|
|
489
|
-
if (!plane) return args;
|
|
490
|
-
const spacing = args.getSpacingTargets;
|
|
491
|
-
const setGaps = args.setActiveGaps;
|
|
492
|
-
return {
|
|
493
|
-
...args,
|
|
494
|
-
getCandidates: () => args.getCandidates().map((g) => guideToPlane(g, plane)),
|
|
495
|
-
setActiveGuides: (guides) => args.setActiveGuides(guides.map((g) => guideFromPlane(g, plane))),
|
|
496
|
-
...spacing ? { getSpacingTargets: () => spacing().map((b) => rectToPlane(plane, b)) } : {},
|
|
497
|
-
...setGaps ? { setActiveGaps: (gaps) => setGaps(gaps.map((g) => gapFromPlane(g, plane))) } : {}
|
|
498
|
-
};
|
|
395
|
+
function worldBoundsOf(scene, ids, opts = {}) {
|
|
396
|
+
const d = opts.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
|
|
397
|
+
const frame = scenePoseFrame(scene);
|
|
398
|
+
const out = [];
|
|
399
|
+
for (const id of ids) {
|
|
400
|
+
if (scene.get(id) === void 0) continue;
|
|
401
|
+
out.push(visualBoundsViaDescriptor(frame.world(id), d));
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
499
404
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
function
|
|
505
|
-
const
|
|
506
|
-
const
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
405
|
+
function worldUnionOf(scene, ids, opts = {}) {
|
|
406
|
+
const boxes = worldBoundsOf(scene, ids, opts);
|
|
407
|
+
return boxes.length === 0 ? null : unionAABB(boxes);
|
|
408
|
+
}
|
|
409
|
+
function siblingIds(scene, ids) {
|
|
410
|
+
const moving = new Set(ids);
|
|
411
|
+
const gates = nodeGates(scene);
|
|
412
|
+
const parents = /* @__PURE__ */ new Set();
|
|
413
|
+
for (const id of ids) {
|
|
414
|
+
const node = scene.get(id);
|
|
415
|
+
if (node !== void 0) parents.add(node.parent);
|
|
511
416
|
}
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
417
|
+
const siblings = [];
|
|
418
|
+
for (const parent of parents) {
|
|
419
|
+
for (const id of parent === null ? scene.roots : scene.childrenOf(parent)) {
|
|
420
|
+
const node = scene.get(id);
|
|
421
|
+
if (moving.has(id) || node === void 0 || gates.hidden(id)) continue;
|
|
422
|
+
siblings.push(id);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return siblings;
|
|
426
|
+
}
|
|
427
|
+
function siblingBounds(scene, ids, opts = {}) {
|
|
428
|
+
return worldBoundsOf(scene, siblingIds(scene, ids), opts);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/strategies/onPlane.ts
|
|
432
|
+
function guideOffsetOn(g, plane) {
|
|
433
|
+
return plane ? g.offset * plane.scale[g.axis] + plane.offset[g.axis] : g.offset;
|
|
434
|
+
}
|
|
435
|
+
function roundToGridOn(value, axis, spacing, plane) {
|
|
436
|
+
if (!plane) return Math.round(value / spacing) * spacing;
|
|
437
|
+
const camera = (value - plane.offset[axis]) / plane.scale[axis];
|
|
438
|
+
return Math.round(camera / spacing) * spacing * plane.scale[axis] + plane.offset[axis];
|
|
525
439
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
440
|
+
|
|
441
|
+
// src/sources/lines.ts
|
|
442
|
+
function axisLine(axis, offset, near) {
|
|
443
|
+
return axis === "x" ? { at: { x: offset, y: near.y }, dirs: [{ x: 0, y: 1 }] } : { at: { x: near.x, y: offset }, dirs: [{ x: 1, y: 0 }] };
|
|
529
444
|
}
|
|
530
|
-
function
|
|
531
|
-
return
|
|
445
|
+
function landedShift(q, delta) {
|
|
446
|
+
return { x: delta.x - q.delta0.x, y: delta.y - q.delta0.y };
|
|
532
447
|
}
|
|
533
|
-
|
|
534
|
-
|
|
448
|
+
|
|
449
|
+
// src/sources/grid.ts
|
|
450
|
+
function gridSnapSource(opts) {
|
|
451
|
+
const pitch = resolveAxes(opts.spacing, opts.unitSystem);
|
|
452
|
+
const o = opts.origin ?? { x: 0, y: 0 };
|
|
453
|
+
const lines = (v, axis, plane) => {
|
|
454
|
+
const s = plane ? plane.scale[axis] : 1;
|
|
455
|
+
const near = roundToGridOn(v - o[axis] * s, axis, pitch[axis], plane) + o[axis] * s;
|
|
456
|
+
const step = pitch[axis] * s;
|
|
457
|
+
return [near, near + (v >= near ? step : -step)];
|
|
458
|
+
};
|
|
535
459
|
return {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
const originPose = ctx.origin.get(id);
|
|
546
|
-
if (originPose === void 0) continue;
|
|
547
|
-
boxes.push(visualBoundsViaDescriptor(
|
|
548
|
-
translatePoseViaDescriptor(originPose, transform.dx, transform.dy, d),
|
|
549
|
-
d
|
|
550
|
-
));
|
|
460
|
+
id: "grid",
|
|
461
|
+
tier: "fallback",
|
|
462
|
+
reachPx: Infinity,
|
|
463
|
+
roles: ["origin", "pointer"],
|
|
464
|
+
*collect(q) {
|
|
465
|
+
for (const p of q.probes) {
|
|
466
|
+
for (const axis of ["x", "y"]) {
|
|
467
|
+
for (const at of lines(p.at[axis], axis, q.plane ?? null)) yield { probe: p.id, locus: axisLine(axis, at, p.at) };
|
|
468
|
+
}
|
|
551
469
|
}
|
|
552
|
-
const union = unionBounds(boxes);
|
|
553
|
-
if (union === null) return;
|
|
554
|
-
const r = resolve(union, args, worldTol(args, ctx), MOVE_ANCHORS, { x: "both", y: "both" });
|
|
555
|
-
publish(args, r);
|
|
556
|
-
if (r === null) return;
|
|
557
|
-
return { transform: { kind: "translate", dx: transform.dx + r.dx, dy: transform.dy + r.dy } };
|
|
558
|
-
},
|
|
559
|
-
onEnd() {
|
|
560
|
-
publish(given, null);
|
|
561
|
-
},
|
|
562
|
-
onCancel() {
|
|
563
|
-
publish(given, null);
|
|
564
470
|
}
|
|
565
471
|
};
|
|
566
472
|
}
|
|
567
|
-
|
|
568
|
-
|
|
473
|
+
|
|
474
|
+
// src/sources/guides.ts
|
|
475
|
+
function guideSnapSource(getGuides) {
|
|
569
476
|
return {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
477
|
+
id: "guides",
|
|
478
|
+
roles: ["origin", "pointer"],
|
|
479
|
+
*collect(q) {
|
|
480
|
+
const guides = getGuides();
|
|
481
|
+
if (guides.length === 0) return;
|
|
482
|
+
const reach = q.reachWorld(q.reachPx);
|
|
483
|
+
for (const p of q.probes) {
|
|
484
|
+
for (const g of guides) {
|
|
485
|
+
const offset = guideOffsetOn(g, q.plane ?? null);
|
|
486
|
+
if (Math.abs(p.at[g.axis] - offset) > reach) continue;
|
|
487
|
+
yield { probe: p.id, locus: axisLine(g.axis, offset, p.at) };
|
|
488
|
+
}
|
|
581
489
|
}
|
|
582
|
-
args.setActiveGuides(activeList(m));
|
|
583
|
-
return { current: { x: current.x + m.dx, y: current.y + m.dy } };
|
|
584
|
-
},
|
|
585
|
-
onEnd() {
|
|
586
|
-
given.setActiveGuides([]);
|
|
587
|
-
},
|
|
588
|
-
onCancel() {
|
|
589
|
-
given.setActiveGuides([]);
|
|
590
490
|
}
|
|
591
491
|
};
|
|
592
492
|
}
|
|
593
|
-
|
|
594
|
-
|
|
493
|
+
|
|
494
|
+
// src/guides/alignment/match.ts
|
|
495
|
+
function stretchSpan(g, min, max) {
|
|
496
|
+
if (g === null || g.span === void 0) return g;
|
|
497
|
+
return { ...g, span: { min: Math.min(g.span.min, min), max: Math.max(g.span.max, max) } };
|
|
498
|
+
}
|
|
499
|
+
function targetIds(scene, exclude) {
|
|
500
|
+
if (exclude.length > 0) return siblingIds(scene, exclude);
|
|
501
|
+
const gates = nodeGates(scene);
|
|
502
|
+
return scene.roots.filter((id) => !gates.hidden(id));
|
|
503
|
+
}
|
|
504
|
+
function targetBoxes(q, scene) {
|
|
505
|
+
const drawn = q.drawnBox;
|
|
506
|
+
return drawn ? targetIds(scene, q.exclude).flatMap((id) => drawn(id) ?? []) : [];
|
|
507
|
+
}
|
|
508
|
+
function cachedPerScene(derive) {
|
|
509
|
+
let key = null;
|
|
510
|
+
let value = null;
|
|
511
|
+
return (q) => {
|
|
512
|
+
const scene = q.scene;
|
|
513
|
+
if (!scene) return null;
|
|
514
|
+
const version = scene.getVersion();
|
|
515
|
+
const exclude = q.exclude.join("\n");
|
|
516
|
+
if (key === null || key.scene !== scene || key.version !== version || key.exclude !== exclude) {
|
|
517
|
+
value = derive(targetBoxes(q, scene));
|
|
518
|
+
key = { scene, version, exclude };
|
|
519
|
+
}
|
|
520
|
+
return value;
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/sources/alignment.ts
|
|
525
|
+
function indexLines(lines) {
|
|
526
|
+
const sorted = [...lines].sort((a, b) => a.offset - b.offset);
|
|
527
|
+
return { x: sorted.filter((g) => g.axis === "x"), y: sorted.filter((g) => g.axis === "y") };
|
|
528
|
+
}
|
|
529
|
+
function* inReach(lines, v, reach) {
|
|
530
|
+
let lo3 = 0;
|
|
531
|
+
let hi3 = lines.length;
|
|
532
|
+
while (lo3 < hi3) {
|
|
533
|
+
const mid2 = lo3 + hi3 >> 1;
|
|
534
|
+
if (lines[mid2].offset < v - reach) lo3 = mid2 + 1;
|
|
535
|
+
else hi3 = mid2;
|
|
536
|
+
}
|
|
537
|
+
for (let i = lo3; i < lines.length && lines[i].offset <= v + reach; i++) yield lines[i];
|
|
538
|
+
}
|
|
539
|
+
var cross2 = (a) => a === "x" ? "y" : "x";
|
|
540
|
+
function lineMark(g, extent, color) {
|
|
541
|
+
const span = stretchSpan(g, extent[0], extent[1])?.span ?? { min: extent[0], max: extent[1] };
|
|
542
|
+
const ends = g.axis === "x" ? { from: { x: g.offset, y: span.min }, to: { x: g.offset, y: span.max } } : { from: { x: span.min, y: g.offset }, to: { x: span.max, y: g.offset } };
|
|
543
|
+
return { kind: "line", ...ends, ...color === void 0 ? {} : { color } };
|
|
544
|
+
}
|
|
545
|
+
function alignmentSnapSource(opts = {}) {
|
|
546
|
+
const page = () => (typeof opts.page === "function" ? opts.page() : opts.page) ?? void 0;
|
|
547
|
+
const fromScene = cachedPerScene((boxes) => indexLines(deriveAlignmentGuides(boxes)));
|
|
548
|
+
const linesFor = (q) => {
|
|
549
|
+
const pageBox = page();
|
|
550
|
+
const own = opts.getCandidates ? indexLines(opts.getCandidates()) : fromScene(q);
|
|
551
|
+
return [...own ? [own] : [], ...pageBox ? [indexLines(deriveAlignmentGuides([], { page: pageBox }))] : []];
|
|
552
|
+
};
|
|
595
553
|
return {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
y += m.ly;
|
|
618
|
-
height -= m.ly;
|
|
554
|
+
id: "alignment",
|
|
555
|
+
...opts.reachPx === void 0 ? {} : { reachPx: opts.reachPx },
|
|
556
|
+
roles: ["box", "pointer"],
|
|
557
|
+
*collect(q) {
|
|
558
|
+
const indexes = linesFor(q);
|
|
559
|
+
if (indexes.length === 0) return;
|
|
560
|
+
const reach = q.reachWorld(q.reachPx);
|
|
561
|
+
const box = q.box ?? null;
|
|
562
|
+
const probes = q.probes.some((p) => p.role === "box") ? q.probes.filter((p) => p.role === "box") : q.probes;
|
|
563
|
+
for (const p of probes) {
|
|
564
|
+
for (const axis of ["x", "y"]) {
|
|
565
|
+
for (const index of indexes) {
|
|
566
|
+
for (const g of inReach(index[axis], p.at[axis], reach)) {
|
|
567
|
+
yield {
|
|
568
|
+
probe: p.id,
|
|
569
|
+
locus: axisLine(axis, g.offset, p.at),
|
|
570
|
+
mark: (delta) => lineMark(g, extentAcross(p, box, axis, landedShift(q, delta), q.edges), opts.color)
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
619
575
|
}
|
|
620
|
-
publish(args, { guides: m.guides, gaps: m.gaps });
|
|
621
|
-
return { pose: { ...pose, x, y, width, height } };
|
|
622
|
-
},
|
|
623
|
-
onEnd() {
|
|
624
|
-
publish(given, null);
|
|
625
|
-
},
|
|
626
|
-
onCancel() {
|
|
627
|
-
publish(given, null);
|
|
628
576
|
}
|
|
629
577
|
};
|
|
630
578
|
}
|
|
631
|
-
function
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
function
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
579
|
+
function extentAcross(p, box, axis, shift, edges = { x: "both", y: "both" }) {
|
|
580
|
+
const c = cross2(axis);
|
|
581
|
+
if (p.role !== "box" || box === null) return [p.at[c] + shift[c], p.at[c] + shift[c]];
|
|
582
|
+
const landed = applyEdges(box, shift.x, shift.y, edges);
|
|
583
|
+
const lo3 = c === "x" ? landed.x : landed.y;
|
|
584
|
+
return [lo3, lo3 + (c === "x" ? landed.width : landed.height)];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/sources/spacing.ts
|
|
588
|
+
var measureAll = (boxes) => ({ boxes, x: measureGapSpans(boxes, "x"), y: measureGapSpans(boxes, "y") });
|
|
589
|
+
function gapMark(g, color) {
|
|
590
|
+
const ends = g.axis === "x" ? { from: { x: g.min, y: g.at }, to: { x: g.max, y: g.at } } : { from: { x: g.at, y: g.min }, to: { x: g.at, y: g.max } };
|
|
591
|
+
return { kind: "gap", ...ends, value: g.max - g.min, ...color === void 0 ? {} : { color } };
|
|
592
|
+
}
|
|
593
|
+
function spacingSnapSource(opts = {}) {
|
|
594
|
+
const fromScene = cachedPerScene(measureAll);
|
|
595
|
+
return {
|
|
596
|
+
id: "spacing",
|
|
597
|
+
...opts.reachPx === void 0 ? {} : { reachPx: opts.reachPx },
|
|
598
|
+
roles: ["box"],
|
|
599
|
+
gestures: ["move", "resize"],
|
|
600
|
+
*collect(q) {
|
|
601
|
+
const box = q.box;
|
|
602
|
+
const probe = q.probes.find((p) => p.role === "box");
|
|
603
|
+
const measured = opts.getTargets ? measureAll(opts.getTargets()) : fromScene(q);
|
|
604
|
+
if (!box || !probe || !measured) return;
|
|
605
|
+
const reach = q.reachWorld(q.reachPx);
|
|
606
|
+
const edges = q.edges ?? { x: "both", y: "both" };
|
|
607
|
+
for (const axis of ["x", "y"]) {
|
|
608
|
+
const edge = edges[axis];
|
|
609
|
+
if (edge === null) continue;
|
|
610
|
+
const refs = referenceGaps(box, measured[axis]);
|
|
611
|
+
const seen = /* @__PURE__ */ new Set();
|
|
612
|
+
for (const o of spacingOffers(box, axis, edge, measured.boxes, refs)) {
|
|
613
|
+
const key = `${o.delta.toFixed(6)}:${o.size.toFixed(6)}`;
|
|
614
|
+
if (Math.abs(o.delta) > reach || seen.has(key)) continue;
|
|
615
|
+
seen.add(key);
|
|
616
|
+
yield {
|
|
617
|
+
probe: probe.id,
|
|
618
|
+
locus: axisLine(axis, probe.at[axis] + o.delta, probe.at),
|
|
619
|
+
mark: (delta) => {
|
|
620
|
+
const shift = landedShift(q, delta);
|
|
621
|
+
const landed = applyEdges(box, shift.x, shift.y, edges);
|
|
622
|
+
return equalGaps(landed, axis, o.size, measured.boxes, refs).map((g) => gapMark(g, opts.color));
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
652
628
|
};
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/spacingHints/distance.ts
|
|
632
|
+
var EPS3 = 1e-6;
|
|
633
|
+
var lo2 = (b, a) => a === "x" ? b.x : b.y;
|
|
634
|
+
var hi2 = (b, a) => a === "x" ? b.x + b.width : b.y + b.height;
|
|
635
|
+
var mid = (b, a) => (lo2(b, a) + hi2(b, a)) / 2;
|
|
636
|
+
var cross3 = (a) => a === "x" ? "y" : "x";
|
|
637
|
+
function measureDistances(from, to) {
|
|
638
|
+
const out = { gaps: [], leaders: [] };
|
|
639
|
+
for (const axis of ["x", "y"]) measureAxis(from, to, axis, out);
|
|
640
|
+
return out;
|
|
641
|
+
}
|
|
642
|
+
function measureAxis(from, to, axis, out) {
|
|
643
|
+
const c = cross3(axis);
|
|
644
|
+
const sharedMin = Math.max(lo2(from, c), lo2(to, c));
|
|
645
|
+
const sharedMax = Math.min(hi2(from, c), hi2(to, c));
|
|
646
|
+
const shares = sharedMax - sharedMin > EPS3;
|
|
647
|
+
const push = (min, max, at2) => {
|
|
648
|
+
if (max - min > EPS3) out.gaps.push({ axis, min, max, at: at2 });
|
|
658
649
|
};
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const edge = movingEdge(along)[0];
|
|
671
|
-
let best = null;
|
|
672
|
-
for (const across3 of movingEdge(along === "x" ? "y" : "x")) {
|
|
673
|
-
const c = along === "x" ? drawn(edge, across3) : drawn(across3, edge);
|
|
674
|
-
for (const g of candidates) {
|
|
675
|
-
const reach = g.axis === "x" ? dir.x : dir.y;
|
|
676
|
-
if (Math.abs(reach) < 1e-9) continue;
|
|
677
|
-
const t = (g.offset - (g.axis === "x" ? c.x : c.y)) / reach;
|
|
678
|
-
if (Math.abs(t * dir.x) > tol.x || Math.abs(t * dir.y) > tol.y) continue;
|
|
679
|
-
if (best === null || Math.abs(t) < Math.abs(best.t)) best = { t, guide: g, at: c };
|
|
650
|
+
const before = hi2(from, axis) <= lo2(to, axis) + EPS3;
|
|
651
|
+
const after = hi2(to, axis) <= lo2(from, axis) + EPS3;
|
|
652
|
+
if (before || after) {
|
|
653
|
+
const [min, max] = before ? [hi2(from, axis), lo2(to, axis)] : [hi2(to, axis), lo2(from, axis)];
|
|
654
|
+
if (max - min <= EPS3) return;
|
|
655
|
+
const at2 = shares ? (sharedMin + sharedMax) / 2 : mid(from, c);
|
|
656
|
+
push(min, max, at2);
|
|
657
|
+
if (!shares) {
|
|
658
|
+
const edge = before ? lo2(to, axis) : hi2(to, axis);
|
|
659
|
+
const span = at2 < lo2(to, c) ? { min: at2, max: lo2(to, c) } : { min: hi2(to, c), max: at2 };
|
|
660
|
+
out.leaders.push({ id: `distance:${axis}`, axis, offset: edge, span });
|
|
680
661
|
}
|
|
662
|
+
return;
|
|
681
663
|
}
|
|
682
|
-
if (
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
664
|
+
if (!shares) return;
|
|
665
|
+
const [inner, outer] = lo2(from, axis) >= lo2(to, axis) - EPS3 && hi2(from, axis) <= hi2(to, axis) + EPS3 ? [from, to] : lo2(to, axis) >= lo2(from, axis) - EPS3 && hi2(to, axis) <= hi2(from, axis) + EPS3 ? [to, from] : [null, null];
|
|
666
|
+
if (inner === null || outer === null) return;
|
|
667
|
+
const at = mid(inner, c);
|
|
668
|
+
push(lo2(outer, axis), lo2(inner, axis), at);
|
|
669
|
+
push(hi2(inner, axis), hi2(outer, axis), at);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/spacingHints/distanceReadout.ts
|
|
673
|
+
function marksOf(r, color) {
|
|
674
|
+
const c = color === void 0 ? {} : { color };
|
|
675
|
+
return [
|
|
676
|
+
...r.gaps.map((g) => g.axis === "x" ? { kind: "distance", from: { x: g.min, y: g.at }, to: { x: g.max, y: g.at }, value: g.max - g.min, ...c } : { kind: "distance", from: { x: g.at, y: g.min }, to: { x: g.at, y: g.max }, value: g.max - g.min, ...c }),
|
|
677
|
+
...r.leaders.flatMap((l) => {
|
|
678
|
+
if (l.span === void 0) return [];
|
|
679
|
+
return [l.axis === "x" ? { kind: "line", from: { x: l.offset, y: l.span.min }, to: { x: l.offset, y: l.span.max }, ...c } : { kind: "line", from: { x: l.span.min, y: l.offset }, to: { x: l.span.max, y: l.offset }, ...c }];
|
|
680
|
+
})
|
|
681
|
+
];
|
|
682
|
+
}
|
|
683
|
+
var EMPTY = { gaps: [], leaders: [] };
|
|
684
|
+
var MODIFIERS = ["alt", "ctrl", "meta", "shift"];
|
|
685
|
+
function subtreeOf(scene, ids) {
|
|
686
|
+
const out = /* @__PURE__ */ new Set();
|
|
687
|
+
const stack = [...ids];
|
|
688
|
+
while (stack.length > 0) {
|
|
689
|
+
const id = stack.pop();
|
|
690
|
+
if (out.has(id)) continue;
|
|
691
|
+
out.add(id);
|
|
692
|
+
stack.push(...scene.childrenOf(id));
|
|
693
|
+
}
|
|
694
|
+
return out;
|
|
695
|
+
}
|
|
696
|
+
function distanceReadout(opts = {}) {
|
|
697
|
+
const id = opts.id ?? "distanceReadout";
|
|
698
|
+
const modifier = opts.modifier ?? "alt";
|
|
699
|
+
let shown = EMPTY;
|
|
700
|
+
let store = null;
|
|
701
|
+
const show = (next) => {
|
|
702
|
+
if (next === shown || next.gaps.length === 0 && shown.gaps.length === 0) return;
|
|
703
|
+
shown = next;
|
|
704
|
+
store?.set(marksOf(next, opts.color));
|
|
705
|
+
};
|
|
706
|
+
const action = {
|
|
707
|
+
id: `${id}.measure`,
|
|
708
|
+
label: "Measure distance to the shape under the pointer",
|
|
709
|
+
requires: ["selection", "scene", "nodeAtPoint", "poseDescriptor", "snapReadout"],
|
|
710
|
+
invoker: {
|
|
711
|
+
timing: "immediate",
|
|
712
|
+
run: (deps, params) => {
|
|
713
|
+
const p = params;
|
|
714
|
+
const { selection, scene, nodeAtPoint, poseDescriptor, snapReadout } = deps;
|
|
715
|
+
if (snapReadout !== void 0) store = snapReadout.store;
|
|
716
|
+
const ids = selection?.get() ?? [];
|
|
717
|
+
if (p?.show !== true || p.worldX === void 0 || p.worldY === void 0 || scene === void 0 || nodeAtPoint === void 0 || ids.length === 0) {
|
|
718
|
+
show(EMPTY);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const hit = nodeAtPoint({ x: p.worldX, y: p.worldY }, subtreeOf(scene, ids));
|
|
722
|
+
const from = worldUnionOf(scene, ids, { poseDescriptor });
|
|
723
|
+
const [to] = hit === null ? [] : worldBoundsOf(scene, [hit], { poseDescriptor });
|
|
724
|
+
show(from === null || to === void 0 ? EMPTY : measureDistances(from, to));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const mods = (held) => Object.fromEntries(
|
|
729
|
+
MODIFIERS.map((m) => [m, m === modifier ? held : "optional"])
|
|
730
|
+
);
|
|
731
|
+
const bindings = [
|
|
732
|
+
{ spec: { kind: "hover", mods: mods(true) }, actionId: action.id, opts: { params: { show: true } } },
|
|
733
|
+
{ spec: { kind: "hover", mods: mods(false) }, actionId: action.id, opts: { params: { show: false } } },
|
|
734
|
+
{ spec: { kind: "hover", motion: "leave" }, actionId: action.id, opts: { params: { show: false } } }
|
|
735
|
+
];
|
|
736
|
+
return {
|
|
737
|
+
id,
|
|
738
|
+
eligibility: { always: true },
|
|
739
|
+
actions: [action],
|
|
740
|
+
bindings,
|
|
741
|
+
attach: (api) => {
|
|
742
|
+
const el = api.element;
|
|
743
|
+
const hide = () => show(EMPTY);
|
|
744
|
+
el?.addEventListener("pointerdown", hide, true);
|
|
745
|
+
return () => {
|
|
746
|
+
el?.removeEventListener("pointerdown", hide, true);
|
|
747
|
+
show(EMPTY);
|
|
748
|
+
store = null;
|
|
749
|
+
};
|
|
750
|
+
},
|
|
751
|
+
read: () => shown
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/spacingHints/preset.ts
|
|
756
|
+
function createSpacingHintsPreset(opts = {}) {
|
|
757
|
+
const id = opts.id ?? "spacingHints";
|
|
758
|
+
const color = opts.color === void 0 ? {} : { color: opts.color };
|
|
759
|
+
const own = { ...color, ...opts.tolerance === void 0 ? {} : { reachPx: opts.tolerance } };
|
|
760
|
+
const snapSources = [
|
|
761
|
+
...opts.spacing ?? true ? [spacingSnapSource(own)] : [],
|
|
762
|
+
...opts.align ?? false ? [alignmentSnapSource(own)] : []
|
|
763
|
+
];
|
|
764
|
+
const contributions = [];
|
|
765
|
+
if (opts.readout !== false) contributions.push(distanceReadout({ ...opts.readout, ...color }));
|
|
766
|
+
return { id, snapSources, contributions };
|
|
686
767
|
}
|
|
687
768
|
|
|
688
769
|
// src/layout/snaps.ts
|
|
@@ -875,7 +956,7 @@ function tileGrid(opts) {
|
|
|
875
956
|
if (fromIdx === null || fromIdx === idx) return null;
|
|
876
957
|
return { occupant: occupant.id, from: occupant.pose, newPose: cellToPose(grid.rectAt(fromIdx), occupant.pose) };
|
|
877
958
|
}
|
|
878
|
-
function pack2(from,
|
|
959
|
+
function pack2(from, into, children) {
|
|
879
960
|
const ordered = children.map((c) => ({ c, cell: from.indexOf(c.pose) })).sort((a, b) => {
|
|
880
961
|
if (a.cell !== b.cell) {
|
|
881
962
|
if (a.cell === null) return 1;
|
|
@@ -886,7 +967,7 @@ function tileGrid(opts) {
|
|
|
886
967
|
});
|
|
887
968
|
const out = /* @__PURE__ */ new Map();
|
|
888
969
|
for (let i = 0; i < ordered.length; i++) {
|
|
889
|
-
out.set(ordered[i].c.id, cellToPose(
|
|
970
|
+
out.set(ordered[i].c.id, cellToPose(into.rectAt(i), ordered[i].c.pose));
|
|
890
971
|
}
|
|
891
972
|
return out;
|
|
892
973
|
}
|
|
@@ -1457,6 +1538,6 @@ function ringSlots(opts = {}) {
|
|
|
1457
1538
|
};
|
|
1458
1539
|
}
|
|
1459
1540
|
|
|
1460
|
-
export {
|
|
1541
|
+
export { alignmentSnapSource, cellAt, containedThenNearest, createCellHighlightLayer, createGridLayer, createGuidesLayer, createSpacingHintsPreset, deriveAlignmentGuides, distanceReadout, freeform, gridSlots, gridSnapSource, guideSnapSource, matchSpacing, measureDistances, measureGaps, nearest, nearestWithin, none, pack, perContainer, pointToGridCell, quadtree, ringSlots, roundToCell, rowSlots, sequenceLayout, siblingBounds, slots, snapPoint, spacingSnapSource, tileGrid, useGridCellHover, useGuides, worldBoundsOf, worldUnionOf };
|
|
1461
1542
|
//# sourceMappingURL=index.js.map
|
|
1462
1543
|
//# sourceMappingURL=index.js.map
|