@weasel-js/guides 1.14.1 → 1.15.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 +17 -1
- package/dist/index.d.ts +203 -105
- package/dist/index.js +368 -222
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,12 +1,197 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
-
import { sceneBoxFrames, scenePoseFrame,
|
|
3
|
-
import { pointInPolygon, invert,
|
|
2
|
+
import { sceneBoxFrames, scenePoseFrame, useLatest, viewportWorldRect, tickedSegment, measuredSegmentCommands, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, rectFromPlane, unionAABB, createTransformOp, asNodeId, sceneUnitScopes, screenToWorld, nodeGates, resolveStrokeWidth, resolveUnit, axisUnits, resolveScoped } from '@weasel-js/core';
|
|
3
|
+
import { pointInPolygon, invert, mat3, applyToPoint, pxExtent, PATH_M, PATH_L, PATH_Z, pxExtentInFrame } from '@weasel-js/geom';
|
|
4
4
|
|
|
5
5
|
// src/grid/roundToCell.ts
|
|
6
6
|
function roundToCell(value, cellSize) {
|
|
7
7
|
return Math.round(value / cellSize) * cellSize || 0;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
// src/grid/axes.ts
|
|
11
|
+
function isGridLength(v) {
|
|
12
|
+
return typeof v === "number" || typeof v === "object" && v !== null && "value" in v;
|
|
13
|
+
}
|
|
14
|
+
function resolveGridLines(spacing, read) {
|
|
15
|
+
const on = (axis) => {
|
|
16
|
+
const a = isGridLength(spacing) ? spacing : spacing[axis];
|
|
17
|
+
if (a === void 0) return null;
|
|
18
|
+
try {
|
|
19
|
+
return resolveAxis(a, (v) => read(v, axis));
|
|
20
|
+
} catch (e) {
|
|
21
|
+
throw new Error(`${axis} axis: ${e instanceof Error ? e.message : String(e)}`);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return { x: on("x"), y: on("y") };
|
|
25
|
+
}
|
|
26
|
+
function resolveAxis(a, read) {
|
|
27
|
+
if (isGridLength(a)) return periodic(read(a), 0, -Infinity, Infinity);
|
|
28
|
+
if ("at" in a) {
|
|
29
|
+
const at = [...new Set(a.at.map(read))].filter(Number.isFinite).sort((p, q) => p - q);
|
|
30
|
+
return at.length ? { at } : null;
|
|
31
|
+
}
|
|
32
|
+
return periodic(
|
|
33
|
+
read(a.pitch),
|
|
34
|
+
a.offset === void 0 ? 0 : read(a.offset),
|
|
35
|
+
a.from === void 0 ? -Infinity : read(a.from),
|
|
36
|
+
a.to === void 0 ? Infinity : read(a.to)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function periodic(pitch, offset, from, to) {
|
|
40
|
+
return pitch > 0 && Number.isFinite(pitch) && from <= to ? { pitch, offset, from, to } : null;
|
|
41
|
+
}
|
|
42
|
+
var EPS = 1e-9;
|
|
43
|
+
function indexRange(a) {
|
|
44
|
+
return [Math.ceil((a.from - a.offset) / a.pitch - EPS), Math.floor((a.to - a.offset) / a.pitch + EPS)];
|
|
45
|
+
}
|
|
46
|
+
function linesNear(a, v) {
|
|
47
|
+
let pair;
|
|
48
|
+
if ("at" in a) {
|
|
49
|
+
const i = Math.min(Math.max(0, lastAtOrBefore(a.at, v)), Math.max(0, a.at.length - 2));
|
|
50
|
+
pair = a.at.slice(i, i + 2);
|
|
51
|
+
} else {
|
|
52
|
+
const [lo3, hi3] = indexRange(a);
|
|
53
|
+
if (lo3 > hi3) return [];
|
|
54
|
+
const k = Math.min(Math.max(Math.floor((v - a.offset) / a.pitch), lo3), Math.max(lo3, hi3 - 1));
|
|
55
|
+
pair = k + 1 <= hi3 ? [a.offset + k * a.pitch, a.offset + (k + 1) * a.pitch] : [a.offset + k * a.pitch];
|
|
56
|
+
}
|
|
57
|
+
return pair.length === 2 && v - pair[0] >= pair[1] - v ? [pair[1], pair[0]] : pair;
|
|
58
|
+
}
|
|
59
|
+
function nearestLine(a, v) {
|
|
60
|
+
let best = null;
|
|
61
|
+
for (const l of linesNear(a, v)) if (best === null || Math.abs(l - v) < Math.abs(best - v)) best = l;
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
64
|
+
function linesIn(a, lo3, hi3) {
|
|
65
|
+
if ("at" in a) return a.at.filter((l) => l >= lo3 - EPS && l <= hi3 + EPS);
|
|
66
|
+
const [k0, k1] = indexRange({ ...a, from: Math.max(a.from, lo3), to: Math.min(a.to, hi3) });
|
|
67
|
+
const out = [];
|
|
68
|
+
for (let k = k0; k <= k1; k++) out.push(a.offset + k * a.pitch);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
function cellOf(a, v) {
|
|
72
|
+
if (!a) return { index: 0, lo: -Infinity, hi: Infinity };
|
|
73
|
+
if ("at" in a) {
|
|
74
|
+
const i = lastAtOrBefore(a.at, v);
|
|
75
|
+
return i >= 0 && i < a.at.length - 1 ? { index: i, lo: a.at[i], hi: a.at[i + 1] } : null;
|
|
76
|
+
}
|
|
77
|
+
const [lo3, hi3] = indexRange(a);
|
|
78
|
+
const k = Math.floor((v - a.offset) / a.pitch);
|
|
79
|
+
if (k < lo3 || k >= hi3) return null;
|
|
80
|
+
return { index: k, lo: a.offset + k * a.pitch, hi: a.offset + (k + 1) * a.pitch };
|
|
81
|
+
}
|
|
82
|
+
function mapLines(a, scale, shift) {
|
|
83
|
+
const m = (p) => p * scale + shift;
|
|
84
|
+
if ("at" in a) return { at: a.at.map(m).sort((p, q) => p - q) };
|
|
85
|
+
const [from, to] = [m(a.from), m(a.to)].sort((p, q) => p - q);
|
|
86
|
+
return { pitch: Math.abs(a.pitch * scale), offset: m(a.offset), from: Number.isNaN(from) ? -Infinity : from, to: Number.isNaN(to) ? Infinity : to };
|
|
87
|
+
}
|
|
88
|
+
function lastAtOrBefore(at, v) {
|
|
89
|
+
let lo3 = 0;
|
|
90
|
+
let hi3 = at.length - 1;
|
|
91
|
+
let found = -1;
|
|
92
|
+
while (lo3 <= hi3) {
|
|
93
|
+
const mid2 = lo3 + hi3 >> 1;
|
|
94
|
+
if (at[mid2] <= v + EPS) {
|
|
95
|
+
found = mid2;
|
|
96
|
+
lo3 = mid2 + 1;
|
|
97
|
+
} else hi3 = mid2 - 1;
|
|
98
|
+
}
|
|
99
|
+
return found;
|
|
100
|
+
}
|
|
101
|
+
var MIN_SCREEN_SPACING = 4;
|
|
102
|
+
function hasArea(r) {
|
|
103
|
+
return r.width > 0 && r.height > 0 && Number.isFinite(r.width) && Number.isFinite(r.height);
|
|
104
|
+
}
|
|
105
|
+
var other = (axis) => axis === "x" ? "y" : "x";
|
|
106
|
+
function lattice(b, lines, shape, view, styles, frame) {
|
|
107
|
+
const px = frame ? pxExtentInFrame(1, view.scale, frame) : pxExtent(1, view.scale);
|
|
108
|
+
const span2 = { x: [b.x, b.x + b.width], y: [b.y, b.y + b.height] };
|
|
109
|
+
const children = [];
|
|
110
|
+
const across = (axis) => {
|
|
111
|
+
const a = lines[axis];
|
|
112
|
+
const [lo3, hi3] = span2[axis];
|
|
113
|
+
return a && "pitch" in a ? [Math.max(lo3, a.from), Math.min(hi3, a.to)] : [lo3, hi3];
|
|
114
|
+
};
|
|
115
|
+
const draw = (axis, at, s) => {
|
|
116
|
+
const [lo3, hi3] = across(other(axis));
|
|
117
|
+
if (!(hi3 > lo3)) return;
|
|
118
|
+
const width = resolveStrokeWidth(s.width ?? 1, 1) * px[axis];
|
|
119
|
+
for (const p of at) {
|
|
120
|
+
const coords = axis === "x" ? [p, lo3, p, hi3] : [lo3, p, hi3, p];
|
|
121
|
+
const path = {
|
|
122
|
+
kind: "polygon",
|
|
123
|
+
commands: new Uint8Array([PATH_M, PATH_L]),
|
|
124
|
+
coords: new Float32Array(coords),
|
|
125
|
+
fillRule: "nonzero"
|
|
126
|
+
};
|
|
127
|
+
children.push({ kind: "path", path, stroke: { ...s, width } });
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const periodic2 = (axis, a, step, skipEvery, s) => {
|
|
131
|
+
if (!(step / px[axis] >= MIN_SCREEN_SPACING)) return;
|
|
132
|
+
const at = linesIn({ ...a, pitch: step }, ...span2[axis]);
|
|
133
|
+
draw(axis, skipEvery > 0 ? at.filter((p) => Math.round((p - a.offset) / step) % skipEvery !== 0) : at, s);
|
|
134
|
+
};
|
|
135
|
+
const { accentEvery, subdivisions } = shape;
|
|
136
|
+
const accentOn = accentEvery !== void 0 && accentEvery > 0;
|
|
137
|
+
const passes = [
|
|
138
|
+
// Sub-lines first so cell lines paint on top, accents last of all.
|
|
139
|
+
(axis, a) => {
|
|
140
|
+
if (subdivisions && subdivisions > 1) periodic2(axis, a, a.pitch / subdivisions, subdivisions, styles.sub);
|
|
141
|
+
},
|
|
142
|
+
(axis, a) => periodic2(axis, a, a.pitch, accentOn ? accentEvery : 0, styles.line),
|
|
143
|
+
(axis, a) => {
|
|
144
|
+
if (accentOn) periodic2(axis, a, a.pitch * accentEvery, 0, styles.accent);
|
|
145
|
+
}
|
|
146
|
+
];
|
|
147
|
+
for (const pass of passes) {
|
|
148
|
+
for (const axis of ["x", "y"]) {
|
|
149
|
+
const a = lines[axis];
|
|
150
|
+
if (a && "pitch" in a) pass(axis, a);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const axis of ["x", "y"]) {
|
|
154
|
+
const a = lines[axis];
|
|
155
|
+
if (a && "at" in a) draw(axis, thinned(linesIn(a, ...span2[axis]), px[axis] * MIN_SCREEN_SPACING), styles.line);
|
|
156
|
+
}
|
|
157
|
+
return children;
|
|
158
|
+
}
|
|
159
|
+
function thinned(at, gap) {
|
|
160
|
+
const out = [];
|
|
161
|
+
for (const p of at) if (out.length === 0 || p - out[out.length - 1] >= gap) out.push(p);
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
function outlineBox(outline) {
|
|
165
|
+
const xs = outline.map((p) => p.x);
|
|
166
|
+
const ys = outline.map((p) => p.y);
|
|
167
|
+
const x = Math.min(...xs);
|
|
168
|
+
const y = Math.min(...ys);
|
|
169
|
+
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
170
|
+
}
|
|
171
|
+
function intersect(a, b) {
|
|
172
|
+
const x = Math.max(a.x, b.x);
|
|
173
|
+
const y = Math.max(a.y, b.y);
|
|
174
|
+
return { x, y, width: Math.min(end(a.x, a.width), end(b.x, b.width)) - x, height: Math.min(end(a.y, a.height), end(b.y, b.height)) - y };
|
|
175
|
+
}
|
|
176
|
+
var end = (start, size) => size === Infinity ? Infinity : start + size;
|
|
177
|
+
function span(v, chain) {
|
|
178
|
+
const zero = typeof v === "number" ? 0 : { ...v, value: 0 };
|
|
179
|
+
return resolveScoped(v, chain) - resolveScoped(zero, chain);
|
|
180
|
+
}
|
|
181
|
+
function gridLines(spacing, unitSystem, scopes, chain) {
|
|
182
|
+
if (!scopes) return resolveGridLines(spacing, (v, axis) => resolveUnit(v, axisUnits(unitSystem, axis)));
|
|
183
|
+
const at = chain ?? [scopes.root];
|
|
184
|
+
return resolveGridLines(spacing, (v) => span(v, at));
|
|
185
|
+
}
|
|
186
|
+
function scopeSpacing(spacing, scopes, chain) {
|
|
187
|
+
const inner = chain && chain.length > 1 ? chain[chain.length - 1] : null;
|
|
188
|
+
return inner && scopes?.spacing ? scopes.spacing(inner) : spacing;
|
|
189
|
+
}
|
|
190
|
+
function containerLines(own, spacing, unitSystem, scopes, chain) {
|
|
191
|
+
if (own === null) return null;
|
|
192
|
+
return gridLines(own ?? scopeSpacing(spacing, scopes, chain), unitSystem, scopes, chain);
|
|
193
|
+
}
|
|
194
|
+
function sceneGridRegions(scene, owners, outlineOf) {
|
|
10
195
|
const frames = sceneBoxFrames(scene);
|
|
11
196
|
const poses = scenePoseFrame(scene);
|
|
12
197
|
const boxOutline = (id, frame) => {
|
|
@@ -18,8 +203,8 @@ function sceneGridRegions(scene, root, outlineOf) {
|
|
|
18
203
|
};
|
|
19
204
|
const children = /* @__PURE__ */ new Map();
|
|
20
205
|
for (const id of scene.nodes.keys()) {
|
|
21
|
-
if (scene
|
|
22
|
-
const parent =
|
|
206
|
+
if (!ownsGrid(scene, id, owners)) continue;
|
|
207
|
+
const parent = gridOwner(scene, id, owners);
|
|
23
208
|
children.set(parent, [...children.get(parent) ?? [], id]);
|
|
24
209
|
}
|
|
25
210
|
const build = (parent) => {
|
|
@@ -29,9 +214,11 @@ function sceneGridRegions(scene, root, outlineOf) {
|
|
|
29
214
|
const outline = outlineOf ? outlineOf(id) : frame && boxOutline(id, frame);
|
|
30
215
|
if (!outline) continue;
|
|
31
216
|
const inner = build(id);
|
|
217
|
+
const own = owners.gridOf?.(id);
|
|
32
218
|
out.push({
|
|
33
219
|
outline,
|
|
34
|
-
chain: sceneUnitScopes(scene, id, root, { inclusive: true }),
|
|
220
|
+
...owners.root ? { chain: sceneUnitScopes(scene, id, owners.root, { inclusive: true }) } : {},
|
|
221
|
+
...own !== void 0 ? { spacing: own } : {},
|
|
35
222
|
...inner.length ? { regions: inner } : {},
|
|
36
223
|
...frame ? { frame } : {}
|
|
37
224
|
});
|
|
@@ -40,9 +227,12 @@ function sceneGridRegions(scene, root, outlineOf) {
|
|
|
40
227
|
};
|
|
41
228
|
return build(null);
|
|
42
229
|
}
|
|
43
|
-
function
|
|
230
|
+
function ownsGrid(scene, id, owners) {
|
|
231
|
+
return owners.gridOf?.(id) !== void 0 || owners.root !== void 0 && scene.get(id)?.units !== void 0;
|
|
232
|
+
}
|
|
233
|
+
function gridOwner(scene, id, owners, inclusive = false) {
|
|
44
234
|
for (let at = inclusive ? id : scene.get(id)?.parent ?? null; at !== null; at = scene.get(at)?.parent ?? null) {
|
|
45
|
-
if (scene
|
|
235
|
+
if (ownsGrid(scene, at, owners)) return at;
|
|
46
236
|
}
|
|
47
237
|
return null;
|
|
48
238
|
}
|
|
@@ -53,48 +243,41 @@ function regionAt(regions, point) {
|
|
|
53
243
|
}
|
|
54
244
|
return null;
|
|
55
245
|
}
|
|
56
|
-
function span(v, chain) {
|
|
57
|
-
const zero = typeof v === "number" ? 0 : { ...v, value: 0 };
|
|
58
|
-
return resolveScoped(v, chain) - resolveScoped(zero, chain);
|
|
59
|
-
}
|
|
60
|
-
function scopedGridPitch(spacing, scopes, chain) {
|
|
61
|
-
const inner = chain.length > 1 ? chain[chain.length - 1] : null;
|
|
62
|
-
const v = inner && scopes.spacing ? scopes.spacing(inner) : spacing;
|
|
63
|
-
const on = (axis) => {
|
|
64
|
-
const a = typeof v === "object" && "x" in v ? v[axis] : v;
|
|
65
|
-
try {
|
|
66
|
-
return span(a, chain);
|
|
67
|
-
} catch (e) {
|
|
68
|
-
throw new Error(`${axis} axis: ${e instanceof Error ? e.message : String(e)}`);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
return { x: on("x"), y: on("y") };
|
|
72
|
-
}
|
|
73
|
-
function gridPitch(spacing, unitSystem, scopes, chain) {
|
|
74
|
-
return scopes ? scopedGridPitch(spacing, scopes, chain ?? [scopes.root]) : resolveAxes(spacing, unitSystem);
|
|
75
|
-
}
|
|
76
246
|
|
|
77
247
|
// src/grid/pointToGridCell.ts
|
|
78
248
|
function pointToGridCell(point, spacing, unitSystem, origin = { x: 0, y: 0 }, scoped) {
|
|
79
|
-
const region =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const [x, y] = applyToPoint(back, point.x, point.y);
|
|
84
|
-
return { col: Math.floor(x / s.x), row: Math.floor(y / s.y), pitch: s, frame: region.frame };
|
|
249
|
+
const region = regionAt(scoped?.regions, point);
|
|
250
|
+
if (!region) {
|
|
251
|
+
const cell2 = cellIn(gridLines(spacing, unitSystem, scoped?.scopes), point.x - origin.x, point.y - origin.y);
|
|
252
|
+
return cell2 && { ...cell2, box: { ...cell2.box, x: cell2.box.x + origin.x, y: cell2.box.y + origin.y } };
|
|
85
253
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
254
|
+
const lines = containerLines(region.spacing, spacing, unitSystem, scoped?.scopes, region.chain);
|
|
255
|
+
if (!lines) return null;
|
|
256
|
+
const back = region.frame && invert(region.frame);
|
|
257
|
+
if (region.frame && !back) return null;
|
|
258
|
+
const local = (p) => {
|
|
259
|
+
if (!back) return p;
|
|
260
|
+
const [x, y] = applyToPoint(back, p.x, p.y);
|
|
261
|
+
return { x, y };
|
|
90
262
|
};
|
|
263
|
+
const at = local(point);
|
|
264
|
+
const cell = cellIn(lines, at.x, at.y, outlineBox(region.outline.map(local)));
|
|
265
|
+
return cell && (region.frame ? { ...cell, frame: region.frame } : cell);
|
|
266
|
+
}
|
|
267
|
+
function cellIn(lines, x, y, within) {
|
|
268
|
+
const cx = cellOf(lines.x, x);
|
|
269
|
+
const cy = cellOf(lines.y, y);
|
|
270
|
+
if (!cx || !cy) return null;
|
|
271
|
+
const [x0, x1] = within && !lines.x ? [within.x, within.x + within.width] : [cx.lo, cx.hi];
|
|
272
|
+
const [y0, y1] = within && !lines.y ? [within.y, within.y + within.height] : [cy.lo, cy.hi];
|
|
273
|
+
return { col: cx.index, row: cy.index, box: { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } };
|
|
91
274
|
}
|
|
92
275
|
|
|
93
276
|
// src/grid/useGridCellHover.ts
|
|
94
277
|
function cellsEqual(a, b) {
|
|
95
278
|
if (a === b) return true;
|
|
96
279
|
if (a === null || b === null) return false;
|
|
97
|
-
return a.col === b.col && a.row === b.row && a.
|
|
280
|
+
return a.col === b.col && a.row === b.row && a.box.x === b.box.x && a.box.y === b.box.y && a.box.width === b.box.width && a.box.height === b.box.height && (a.frame === b.frame || !!a.frame && !!b.frame && a.frame.every((v, i) => v === b.frame[i]));
|
|
98
281
|
}
|
|
99
282
|
function useGridCellHover(opts) {
|
|
100
283
|
const [cell, setCell] = useState(null);
|
|
@@ -118,7 +301,7 @@ function useGridCellHover(opts) {
|
|
|
118
301
|
const sx = e.clientX - rect.left;
|
|
119
302
|
const sy = e.clientY - rect.top;
|
|
120
303
|
const [wx, wy] = screenToWorld(sx, sy, o.view());
|
|
121
|
-
const scoped = o.scopes ? { scopes: o.scopes, ...o.regions ? { regions: o.regions() } : {} }
|
|
304
|
+
const scoped = { ...o.scopes ? { scopes: o.scopes } : {}, ...o.regions ? { regions: o.regions() } : {} };
|
|
122
305
|
update(pointToGridCell({ x: wx, y: wy }, o.spacing, o.unitSystem, o.origin, scoped));
|
|
123
306
|
};
|
|
124
307
|
const onLeave = () => update(null);
|
|
@@ -133,66 +316,6 @@ function useGridCellHover(opts) {
|
|
|
133
316
|
}, [enabled, opts.ref, optsRef]);
|
|
134
317
|
return { cell, getCell };
|
|
135
318
|
}
|
|
136
|
-
var MIN_SCREEN_SPACING = 4;
|
|
137
|
-
function hasArea(r) {
|
|
138
|
-
return r.width > 0 && r.height > 0 && Number.isFinite(r.width) && Number.isFinite(r.height);
|
|
139
|
-
}
|
|
140
|
-
function lattice(b, spacing, shape, view, styles, frame) {
|
|
141
|
-
const { accentEvery, subdivisions } = shape;
|
|
142
|
-
const x0 = b.x;
|
|
143
|
-
const y0 = b.y;
|
|
144
|
-
const x1 = b.x + b.width;
|
|
145
|
-
const y1 = b.y + b.height;
|
|
146
|
-
const px = frame ? pxExtentInFrame(1, view.scale, frame) : pxExtent(1, view.scale);
|
|
147
|
-
const children = [];
|
|
148
|
-
const pushSegment = (sx, sy, ex, ey, s) => {
|
|
149
|
-
const path = {
|
|
150
|
-
kind: "polygon",
|
|
151
|
-
commands: new Uint8Array([PATH_M, PATH_L]),
|
|
152
|
-
coords: new Float32Array([sx, sy, ex, ey]),
|
|
153
|
-
fillRule: "nonzero"
|
|
154
|
-
};
|
|
155
|
-
const across = sx === ex ? px.x : px.y;
|
|
156
|
-
children.push({
|
|
157
|
-
kind: "path",
|
|
158
|
-
path,
|
|
159
|
-
stroke: { ...s, width: resolveStrokeWidth(s.width ?? 1, 1) * across }
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
|
-
const family = (axis, step, skipEvery, s) => {
|
|
163
|
-
if (!(step / px[axis] >= MIN_SCREEN_SPACING)) return;
|
|
164
|
-
const [from, to] = axis === "x" ? [x0, x1] : [y0, y1];
|
|
165
|
-
for (let k = 0; from + k * step <= to + 1e-9; k++) {
|
|
166
|
-
if (skipEvery > 0 && k % skipEvery === 0) continue;
|
|
167
|
-
const at = from + k * step;
|
|
168
|
-
if (axis === "x") pushSegment(at, y0, at, y1, s);
|
|
169
|
-
else pushSegment(x0, at, x1, at, s);
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
const accentOn = accentEvery !== void 0 && accentEvery > 0;
|
|
173
|
-
for (const axis of ["x", "y"]) {
|
|
174
|
-
if (subdivisions && subdivisions > 1) family(axis, spacing[axis] / subdivisions, subdivisions, styles.sub);
|
|
175
|
-
}
|
|
176
|
-
for (const axis of ["x", "y"]) family(axis, spacing[axis], accentOn ? accentEvery : 0, styles.line);
|
|
177
|
-
if (accentOn) {
|
|
178
|
-
for (const axis of ["x", "y"]) family(axis, spacing[axis] * accentEvery, 0, styles.accent);
|
|
179
|
-
}
|
|
180
|
-
return children;
|
|
181
|
-
}
|
|
182
|
-
function onLattice(r, spacing, accentEvery, anchor) {
|
|
183
|
-
const px = spacing.x * accentEvery;
|
|
184
|
-
const py = spacing.y * accentEvery;
|
|
185
|
-
const x = anchor.x + Math.floor((r.x - anchor.x) / px) * px;
|
|
186
|
-
const y = anchor.y + Math.floor((r.y - anchor.y) / py) * py;
|
|
187
|
-
return {
|
|
188
|
-
x,
|
|
189
|
-
y,
|
|
190
|
-
width: anchor.x + Math.ceil((r.x + r.width - anchor.x) / px) * px - x,
|
|
191
|
-
height: anchor.y + Math.ceil((r.y + r.height - anchor.y) / py) * py - y
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// src/grid/layer.ts
|
|
196
319
|
var DEFAULT_LINE = {
|
|
197
320
|
paint: { kind: "solid", color: "rgba(255, 255, 255, 0.08)" },
|
|
198
321
|
width: 1
|
|
@@ -215,20 +338,16 @@ function createGridLayer(opts) {
|
|
|
215
338
|
id: "grid",
|
|
216
339
|
label: "Grid",
|
|
217
340
|
draw: (_data, view, dims) => {
|
|
218
|
-
const
|
|
219
|
-
const spacing = gridPitch(opts.spacing, opts.unitSystem, opts.scopes);
|
|
220
|
-
const shown = viewportWorldRect(view, dims);
|
|
221
|
-
const b = opts.bounds ? opts.bounds(view, dims) : onLattice(shown, spacing, every, { x: 0, y: 0 });
|
|
341
|
+
const b = opts.bounds ? opts.bounds(view, dims) : padded(viewportWorldRect(view, dims), view);
|
|
222
342
|
if (!hasArea(b)) return [];
|
|
223
|
-
const root = lattice(b, spacing, opts, view, styles);
|
|
224
|
-
const regions = opts.scopes && opts.regions ? opts.regions() : [];
|
|
225
|
-
if (regions.length === 0) return root;
|
|
226
343
|
const anchor = opts.bounds ? { x: b.x, y: b.y } : { x: 0, y: 0 };
|
|
227
|
-
const
|
|
344
|
+
const root = lattice(b, placed(gridLines(opts.spacing, opts.unitSystem, opts.scopes), anchor), opts, view, styles);
|
|
345
|
+
const regions = opts.regions?.() ?? [];
|
|
346
|
+
if (regions.length === 0) return root;
|
|
228
347
|
const children = [{ kind: "group", clip: withHoles(rectOutline(b), regions), children: root }];
|
|
229
348
|
const visit = (r) => {
|
|
230
|
-
const
|
|
231
|
-
const lines = regionLattice(r,
|
|
349
|
+
const own = containerLines(r.spacing, opts.spacing, opts.unitSystem, opts.scopes, r.chain);
|
|
350
|
+
const lines = own ? regionLattice(r, b, (box, frame) => lattice(box, placed(own, frame ? { x: 0, y: 0 } : anchor), opts, view, styles, frame)) : [];
|
|
232
351
|
if (lines.length) children.push({ kind: "group", clip: withHoles(r.outline, r.regions ?? []), children: lines });
|
|
233
352
|
for (const inner of r.regions ?? []) visit(inner);
|
|
234
353
|
};
|
|
@@ -237,6 +356,14 @@ function createGridLayer(opts) {
|
|
|
237
356
|
}
|
|
238
357
|
};
|
|
239
358
|
}
|
|
359
|
+
function padded(r, view) {
|
|
360
|
+
const px = 1 / Math.abs(view.scale.x);
|
|
361
|
+
const py = 1 / Math.abs(view.scale.y);
|
|
362
|
+
return { x: r.x - px, y: r.y - py, width: r.width + 2 * px, height: r.height + 2 * py };
|
|
363
|
+
}
|
|
364
|
+
function placed(lines, anchor) {
|
|
365
|
+
return { x: lines.x && mapLines(lines.x, 1, anchor.x), y: lines.y && mapLines(lines.y, 1, anchor.y) };
|
|
366
|
+
}
|
|
240
367
|
function rectOutline(r) {
|
|
241
368
|
return [
|
|
242
369
|
{ x: r.x, y: r.y },
|
|
@@ -273,51 +400,36 @@ function regionLattice(r, area, draw) {
|
|
|
273
400
|
const lines = draw(box, r.frame);
|
|
274
401
|
return lines.length ? [{ kind: "group", transform: mat3.fromAffine(r.frame), children: lines }] : [];
|
|
275
402
|
}
|
|
276
|
-
function outlineBox(outline) {
|
|
277
|
-
const xs = outline.map((p) => p.x);
|
|
278
|
-
const ys = outline.map((p) => p.y);
|
|
279
|
-
const x = Math.min(...xs);
|
|
280
|
-
const y = Math.min(...ys);
|
|
281
|
-
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
282
|
-
}
|
|
283
|
-
function intersect(a, b) {
|
|
284
|
-
const x = Math.max(a.x, b.x);
|
|
285
|
-
const y = Math.max(a.y, b.y);
|
|
286
|
-
return { x, y, width: Math.min(a.x + a.width, b.x + b.width) - x, height: Math.min(a.y + a.height, b.y + b.height) - y };
|
|
287
|
-
}
|
|
288
403
|
var DEFAULT_FILL = { paint: { kind: "solid", color: "rgba(127,176,105,0.15)" } };
|
|
289
404
|
function createCellHighlightLayer(opts) {
|
|
290
405
|
const fill = opts.fill ?? DEFAULT_FILL;
|
|
291
406
|
return {
|
|
292
407
|
id: "cell-highlight",
|
|
293
408
|
label: "Cell highlight",
|
|
294
|
-
draw: () => {
|
|
409
|
+
draw: (_data, view, dims) => {
|
|
295
410
|
const cell = opts.getCell();
|
|
296
411
|
if (!cell) return [];
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
const y = o.y + cell.row * spacing.y;
|
|
301
|
-
const children = [
|
|
302
|
-
{
|
|
303
|
-
kind: "path",
|
|
304
|
-
path: { kind: "rect", x, y, width: spacing.x, height: spacing.y },
|
|
305
|
-
fill
|
|
306
|
-
}
|
|
307
|
-
];
|
|
412
|
+
const box = cell.frame ? cell.box : intersect(cell.box, viewportWorldRect(view, dims));
|
|
413
|
+
if (!hasArea(box)) return [];
|
|
414
|
+
const children = [{ kind: "path", path: { kind: "rect", ...box }, fill }];
|
|
308
415
|
return cell.frame ? [{ kind: "group", transform: mat3.fromAffine(cell.frame), children }] : children;
|
|
309
416
|
}
|
|
310
417
|
};
|
|
311
418
|
}
|
|
312
419
|
|
|
313
|
-
// src/
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
420
|
+
// src/grid/fit.ts
|
|
421
|
+
function gridFit(points, lines, tolerance = 1e-6) {
|
|
422
|
+
const misses = [];
|
|
423
|
+
points.forEach((p, index) => {
|
|
424
|
+
for (const axis of ["x", "y"]) {
|
|
425
|
+
const a = lines[axis];
|
|
426
|
+
if (!a) continue;
|
|
427
|
+
const near = nearestLine(a, p[axis]);
|
|
428
|
+
const distance = near === null ? Infinity : Math.abs(p[axis] - near);
|
|
429
|
+
if (distance > tolerance) misses.push({ index, axis, distance });
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
return { fits: misses.length === 0, misses };
|
|
321
433
|
}
|
|
322
434
|
|
|
323
435
|
// src/sources/lines.ts
|
|
@@ -333,67 +445,89 @@ function frameLine(frame, axis, offset, near) {
|
|
|
333
445
|
function landedShift(q, delta) {
|
|
334
446
|
return { x: delta.x - q.delta0.x, y: delta.y - q.delta0.y };
|
|
335
447
|
}
|
|
448
|
+
function sideLine(near, rate, by) {
|
|
449
|
+
const k = by / (rate.x * rate.x + rate.y * rate.y);
|
|
450
|
+
return { at: { x: near.x + rate.x * k, y: near.y + rate.y * k }, dirs: [{ x: -rate.y, y: rate.x }] };
|
|
451
|
+
}
|
|
452
|
+
function landedBox(q, delta) {
|
|
453
|
+
if (q.boxAt) return q.boxAt(delta);
|
|
454
|
+
if (!q.box) return null;
|
|
455
|
+
const shift = landedShift(q, delta);
|
|
456
|
+
return { ...q.box, x: q.box.x + shift.x, y: q.box.y + shift.y };
|
|
457
|
+
}
|
|
336
458
|
|
|
337
459
|
// src/sources/grid.ts
|
|
460
|
+
var AXES = ["x", "y"];
|
|
338
461
|
function gridSnapSource(opts) {
|
|
339
|
-
const { scopes } = opts;
|
|
340
|
-
const
|
|
462
|
+
const { scopes, gridOf } = opts;
|
|
463
|
+
const owners = { ...scopes ? { root: scopes.root } : {}, ...gridOf ? { gridOf } : {} };
|
|
464
|
+
const rootLines = gridLines(opts.spacing, opts.unitSystem, scopes);
|
|
465
|
+
const latticeOf = (q, owner) => {
|
|
466
|
+
if (owner === null) return { lines: rootLines };
|
|
467
|
+
const chain = scopes && q.scene ? sceneUnitScopes(q.scene, owner, scopes.root, { inclusive: true }) : void 0;
|
|
468
|
+
return {
|
|
469
|
+
lines: containerLines(gridOf?.(owner), opts.spacing, opts.unitSystem, scopes, chain),
|
|
470
|
+
frame: q.frameOf?.(owner)
|
|
471
|
+
};
|
|
472
|
+
};
|
|
341
473
|
const latticeFor = (q) => {
|
|
342
474
|
const scene = q.scene;
|
|
343
|
-
if (!scopes || !scene) return {
|
|
344
|
-
const within = (owner) => owner === null ? void 0 : q.frameOf?.(owner);
|
|
475
|
+
if (!scopes && !gridOf || !scene) return { lines: rootLines };
|
|
345
476
|
if (q.ids?.length) {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// Nodes in two containers share no one lattice, whatever their scopes.
|
|
351
|
-
frame: owners.size === 1 ? within([...owners][0]) : void 0
|
|
352
|
-
};
|
|
477
|
+
const held = [...new Set(q.ids.map((id) => gridOwner(scene, asNodeId(id), owners)))];
|
|
478
|
+
if (held.length === 1) return latticeOf(q, held[0]);
|
|
479
|
+
const each = held.map((owner) => JSON.stringify(latticeOf(q, owner).lines));
|
|
480
|
+
return { lines: each.every((l) => l === each[0]) ? latticeOf(q, held[0]).lines : null };
|
|
353
481
|
}
|
|
354
|
-
if (q.parent === void 0) return {
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
pitch: scopedGridPitch(opts.spacing, scopes, sceneUnitScopes(scene, parent, scopes.root, { inclusive: true })),
|
|
358
|
-
frame: within(scopeOwner(scene, parent, true))
|
|
359
|
-
};
|
|
482
|
+
if (q.parent === void 0) return { lines: rootLines };
|
|
483
|
+
return latticeOf(q, gridOwner(scene, asNodeId(q.parent), owners, true));
|
|
360
484
|
};
|
|
361
485
|
const o = opts.origin ?? { x: 0, y: 0 };
|
|
362
|
-
const lines = (v, axis, pitch, plane) => {
|
|
363
|
-
const s = plane ? plane.scale[axis] : 1;
|
|
364
|
-
const near = roundToGridOn(v - o[axis] * s, axis, pitch[axis], plane) + o[axis] * s;
|
|
365
|
-
const step = pitch[axis] * s;
|
|
366
|
-
return [near, near + (v >= near ? step : -step)];
|
|
367
|
-
};
|
|
368
486
|
return {
|
|
369
|
-
id: "grid",
|
|
487
|
+
id: opts.id ?? "grid",
|
|
370
488
|
tier: "fallback",
|
|
371
489
|
reachPx: Infinity,
|
|
372
|
-
roles: ["origin", "pointer"],
|
|
490
|
+
roles: ["box", "origin", "pointer", "anchor"],
|
|
373
491
|
*collect(q) {
|
|
374
|
-
const {
|
|
492
|
+
const { lines, frame } = latticeFor(q);
|
|
493
|
+
if (!lines) return;
|
|
375
494
|
const plane = q.plane ?? null;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
495
|
+
const scale = (axis) => plane ? plane.scale[axis] : 1;
|
|
496
|
+
const anchored = q.probes.some((p) => p.role === "anchor");
|
|
497
|
+
const skip = frame && turns(frame) ? "box" : "origin";
|
|
498
|
+
const probes = q.probes.filter((p) => anchored ? p.role === "anchor" || p.role === "pointer" : p.role !== skip);
|
|
499
|
+
if (frame) {
|
|
500
|
+
const local = { x: lines.x && mapLines(lines.x, scale("x"), 0), y: lines.y && mapLines(lines.y, scale("y"), 0) };
|
|
501
|
+
for (const p of probes) yield* framedLines(p.id, p.at, local, frame);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const shift = (axis) => o[axis] * scale(axis) + (plane ? plane.offset[axis] : 0);
|
|
505
|
+
const world = { x: lines.x && mapLines(lines.x, scale("x"), shift("x")), y: lines.y && mapLines(lines.y, scale("y"), shift("y")) };
|
|
506
|
+
for (const p of probes) {
|
|
507
|
+
for (const axis of AXES) {
|
|
508
|
+
const a = world[axis];
|
|
509
|
+
if (a) for (const at of linesNear(a, p.at[axis])) yield { probe: p.id, locus: axisLine(axis, at, p.at) };
|
|
383
510
|
}
|
|
384
511
|
}
|
|
385
512
|
}
|
|
386
513
|
};
|
|
387
514
|
}
|
|
388
|
-
function
|
|
515
|
+
function turns(frame) {
|
|
516
|
+
const o = frame.toWorld({ x: 0, y: 0 });
|
|
517
|
+
const ex = frame.toWorld({ x: 1, y: 0 });
|
|
518
|
+
const ey = frame.toWorld({ x: 0, y: 1 });
|
|
519
|
+
const along = (a, axis) => {
|
|
520
|
+
const d = { x: a.x - o.x, y: a.y - o.y };
|
|
521
|
+
return axis === "x" ? Math.abs(d.y) <= 1e-9 * Math.abs(d.x) : Math.abs(d.x) <= 1e-9 * Math.abs(d.y);
|
|
522
|
+
};
|
|
523
|
+
return !(along(ex, "x") && along(ey, "y") || along(ex, "y") && along(ey, "x"));
|
|
524
|
+
}
|
|
525
|
+
function* framedLines(probe, at, lines, frame) {
|
|
389
526
|
const local = frame.toLocal(at);
|
|
390
527
|
if (!local) return;
|
|
391
|
-
for (const axis of
|
|
392
|
-
const
|
|
393
|
-
const
|
|
394
|
-
for (const offset of [near, near + (local[axis] >= near ? step : -step)]) {
|
|
395
|
-
yield { probe, locus: frameLine(frame, axis, offset, local) };
|
|
396
|
-
}
|
|
528
|
+
for (const axis of AXES) {
|
|
529
|
+
const a = lines[axis];
|
|
530
|
+
if (a) for (const offset of linesNear(a, local[axis])) yield { probe, locus: frameLine(frame, axis, offset, local) };
|
|
397
531
|
}
|
|
398
532
|
}
|
|
399
533
|
|
|
@@ -405,6 +539,7 @@ function createGridPreset(opts) {
|
|
|
405
539
|
id,
|
|
406
540
|
layers: { grid: { layer: createGridLayer(layer) } },
|
|
407
541
|
snapSources: snap === false ? [] : [gridSnapSource({
|
|
542
|
+
id,
|
|
408
543
|
spacing,
|
|
409
544
|
...unitSystem === void 0 ? {} : { unitSystem },
|
|
410
545
|
...scopes === void 0 ? {} : { scopes },
|
|
@@ -482,7 +617,7 @@ function createGuidesLayer(opts) {
|
|
|
482
617
|
}
|
|
483
618
|
};
|
|
484
619
|
}
|
|
485
|
-
var
|
|
620
|
+
var EPS2 = 1e-3;
|
|
486
621
|
function deriveAlignmentGuides(targets, opts = {}) {
|
|
487
622
|
const d = opts.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
|
|
488
623
|
const edges = opts.edges ?? true;
|
|
@@ -491,7 +626,7 @@ function deriveAlignmentGuides(targets, opts = {}) {
|
|
|
491
626
|
const seenY = /* @__PURE__ */ new Map();
|
|
492
627
|
const add = (axis, offset, b) => {
|
|
493
628
|
const seen = axis === "x" ? seenX : seenY;
|
|
494
|
-
const key = Math.round(offset /
|
|
629
|
+
const key = Math.round(offset / EPS2);
|
|
495
630
|
const min = axis === "x" ? b.y : b.x;
|
|
496
631
|
const max = min + (axis === "x" ? b.height : b.width);
|
|
497
632
|
const prev = seen.get(key);
|
|
@@ -523,14 +658,14 @@ function deriveAlignmentGuides(targets, opts = {}) {
|
|
|
523
658
|
}
|
|
524
659
|
|
|
525
660
|
// src/guides/alignment/spacing.ts
|
|
526
|
-
var
|
|
661
|
+
var EPS3 = 1e-6;
|
|
527
662
|
var lo = (b, a) => a === "x" ? b.x : b.y;
|
|
528
663
|
var hi = (b, a) => a === "x" ? b.x + b.width : b.y + b.height;
|
|
529
664
|
var cross = (a) => a === "x" ? "y" : "x";
|
|
530
665
|
function overlap(a, b, axis) {
|
|
531
666
|
const min = Math.max(lo(a, axis), lo(b, axis));
|
|
532
667
|
const max = Math.min(hi(a, axis), hi(b, axis));
|
|
533
|
-
return max - min >
|
|
668
|
+
return max - min > EPS3 ? [min, max] : null;
|
|
534
669
|
}
|
|
535
670
|
function gapBetween(a, b, axis) {
|
|
536
671
|
const shared = overlap(a, b, cross(axis));
|
|
@@ -539,7 +674,7 @@ function gapBetween(a, b, axis) {
|
|
|
539
674
|
}
|
|
540
675
|
function hitsStrip(g, b, span2) {
|
|
541
676
|
const c = cross(g.axis);
|
|
542
|
-
return lo(b, g.axis) < g.max -
|
|
677
|
+
return lo(b, g.axis) < g.max - EPS3 && hi(b, g.axis) > g.min + EPS3 && lo(b, c) < span2[1] - EPS3 && hi(b, c) > span2[0] + EPS3;
|
|
543
678
|
}
|
|
544
679
|
function measureGaps(targets, axis) {
|
|
545
680
|
return measureGapSpans(targets, axis).map((m) => m.gap);
|
|
@@ -549,7 +684,7 @@ function measureGapSpans(targets, axis) {
|
|
|
549
684
|
for (const a of targets) {
|
|
550
685
|
let next = null;
|
|
551
686
|
for (const c of targets) {
|
|
552
|
-
if (c === a || lo(c, axis) < hi(a, axis) +
|
|
687
|
+
if (c === a || lo(c, axis) < hi(a, axis) + EPS3) continue;
|
|
553
688
|
if (overlap(a, c, cross(axis)) === null) continue;
|
|
554
689
|
if (next === null || lo(c, axis) < lo(next, axis)) next = c;
|
|
555
690
|
}
|
|
@@ -576,14 +711,14 @@ function spacingOffers(b, axis, edge, targets, refs) {
|
|
|
576
711
|
const sizes = refs.map((g) => g.max - g.min);
|
|
577
712
|
const out = [];
|
|
578
713
|
const offer = (delta, gap) => {
|
|
579
|
-
if (gap >
|
|
714
|
+
if (gap > EPS3) out.push({ delta, size: gap });
|
|
580
715
|
};
|
|
581
716
|
const L = left === null ? null : hi(left, axis);
|
|
582
717
|
const R = right === null ? null : lo(right, axis);
|
|
583
718
|
if (edge === "both") {
|
|
584
719
|
for (const g of sizes) {
|
|
585
|
-
if (L !== null && (R === null || L + g + size <= R +
|
|
586
|
-
if (R !== null && (L === null || R - g - size >= L -
|
|
720
|
+
if (L !== null && (R === null || L + g + size <= R + EPS3)) offer(L + g - bLo, g);
|
|
721
|
+
if (R !== null && (L === null || R - g - size >= L - EPS3)) offer(R - g - size - bLo, g);
|
|
587
722
|
}
|
|
588
723
|
if (L !== null && R !== null) {
|
|
589
724
|
const g = (R - L - size) / 2;
|
|
@@ -591,10 +726,10 @@ function spacingOffers(b, axis, edge, targets, refs) {
|
|
|
591
726
|
}
|
|
592
727
|
} else if (edge === "max" && R !== null) {
|
|
593
728
|
const own = L !== null ? [bLo - L] : [];
|
|
594
|
-
for (const g of [...sizes, ...own]) if (R - g > bLo +
|
|
729
|
+
for (const g of [...sizes, ...own]) if (R - g > bLo + EPS3) offer(R - g - bHi, g);
|
|
595
730
|
} else if (edge === "min" && L !== null) {
|
|
596
731
|
const own = R !== null ? [R - bHi] : [];
|
|
597
|
-
for (const g of [...sizes, ...own]) if (L + g < bHi -
|
|
732
|
+
for (const g of [...sizes, ...own]) if (L + g < bHi - EPS3) offer(L + g - bLo, g);
|
|
598
733
|
}
|
|
599
734
|
return out;
|
|
600
735
|
}
|
|
@@ -609,8 +744,8 @@ function bestAxis(b, axis, edge, targets, refs, tol) {
|
|
|
609
744
|
function equalGaps(b, axis, size, targets, refs) {
|
|
610
745
|
const eq = (g) => Math.abs(g.max - g.min - size) < 1e-3;
|
|
611
746
|
const out = refs.filter(eq);
|
|
612
|
-
const near = targets.filter((t) => hi(t, axis) > lo(b, axis) - size -
|
|
613
|
-
const own = measureGaps([...near, b], axis).filter((g) => (Math.abs(g.max - lo(b, axis)) <
|
|
747
|
+
const near = targets.filter((t) => hi(t, axis) > lo(b, axis) - size - EPS3 && lo(t, axis) < hi(b, axis) + size + EPS3);
|
|
748
|
+
const own = measureGaps([...near, b], axis).filter((g) => (Math.abs(g.max - lo(b, axis)) < EPS3 || Math.abs(g.min - hi(b, axis)) < EPS3) && eq(g));
|
|
614
749
|
return [...out, ...own];
|
|
615
750
|
}
|
|
616
751
|
function referenceGaps(bounds, measured) {
|
|
@@ -681,11 +816,16 @@ function siblingBounds(scene, ids, opts = {}) {
|
|
|
681
816
|
return worldBoundsOf(scene, siblingIds(scene, ids), opts);
|
|
682
817
|
}
|
|
683
818
|
|
|
819
|
+
// src/strategies/onPlane.ts
|
|
820
|
+
function guideOffsetOn(g, plane) {
|
|
821
|
+
return plane ? g.offset * plane.scale[g.axis] + plane.offset[g.axis] : g.offset;
|
|
822
|
+
}
|
|
823
|
+
|
|
684
824
|
// src/sources/guides.ts
|
|
685
825
|
function guideSnapSource(getGuides) {
|
|
686
826
|
return {
|
|
687
827
|
id: "guides",
|
|
688
|
-
roles: ["
|
|
828
|
+
roles: ["box", "pointer"],
|
|
689
829
|
*collect(q) {
|
|
690
830
|
const guides = getGuides();
|
|
691
831
|
if (guides.length === 0) return;
|
|
@@ -775,7 +915,6 @@ function alignmentSnapSource(opts = {}) {
|
|
|
775
915
|
const indexes = linesFor(q);
|
|
776
916
|
if (indexes.length === 0) return;
|
|
777
917
|
const reach = q.reachWorld(q.reachPx);
|
|
778
|
-
const box = q.box ?? null;
|
|
779
918
|
const probes = q.probes.some((p) => p.role === "box") ? q.probes.filter((p) => p.role === "box") : q.probes;
|
|
780
919
|
for (const p of probes) {
|
|
781
920
|
for (const axis of ["x", "y"]) {
|
|
@@ -784,7 +923,7 @@ function alignmentSnapSource(opts = {}) {
|
|
|
784
923
|
yield {
|
|
785
924
|
probe: p.id,
|
|
786
925
|
locus: axisLine(axis, g.offset, p.at),
|
|
787
|
-
mark: (delta) => lineMark(g, extentAcross(
|
|
926
|
+
mark: (delta) => lineMark(g, extentAcross(q, p, axis, delta), opts.color)
|
|
788
927
|
};
|
|
789
928
|
}
|
|
790
929
|
}
|
|
@@ -793,15 +932,25 @@ function alignmentSnapSource(opts = {}) {
|
|
|
793
932
|
}
|
|
794
933
|
};
|
|
795
934
|
}
|
|
796
|
-
function extentAcross(
|
|
935
|
+
function extentAcross(q, p, axis, delta) {
|
|
797
936
|
const c = cross2(axis);
|
|
798
|
-
|
|
799
|
-
|
|
937
|
+
const landed = p.role === "box" ? landedBox(q, delta) : null;
|
|
938
|
+
if (landed === null) {
|
|
939
|
+
const shift = landedShift(q, delta);
|
|
940
|
+
return [p.at[c] + shift[c], p.at[c] + shift[c]];
|
|
941
|
+
}
|
|
800
942
|
const lo3 = c === "x" ? landed.x : landed.y;
|
|
801
943
|
return [lo3, lo3 + (c === "x" ? landed.width : landed.height)];
|
|
802
944
|
}
|
|
803
945
|
|
|
804
946
|
// src/sources/spacing.ts
|
|
947
|
+
var TRANSLATE = [
|
|
948
|
+
{ axis: "x", edge: "both", rate: { x: 1, y: 0 } },
|
|
949
|
+
{ axis: "y", edge: "both", rate: { x: 0, y: 1 } }
|
|
950
|
+
];
|
|
951
|
+
function movingSides(q) {
|
|
952
|
+
return q.sides ? q.sides.map((s) => ({ axis: s.axis, edge: s.side, rate: s.rate })) : TRANSLATE;
|
|
953
|
+
}
|
|
805
954
|
var measureAll = (boxes) => ({ boxes, x: measureGapSpans(boxes, "x"), y: measureGapSpans(boxes, "y") });
|
|
806
955
|
function gapMark(g, color) {
|
|
807
956
|
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 } };
|
|
@@ -820,23 +969,20 @@ function spacingSnapSource(opts = {}) {
|
|
|
820
969
|
const measured = opts.getTargets ? measureAll(opts.getTargets()) : fromScene(q);
|
|
821
970
|
if (!box || !probe || !measured) return;
|
|
822
971
|
const reach = q.reachWorld(q.reachPx);
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const key = `${o.delta.toFixed(6)}:${o.size.toFixed(6)}`;
|
|
831
|
-
if (Math.abs(o.delta) > reach || seen.has(key)) continue;
|
|
972
|
+
const refs = { x: referenceGaps(box, measured.x), y: referenceGaps(box, measured.y) };
|
|
973
|
+
const seen = /* @__PURE__ */ new Set();
|
|
974
|
+
for (const { axis, edge, rate } of movingSides(q)) {
|
|
975
|
+
const speed = Math.hypot(rate.x, rate.y);
|
|
976
|
+
for (const o of spacingOffers(box, axis, edge, measured.boxes, refs[axis])) {
|
|
977
|
+
const key = `${axis}:${edge}:${o.delta.toFixed(6)}:${o.size.toFixed(6)}`;
|
|
978
|
+
if (Math.abs(o.delta) / speed > reach || seen.has(key)) continue;
|
|
832
979
|
seen.add(key);
|
|
833
980
|
yield {
|
|
834
981
|
probe: probe.id,
|
|
835
|
-
locus:
|
|
982
|
+
locus: sideLine(probe.at, rate, o.delta),
|
|
836
983
|
mark: (delta) => {
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
return equalGaps(landed, axis, o.size, measured.boxes, refs).map((g) => gapMark(g, opts.color));
|
|
984
|
+
const landed = landedBox(q, delta) ?? box;
|
|
985
|
+
return equalGaps(landed, axis, o.size, measured.boxes, refs[axis]).map((g) => gapMark(g, opts.color));
|
|
840
986
|
}
|
|
841
987
|
};
|
|
842
988
|
}
|
|
@@ -846,7 +992,7 @@ function spacingSnapSource(opts = {}) {
|
|
|
846
992
|
}
|
|
847
993
|
|
|
848
994
|
// src/spacingHints/distance.ts
|
|
849
|
-
var
|
|
995
|
+
var EPS4 = 1e-6;
|
|
850
996
|
var lo2 = (b, a) => a === "x" ? b.x : b.y;
|
|
851
997
|
var hi2 = (b, a) => a === "x" ? b.x + b.width : b.y + b.height;
|
|
852
998
|
var mid = (b, a) => (lo2(b, a) + hi2(b, a)) / 2;
|
|
@@ -860,15 +1006,15 @@ function measureAxis(from, to, axis, out) {
|
|
|
860
1006
|
const c = cross3(axis);
|
|
861
1007
|
const sharedMin = Math.max(lo2(from, c), lo2(to, c));
|
|
862
1008
|
const sharedMax = Math.min(hi2(from, c), hi2(to, c));
|
|
863
|
-
const shares = sharedMax - sharedMin >
|
|
1009
|
+
const shares = sharedMax - sharedMin > EPS4;
|
|
864
1010
|
const push = (min, max, at2) => {
|
|
865
|
-
if (max - min >
|
|
1011
|
+
if (max - min > EPS4) out.gaps.push({ axis, min, max, at: at2 });
|
|
866
1012
|
};
|
|
867
|
-
const before = hi2(from, axis) <= lo2(to, axis) +
|
|
868
|
-
const after = hi2(to, axis) <= lo2(from, axis) +
|
|
1013
|
+
const before = hi2(from, axis) <= lo2(to, axis) + EPS4;
|
|
1014
|
+
const after = hi2(to, axis) <= lo2(from, axis) + EPS4;
|
|
869
1015
|
if (before || after) {
|
|
870
1016
|
const [min, max] = before ? [hi2(from, axis), lo2(to, axis)] : [hi2(to, axis), lo2(from, axis)];
|
|
871
|
-
if (max - min <=
|
|
1017
|
+
if (max - min <= EPS4) return;
|
|
872
1018
|
const at2 = shares ? (sharedMin + sharedMax) / 2 : mid(from, c);
|
|
873
1019
|
push(min, max, at2);
|
|
874
1020
|
if (!shares) {
|
|
@@ -879,7 +1025,7 @@ function measureAxis(from, to, axis, out) {
|
|
|
879
1025
|
return;
|
|
880
1026
|
}
|
|
881
1027
|
if (!shares) return;
|
|
882
|
-
const [inner, outer] = lo2(from, axis) >= lo2(to, axis) -
|
|
1028
|
+
const [inner, outer] = lo2(from, axis) >= lo2(to, axis) - EPS4 && hi2(from, axis) <= hi2(to, axis) + EPS4 ? [from, to] : lo2(to, axis) >= lo2(from, axis) - EPS4 && hi2(to, axis) <= hi2(from, axis) + EPS4 ? [to, from] : [null, null];
|
|
883
1029
|
if (inner === null || outer === null) return;
|
|
884
1030
|
const at = mid(inner, c);
|
|
885
1031
|
push(lo2(outer, axis), lo2(inner, axis), at);
|
|
@@ -1759,6 +1905,6 @@ function ringSlots(opts = {}) {
|
|
|
1759
1905
|
};
|
|
1760
1906
|
}
|
|
1761
1907
|
|
|
1762
|
-
export { alignmentSnapSource, cellAt, containedThenNearest, createCellHighlightLayer, createGridLayer, createGridPreset, createGuidesLayer, createSpacingHintsPreset, deriveAlignmentGuides, distanceReadout, freeform,
|
|
1908
|
+
export { alignmentSnapSource, cellAt, cellOf, containedThenNearest, containerLines, createCellHighlightLayer, createGridLayer, createGridPreset, createGuidesLayer, createSpacingHintsPreset, deriveAlignmentGuides, distanceReadout, freeform, gridFit, gridLines, gridOwner, gridSlots, gridSnapSource, guideSnapSource, linesIn, linesNear, mapLines, matchSpacing, measureDistances, measureGaps, nearest, nearestLine, nearestWithin, none, pack, perContainer, pointToGridCell, quadtree, regionAt, resolveGridLines, ringSlots, roundToCell, rowSlots, sceneGridRegions, sequenceLayout, siblingBounds, slots, snapPoint, spacingSnapSource, tileGrid, useGridCellHover, useGuides, worldBoundsOf, worldUnionOf };
|
|
1763
1909
|
//# sourceMappingURL=index.js.map
|
|
1764
1910
|
//# sourceMappingURL=index.js.map
|