@weasel-js/guides 1.9.4 → 1.10.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,10 +1,10 @@
1
- import { pointToGridCell } from './chunk-JETW4ZA3.js';
2
- export { gridSnapStrategy, pointToGridCell } from './chunk-JETW4ZA3.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
- import { guideOffsetOn } from './chunk-NIEZTECL.js';
5
- export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy } from './chunk-NIEZTECL.js';
4
+ import { guideOffsetOn } from './chunk-LOFNN7EQ.js';
5
+ export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy } from './chunk-LOFNN7EQ.js';
6
6
  import { useState, useRef, useCallback, useEffect } from 'react';
7
- import { useLatest, resolveUnit, pxExtent, textCommandFromRuns, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, rectFromPlane, translatePoseViaDescriptor, unionBounds, fixedCornerOf, createTransformOp, resolveStrokeWidth, screenToWorld, rectToPlane, 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';
8
8
  import { rectPath, PATH_M, PATH_L } from '@weasel-js/geom';
9
9
 
10
10
  // src/grid/roundToCell.ts
@@ -53,15 +53,15 @@ function useGridCellHover(opts) {
53
53
  return { cell, getCell };
54
54
  }
55
55
  var DEFAULT_LINE = {
56
- paint: { fill: "solid", color: "rgba(255, 255, 255, 0.08)" },
56
+ paint: { kind: "solid", color: "rgba(255, 255, 255, 0.08)" },
57
57
  width: 1
58
58
  };
59
59
  var DEFAULT_ACCENT = {
60
- paint: { fill: "solid", color: "rgba(255, 255, 255, 0.16)" },
60
+ paint: { kind: "solid", color: "rgba(255, 255, 255, 0.16)" },
61
61
  width: 1
62
62
  };
63
63
  var DEFAULT_SUB = {
64
- paint: { fill: "solid", color: "rgba(255, 255, 255, 0.04)" },
64
+ paint: { kind: "solid", color: "rgba(255, 255, 255, 0.04)" },
65
65
  width: 1
66
66
  };
67
67
  function createGridLayer(opts) {
@@ -75,7 +75,7 @@ function createGridLayer(opts) {
75
75
  const b = opts.bounds();
76
76
  if (b.width <= 0 || b.height <= 0) return [];
77
77
  const { accentEvery, subdivisions } = opts;
78
- const spacing = resolveUnit(opts.spacing, opts.unitSystem);
78
+ const spacing = resolveAxes(opts.spacing, opts.unitSystem);
79
79
  const x0 = b.x;
80
80
  const y0 = b.y;
81
81
  const x1 = b.x + b.width;
@@ -97,38 +97,39 @@ function createGridLayer(opts) {
97
97
  });
98
98
  };
99
99
  if (subdivisions && subdivisions > 1) {
100
- const step = spacing / subdivisions;
101
- for (let x = x0; x <= x1 + 1e-9; x += step) {
102
- 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);
103
103
  if (k % subdivisions === 0) continue;
104
104
  pushSegment(x, y0, x, y1, sub);
105
105
  }
106
- for (let y = y0; y <= y1 + 1e-9; y += step) {
107
- 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);
108
109
  if (k % subdivisions === 0) continue;
109
110
  pushSegment(x0, y, x1, y, sub);
110
111
  }
111
112
  }
112
113
  let idx = 0;
