@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/README.md +2 -2
- package/dist/{chunk-NIEZTECL.js → chunk-LOFNN7EQ.js} +2 -2
- package/dist/{chunk-NIEZTECL.js.map → chunk-LOFNN7EQ.js.map} +1 -1
- package/dist/{chunk-JETW4ZA3.js → chunk-TLFMAZ6M.js} +11 -11
- package/dist/chunk-TLFMAZ6M.js.map +1 -0
- package/dist/index.d.ts +249 -59
- package/dist/index.js +441 -35
- package/dist/index.js.map +1 -1
- package/dist/insert.d.ts +1 -1
- package/dist/insert.js +1 -1
- package/dist/move.d.ts +1 -1
- package/dist/move.js +2 -2
- package/dist/resize.d.ts +1 -1
- package/dist/resize.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-JETW4ZA3.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { pointToGridCell } from './chunk-
|
|
2
|
-
export { gridSnapStrategy, pointToGridCell } from './chunk-
|
|
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-
|
|
5
|
-
export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy } from './chunk-
|
|
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,
|
|
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: {
|
|
56
|
+
paint: { kind: "solid", color: "rgba(255, 255, 255, 0.08)" },
|
|
57
57
|
width: 1
|
|
58
58
|
};
|
|
59
59
|
var DEFAULT_ACCENT = {
|
|
60
|
-
paint: {
|
|
60
|
+
paint: { kind: "solid", color: "rgba(255, 255, 255, 0.16)" },
|
|
61
61
|
width: 1
|
|
62
62
|
};
|
|
63
63
|
var DEFAULT_SUB = {
|
|
64
|
-
paint: {
|
|
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 =
|
|
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
|
|
101
|
-
for (let x = x0; x <= x1 + 1e-9; x +=
|
|
102
|
-
const k = Math.round((x - x0) /
|
|
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
|
-
|
|
107
|
-
|
|
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 = {
|
|
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 =
|
|
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: {
|
|
218
|
-
const gapStroke = { paint: {
|
|
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: {
|
|
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: {
|
|
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
|
|
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
|
|
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
|
|
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 <
|
|
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 <
|
|
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:
|
|
1009
|
+
return { poses: pack2(before, before, children) };
|
|
974
1010
|
}
|
|
975
1011
|
const bounds = before.boundsFor(lines);
|
|
976
|
-
return { poses:
|
|
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
|