@weasel-js/guides 1.9.3 → 1.10.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/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
- import { pointToGridCell } from './chunk-5BCCIJY3.js';
2
- export { gridSnapStrategy, pointToGridCell } from './chunk-5BCCIJY3.js';
1
+ import { pointToGridCell } from './chunk-TLFMAZ6M.js';
2
+ export { gridSnapStrategy, pointToGridCell } from './chunk-TLFMAZ6M.js';
3
3
  export { pointSnapToGrid } from './chunk-B25KFI3I.js';
4
- export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy } from './chunk-GEDAPTOM.js';
4
+ import { guideOffsetOn } from './chunk-LOFNN7EQ.js';
5
+ export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy } from './chunk-LOFNN7EQ.js';
5
6
  import { useState, useRef, useCallback, useEffect } from 'react';
6
- import { useLatest, resolveUnit, pxExtent, textCommandFromRuns, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, translatePoseViaDescriptor, unionBounds, fixedCornerOf, createTransformOp, resolveStrokeWidth, screenToWorld, screenTolerance } from '@weasel-js/core';
7
+ import { useLatest, resolveAxes, pxExtent, textCommandFromRuns, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, rectFromPlane, translatePoseViaDescriptor, unionBounds, fixedCornerOf, createTransformOp, resolveStrokeWidth, screenToWorld, rectToPlane, screenTolerance } from '@weasel-js/core';
7
8
  import { rectPath, PATH_M, PATH_L } from '@weasel-js/geom';
8
9
 
9
10
  // src/grid/roundToCell.ts
@@ -52,15 +53,15 @@ function useGridCellHover(opts) {
52
53
  return { cell, getCell };
53
54
  }
54
55
  var DEFAULT_LINE = {
55
- paint: { fill: "solid", color: "rgba(255, 255, 255, 0.08)" },
56
+ paint: { kind: "solid", color: "rgba(255, 255, 255, 0.08)" },
56
57
  width: 1
57
58
  };
58
59
  var DEFAULT_ACCENT = {
59
- paint: { fill: "solid", color: "rgba(255, 255, 255, 0.16)" },
60
+ paint: { kind: "solid", color: "rgba(255, 255, 255, 0.16)" },
60
61
  width: 1
61
62
  };
62
63
  var DEFAULT_SUB = {
63
- paint: { fill: "solid", color: "rgba(255, 255, 255, 0.04)" },
64
+ paint: { kind: "solid", color: "rgba(255, 255, 255, 0.04)" },
64
65
  width: 1
65
66
  };