113
- for (let x = x0; x <= x1 + 1e-9; x += spacing) {
114
+ for (let x = x0; x <= x1 + 1e-9; x += spacing.x) {
114
115
  const isAccent = !!accentEvery && accentEvery > 0 && idx % accentEvery === 0;
115
116
  if (!isAccent) pushSegment(x, y0, x, y1, line);
116
117
  idx++;
117
118
  }
118
119
  idx = 0;
119
- for (let y = y0; y <= y1 + 1e-9; y += spacing) {
120
+ for (let y = y0; y <= y1 + 1e-9; y += spacing.y) {
120
121
  const isAccent = !!accentEvery && accentEvery > 0 && idx % accentEvery === 0;
121
122
  if (!isAccent) pushSegment(x0, y, x1, y, line);
122
123
  idx++;
123
124
  }
124
125
  if (accentEvery && accentEvery > 0) {
125
126
  idx = 0;
126
- for (let x = x0; x <= x1 + 1e-9; x += spacing) {
127
+ for (let x = x0; x <= x1 + 1e-9; x += spacing.x) {
127
128
  if (idx % accentEvery === 0) pushSegment(x, y0, x, y1, accent);
128
129
  idx++;
129
130
  }
130
131
  idx = 0;
131
- for (let y = y0; y <= y1 + 1e-9; y += spacing) {
132
+ for (let y = y0; y <= y1 + 1e-9; y += spacing.y) {
132
133
  if (idx % accentEvery === 0) pushSegment(x0, y, x1, y, accent);
133
134
  idx++;
134
135
  }
@@ -137,7 +138,7 @@ function createGridLayer(opts) {
137
138
  }
138
139
  };
139
140
  }
140
- 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)" } };
141
142
  function createCellHighlightLayer(opts) {
142
143
  const fill = opts.fill ?? DEFAULT_FILL;
143
144
  return {
@@ -146,14 +147,14 @@ function createCellHighlightLayer(opts) {
146
147
  draw: () => {
147
148
  const cell = opts.getCell();
148
149
  if (!cell) return [];
149
- const spacing = resolveUnit(opts.spacing, opts.unitSystem);
150
+ const spacing = resolveAxes(opts.spacing, opts.unitSystem);
150
151
  const o = opts.origin ? opts.origin() : { x: 0, y: 0 };
151
- const x = o.x + cell.col * spacing;
152
- 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;
153
154
  const children = [
154
155
  {
155
156
  kind: "path",
156
- path: { kind: "rect", x, y, width: spacing, height: spacing },
157
+ path: { kind: "rect", x, y, width: spacing.x, height: spacing.y },
157
158
  fill
158
159
  }
159
160
  ];
@@ -214,8 +215,8 @@ function createGuidesLayer(opts) {
214
215
  const ticks = opts.ticks ?? 6;
215
216
  const spanned = (opts.extent ?? "span") === "span";
216
217
  const format = opts.gapLabels === false ? null : typeof opts.gapLabels === "function" ? opts.gapLabels : (n) => String(Math.round(n));
217
- const stroke = { paint: { fill: "solid", color }, width: lineWidth };
218
- 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 };
219
220
  return {
220
221
  id: opts.id ?? "guides",
221
222
  label: opts.label ?? "Guides",
@@ -249,11 +250,11 @@ function createGuidesLayer(opts) {
249
250
  const w = text.length * LABEL_CHAR_W + LABEL_PAD_X * 2;
250
251
  const left = (x0 + x1) / 2 - w / 2;
251
252
  const top = (y0 + y1) / 2 - LABEL_H / 2;
252
- 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 } } });
253
254
  out.push(textCommandFromRuns(
254
255
  left + LABEL_PAD_X,
255
256
  top + 2,
256
- [{ text, fill: { fill: "solid", color: "#fff" } }],
257
+ [{ text, fill: { paint: { kind: "solid", color: "#fff" } } }],
257
258
  LABEL_FONT
258
259
  ));
259
260
  }
@@ -746,6 +747,41 @@ function cellAt() {
746
747
  }
747
748
  };
748
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
+ }
749
785
  function freeform(opts = {}) {
750
786
  const snap = opts.snap ?? none();
751
787
  return {
@@ -839,7 +875,7 @@ function tileGrid(opts) {
839
875
  if (fromIdx === null || fromIdx === idx) return null;
840
876
  return { occupant: occupant.id, from: occupant.pose, newPose: cellToPose(grid.rectAt(fromIdx), occupant.pose) };
841
877
  }
842
- function pack(from, into2, children) {
878
+ function pack2(from, into2, children) {
843
879
  const ordered = children.map((c) => ({ c, cell: from.indexOf(c.pose) })).sort((a, b) => {
844
880
  if (a.cell !== b.cell) {
845
881
  if (a.cell === null) return 1;
@@ -858,7 +894,7 @@ function tileGrid(opts) {
858
894
  snap,
859
895
  childPoses(container, children) {
860
896
  const grid = gridOf(container.bounds, children.length);
861
- return pack(grid, grid, children);
897
+ return pack2(grid, grid, children);
862
898
  },
863
899
  getDropTargets(container, children, dragged) {
864
900
  const grid = gridOf(container.bounds, heldBy(container, children, dragged));
@@ -925,7 +961,7 @@ function tileGrid(opts) {
925
961
  const n = children.length;
926
962
  if (overflow === "reject" && n > capacity) return null;
927
963
  const grid = gridOf(container.bounds, n - arrivals.size);
928
- const slots = overflow === "reject" ? capacity : linesFor(n) * perLine;
964
+ const slots2 = overflow === "reject" ? capacity : linesFor(n) * perLine;
929
965
  const taken = /* @__PURE__ */ new Set();
930
966
  for (const c of children) {
931
967
  if (arrivals.has(c.id)) continue;
@@ -937,13 +973,13 @@ function tileGrid(opts) {
937
973
  if (!arrivals.has(c.id)) continue;
938
974
  const own = grid.indexOf(c.pose);
939
975
  let idx;
940
- if (own !== null && own < slots && !taken.has(own)) {
976
+ if (own !== null && own < slots2 && !taken.has(own)) {
941
977
  idx = own;
942
978
  } else {
943
979
  const at = centerOf(c.pose);
944
980
  let best = -1;
945
981
  let bestD = Infinity;
946
- for (let i = 0; i < slots; i++) {
982
+ for (let i = 0; i < slots2; i++) {
947
983
  if (taken.has(i)) continue;
948
984
  const r = grid.rectAt(i);
949
985
  const dx = r.x + r.width / 2 - at.x;
@@ -970,10 +1006,10 @@ function tileGrid(opts) {
970
1006
  const before = gridOf(container.bounds, n + departed.size);
971
1007
  const lines = linesFor(n);
972
1008
  if (overflow !== "grow" || lines === linesFor(n + departed.size)) {
973
- return { poses: pack(before, before, children) };
1009
+ return { poses: pack2(before, before, children) };
974
1010
  }
975
1011
  const bounds = before.boundsFor(lines);
976
- return { poses: pack(before, gridOf(bounds, n), children), bounds };
1012
+ return { poses: pack2(before, gridOf(bounds, n), children), bounds };
977
1013
  },
978
1014
  contentExtent(container, children) {
979
1015
  const grid = gridOf(container.bounds, children.length);
@@ -1050,7 +1086,377 @@ function snapPoint(opts) {
1050
1086
  }
1051
1087
  };
1052
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
+ }
1053
1459
 
1054
- 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 };
1055
1461
  //# sourceMappingURL=index.js.map
1056
1462
  //# sourceMappingURL=index.js.map