@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 CHANGED
@@ -240,8 +240,10 @@ interface GridSnapSourceOptions {
240
240
  scopes?: GridScopes;
241
241
  }
242
242
  /**
243
- * A grid as snap targets: for each origin or pointer probe, the grid line on
244
- * either side of it on each axis. Its reach is unlimited and it is a
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 an origin or
463
- * pointer probe is a line it may land on. Reads `getGuides` every frame, so
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 edges it drags. When the result meets one, it marks every gap of that
510
- * size, the box's own included.
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: ["origin", "pointer"],
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(p, box, axis, landedShift(q, delta), q.edges), opts.color)
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(p, box, axis, shift, edges = { x: "both", y: "both" }) {
817
+ function extentAcross(q, p, axis, delta) {
797
818
  const c = cross2(axis);
798
- if (p.role !== "box" || box === null) return [p.at[c] + shift[c], p.at[c] + shift[c]];
799
- const landed = applyEdges(box, shift.x, shift.y, edges);
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 edges = q.edges ?? { x: "both", y: "both" };
824
- for (const axis of ["x", "y"]) {
825
- const edge = edges[axis];
826
- if (edge === null) continue;
827
- const refs = referenceGaps(box, measured[axis]);
828
- const seen = /* @__PURE__ */ new Set();
829
- for (const o of spacingOffers(box, axis, edge, measured.boxes, refs)) {
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: axisLine(axis, probe.at[axis] + o.delta, probe.at),
864
+ locus: sideLine(probe.at, rate, o.delta),
836
865
  mark: (delta) => {
837
- const shift = landedShift(q, delta);
838
- const landed = applyEdges(box, shift.x, shift.y, edges);
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
  }