66
67
  function createGridLayer(opts) {
@@ -74,7 +75,7 @@ function createGridLayer(opts) {
74
75
  const b = opts.bounds();
75
76
  if (b.width <= 0 || b.height <= 0) return [];
76
77
  const { accentEvery, subdivisions } = opts;
77
- const spacing = resolveUnit(opts.spacing, opts.unitSystem);
78
+ const spacing = resolveAxes(opts.spacing, opts.unitSystem);
78
79
  const x0 = b.x;
79
80
  const y0 = b.y;
80
81
  const x1 = b.x + b.width;
@@ -88,46 +89,47 @@ function createGridLayer(opts) {
88
89
  coords: new Float32Array([sx, sy, ex, ey]),
89
90
  fillRule: "nonzero"
90
91
  };
91
- const across = sx === ex ? px.x : px.y;
92
+ const across2 = sx === ex ? px.x : px.y;
92
93
  children.push({
93
94
  kind: "path",
94
95
  path,
95
- stroke: { ...s, width: resolveStrokeWidth(s.width ?? 1, 1) * across }
96
+ stroke: { ...s, width: resolveStrokeWidth(s.width ?? 1, 1) * across2 }
96
97
  });
97
98
  };
98
99
  if (subdivisions && subdivisions > 1) {
99
- const step = spacing / subdivisions;
100
- for (let x = x0; x <= x1 + 1e-9; x += step) {
101
- const k = Math.round((x - x0) / step);
100
+ const stepX = spacing.x / subdivisions;
101
+ for (let x = x0; x <= x1 + 1e-9; x += stepX) {
102
+ const k = Math.round((x - x0) / stepX);
102
103
  if (k % subdivisions === 0) continue;
103
104
  pushSegment(x, y0, x, y1, sub);
104
105
  }
105
- for (let y = y0; y <= y1 + 1e-9; y += step) {
106
- const k = Math.round((y - y0) / step);
106
+ const stepY = spacing.y / subdivisions;
107
+ for (let y = y0; y <= y1 + 1e-9; y += stepY) {
108
+ const k = Math.round((y - y0) / stepY);
107
109
  if (k % subdivisions === 0) continue;
108
110
  pushSegment(x0, y, x1, y, sub);
109
111
  }
110
112
  }
111
113
  let idx = 0;
112
- for (let x = x0; x <= x1 + 1e-9; x += spacing) {
114
+ for (let x = x0; x <= x1 + 1e-9; x += spacing.x) {
113
115
  const isAccent = !!accentEvery && accentEvery > 0 && idx % accentEvery === 0;
114
116
  if (!isAccent) pushSegment(x, y0, x, y1, line);
115
117
  idx++;
116
118
  }
117
119
  idx = 0;
118
- for (let y = y0; y <= y1 + 1e-9; y += spacing) {
120
+ for (let y = y0; y <= y1 + 1e-9; y += spacing.y) {
119
121
  const isAccent = !!accentEvery && accentEvery > 0 && idx % accentEvery === 0;
120
122
  if (!isAccent) pushSegment(x0, y, x1, y, line);
121
123
  idx++;
122
124
  }
123
125
  if (accentEvery && accentEvery > 0) {
124
126
  idx = 0;
125
- for (let x = x0; x <= x1 + 1e-9; x += spacing) {
127
+ for (let x = x0; x <= x1 + 1e-9; x += spacing.x) {
126
128
  if (idx % accentEvery === 0) pushSegment(x, y0, x, y1, accent);
127
129
  idx++;
128
130
  }
129
131
  idx = 0;
130
- for (let y = y0; y <= y1 + 1e-9; y += spacing) {
132
+ for (let y = y0; y <= y1 + 1e-9; y += spacing.y) {
131
133
  if (idx % accentEvery === 0) pushSegment(x0, y, x1, y, accent);
132
134
  idx++;
133
135
  }
@@ -136,7 +138,7 @@ function createGridLayer(opts) {
136
138
  }
137
139
  };
138
140
  }
139
- var DEFAULT_FILL = { fill: "solid", color: "rgba(127,176,105,0.15)" };
141
+ var DEFAULT_FILL = { paint: { kind: "solid", color: "rgba(127,176,105,0.15)" } };
140
142
  function createCellHighlightLayer(opts) {
141
143
  const fill = opts.fill ?? DEFAULT_FILL;
142
144
  return {
@@ -145,14 +147,14 @@ function createCellHighlightLayer(opts) {
145
147
  draw: () => {
146
148
  const cell = opts.getCell();
147
149
  if (!cell) return [];
148
- const spacing = resolveUnit(opts.spacing, opts.unitSystem);
150
+ const spacing = resolveAxes(opts.spacing, opts.unitSystem);
149
151
  const o = opts.origin ? opts.origin() : { x: 0, y: 0 };
150
- const x = o.x + cell.col * spacing;
151
- const y = o.y + cell.row * spacing;
152
+ const x = o.x + cell.col * spacing.x;
153
+ const y = o.y + cell.row * spacing.y;
152
154
  const children = [
153
155
  {
154
156
  kind: "path",
155
- path: { kind: "rect", x, y, width: spacing, height: spacing },
157
+ path: { kind: "rect", x, y, width: spacing.x, height: spacing.y },
156
158
  fill
157
159
  }
158
160
  ];
@@ -213,8 +215,8 @@ function createGuidesLayer(opts) {
213
215
  const ticks = opts.ticks ?? 6;
214
216
  const spanned = (opts.extent ?? "span") === "span";
215
217
  const format = opts.gapLabels === false ? null : typeof opts.gapLabels === "function" ? opts.gapLabels : (n) => String(Math.round(n));
216
- const stroke = { paint: { fill: "solid", color }, width: lineWidth };
217
- const gapStroke = { paint: { fill: "solid", color: gapColor }, width: lineWidth };
218
+ const stroke = { paint: { kind: "solid", color }, width: lineWidth };
219
+ const gapStroke = { paint: { kind: "solid", color: gapColor }, width: lineWidth };
218
220
  return {
219
221
  id: opts.id ?? "guides",
220
222
  label: opts.label ?? "Guides",
@@ -248,11 +250,11 @@ function createGuidesLayer(opts) {
248
250
  const w = text.length * LABEL_CHAR_W + LABEL_PAD_X * 2;
249
251
  const left = (x0 + x1) / 2 - w / 2;
250
252
  const top = (y0 + y1) / 2 - LABEL_H / 2;
251
- out.push({ kind: "path", path: rectPath(left, top, w, LABEL_H), fill: { fill: "solid", color: gapColor } });
253
+ out.push({ kind: "path", path: rectPath(left, top, w, LABEL_H), fill: { paint: { kind: "solid", color: gapColor } } });
252
254
  out.push(textCommandFromRuns(
253
255
  left + LABEL_PAD_X,
254
256
  top + 2,
255
- [{ text, fill: { fill: "solid", color: "#fff" } }],
257
+ [{ text, fill: { paint: { kind: "solid", color: "#fff" } } }],
256
258
  LABEL_FONT
257
259
  ));
258
260
  }
@@ -291,7 +293,11 @@ function deriveAlignmentGuides(targets, opts = {}) {
291
293
  add("y", b.y + b.height / 2, b);
292
294
  }
293
295
  };
294
- for (const t of targets) emit(visualBoundsViaDescriptor(t, d));
296
+ targets.forEach((t, i) => {
297
+ const box = visualBoundsViaDescriptor(t, d);
298
+ const plane = opts.planeOf?.(t, i);
299
+ emit(plane ? rectFromPlane(plane, box) : box);
300
+ });
295
301
  if (opts.page) emit(opts.page);
296
302
  return [...seenX.values(), ...seenY.values()];
297
303
  }
@@ -460,6 +466,39 @@ function applyEdges(b, dx, dy, moving) {
460
466
  } else if (moving.y === "max") height += dy;
461
467
  return { x, y, width, height };
462
468
  }
469
+ var across = (axis) => axis === "x" ? "y" : "x";
470
+ var into = (m, axis, v) => v * m.scale[axis] + m.offset[axis];
471
+ var outOf = (m, axis, v) => (v - m.offset[axis]) / m.scale[axis];
472
+ function range(r, map) {
473
+ const a = map(r.min);
474
+ const b = map(r.max);
475
+ return { ...r, min: Math.min(a, b), max: Math.max(a, b) };
476
+ }
477
+ function guideToPlane(g, m) {
478
+ const span = g.span && range(g.span, (v) => into(m, across(g.axis), v));
479
+ return { ...g, offset: guideOffsetOn(g, m), ...span ? { span } : {} };
480
+ }
481
+ function guideFromPlane(g, m) {
482
+ const span = g.span && range(g.span, (v) => outOf(m, across(g.axis), v));
483
+ return { ...g, offset: outOf(m, g.axis, g.offset), ...span ? { span } : {} };
484
+ }
485
+ function gapFromPlane(g, m) {
486
+ return { ...range(g, (v) => outOf(m, g.axis, v)), at: outOf(m, across(g.axis), g.at) };
487
+ }
488
+ function alignmentOnPlane(args, plane) {
489
+ if (!plane) return args;
490
+ const spacing = args.getSpacingTargets;
491
+ const setGaps = args.setActiveGaps;
492
+ return {
493
+ ...args,
494
+ getCandidates: () => args.getCandidates().map((g) => guideToPlane(g, plane)),
495
+ setActiveGuides: (guides) => args.setActiveGuides(guides.map((g) => guideFromPlane(g, plane))),
496
+ ...spacing ? { getSpacingTargets: () => spacing().map((b) => rectToPlane(plane, b)) } : {},
497
+ ...setGaps ? { setActiveGaps: (gaps) => setGaps(gaps.map((g) => gapFromPlane(g, plane))) } : {}
498
+ };
499
+ }
500
+
501
+ // src/guides/alignment/behaviors.ts
463
502
  var activeList = (m) => [m.activeX, m.activeY].filter((g) => g !== null);
464
503
  var EXACT = { x: 1e-6, y: 1e-6 };
465
504
  function resolve(b, args, tol, anchors, edges) {
@@ -491,10 +530,11 @@ function publish(args, r) {
491
530
  function worldTol(base, ctx) {
492
531
  return screenTolerance(base.tolerance ?? 6, ctx);
493
532
  }
494
- function alignMoveBehavior(args) {
495
- const d = args.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
533
+ function alignMoveBehavior(given) {
534
+ const d = given.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
496
535
  return {
497
536
  onMove(ctx, transform) {
537
+ const args = alignmentOnPlane(given, ctx.plane);
498
538
  if (args.bypassKey && ctx.modifiers[args.bypassKey]) {
499
539
  publish(args, null);
500
540
  return;
@@ -517,17 +557,18 @@ function alignMoveBehavior(args) {
517
557
  return { transform: { kind: "translate", dx: transform.dx + r.dx, dy: transform.dy + r.dy } };
518
558
  },
519
559
  onEnd() {
520
- publish(args, null);
560
+ publish(given, null);
521
561
  },
522
562
  onCancel() {
523
- publish(args, null);
563
+ publish(given, null);
524
564
  }
525
565
  };
526
566
  }
527
- function alignInsertBehavior(args) {
567
+ function alignInsertBehavior(given) {
528
568
  const pointAnchors = { x: ["min"], y: ["min"] };
529
569
  return {
530
570
  onMove(ctx, { current }) {
571
+ const args = alignmentOnPlane(given, ctx.plane);
531
572
  if (args.bypassKey && ctx.modifiers[args.bypassKey]) {
532
573
  args.setActiveGuides([]);
533
574
  return;
@@ -542,17 +583,18 @@ function alignInsertBehavior(args) {
542
583
  return { current: { x: current.x + m.dx, y: current.y + m.dy } };
543
584
  },
544
585
  onEnd() {
545
- args.setActiveGuides([]);
586
+ given.setActiveGuides([]);
546
587
  },
547
588
  onCancel() {
548
- args.setActiveGuides([]);
589
+ given.setActiveGuides([]);
549
590
  }
550
591
  };
551
592
  }
552
- function alignResizeBehavior(args) {
553
- const d = args.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
593
+ function alignResizeBehavior(given) {
594
+ const d = given.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
554
595
  return {
555
596
  onMove(ctx, { pose, anchor }) {
597
+ const args = alignmentOnPlane(given, ctx.plane);
556
598
  if (args.bypassKey && ctx.modifiers[args.bypassKey]) {
557
599
  publish(args, null);
558
600
  return;
@@ -579,10 +621,10 @@ function alignResizeBehavior(args) {
579
621
  return { pose: { ...pose, x, y, width, height } };
580
622
  },
581
623
  onEnd() {
582
- publish(args, null);
624
+ publish(given, null);
583
625
  },
584
626
  onCancel() {
585
- publish(args, null);
627
+ publish(given, null);
586
628
  }
587
629
  };
588
630
  }
@@ -627,8 +669,8 @@ function matchRotatedCorners(pose, anchor, originBox, rotation, candidates, tol)
627
669
  const dir = along === "x" ? toWorld(1, 0) : toWorld(0, 1);
628
670
  const edge = movingEdge(along)[0];
629
671
  let best = null;
630
- for (const across2 of movingEdge(along === "x" ? "y" : "x")) {
631
- const c = along === "x" ? drawn(edge, across2) : drawn(across2, edge);
672
+ for (const across3 of movingEdge(along === "x" ? "y" : "x")) {
673
+ const c = along === "x" ? drawn(edge, across3) : drawn(across3, edge);
632
674
  for (const g of candidates) {
633
675
  const reach = g.axis === "x" ? dir.x : dir.y;
634
676
  if (Math.abs(reach) < 1e-9) continue;
@@ -638,8 +680,8 @@ function matchRotatedCorners(pose, anchor, originBox, rotation, candidates, tol)
638
680
  }
639
681
  }
640
682
  if (best === null) return null;
641
- const across = best.guide.axis === "x" ? best.at.y + best.t * dir.y : best.at.x + best.t * dir.x;
642
- const guides = [stretchSpan(best.guide, across, across)];
683
+ const across2 = best.guide.axis === "x" ? best.at.y + best.t * dir.y : best.at.x + best.t * dir.x;
684
+ const guides = [stretchSpan(best.guide, across2, across2)];
643
685
  return along === "x" ? { lx: best.t, ly: 0, guides, gaps: [] } : { lx: 0, ly: best.t, guides, gaps: [] };
644
686
  }
645
687
 
@@ -705,6 +747,41 @@ function cellAt() {
705
747
  }
706
748
  };
707
749
  }
750
+
751
+ // src/layout/perContainer.ts
752
+ function perContainer(factory, opts) {
753
+ const max = opts.max ?? 256;
754
+ const built = /* @__PURE__ */ new Map();
755
+ const of = (container) => {
756
+ let strategy = built.get(container.id);
757
+ if (strategy) built.delete(container.id);
758
+ else strategy = factory(container);
759
+ built.set(container.id, strategy);
760
+ if (built.size > max) built.delete(built.keys().next().value);
761
+ return strategy;
762
+ };
763
+ return {
764
+ snap: opts.snap,
765
+ contains: opts.contains,
766
+ forget: (containerId) => void built.delete(containerId),
767
+ childPoses: (container, children) => of(container).childPoses(container, children),
768
+ getDropTargets: (container, children, dragged) => of(container).getDropTargets(container, children, dragged),
769
+ reflowPoses: (container, children, dragged, target) => of(container).reflowPoses(container, children, dragged, target),
770
+ commitDrop: (container, children, dragged, target) => of(container).commitDrop(container, children, dragged, target),
771
+ dropRegion: (container) => of(container).dropRegion?.(container) ?? null,
772
+ acceptsDrop: (container, dragged) => of(container).acceptsDrop?.(container, dragged) ?? true,
773
+ arrive(container, children, arrivals) {
774
+ const s = of(container);
775
+ return s.arrive ? s.arrive(container, children, arrivals) : { poses: s.childPoses(container, children) };
776
+ },
777
+ depart(container, children, departed) {
778
+ const s = of(container);
779
+ return s.depart ? s.depart(container, children, departed) : { poses: s.childPoses(container, children) };
780
+ },
781
+ contentExtent: (container, children) => of(container).contentExtent?.(container, children) ?? container.bounds,
782
+ releaseDrop: (container, children, dragged) => of(container).releaseDrop?.(container, children, dragged) ?? null
783
+ };
784
+ }
708
785
  function freeform(opts = {}) {
709
786
  const snap = opts.snap ?? none();
710
787
  return {
@@ -798,7 +875,7 @@ function tileGrid(opts) {
798
875
  if (fromIdx === null || fromIdx === idx) return null;
799
876
  return { occupant: occupant.id, from: occupant.pose, newPose: cellToPose(grid.rectAt(fromIdx), occupant.pose) };
800
877
  }
801
- function pack(from, into, children) {
878
+ function pack2(from, into2, children) {
802
879
  const ordered = children.map((c) => ({ c, cell: from.indexOf(c.pose) })).sort((a, b) => {
803
880
  if (a.cell !== b.cell) {
804
881
  if (a.cell === null) return 1;
@@ -809,7 +886,7 @@ function tileGrid(opts) {
809
886
  });
810
887
  const out = /* @__PURE__ */ new Map();
811
888
  for (let i = 0; i < ordered.length; i++) {
812
- out.set(ordered[i].c.id, cellToPose(into.rectAt(i), ordered[i].c.pose));
889
+ out.set(ordered[i].c.id, cellToPose(into2.rectAt(i), ordered[i].c.pose));
813
890
  }
814
891
  return out;
815
892
  }
@@ -817,7 +894,7 @@ function tileGrid(opts) {
817
894
  snap,
818
895
  childPoses(container, children) {
819
896
  const grid = gridOf(container.bounds, children.length);
820
- return pack(grid, grid, children);
897
+ return pack2(grid, grid, children);
821
898
  },
822
899
  getDropTargets(container, children, dragged) {
823
900
  const grid = gridOf(container.bounds, heldBy(container, children, dragged));
@@ -884,7 +961,7 @@ function tileGrid(opts) {
884
961
  const n = children.length;
885
962
  if (overflow === "reject" && n > capacity) return null;
886
963
  const grid = gridOf(container.bounds, n - arrivals.size);
887
- const slots = overflow === "reject" ? capacity : linesFor(n) * perLine;
964
+ const slots2 = overflow === "reject" ? capacity : linesFor(n) * perLine;
888
965
  const taken = /* @__PURE__ */ new Set();
889
966
  for (const c of children) {
890
967
  if (arrivals.has(c.id)) continue;
@@ -896,13 +973,13 @@ function tileGrid(opts) {
896
973
  if (!arrivals.has(c.id)) continue;
897
974
  const own = grid.indexOf(c.pose);
898
975
  let idx;
899
- if (own !== null && own < slots && !taken.has(own)) {
976
+ if (own !== null && own < slots2 && !taken.has(own)) {
900
977
  idx = own;
901
978
  } else {
902
979
  const at = centerOf(c.pose);
903
980
  let best = -1;
904
981
  let bestD = Infinity;
905
- for (let i = 0; i < slots; i++) {
982
+ for (let i = 0; i < slots2; i++) {
906
983
  if (taken.has(i)) continue;
907
984
  const r = grid.rectAt(i);
908
985
  const dx = r.x + r.width / 2 - at.x;
@@ -929,10 +1006,10 @@ function tileGrid(opts) {
929
1006
  const before = gridOf(container.bounds, n + departed.size);
930
1007
  const lines = linesFor(n);
931
1008
  if (overflow !== "grow" || lines === linesFor(n + departed.size)) {
932
- return { poses: pack(before, before, children) };
1009
+ return { poses: pack2(before, before, children) };
933
1010
  }
934
1011
  const bounds = before.boundsFor(lines);
935
- return { poses: pack(before, gridOf(bounds, n), children), bounds };
1012
+ return { poses: pack2(before, gridOf(bounds, n), children), bounds };
936
1013
  },
937
1014
  contentExtent(container, children) {
938
1015
  const grid = gridOf(container.bounds, children.length);
@@ -1009,7 +1086,377 @@ function snapPoint(opts) {
1009
1086
  }
1010
1087
  };
1011
1088
  }
1089
+ function defaultCenterOf2(pose) {
1090
+ const p = pose;
1091
+ return { x: (p.x ?? 0) + (p.width ?? 0) / 2, y: (p.y ?? 0) + (p.height ?? 0) / 2 };
1092
+ }
1093
+ var withAt = (list, index, item) => [
1094
+ ...list.slice(0, index),
1095
+ item,
1096
+ ...list.slice(index)
1097
+ ];
1098
+ function othersOf(seq, container, children, dragged) {
1099
+ const held = children.filter((c) => c.id !== dragged.id);
1100
+ const count = held.length + (dragged.sourceContainerId === container.id ? 1 : 0);
1101
+ return seq.order(container, held, count);
1102
+ }
1103
+ function nearestIndex(seq, container, sequence, child) {
1104
+ const at = seq.centerOf(child.pose);
1105
+ let best = null;
1106
+ let bestD = Infinity;
1107
+ for (let i = 0; i <= sequence.length; i++) {
1108
+ const pose = seq.place(container, withAt(sequence, i, child))?.get(child.id);
1109
+ if (pose === void 0) continue;
1110
+ const p = seq.centerOf(pose);
1111
+ const d = (p.x - at.x) ** 2 + (p.y - at.y) ** 2;
1112
+ if (d < bestD) {
1113
+ bestD = d;
1114
+ best = i;
1115
+ }
1116
+ }
1117
+ return best;
1118
+ }
1119
+ function sequenceLayout(seq) {
1120
+ const { centerOf } = seq;
1121
+ const moved = (a, b) => {
1122
+ const p = centerOf(a);
1123
+ const q = centerOf(b);
1124
+ return p.x !== q.x || p.y !== q.y;
1125
+ };
1126
+ const arranged = (container, sequence, poses) => {
1127
+ const bounds = seq.resize?.(container, sequence);
1128
+ return bounds ? { poses, bounds } : { poses };
1129
+ };
1130
+ const shifted = (container, children, dragged, target) => {
1131
+ if (!target) return /* @__PURE__ */ new Map();
1132
+ const others = othersOf(seq, container, children, dragged);
1133
+ const self = { id: dragged.id, pose: dragged.pose };
1134
+ const poses = seq.place(container, withAt(others, target.meta.index, self)) ?? /* @__PURE__ */ new Map();
1135
+ poses.delete(dragged.id);
1136
+ return poses;
1137
+ };
1138
+ return {
1139
+ snap: seq.snap,
1140
+ childPoses(container, children) {
1141
+ const sequence = seq.order(container, children, children.length);
1142
+ return seq.place(container, sequence) ?? new Map(children.map((c) => [c.id, c.pose]));
1143
+ },
1144
+ getDropTargets(container, children, dragged) {
1145
+ const others = othersOf(seq, container, children, dragged);
1146
+ const self = { id: dragged.id, pose: dragged.pose };
1147
+ const targets = [];
1148
+ for (let index = 0; index <= others.length; index++) {
1149
+ const pose = seq.place(container, withAt(others, index, self))?.get(dragged.id);
1150
+ if (pose === void 0) continue;
1151
+ targets.push({ pose, origin: centerOf(pose), meta: { index } });
1152
+ }
1153
+ return targets;
1154
+ },
1155
+ reflowPoses: shifted,
1156
+ commitDrop(container, children, dragged, target) {
1157
+ const ops = [];
1158
+ if (target) {
1159
+ const poses = shifted(container, children, dragged, target);
1160
+ for (const c of children) {
1161
+ const to = poses.get(c.id);
1162
+ if (to !== void 0 && moved(c.pose, to)) {
1163
+ ops.push(createTransformOp({ id: c.id, from: c.pose, to, label: seq.labels.shift }));
1164
+ }
1165
+ }
1166
+ }
1167
+ ops.push(
1168
+ createTransformOp({
1169
+ id: dragged.id,
1170
+ from: dragged.originPose,
1171
+ to: target?.pose ?? dragged.pose,
1172
+ label: seq.labels.drop
1173
+ })
1174
+ );
1175
+ return ops;
1176
+ },
1177
+ arrive(container, children, arrivals) {
1178
+ const held = children.filter((c) => !arrivals.has(c.id));
1179
+ let sequence = seq.order(container, held, held.length);
1180
+ for (const c of children) {
1181
+ if (!arrivals.has(c.id)) continue;
1182
+ const index = nearestIndex(seq, container, sequence, c);
1183
+ if (index === null) return null;
1184
+ sequence = withAt(sequence, index, c);
1185
+ }
1186
+ const poses = seq.place(container, sequence);
1187
+ return poses && arranged(container, sequence, poses);
1188
+ },
1189
+ depart(container, children, departed) {
1190
+ const sequence = seq.order(container, children, children.length + departed.size);
1191
+ const poses = seq.place(container, sequence) ?? /* @__PURE__ */ new Map();
1192
+ return arranged(container, sequence, poses);
1193
+ }
1194
+ };
1195
+ }
1196
+
1197
+ // src/layout/strategies/slots.ts
1198
+ function defaultSlotToPose(slot, pose) {
1199
+ const { width = 0, height = 0 } = pose;
1200
+ return { ...pose, x: slot.x - width / 2, y: slot.y - height / 2 };
1201
+ }
1202
+ function nearestSlot(at, places) {
1203
+ let best = -1;
1204
+ let bestD = Infinity;
1205
+ for (let i = 0; i < places.length; i++) {
1206
+ const d = (places[i].x - at.x) ** 2 + (places[i].y - at.y) ** 2;
1207
+ if (d < bestD) {
1208
+ bestD = d;
1209
+ best = i;
1210
+ }
1211
+ }
1212
+ return best;
1213
+ }
1214
+ function slots(opts) {
1215
+ const { arrangement } = opts;
1216
+ const slotToPose = opts.slotToPose ?? defaultSlotToPose;
1217
+ const centerOf = opts.centerOf ?? defaultCenterOf2;
1218
+ return sequenceLayout({
1219
+ snap: opts.snap ?? nearest(),
1220
+ centerOf,
1221
+ labels: { drop: "Slot drop", shift: "Slot shift" },
1222
+ // Ties break by id, so a container nobody has arranged yet still has an order.
1223
+ order(container, children, count) {
1224
+ const places = arrangement(container.bounds, count);
1225
+ return children.map((c) => ({ c, slot: nearestSlot(centerOf(c.pose), places) })).sort((a, b) => a.slot - b.slot || (a.c.id < b.c.id ? -1 : a.c.id > b.c.id ? 1 : 0)).map((e) => e.c);
1226
+ },
1227
+ place(container, sequence) {
1228
+ const places = arrangement(container.bounds, sequence.length);
1229
+ if (places.length < sequence.length) return null;
1230
+ return new Map(sequence.map((c, i) => [c.id, slotToPose(places[i], c.pose)]));
1231
+ }
1232
+ });
1233
+ }
1234
+
1235
+ // src/layout/strategies/pack.ts
1236
+ function pack(opts = {}) {
1237
+ const gap = opts.gap ?? 0;
1238
+ const overflow = opts.overflow ?? "reject";
1239
+ const minHeight = opts.minHeight ?? 0;
1240
+ const rectOf2 = opts.rectOf ?? ((pose) => pose);
1241
+ const moveTo = opts.moveTo ?? ((pose, at) => ({ ...pose, ...at }));
1242
+ const readingOrder = (children) => children.map((c) => ({ c, r: rectOf2(c.pose) })).sort((a, b) => a.r.y - b.r.y || a.r.x - b.r.x || (a.c.id < b.c.id ? -1 : a.c.id > b.c.id ? 1 : 0)).map((e) => e.c);
1243
+ function shelve(bounds, sequence) {
1244
+ const poses = /* @__PURE__ */ new Map();
1245
+ let x = 0;
1246
+ let y = 0;
1247
+ let shelf = 0;
1248
+ for (const c of sequence) {
1249
+ const { width, height } = rectOf2(c.pose);
1250
+ if (width > bounds.width) return null;
1251
+ if (x > 0 && x + width > bounds.width) {
1252
+ y += shelf + gap;
1253
+ x = 0;
1254
+ shelf = 0;
1255
+ }
1256
+ poses.set(c.id, moveTo(c.pose, { x: bounds.x + x, y: bounds.y + y }));
1257
+ x += width + gap;
1258
+ shelf = Math.max(shelf, height);
1259
+ }
1260
+ return { poses, height: y + shelf };
1261
+ }
1262
+ const layout = sequenceLayout({
1263
+ snap: opts.snap ?? nearest(),
1264
+ labels: { drop: "Pack drop", shift: "Pack shift" },
1265
+ centerOf(pose) {
1266
+ const r = rectOf2(pose);
1267
+ return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
1268
+ },
1269
+ order: (_container, children) => readingOrder(children),
1270
+ place(container, sequence) {
1271
+ const shelved = shelve(container.bounds, sequence);
1272
+ if (!shelved) return null;
1273
+ return overflow === "reject" && shelved.height > container.bounds.height ? null : shelved.poses;
1274
+ },
1275
+ resize(container, sequence) {
1276
+ if (overflow !== "grow") return void 0;
1277
+ const height = Math.max(minHeight, shelve(container.bounds, sequence)?.height ?? 0);
1278
+ return height === container.bounds.height ? void 0 : { ...container.bounds, height };
1279
+ }
1280
+ });
1281
+ return {
1282
+ ...layout,
1283
+ contentExtent(container, children) {
1284
+ const reach = shelve(container.bounds, readingOrder(children))?.height ?? 0;
1285
+ return { ...container.bounds, height: Math.max(container.bounds.height, reach) };
1286
+ }
1287
+ };
1288
+ }
1289
+ function overlaps(a, b) {
1290
+ const [outer, inner] = a.depth <= b.depth ? [a, b] : [b, a];
1291
+ const shift = inner.depth - outer.depth;
1292
+ return inner.col >> shift === outer.col && inner.row >> shift === outer.row;
1293
+ }
1294
+ function rectOf(bounds, cell) {
1295
+ const width = bounds.width / 2 ** cell.depth;
1296
+ const height = bounds.height / 2 ** cell.depth;
1297
+ return { x: bounds.x + cell.col * width, y: bounds.y + cell.row * height, width, height };
1298
+ }
1299
+ function freeCell(bounds, taken, depth, at) {
1300
+ let best = null;
1301
+ let bestD = Infinity;
1302
+ for (const cell of freeCells(taken, depth)) {
1303
+ const r = rectOf(bounds, cell);
1304
+ const d = (r.x + r.width / 2 - at.x) ** 2 + (r.y + r.height / 2 - at.y) ** 2;
1305
+ if (d < bestD) {
1306
+ bestD = d;
1307
+ best = cell;
1308
+ }
1309
+ }
1310
+ return best;
1311
+ }
1312
+ function freeCells(taken, depth) {
1313
+ const out = [];
1314
+ const n = 2 ** depth;
1315
+ for (let row = 0; row < n; row++) {
1316
+ for (let col = 0; col < n; col++) {
1317
+ const cell = { depth, col, row };
1318
+ if (!taken.some((t) => overlaps(t, cell))) out.push(cell);
1319
+ }
1320
+ }
1321
+ return out;
1322
+ }
1323
+ function defaultCellToPose(cell, pose) {
1324
+ const { width = 0, height = 0 } = pose;
1325
+ return {
1326
+ ...pose,
1327
+ x: cell.x + (cell.width - width) / 2,
1328
+ y: cell.y + (cell.height - height) / 2
1329
+ };
1330
+ }
1331
+ function quadtree(opts = {}) {
1332
+ const maxDepth = opts.maxDepth ?? 4;
1333
+ const snap = opts.snap ?? containedThenNearest();
1334
+ const cellToPose = opts.cellToPose ?? defaultCellToPose;
1335
+ const centerOf = opts.centerOf ?? defaultCenterOf2;
1336
+ const sizeOf = opts.sizeOf ?? ((pose) => {
1337
+ const { width = 0, height = 0 } = pose;
1338
+ return Math.max(width, height);
1339
+ });
1340
+ function depthOf(bounds, pose) {
1341
+ const size = sizeOf(pose);
1342
+ const side = Math.min(bounds.width, bounds.height);
1343
+ let depth = 0;
1344
+ while (depth < maxDepth && side / 2 ** (depth + 1) >= size) depth++;
1345
+ return depth;
1346
+ }
1347
+ function assign(bounds, children, held = []) {
1348
+ const taken = [...held];
1349
+ const cells = /* @__PURE__ */ new Map();
1350
+ const bySize = children.map((c) => ({ c, depth: depthOf(bounds, c.pose) })).sort((a, b) => a.depth - b.depth || (a.c.id < b.c.id ? -1 : a.c.id > b.c.id ? 1 : 0));
1351
+ for (const { c, depth } of bySize) {
1352
+ const cell = freeCell(bounds, taken, depth, centerOf(c.pose));
1353
+ if (!cell) continue;
1354
+ taken.push(cell);
1355
+ cells.set(c.id, cell);
1356
+ }
1357
+ return { cells, taken };
1358
+ }
1359
+ return {
1360
+ snap,
1361
+ childPoses(container, children) {
1362
+ const { cells } = assign(container.bounds, children);
1363
+ return new Map(
1364
+ children.map((c) => {
1365
+ const cell = cells.get(c.id);
1366
+ return [c.id, cell ? cellToPose(rectOf(container.bounds, cell), c.pose) : c.pose];
1367
+ })
1368
+ );
1369
+ },
1370
+ getDropTargets(container, children, dragged) {
1371
+ const { taken } = assign(container.bounds, children.filter((c) => c.id !== dragged.id));
1372
+ return freeCells(taken, depthOf(container.bounds, dragged.pose)).map((cell) => {
1373
+ const hitBounds = rectOf(container.bounds, cell);
1374
+ return {
1375
+ pose: cellToPose(hitBounds, dragged.pose),
1376
+ origin: { x: hitBounds.x + hitBounds.width / 2, y: hitBounds.y + hitBounds.height / 2 },
1377
+ hitBounds
1378
+ };
1379
+ });
1380
+ },
1381
+ reflowPoses: () => /* @__PURE__ */ new Map(),
1382
+ commitDrop: (_container, _children, dragged, target) => [
1383
+ createTransformOp({
1384
+ id: dragged.id,
1385
+ from: dragged.originPose,
1386
+ to: target?.pose ?? dragged.pose,
1387
+ label: "Quadtree drop"
1388
+ })
1389
+ ],
1390
+ arrive(container, children, arrivals) {
1391
+ const { taken } = assign(container.bounds, children.filter((c) => !arrivals.has(c.id)));
1392
+ const joining = children.filter((c) => arrivals.has(c.id));
1393
+ const { cells } = assign(container.bounds, joining, taken);
1394
+ if (cells.size < joining.length) return null;
1395
+ return {
1396
+ poses: new Map(joining.map((c) => [c.id, cellToPose(rectOf(container.bounds, cells.get(c.id)), c.pose)]))
1397
+ };
1398
+ },
1399
+ cells(container, children) {
1400
+ const { cells } = assign(container.bounds, children);
1401
+ return [...cells].map(([id, cell]) => ({ id, depth: cell.depth, rect: rectOf(container.bounds, cell) })).sort((a, b) => a.depth - b.depth || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
1402
+ }
1403
+ };
1404
+ }
1405
+
1406
+ // src/layout/strategies/arrangements.ts
1407
+ function rowSlots(opts = {}) {
1408
+ const alongX = opts.axis !== "y";
1409
+ return (bounds, count) => {
1410
+ const length = alongX ? bounds.width : bounds.height;
1411
+ const pitch = opts.pitch ?? length / Math.max(count, 1);
1412
+ const first = length / 2 - pitch * (count - 1) / 2;
1413
+ const out = [];
1414
+ for (let i = 0; i < count; i++) {
1415
+ const along = first + i * pitch;
1416
+ out.push(
1417
+ alongX ? { x: bounds.x + along, y: bounds.y + bounds.height / 2 } : { x: bounds.x + bounds.width / 2, y: bounds.y + along }
1418
+ );
1419
+ }
1420
+ return out;
1421
+ };
1422
+ }
1423
+ function gridSlots(opts) {
1424
+ return (bounds, count) => {
1425
+ const rows = Math.max(opts.rows ?? 1, Math.ceil(count / opts.cols));
1426
+ const cw = bounds.width / opts.cols;
1427
+ const ch = bounds.height / rows;
1428
+ const out = [];
1429
+ for (let i = 0; i < count; i++) {
1430
+ out.push({
1431
+ x: bounds.x + (i % opts.cols + 0.5) * cw,
1432
+ y: bounds.y + (Math.floor(i / opts.cols) + 0.5) * ch
1433
+ });
1434
+ }
1435
+ return out;
1436
+ };
1437
+ }
1438
+ function ringSlots(opts = {}) {
1439
+ const start = opts.start ?? -Math.PI / 2;
1440
+ const sweep = opts.sweep ?? Math.PI * 2;
1441
+ const inset = opts.inset ?? 0;
1442
+ const closed = Math.abs(sweep) >= Math.PI * 2;
1443
+ return (bounds, count) => {
1444
+ const rx = bounds.width / 2 - inset;
1445
+ const ry = bounds.height / 2 - inset;
1446
+ const steps = closed ? count : Math.max(count - 1, 1);
1447
+ const out = [];
1448
+ for (let i = 0; i < count; i++) {
1449
+ const angle = start + sweep * i / steps;
1450
+ out.push({
1451
+ x: bounds.x + bounds.width / 2 + rx * Math.cos(angle),
1452
+ y: bounds.y + bounds.height / 2 + ry * Math.sin(angle),
1453
+ angle
1454
+ });
1455
+ }
1456
+ return out;
1457
+ };
1458
+ }
1012
1459
 
1013
- export { MOVE_ANCHORS, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, cellAt, containedThenNearest, createCellHighlightLayer, createGridLayer, createGuidesLayer, deriveAlignmentGuides, freeform, matchAlignment, matchSpacing, measureGaps, nearest, nearestWithin, none, roundToCell, snapPoint, tileGrid, useGridCellHover, useGuides };
1460
+ export { MOVE_ANCHORS, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, cellAt, containedThenNearest, createCellHighlightLayer, createGridLayer, createGuidesLayer, deriveAlignmentGuides, freeform, gridSlots, matchAlignment, matchSpacing, measureGaps, nearest, nearestWithin, none, pack, perContainer, quadtree, ringSlots, roundToCell, rowSlots, sequenceLayout, slots, snapPoint, tileGrid, useGridCellHover, useGuides };
1014
1461
  //# sourceMappingURL=index.js.map
1015
1462
  //# sourceMappingURL=index.js.map