@weasel-js/guides 1.14.2 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,197 @@
1
1
  import { useState, useRef, useCallback, useEffect } from 'react';
2
- import { sceneBoxFrames, scenePoseFrame, resolveAxes, useLatest, viewportWorldRect, tickedSegment, measuredSegmentCommands, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, rectFromPlane, unionAABB, createTransformOp, asNodeId, sceneSharedUnitScopes, sceneUnitScopes, screenToWorld, nodeGates, resolveScoped, resolveStrokeWidth } from '@weasel-js/core';
3
- import { pointInPolygon, invert, applyToPoint, mat3, pxExtent, PATH_M, PATH_L, PATH_Z, pxExtentInFrame } from '@weasel-js/geom';
2
+ import { sceneBoxFrames, scenePoseFrame, useLatest, viewportWorldRect, tickedSegment, measuredSegmentCommands, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, rectFromPlane, unionAABB, sceneMagnification, asNodeId, createTransformOp, 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
- function sceneGridRegions(scene, root, outlineOf) {
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.get(id)?.units === void 0) continue;
22
- const parent = scopeOwner(scene, id);
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 scopeOwner(scene, id, inclusive = false) {
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.get(at)?.units !== void 0) return at;
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 = scoped ? regionAt(scoped.regions, point) : null;
80
- const s = gridPitch(spacing, unitSystem, scoped?.scopes, region?.chain);
81
- const back = region?.frame && invert(region.frame);
82
- if (region?.frame && back) {
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
- return {
87
- col: Math.floor((point.x - origin.x) / s.x),
88
- row: Math.floor((point.y - origin.y) / s.y),
89
- ...scoped ? { pitch: s } : {}
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.pitch?.x === b.pitch?.x && a.pitch?.y === b.pitch?.y && (a.frame === b.frame || !!a.frame && !!b.frame && a.frame.every((v, i) => v === b.frame[i]));
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() } : {} } : void 0;
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 every = opts.accentEvery && opts.accentEvery > 0 ? opts.accentEvery : 1;
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 area = opts.bounds ? b : shown;
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 pitch = gridPitch(opts.spacing, opts.unitSystem, opts.scopes, r.chain);
231
- const lines = regionLattice(r, area, (box, frame) => lattice(onLattice(box, pitch, every, frame ? { x: 0, y: 0 } : anchor), pitch, opts, view, styles, frame));
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 spacing = cell.pitch ?? resolveAxes(opts.spacing, opts.unitSystem);
298
- const o = cell.frame || !opts.origin ? { x: 0, y: 0 } : opts.origin();
299
- const x = o.x + cell.col * spacing.x;
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/strategies/onPlane.ts
314
- function guideOffsetOn(g, plane) {
315
- return plane ? g.offset * plane.scale[g.axis] + plane.offset[g.axis] : g.offset;
316
- }
317
- function roundToGridOn(value, axis, spacing, plane) {
318
- if (!plane) return Math.round(value / spacing) * spacing;
319
- const camera = (value - plane.offset[axis]) / plane.scale[axis];
320
- return Math.round(camera / spacing) * spacing * plane.scale[axis] + plane.offset[axis];
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
@@ -345,53 +457,56 @@ function landedBox(q, delta) {
345
457
  }
346
458
 
347
459
  // src/sources/grid.ts
460
+ var AXES = ["x", "y"];
348
461
  function gridSnapSource(opts) {
349
- const { scopes } = opts;
350
- const rootPitch = gridPitch(opts.spacing, opts.unitSystem, scopes);
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
+ };
351
473
  const latticeFor = (q) => {
352
474
  const scene = q.scene;
353
- if (!scopes || !scene) return { pitch: rootPitch };
354
- const within = (owner) => owner === null ? void 0 : q.frameOf?.(owner);
475
+ if (!scopes && !gridOf || !scene) return { lines: rootLines };
355
476
  if (q.ids?.length) {
356
- const ids = q.ids.map(asNodeId);
357
- const owners = new Set(ids.map((id) => scopeOwner(scene, id)));
358
- return {
359
- pitch: scopedGridPitch(opts.spacing, scopes, sceneSharedUnitScopes(scene, ids, scopes.root)),
360
- // Nodes in two containers share no one lattice, whatever their scopes.
361
- frame: owners.size === 1 ? within([...owners][0]) : void 0
362
- };
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 };
363
481
  }
364
- if (q.parent === void 0) return { pitch: rootPitch };
365
- const parent = asNodeId(q.parent);
366
- return {
367
- pitch: scopedGridPitch(opts.spacing, scopes, sceneUnitScopes(scene, parent, scopes.root, { inclusive: true })),
368
- frame: within(scopeOwner(scene, parent, true))
369
- };
482
+ if (q.parent === void 0) return { lines: rootLines };
483
+ return latticeOf(q, gridOwner(scene, asNodeId(q.parent), owners, true));
370
484
  };
371
485
  const o = opts.origin ?? { x: 0, y: 0 };
372
- const lines = (v, axis, pitch, plane) => {
373
- const s = plane ? plane.scale[axis] : 1;
374
- const near = roundToGridOn(v - o[axis] * s, axis, pitch[axis], plane) + o[axis] * s;
375
- const step = pitch[axis] * s;
376
- return [near, near + (v >= near ? step : -step)];
377
- };
378
486
  return {
379
- id: "grid",
487
+ id: opts.id ?? "grid",
380
488
  tier: "fallback",
381
489
  reachPx: Infinity,
382
- roles: ["box", "origin", "pointer"],
490
+ roles: ["box", "origin", "pointer", "anchor"],
383
491
  *collect(q) {
384
- const { pitch, frame } = latticeFor(q);
492
+ const { lines, frame } = latticeFor(q);
493
+ if (!lines) return;
385
494
  const plane = q.plane ?? null;
495
+ const scale = (axis) => plane ? plane.scale[axis] : 1;
496
+ const anchored = q.probes.some((p) => p.role === "anchor");
386
497
  const skip = frame && turns(frame) ? "box" : "origin";
387
- for (const p of q.probes) {
388
- if (p.role === skip) continue;
389
- if (frame) {
390
- yield* framedLines(p.id, p.at, pitch, frame, plane);
391
- continue;
392
- }
393
- for (const axis of ["x", "y"]) {
394
- for (const at of lines(p.at[axis], axis, pitch, plane)) yield { probe: p.id, locus: axisLine(axis, at, p.at) };
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) };
395
510
  }
396
511
  }
397
512
  }
