@weasel-js/guides 1.14.0 → 1.14.2
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.d.ts +10 -6
- package/dist/index.js +48 -20
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -240,8 +240,10 @@ interface GridSnapSourceOptions {
|
|
|
240
240
|
scopes?: GridScopes;
|
|
241
241
|
}
|
|
242
242
|
/**
|
|
243
|
-
* A grid as snap targets: for each
|
|
244
|
-
* either side of it on each axis
|
|
243
|
+
* A grid as snap targets: for each box or pointer probe, the grid line on
|
|
244
|
+
* either side of it on each axis, so a move lands the drawn box, stroke
|
|
245
|
+
* included, with an edge or its center on a line; on a turned container's
|
|
246
|
+
* lattice, which no world-axis box lines up with, its pose origin. Its reach is unlimited and it is a
|
|
245
247
|
* fallback, so it holds every axis no target claims and never displaces one.
|
|
246
248
|
* Its lines are the camera's, carried into the plane a gesture runs in.
|
|
247
249
|
*/
|
|
@@ -459,8 +461,9 @@ declare function worldUnionOf(scene: Scene<unknown, string, unknown>, ids: Itera
|
|
|
459
461
|
declare function siblingBounds(scene: Scene<unknown, string, unknown>, ids: readonly NodeId[], opts?: SceneBoundsOptions): Bounds[];
|
|
460
462
|
|
|
461
463
|
/**
|
|
462
|
-
* Ruler guides as snap targets: each guide within reach of
|
|
463
|
-
* pointer probe is a line it may land on
|
|
464
|
+
* Ruler guides as snap targets: each guide within reach of a box or
|
|
465
|
+
* pointer probe is a line it may land on: a moved box's edges and center,
|
|
466
|
+
* stroke included. Reads `getGuides` every frame, so
|
|
464
467
|
* a guide added mid-drag counts at once.
|
|
465
468
|
*/
|
|
466
469
|
declare function guideSnapSource(getGuides: () => readonly Guide[]): SnapSource;
|
|
@@ -506,8 +509,9 @@ interface SpacingSnapSourceOptions {
|
|
|
506
509
|
* Equal spacing as snap targets, Figma's: on each axis, the positions of the
|
|
507
510
|
* subject's box that make its gap to a neighbor equal one already between
|
|
508
511
|
* two siblings, or center it between its two neighbors. A resize moves only
|
|
509
|
-
* the
|
|
510
|
-
*
|
|
512
|
+
* the sides it drags; on a turned or skewed box, the sides of its
|
|
513
|
+
* axis-aligned extent the drag moves. When the result meets one, it marks
|
|
514
|
+
* every gap of that size, the box's own included, where the box lands.
|
|
511
515
|
*/
|
|
512
516
|
declare function spacingSnapSource(opts?: SpacingSnapSourceOptions): SnapSource;
|
|
513
517
|
|
package/dist/index.js
CHANGED
|
@@ -333,6 +333,16 @@ function frameLine(frame, axis, offset, near) {
|
|
|
333
333
|
function landedShift(q, delta) {
|
|
334
334
|
return { x: delta.x - q.delta0.x, y: delta.y - q.delta0.y };
|
|
335
335
|
}
|
|
336
|
+
function sideLine(near, rate, by) {
|
|
337
|
+
const k = by / (rate.x * rate.x + rate.y * rate.y);
|
|
338
|
+
return { at: { x: near.x + rate.x * k, y: near.y + rate.y * k }, dirs: [{ x: -rate.y, y: rate.x }] };
|
|
339
|
+
}
|
|
340
|
+
function landedBox(q, delta) {
|
|
341
|
+
if (q.boxAt) return q.boxAt(delta);
|
|
342
|
+
if (!q.box) return null;
|
|
343
|
+
const shift = landedShift(q, delta);
|
|
344
|
+
return { ...q.box, x: q.box.x + shift.x, y: q.box.y + shift.y };
|
|
345
|
+
}
|
|
336
346
|
|
|
337
347
|
// src/sources/grid.ts
|
|
338
348
|
function gridSnapSource(opts) {
|
|
@@ -369,11 +379,13 @@ function gridSnapSource(opts) {
|
|
|
369
379
|
id: "grid",
|
|
370
380
|
tier: "fallback",
|
|
371
381
|
reachPx: Infinity,
|
|
372
|
-
roles: ["origin", "pointer"],
|
|
382
|
+
roles: ["box", "origin", "pointer"],
|
|
373
383
|
*collect(q) {
|
|
374
384
|
const { pitch, frame } = latticeFor(q);
|
|
375
385
|
const plane = q.plane ?? null;
|
|
386
|
+
const skip = frame && turns(frame) ? "box" : "origin";
|
|
376
387
|
for (const p of q.probes) {
|
|
388
|
+
if (p.role === skip) continue;
|
|
377
389
|
if (frame) {
|
|
378
390
|
yield* framedLines(p.id, p.at, pitch, frame, plane);
|
|
379
391
|
continue;
|
|
@@ -385,6 +397,16 @@ function gridSnapSource(opts) {
|
|
|
385
397
|
}
|
|
386
398
|
};
|
|
387
399
|
}
|
|
400
|
+
function turns(frame) {
|
|
401
|
+
const o = frame.toWorld({ x: 0, y: 0 });
|
|
402
|
+
const ex = frame.toWorld({ x: 1, y: 0 });
|
|
403
|
+
const ey = frame.toWorld({ x: 0, y: 1 });
|
|
404
|
+
const along = (a, axis) => {
|
|
405
|
+
const d = { x: a.x - o.x, y: a.y - o.y };
|
|
406
|
+
return axis === "x" ? Math.abs(d.y) <= 1e-9 * Math.abs(d.x) : Math.abs(d.x) <= 1e-9 * Math.abs(d.y);
|
|
407
|
+
};
|
|
408
|
+
return !(along(ex, "x") && along(ey, "y") || along(ex, "y") && along(ey, "x"));
|
|
409
|
+
}
|
|
388
410
|
function* framedLines(probe, at, pitch, frame, plane) {
|
|
389
411
|
const local = frame.toLocal(at);
|
|
390
412
|
if (!local) return;
|
|
@@ -685,7 +707,7 @@ function siblingBounds(scene, ids, opts = {}) {
|
|
|
685
707
|
function guideSnapSource(getGuides) {
|
|
686
708
|
return {
|
|
687
709
|
id: "guides",
|
|
688
|
-
roles: ["
|
|
710
|
+
roles: ["box", "pointer"],
|
|
689
711
|
*collect(q) {
|
|
690
712
|
const guides = getGuides();
|
|
691
713
|
if (guides.length === 0) return;
|
|
@@ -775,7 +797,6 @@ function alignmentSnapSource(opts = {}) {
|
|
|
775
797
|
const indexes = linesFor(q);
|
|
776
798
|
if (indexes.length === 0) return;
|
|
777
799
|
const reach = q.reachWorld(q.reachPx);
|
|
778
|
-
const box = q.box ?? null;
|
|
779
800
|
const probes = q.probes.some((p) => p.role === "box") ? q.probes.filter((p) => p.role === "box") : q.probes;
|
|
780
801
|
for (const p of probes) {
|
|
781
802
|
for (const axis of ["x", "y"]) {
|
|
@@ -784,7 +805,7 @@ function alignmentSnapSource(opts = {}) {
|
|
|
784
805
|
yield {
|
|
785
806
|
probe: p.id,
|
|
786
807
|
locus: axisLine(axis, g.offset, p.at),
|
|
787
|
-
mark: (delta) => lineMark(g, extentAcross(
|
|
808
|
+
mark: (delta) => lineMark(g, extentAcross(q, p, axis, delta), opts.color)
|
|
788
809
|
};
|
|
789
810
|
}
|
|
790
811
|
}
|
|
@@ -793,15 +814,25 @@ function alignmentSnapSource(opts = {}) {
|
|
|
793
814
|
}
|
|
794
815
|
};
|
|
795
816
|
}
|
|
796
|
-
function extentAcross(
|
|
817
|
+
function extentAcross(q, p, axis, delta) {
|
|
797
818
|
const c = cross2(axis);
|
|
798
|
-
|
|
799
|
-
|
|
819
|
+
const landed = p.role === "box" ? landedBox(q, delta) : null;
|
|
820
|
+
if (landed === null) {
|
|
821
|
+
const shift = landedShift(q, delta);
|
|
822
|
+
return [p.at[c] + shift[c], p.at[c] + shift[c]];
|
|
823
|
+
}
|
|
800
824
|
const lo3 = c === "x" ? landed.x : landed.y;
|
|
801
825
|
return [lo3, lo3 + (c === "x" ? landed.width : landed.height)];
|
|
802
826
|
}
|
|
803
827
|
|
|
804
828
|
// src/sources/spacing.ts
|
|
829
|
+
var TRANSLATE = [
|
|
830
|
+
{ axis: "x", edge: "both", rate: { x: 1, y: 0 } },
|
|
831
|
+
{ axis: "y", edge: "both", rate: { x: 0, y: 1 } }
|
|
832
|
+
];
|
|
833
|
+
function movingSides(q) {
|
|
834
|
+
return q.sides ? q.sides.map((s) => ({ axis: s.axis, edge: s.side, rate: s.rate })) : TRANSLATE;
|
|
835
|
+
}
|
|
805
836
|
var measureAll = (boxes) => ({ boxes, x: measureGapSpans(boxes, "x"), y: measureGapSpans(boxes, "y") });
|
|
806
837
|
function gapMark(g, color) {
|
|
807
838
|
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 +851,20 @@ function spacingSnapSource(opts = {}) {
|
|
|
820
851
|
const measured = opts.getTargets ? measureAll(opts.getTargets()) : fromScene(q);
|
|
821
852
|
if (!box || !probe || !measured) return;
|
|
822
853
|
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;
|
|
854
|
+
const refs = { x: referenceGaps(box, measured.x), y: referenceGaps(box, measured.y) };
|
|
855
|
+
const seen = /* @__PURE__ */ new Set();
|
|
856
|
+
for (const { axis, edge, rate } of movingSides(q)) {
|
|
857
|
+
const speed = Math.hypot(rate.x, rate.y);
|
|
858
|
+
for (const o of spacingOffers(box, axis, edge, measured.boxes, refs[axis])) {
|
|
859
|
+
const key = `${axis}:${edge}:${o.delta.toFixed(6)}:${o.size.toFixed(6)}`;
|
|
860
|
+
if (Math.abs(o.delta) / speed > reach || seen.has(key)) continue;
|
|
832
861
|
seen.add(key);
|
|
833
862
|
yield {
|
|
834
863
|
probe: probe.id,
|
|
835
|
-
locus:
|
|
864
|
+
locus: sideLine(probe.at, rate, o.delta),
|
|
836
865
|
mark: (delta) => {
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
return equalGaps(landed, axis, o.size, measured.boxes, refs).map((g) => gapMark(g, opts.color));
|
|
866
|
+
const landed = landedBox(q, delta) ?? box;
|
|
867
|
+
return equalGaps(landed, axis, o.size, measured.boxes, refs[axis]).map((g) => gapMark(g, opts.color));
|
|
840
868
|
}
|
|
841
869
|
};
|
|
842
870
|
}
|