@@ -407,15 +522,12 @@ function turns(frame) {
407
522
  };
408
523
  return !(along(ex, "x") && along(ey, "y") || along(ex, "y") && along(ey, "x"));
409
524
  }
410
- function* framedLines(probe, at, pitch, frame, plane) {
525
+ function* framedLines(probe, at, lines, frame) {
411
526
  const local = frame.toLocal(at);
412
527
  if (!local) return;
413
- for (const axis of ["x", "y"]) {
414
- const step = pitch[axis] * (plane ? plane.scale[axis] : 1);
415
- const near = Math.round(local[axis] / step) * step;
416
- for (const offset of [near, near + (local[axis] >= near ? step : -step)]) {
417
- yield { probe, locus: frameLine(frame, axis, offset, local) };
418
- }
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) };
419
531
  }
420
532
  }
421
533
 
@@ -427,6 +539,7 @@ function createGridPreset(opts) {
427
539
  id,
428
540
  layers: { grid: { layer: createGridLayer(layer) } },
429
541
  snapSources: snap === false ? [] : [gridSnapSource({
542
+ id,
430
543
  spacing,
431
544
  ...unitSystem === void 0 ? {} : { unitSystem },
432
545
  ...scopes === void 0 ? {} : { scopes },
@@ -504,7 +617,7 @@ function createGuidesLayer(opts) {
504
617
  }
505
618
  };
506
619
  }
507
- var EPS = 1e-3;
620
+ var EPS2 = 1e-3;
508
621
  function deriveAlignmentGuides(targets, opts = {}) {
509
622
  const d = opts.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
510
623
  const edges = opts.edges ?? true;
@@ -513,7 +626,7 @@ function deriveAlignmentGuides(targets, opts = {}) {
513
626
  const seenY = /* @__PURE__ */ new Map();
514
627
  const add = (axis, offset, b) => {
515
628
  const seen = axis === "x" ? seenX : seenY;
516
- const key = Math.round(offset / EPS);
629
+ const key = Math.round(offset / EPS2);
517
630
  const min = axis === "x" ? b.y : b.x;
518
631
  const max = min + (axis === "x" ? b.height : b.width);
519
632
  const prev = seen.get(key);
@@ -545,14 +658,14 @@ function deriveAlignmentGuides(targets, opts = {}) {
545
658
  }
546
659
 
547
660
  // src/guides/alignment/spacing.ts
548
- var EPS2 = 1e-6;
661
+ var EPS3 = 1e-6;
549
662
  var lo = (b, a) => a === "x" ? b.x : b.y;
550
663
  var hi = (b, a) => a === "x" ? b.x + b.width : b.y + b.height;
551
664
  var cross = (a) => a === "x" ? "y" : "x";
552
665
  function overlap(a, b, axis) {
553
666
  const min = Math.max(lo(a, axis), lo(b, axis));
554
667
  const max = Math.min(hi(a, axis), hi(b, axis));
555
- return max - min > EPS2 ? [min, max] : null;
668
+ return max - min > EPS3 ? [min, max] : null;
556
669
  }
557
670
  function gapBetween(a, b, axis) {
558
671
  const shared = overlap(a, b, cross(axis));
@@ -561,7 +674,7 @@ function gapBetween(a, b, axis) {
561
674
  }
562
675
  function hitsStrip(g, b, span2) {
563
676
  const c = cross(g.axis);
564
- return lo(b, g.axis) < g.max - EPS2 && hi(b, g.axis) > g.min + EPS2 && lo(b, c) < span2[1] - EPS2 && hi(b, c) > span2[0] + EPS2;
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;
565
678
  }
566
679
  function measureGaps(targets, axis) {
567
680
  return measureGapSpans(targets, axis).map((m) => m.gap);
@@ -571,7 +684,7 @@ function measureGapSpans(targets, axis) {
571
684
  for (const a of targets) {
572
685
  let next = null;
573
686
  for (const c of targets) {
574
- if (c === a || lo(c, axis) < hi(a, axis) + EPS2) continue;
687
+ if (c === a || lo(c, axis) < hi(a, axis) + EPS3) continue;
575
688
  if (overlap(a, c, cross(axis)) === null) continue;
576
689
  if (next === null || lo(c, axis) < lo(next, axis)) next = c;
577
690
  }
@@ -598,14 +711,14 @@ function spacingOffers(b, axis, edge, targets, refs) {
598
711
  const sizes = refs.map((g) => g.max - g.min);
599
712
  const out = [];
600
713
  const offer = (delta, gap) => {
601
- if (gap > EPS2) out.push({ delta, size: gap });
714
+ if (gap > EPS3) out.push({ delta, size: gap });
602
715
  };
603
716
  const L = left === null ? null : hi(left, axis);
604
717
  const R = right === null ? null : lo(right, axis);
605
718
  if (edge === "both") {
606
719
  for (const g of sizes) {
607
- if (L !== null && (R === null || L + g + size <= R + EPS2)) offer(L + g - bLo, g);
608
- if (R !== null && (L === null || R - g - size >= L - EPS2)) offer(R - g - size - bLo, g);
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);
609
722
  }
610
723
  if (L !== null && R !== null) {
611
724
  const g = (R - L - size) / 2;
@@ -613,10 +726,10 @@ function spacingOffers(b, axis, edge, targets, refs) {
613
726
  }
614
727
  } else if (edge === "max" && R !== null) {
615
728
  const own = L !== null ? [bLo - L] : [];
616
- for (const g of [...sizes, ...own]) if (R - g > bLo + EPS2) offer(R - g - bHi, g);
729
+ for (const g of [...sizes, ...own]) if (R - g > bLo + EPS3) offer(R - g - bHi, g);
617
730
  } else if (edge === "min" && L !== null) {
618
731
  const own = R !== null ? [R - bHi] : [];
619
- for (const g of [...sizes, ...own]) if (L + g < bHi - EPS2) offer(L + g - bLo, g);
732
+ for (const g of [...sizes, ...own]) if (L + g < bHi - EPS3) offer(L + g - bLo, g);
620
733
  }
621
734
  return out;
622
735
  }
@@ -631,8 +744,8 @@ function bestAxis(b, axis, edge, targets, refs, tol) {
631
744
  function equalGaps(b, axis, size, targets, refs) {
632
745
  const eq = (g) => Math.abs(g.max - g.min - size) < 1e-3;
633
746
  const out = refs.filter(eq);
634
- const near = targets.filter((t) => hi(t, axis) > lo(b, axis) - size - EPS2 && lo(t, axis) < hi(b, axis) + size + EPS2);
635
- 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));
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));
636
749
  return [...out, ...own];
637
750
  }
638
751
  function referenceGaps(bounds, measured) {
@@ -703,6 +816,11 @@ function siblingBounds(scene, ids, opts = {}) {
703
816
  return worldBoundsOf(scene, siblingIds(scene, ids), opts);
704
817
  }
705
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
+
706
824
  // src/sources/guides.ts
707
825
  function guideSnapSource(getGuides) {
708
826
  return {
@@ -824,8 +942,6 @@ function extentAcross(q, p, axis, delta) {
824
942
  const lo3 = c === "x" ? landed.x : landed.y;
825
943
  return [lo3, lo3 + (c === "x" ? landed.width : landed.height)];
826
944
  }
827
-
828
- // src/sources/spacing.ts
829
945
  var TRANSLATE = [
830
946
  { axis: "x", edge: "both", rate: { x: 1, y: 0 } },
831
947
  { axis: "y", edge: "both", rate: { x: 0, y: 1 } }
@@ -834,9 +950,9 @@ function movingSides(q) {
834
950
  return q.sides ? q.sides.map((s) => ({ axis: s.axis, edge: s.side, rate: s.rate })) : TRANSLATE;
835
951
  }
836
952
  var measureAll = (boxes) => ({ boxes, x: measureGapSpans(boxes, "x"), y: measureGapSpans(boxes, "y") });
837
- function gapMark(g, color) {
953
+ function gapMark(g, color, magnification) {
838
954
  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 } };
839
- return { kind: "gap", ...ends, value: g.max - g.min, ...color === void 0 ? {} : { color } };
955
+ return { kind: "gap", ...ends, value: (g.max - g.min) / magnification, ...color === void 0 ? {} : { color } };
840
956
  }
841
957
  function spacingSnapSource(opts = {}) {
842
958
  const fromScene = cachedPerScene(measureAll, opts.filter);
@@ -853,6 +969,7 @@ function spacingSnapSource(opts = {}) {
853
969
  const reach = q.reachWorld(q.reachPx);
854
970
  const refs = { x: referenceGaps(box, measured.x), y: referenceGaps(box, measured.y) };
855
971
  const seen = /* @__PURE__ */ new Set();
972
+ const magnification = q.scene && q.ids?.length ? sceneMagnification(q.scene, q.ids.map(asNodeId)) : 1;
856
973
  for (const { axis, edge, rate } of movingSides(q)) {
857
974
  const speed = Math.hypot(rate.x, rate.y);
858
975
  for (const o of spacingOffers(box, axis, edge, measured.boxes, refs[axis])) {
@@ -864,7 +981,7 @@ function spacingSnapSource(opts = {}) {
864
981
  locus: sideLine(probe.at, rate, o.delta),
865
982
  mark: (delta) => {
866
983
  const landed = landedBox(q, delta) ?? box;
867
- return equalGaps(landed, axis, o.size, measured.boxes, refs[axis]).map((g) => gapMark(g, opts.color));
984
+ return equalGaps(landed, axis, o.size, measured.boxes, refs[axis]).map((g) => gapMark(g, opts.color, magnification));
868
985
  }
869
986
  };
870
987
  }
@@ -874,7 +991,7 @@ function spacingSnapSource(opts = {}) {
874
991
  }
875
992
 
876
993
  // src/spacingHints/distance.ts
877
- var EPS3 = 1e-6;
994
+ var EPS4 = 1e-6;
878
995
  var lo2 = (b, a) => a === "x" ? b.x : b.y;
879
996
  var hi2 = (b, a) => a === "x" ? b.x + b.width : b.y + b.height;
880
997
  var mid = (b, a) => (lo2(b, a) + hi2(b, a)) / 2;
@@ -888,15 +1005,15 @@ function measureAxis(from, to, axis, out) {
888
1005
  const c = cross3(axis);
889
1006
  const sharedMin = Math.max(lo2(from, c), lo2(to, c));
890
1007
  const sharedMax = Math.min(hi2(from, c), hi2(to, c));
891
- const shares = sharedMax - sharedMin > EPS3;
1008
+ const shares = sharedMax - sharedMin > EPS4;
892
1009
  const push = (min, max, at2) => {
893
- if (max - min > EPS3) out.gaps.push({ axis, min, max, at: at2 });
1010
+ if (max - min > EPS4) out.gaps.push({ axis, min, max, at: at2 });
894
1011
  };
895
- const before = hi2(from, axis) <= lo2(to, axis) + EPS3;
896
- const after = hi2(to, axis) <= lo2(from, axis) + EPS3;
1012
+ const before = hi2(from, axis) <= lo2(to, axis) + EPS4;
1013
+ const after = hi2(to, axis) <= lo2(from, axis) + EPS4;
897
1014
  if (before || after) {
898
1015
  const [min, max] = before ? [hi2(from, axis), lo2(to, axis)] : [hi2(to, axis), lo2(from, axis)];
899
- if (max - min <= EPS3) return;
1016
+ if (max - min <= EPS4) return;
900
1017
  const at2 = shares ? (sharedMin + sharedMax) / 2 : mid(from, c);
901
1018
  push(min, max, at2);
902
1019
  if (!shares) {
@@ -907,7 +1024,7 @@ function measureAxis(from, to, axis, out) {
907
1024
  return;
908
1025
  }
909
1026
  if (!shares) return;
910
- 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];
1027
+ 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];
911
1028
  if (inner === null || outer === null) return;
912
1029
  const at = mid(inner, c);
913
1030
  push(lo2(outer, axis), lo2(inner, axis), at);
@@ -915,10 +1032,13 @@ function measureAxis(from, to, axis, out) {
915
1032
  }
916
1033
 
917
1034
  // src/spacingHints/distanceReadout.ts
918
- function marksOf(r, color) {
1035
+ function marksOf(r, color, magnification) {
919
1036
  const c = color === void 0 ? {} : { color };
920
1037
  return [
921
- ...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 }),
1038
+ ...r.gaps.map((g) => {
1039
+ const value = (g.max - g.min) / magnification;
1040
+ return g.axis === "x" ? { kind: "distance", from: { x: g.min, y: g.at }, to: { x: g.max, y: g.at }, value, ...c } : { kind: "distance", from: { x: g.at, y: g.min }, to: { x: g.at, y: g.max }, value, ...c };
1041
+ }),
922
1042
  ...r.leaders.flatMap((l) => {
923
1043
  if (l.span === void 0) return [];
924
1044
  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 }];
@@ -943,10 +1063,10 @@ function distanceReadout(opts = {}) {
943
1063
  const modifier = opts.modifier ?? "alt";
944
1064
  let shown = EMPTY;
945
1065
  let store = null;
946
- const show = (next) => {
1066
+ const show = (next, magnification = 1) => {
947
1067
  if (next === shown || next.gaps.length === 0 && shown.gaps.length === 0) return;
948
1068
  shown = next;
949
- store?.set(marksOf(next, opts.color));
1069
+ store?.set(marksOf(next, opts.color, magnification));
950
1070
  };
951
1071
  const action = {
952
1072
  id: `${id}.measure`,
@@ -966,7 +1086,8 @@ function distanceReadout(opts = {}) {
966
1086
  const hit = nodeAtPoint(p.world, subtreeOf(scene, ids));
967
1087
  const from = worldUnionOf(scene, ids, { poseDescriptor });
968
1088
  const [to] = hit === null ? [] : worldBoundsOf(scene, [hit], { poseDescriptor });
969
- show(from === null || to === void 0 ? EMPTY : measureDistances(from, to));
1089
+ if (from === null || to === void 0) show(EMPTY);
1090
+ else show(measureDistances(from, to), sceneMagnification(scene, [...ids, hit]));
970
1091
  }
971
1092
  }
972
1093
  };
@@ -1787,6 +1908,6 @@ function ringSlots(opts = {}) {
1787
1908
  };
1788
1909
  }
1789
1910
 
1790
- export { alignmentSnapSource, cellAt, containedThenNearest, createCellHighlightLayer, createGridLayer, createGridPreset, createGuidesLayer, createSpacingHintsPreset, deriveAlignmentGuides, distanceReadout, freeform, gridPitch, gridSlots, gridSnapSource, guideSnapSource, matchSpacing, measureDistances, measureGaps, nearest, nearestWithin, none, pack, perContainer, pointToGridCell, quadtree, regionAt, ringSlots, roundToCell, rowSlots, sceneGridRegions, scopedGridPitch, sequenceLayout, siblingBounds, slots, snapPoint, spacingSnapSource, tileGrid, useGridCellHover, useGuides, worldBoundsOf, worldUnionOf };
1911
+ 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 };
1791
1912
  //# sourceMappingURL=index.js.map
1792
1913
  //# sourceMappingURL=index.js.map