@weasel-js/guides 1.7.0 → 1.7.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.d.ts +108 -18
- package/dist/index.js +391 -132
- package/dist/index.js.map +1 -1
- package/dist/insert.d.ts +1 -1
- package/dist/move.d.ts +1 -1
- package/dist/resize.d.ts +1 -1
- package/dist/types-DRSPiXfk.d.ts +38 -0
- package/package.json +3 -3
- package/dist/types-D2tTKEU0.d.ts +0 -18
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ export { gridSnapStrategy, pointToGridCell } from './chunk-5BCCIJY3.js';
|
|
|
3
3
|
export { pointSnapToGrid } from './chunk-B25KFI3I.js';
|
|
4
4
|
export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy } from './chunk-GEDAPTOM.js';
|
|
5
5
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
6
|
-
import { useLatest, resolveUnit, pxExtent, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, translatePoseViaDescriptor, unionBounds, fixedCornerOf, createTransformOp, resolveStrokeWidth, screenToWorld, screenTolerance } from '@weasel-js/core';
|
|
7
|
-
import { PATH_M, PATH_L } from '@weasel-js/geom';
|
|
6
|
+
import { useLatest, resolveUnit, pxExtent, textCommandFromRuns, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, translatePoseViaDescriptor, unionBounds, fixedCornerOf, createTransformOp, resolveStrokeWidth, screenToWorld, screenTolerance } from '@weasel-js/core';
|
|
7
|
+
import { rectPath, PATH_M, PATH_L } from '@weasel-js/geom';
|
|
8
8
|
|
|
9
9
|
// src/grid/roundToCell.ts
|
|
10
10
|
function roundToCell(value, cellSize) {
|
|
@@ -188,44 +188,74 @@ function useGuides(initial = []) {
|
|
|
188
188
|
return { guides, getGuides, addGuide, removeGuide, clearGuides };
|
|
189
189
|
}
|
|
190
190
|
var DEFAULT_COLOR = "#3aa0ff";
|
|
191
|
+
var LABEL_FONT = { fontSize: 10 };
|
|
192
|
+
var LABEL_CHAR_W = 6;
|
|
193
|
+
var LABEL_H = 14;
|
|
194
|
+
var LABEL_PAD_X = 4;
|
|
195
|
+
function tickedSegment(x0, y0, x1, y1, t) {
|
|
196
|
+
const h = t / 2;
|
|
197
|
+
const vertical = x0 === x1;
|
|
198
|
+
const ends = t <= 0 ? [] : vertical ? [x0 - h, y0, x0 + h, y0, x1 - h, y1, x1 + h, y1] : [x0, y0 - h, x0, y0 + h, x1, y1 - h, x1, y1 + h];
|
|
199
|
+
const coords = [x0, y0, x1, y1, ...ends];
|
|
200
|
+
const commands = [];
|
|
201
|
+
for (let i = 0; i < coords.length; i += 4) commands.push(PATH_M, PATH_L);
|
|
202
|
+
return {
|
|
203
|
+
kind: "polygon",
|
|
204
|
+
commands: new Uint8Array(commands),
|
|
205
|
+
coords: new Float32Array(coords),
|
|
206
|
+
fillRule: "nonzero"
|
|
207
|
+
};
|
|
208
|
+
}
|
|
191
209
|
function createGuidesLayer(opts) {
|
|
192
210
|
const lineWidth = opts.lineWidth ?? 1;
|
|
193
211
|
const color = opts.color ?? DEFAULT_COLOR;
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
const gapColor = opts.gapColor ?? color;
|
|
213
|
+
const ticks = opts.ticks ?? 6;
|
|
214
|
+
const spanned = (opts.extent ?? "span") === "span";
|
|
215
|
+
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 };
|
|
198
218
|
return {
|
|
199
219
|
id: opts.id ?? "guides",
|
|
200
220
|
label: opts.label ?? "Guides",
|
|
201
221
|
space: "screen",
|
|
202
222
|
draw: (_data, view, dims) => {
|
|
203
223
|
const guides = opts.getGuides();
|
|
204
|
-
|
|
224
|
+
const gaps = opts.getGaps?.() ?? [];
|
|
225
|
+
if (guides.length === 0 && gaps.length === 0) return [];
|
|
226
|
+
const sx = (x) => (x - view.x) * view.scale.x;
|
|
227
|
+
const sy = (y) => (y - view.y) * view.scale.y;
|
|
205
228
|
const out = [];
|
|
206
229
|
for (const g of guides) {
|
|
230
|
+
const span = spanned ? g.span : void 0;
|
|
207
231
|
if (g.axis === "x") {
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
210
|
-
const path =
|
|
211
|
-
kind: "polygon",
|
|
212
|
-
commands: new Uint8Array([PATH_M, PATH_L]),
|
|
213
|
-
coords: new Float32Array([sx, 0, sx, dims.height]),
|
|
214
|
-
fillRule: "nonzero"
|
|
215
|
-
};
|
|
232
|
+
const x = sx(g.offset);
|
|
233
|
+
if (x < -lineWidth || x > dims.width + lineWidth) continue;
|
|
234
|
+
const path = span ? tickedSegment(x, sy(span.min), x, sy(span.max), ticks) : tickedSegment(x, 0, x, dims.height, 0);
|
|
216
235
|
out.push({ kind: "path", path, stroke });
|
|
217
236
|
} else {
|
|
218
|
-
const
|
|
219
|
-
if (
|
|
220
|
-
const path =
|
|
221
|
-
kind: "polygon",
|
|
222
|
-
commands: new Uint8Array([PATH_M, PATH_L]),
|
|
223
|
-
coords: new Float32Array([0, sy, dims.width, sy]),
|
|
224
|
-
fillRule: "nonzero"
|
|
225
|
-
};
|
|
237
|
+
const y = sy(g.offset);
|
|
238
|
+
if (y < -lineWidth || y > dims.height + lineWidth) continue;
|
|
239
|
+
const path = span ? tickedSegment(sx(span.min), y, sx(span.max), y, ticks) : tickedSegment(0, y, dims.width, y, 0);
|
|
226
240
|
out.push({ kind: "path", path, stroke });
|
|
227
241
|
}
|
|
228
242
|
}
|
|
243
|
+
for (const g of gaps) {
|
|
244
|
+
const [x0, y0, x1, y1] = g.axis === "x" ? [sx(g.min), sy(g.at), sx(g.max), sy(g.at)] : [sx(g.at), sy(g.min), sx(g.at), sy(g.max)];
|
|
245
|
+
out.push({ kind: "path", path: tickedSegment(x0, y0, x1, y1, ticks), stroke: gapStroke });
|
|
246
|
+
if (format === null) continue;
|
|
247
|
+
const text = format(g.max - g.min);
|
|
248
|
+
const w = text.length * LABEL_CHAR_W + LABEL_PAD_X * 2;
|
|
249
|
+
const left = (x0 + x1) / 2 - w / 2;
|
|
250
|
+
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 } });
|
|
252
|
+
out.push(textCommandFromRuns(
|
|
253
|
+
left + LABEL_PAD_X,
|
|
254
|
+
top + 2,
|
|
255
|
+
[{ text, fill: { fill: "solid", color: "#fff" } }],
|
|
256
|
+
LABEL_FONT
|
|
257
|
+
));
|
|
258
|
+
}
|
|
229
259
|
return out;
|
|
230
260
|
}
|
|
231
261
|
};
|
|
@@ -237,22 +267,28 @@ function deriveAlignmentGuides(targets, opts = {}) {
|
|
|
237
267
|
const centers = opts.centers ?? true;
|
|
238
268
|
const seenX = /* @__PURE__ */ new Map();
|
|
239
269
|
const seenY = /* @__PURE__ */ new Map();
|
|
240
|
-
const add = (axis, offset) => {
|
|
270
|
+
const add = (axis, offset, b) => {
|
|
241
271
|
const seen = axis === "x" ? seenX : seenY;
|
|
242
272
|
const key = Math.round(offset / EPS);
|
|
243
|
-
|
|
244
|
-
|
|
273
|
+
const min = axis === "x" ? b.y : b.x;
|
|
274
|
+
const max = min + (axis === "x" ? b.height : b.width);
|
|
275
|
+
const prev = seen.get(key);
|
|
276
|
+
if (prev) {
|
|
277
|
+
prev.span = { min: Math.min(prev.span.min, min), max: Math.max(prev.span.max, max) };
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
seen.set(key, { id: `align:${axis}:${offset.toFixed(3)}`, axis, offset, span: { min, max } });
|
|
245
281
|
};
|
|
246
282
|
const emit = (b) => {
|
|
247
283
|
if (edges) {
|
|
248
|
-
add("x", b.x);
|
|
249
|
-
add("x", b.x + b.width);
|
|
250
|
-
add("y", b.y);
|
|
251
|
-
add("y", b.y + b.height);
|
|
284
|
+
add("x", b.x, b);
|
|
285
|
+
add("x", b.x + b.width, b);
|
|
286
|
+
add("y", b.y, b);
|
|
287
|
+
add("y", b.y + b.height, b);
|
|
252
288
|
}
|
|
253
289
|
if (centers) {
|
|
254
|
-
add("x", b.x + b.width / 2);
|
|
255
|
-
add("y", b.y + b.height / 2);
|
|
290
|
+
add("x", b.x + b.width / 2, b);
|
|
291
|
+
add("y", b.y + b.height / 2, b);
|
|
256
292
|
}
|
|
257
293
|
};
|
|
258
294
|
for (const t of targets) emit(visualBoundsViaDescriptor(t, d));
|
|
@@ -297,9 +333,161 @@ function bestAxis(b, axis, anchors, candidates, worldTolerance) {
|
|
|
297
333
|
function matchAlignment(bounds, candidates, worldTolerance, anchors) {
|
|
298
334
|
const x = bestAxis(bounds, "x", anchors.x, candidates, worldTolerance.x);
|
|
299
335
|
const y = bestAxis(bounds, "y", anchors.y, candidates, worldTolerance.y);
|
|
300
|
-
|
|
336
|
+
const top = bounds.y + y.delta;
|
|
337
|
+
const left = bounds.x + x.delta;
|
|
338
|
+
return {
|
|
339
|
+
dx: x.delta,
|
|
340
|
+
dy: y.delta,
|
|
341
|
+
activeX: stretchSpan(x.guide, top, top + bounds.height),
|
|
342
|
+
activeY: stretchSpan(y.guide, left, left + bounds.width)
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function stretchSpan(g, min, max) {
|
|
346
|
+
if (g === null || g.span === void 0) return g;
|
|
347
|
+
return { ...g, span: { min: Math.min(g.span.min, min), max: Math.max(g.span.max, max) } };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/guides/alignment/spacing.ts
|
|
351
|
+
var EPS2 = 1e-6;
|
|
352
|
+
var lo = (b, a) => a === "x" ? b.x : b.y;
|
|
353
|
+
var hi = (b, a) => a === "x" ? b.x + b.width : b.y + b.height;
|
|
354
|
+
var cross = (a) => a === "x" ? "y" : "x";
|
|
355
|
+
function overlap(a, b, axis) {
|
|
356
|
+
const min = Math.max(lo(a, axis), lo(b, axis));
|
|
357
|
+
const max = Math.min(hi(a, axis), hi(b, axis));
|
|
358
|
+
return max - min > EPS2 ? [min, max] : null;
|
|
359
|
+
}
|
|
360
|
+
function gapBetween(a, b, axis) {
|
|
361
|
+
const shared = overlap(a, b, cross(axis));
|
|
362
|
+
if (shared === null) return null;
|
|
363
|
+
return { axis, min: hi(a, axis), max: lo(b, axis), at: (shared[0] + shared[1]) / 2 };
|
|
364
|
+
}
|
|
365
|
+
function hitsStrip(g, b, span) {
|
|
366
|
+
const c = cross(g.axis);
|
|
367
|
+
return lo(b, g.axis) < g.max - EPS2 && hi(b, g.axis) > g.min + EPS2 && lo(b, c) < span[1] - EPS2 && hi(b, c) > span[0] + EPS2;
|
|
368
|
+
}
|
|
369
|
+
function measureGaps(targets, axis) {
|
|
370
|
+
return measure(targets, axis).map((m) => m.gap);
|
|
371
|
+
}
|
|
372
|
+
function measure(targets, axis) {
|
|
373
|
+
const out = [];
|
|
374
|
+
for (const a of targets) {
|
|
375
|
+
let next = null;
|
|
376
|
+
for (const c of targets) {
|
|
377
|
+
if (c === a || lo(c, axis) < hi(a, axis) + EPS2) continue;
|
|
378
|
+
if (overlap(a, c, cross(axis)) === null) continue;
|
|
379
|
+
if (next === null || lo(c, axis) < lo(next, axis)) next = c;
|
|
380
|
+
}
|
|
381
|
+
if (next === null) continue;
|
|
382
|
+
const gap = gapBetween(a, next, axis);
|
|
383
|
+
const span = overlap(a, next, cross(axis));
|
|
384
|
+
if (targets.some((t) => t !== a && t !== next && hitsStrip(gap, t, span))) continue;
|
|
385
|
+
out.push({ gap, span });
|
|
386
|
+
}
|
|
387
|
+
return out.sort((p, q) => p.gap.min - q.gap.min || p.gap.at - q.gap.at);
|
|
388
|
+
}
|
|
389
|
+
function bestAxis2(b, axis, edge, targets, refs, tol) {
|
|
390
|
+
const bLo = lo(b, axis);
|
|
391
|
+
const bHi = hi(b, axis);
|
|
392
|
+
const size = bHi - bLo;
|
|
393
|
+
const center = (bLo + bHi) / 2;
|
|
394
|
+
let left = null;
|
|
395
|
+
let right = null;
|
|
396
|
+
for (const t of targets) {
|
|
397
|
+
if (overlap(t, b, cross(axis)) === null) continue;
|
|
398
|
+
if (hi(t, axis) <= center && (left === null || hi(t, axis) > hi(left, axis))) left = t;
|
|
399
|
+
if (lo(t, axis) >= center && (right === null || lo(t, axis) < lo(right, axis))) right = t;
|
|
400
|
+
}
|
|
401
|
+
const sizes = refs.map((g) => g.max - g.min);
|
|
402
|
+
let best = null;
|
|
403
|
+
const offer = (delta, gap) => {
|
|
404
|
+
if (gap <= EPS2 || Math.abs(delta) > tol) return;
|
|
405
|
+
if (best === null || Math.abs(delta) < Math.abs(best.delta)) best = { delta, size: gap };
|
|
406
|
+
};
|
|
407
|
+
const L = left === null ? null : hi(left, axis);
|
|
408
|
+
const R = right === null ? null : lo(right, axis);
|
|
409
|
+
if (edge === "both") {
|
|
410
|
+
for (const g of sizes) {
|
|
411
|
+
if (L !== null && (R === null || L + g + size <= R + EPS2)) offer(L + g - bLo, g);
|
|
412
|
+
if (R !== null && (L === null || R - g - size >= L - EPS2)) offer(R - g - size - bLo, g);
|
|
413
|
+
}
|
|
414
|
+
if (L !== null && R !== null) {
|
|
415
|
+
const g = (R - L - size) / 2;
|
|
416
|
+
offer(L + g - bLo, g);
|
|
417
|
+
}
|
|
418
|
+
} else if (edge === "max" && R !== null) {
|
|
419
|
+
const own = L !== null ? [bLo - L] : [];
|
|
420
|
+
for (const g of [...sizes, ...own]) if (R - g > bLo + EPS2) offer(R - g - bHi, g);
|
|
421
|
+
} else if (edge === "min" && L !== null) {
|
|
422
|
+
const own = R !== null ? [R - bHi] : [];
|
|
423
|
+
for (const g of [...sizes, ...own]) if (L + g < bHi - EPS2) offer(L + g - bLo, g);
|
|
424
|
+
}
|
|
425
|
+
return best;
|
|
426
|
+
}
|
|
427
|
+
function markers(b, axis, size, targets, refs) {
|
|
428
|
+
const eq = (g) => Math.abs(g.max - g.min - size) < 1e-3;
|
|
429
|
+
const out = refs.filter(eq);
|
|
430
|
+
const own = measureGaps([...targets, b], axis).filter((g) => (Math.abs(g.max - lo(b, axis)) < EPS2 || Math.abs(g.min - hi(b, axis)) < EPS2) && eq(g));
|
|
431
|
+
return [...out, ...own];
|
|
432
|
+
}
|
|
433
|
+
function matchSpacing(bounds, targets, worldTolerance, moving = { x: "both", y: "both" }) {
|
|
434
|
+
const refsFor = (axis) => measure(targets, axis).filter((m) => !hitsStrip(m.gap, bounds, m.span)).map((m) => m.gap);
|
|
435
|
+
const refsX = refsFor("x");
|
|
436
|
+
const refsY = refsFor("y");
|
|
437
|
+
const x = moving.x === null ? null : bestAxis2(bounds, "x", moving.x, targets, refsX, worldTolerance.x);
|
|
438
|
+
const y = moving.y === null ? null : bestAxis2(bounds, "y", moving.y, targets, refsY, worldTolerance.y);
|
|
439
|
+
const dx = x?.delta ?? 0;
|
|
440
|
+
const dy = y?.delta ?? 0;
|
|
441
|
+
const final = applyEdges(bounds, dx, dy, moving);
|
|
442
|
+
return {
|
|
443
|
+
dx,
|
|
444
|
+
dy,
|
|
445
|
+
gapsX: x === null ? [] : markers(final, "x", x.size, targets, refsX),
|
|
446
|
+
gapsY: y === null ? [] : markers(final, "y", y.size, targets, refsY)
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function applyEdges(b, dx, dy, moving) {
|
|
450
|
+
let { x, y, width, height } = b;
|
|
451
|
+
if (moving.x === "both") x += dx;
|
|
452
|
+
else if (moving.x === "min") {
|
|
453
|
+
x += dx;
|
|
454
|
+
width -= dx;
|
|
455
|
+
} else if (moving.x === "max") width += dx;
|
|
456
|
+
if (moving.y === "both") y += dy;
|
|
457
|
+
else if (moving.y === "min") {
|
|
458
|
+
y += dy;
|
|
459
|
+
height -= dy;
|
|
460
|
+
} else if (moving.y === "max") height += dy;
|
|
461
|
+
return { x, y, width, height };
|
|
301
462
|
}
|
|
302
463
|
var activeList = (m) => [m.activeX, m.activeY].filter((g) => g !== null);
|
|
464
|
+
var EXACT = { x: 1e-6, y: 1e-6 };
|
|
465
|
+
function resolve(b, args, tol, anchors, edges) {
|
|
466
|
+
const candidates = args.getCandidates();
|
|
467
|
+
const m = matchAlignment(b, candidates, tol, anchors);
|
|
468
|
+
const targets = args.getSpacingTargets?.();
|
|
469
|
+
if (targets === void 0) {
|
|
470
|
+
if (m.activeX === null && m.activeY === null) return null;
|
|
471
|
+
return { dx: m.dx, dy: m.dy, guides: activeList(m), gaps: [] };
|
|
472
|
+
}
|
|
473
|
+
const s = matchSpacing(b, targets, tol, edges);
|
|
474
|
+
const pick = (hit, d, spaced, sd) => hit && (!spaced || Math.abs(d) <= Math.abs(sd)) ? d : spaced ? sd : null;
|
|
475
|
+
const dx = pick(m.activeX !== null, m.dx, s.gapsX.length > 0, s.dx);
|
|
476
|
+
const dy = pick(m.activeY !== null, m.dy, s.gapsY.length > 0, s.dy);
|
|
477
|
+
if (dx === null && dy === null) return null;
|
|
478
|
+
const settled = applyEdges(b, dx ?? 0, dy ?? 0, edges);
|
|
479
|
+
const shown = matchSpacing(settled, targets, EXACT, edges);
|
|
480
|
+
return {
|
|
481
|
+
dx: dx ?? 0,
|
|
482
|
+
dy: dy ?? 0,
|
|
483
|
+
guides: activeList(matchAlignment(settled, candidates, EXACT, anchors)),
|
|
484
|
+
gaps: [...shown.gapsX, ...shown.gapsY]
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function publish(args, r) {
|
|
488
|
+
args.setActiveGuides(r?.guides ?? []);
|
|
489
|
+
args.setActiveGaps?.(r?.gaps ?? []);
|
|
490
|
+
}
|
|
303
491
|
function worldTol(base, ctx) {
|
|
304
492
|
return screenTolerance(base.tolerance ?? 6, ctx);
|
|
305
493
|
}
|
|
@@ -308,7 +496,7 @@ function alignMoveBehavior(args) {
|
|
|
308
496
|
return {
|
|
309
497
|
onMove(ctx, transform) {
|
|
310
498
|
if (args.bypassKey && ctx.modifiers[args.bypassKey]) {
|
|
311
|
-
args
|
|
499
|
+
publish(args, null);
|
|
312
500
|
return;
|
|
313
501
|
}
|
|
314
502
|
if (transform.kind !== "translate") return;
|
|
@@ -323,19 +511,16 @@ function alignMoveBehavior(args) {
|
|
|
323
511
|
}
|
|
324
512
|
const union = unionBounds(boxes);
|
|
325
513
|
if (union === null) return;
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
args.setActiveGuides(activeList(m));
|
|
332
|
-
return { transform: { kind: "translate", dx: transform.dx + m.dx, dy: transform.dy + m.dy } };
|
|
514
|
+
const r = resolve(union, args, worldTol(args, ctx), MOVE_ANCHORS, { x: "both", y: "both" });
|
|
515
|
+
publish(args, r);
|
|
516
|
+
if (r === null) return;
|
|
517
|
+
return { transform: { kind: "translate", dx: transform.dx + r.dx, dy: transform.dy + r.dy } };
|
|
333
518
|
},
|
|
334
519
|
onEnd() {
|
|
335
|
-
args
|
|
520
|
+
publish(args, null);
|
|
336
521
|
},
|
|
337
522
|
onCancel() {
|
|
338
|
-
args
|
|
523
|
+
publish(args, null);
|
|
339
524
|
}
|
|
340
525
|
};
|
|
341
526
|
}
|
|
@@ -369,14 +554,14 @@ function alignResizeBehavior(args) {
|
|
|
369
554
|
return {
|
|
370
555
|
onMove(ctx, { pose, anchor }) {
|
|
371
556
|
if (args.bypassKey && ctx.modifiers[args.bypassKey]) {
|
|
372
|
-
args
|
|
557
|
+
publish(args, null);
|
|
373
558
|
return;
|
|
374
559
|
}
|
|
375
560
|
const origin = ctx.origin.get(ctx.draggedIds[0]);
|
|
376
561
|
const rotation = origin === void 0 ? 0 : d.getRotation?.(origin) ?? 0;
|
|
377
|
-
const m = rotation === 0 ? matchMovingEdges(pose, anchor, args
|
|
562
|
+
const m = rotation === 0 ? matchMovingEdges(pose, anchor, args, worldTol(args, ctx)) : matchRotatedCorners(pose, anchor, d.getBounds(origin), rotation, args.getCandidates(), worldTol(args, ctx));
|
|
378
563
|
if (m === null) {
|
|
379
|
-
args
|
|
564
|
+
publish(args, null);
|
|
380
565
|
return;
|
|
381
566
|
}
|
|
382
567
|
let { x, y, width, height } = pose;
|
|
@@ -390,23 +575,26 @@ function alignResizeBehavior(args) {
|
|
|
390
575
|
y += m.ly;
|
|
391
576
|
height -= m.ly;
|
|
392
577
|
}
|
|
393
|
-
args.
|
|
578
|
+
publish(args, { guides: m.guides, gaps: m.gaps });
|
|
394
579
|
return { pose: { ...pose, x, y, width, height } };
|
|
395
580
|
},
|
|
396
581
|
onEnd() {
|
|
397
|
-
args
|
|
582
|
+
publish(args, null);
|
|
398
583
|
},
|
|
399
584
|
onCancel() {
|
|
400
|
-
args
|
|
585
|
+
publish(args, null);
|
|
401
586
|
}
|
|
402
587
|
};
|
|
403
588
|
}
|
|
404
|
-
function matchMovingEdges(pose, anchor,
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
589
|
+
function matchMovingEdges(pose, anchor, args, tol) {
|
|
590
|
+
const moving = (a) => a === "min" ? "max" : a === "max" ? "min" : null;
|
|
591
|
+
const edges = { x: moving(anchor.x), y: moving(anchor.y) };
|
|
592
|
+
const anchors = {
|
|
593
|
+
x: edges.x === null ? [] : [edges.x],
|
|
594
|
+
y: edges.y === null ? [] : [edges.y]
|
|
595
|
+
};
|
|
596
|
+
const r = resolve(pose, args, tol, anchors, edges);
|
|
597
|
+
return r === null ? null : { lx: r.dx, ly: r.dy, guides: r.guides, gaps: r.gaps };
|
|
410
598
|
}
|
|
411
599
|
function matchRotatedCorners(pose, anchor, originBox, rotation, candidates, tol) {
|
|
412
600
|
const cos = Math.cos(rotation);
|
|
@@ -421,10 +609,10 @@ function matchRotatedCorners(pose, anchor, originBox, rotation, candidates, tol)
|
|
|
421
609
|
return { x: fixedWorld.x + o.x, y: fixedWorld.y + o.y };
|
|
422
610
|
};
|
|
423
611
|
const movingEdge = (axis) => {
|
|
424
|
-
const
|
|
425
|
-
const
|
|
612
|
+
const lo2 = axis === "x" ? pose.x : pose.y;
|
|
613
|
+
const hi2 = lo2 + (axis === "x" ? pose.width : pose.height);
|
|
426
614
|
const a = anchor[axis];
|
|
427
|
-
return a === "min" ? [
|
|
615
|
+
return a === "min" ? [hi2] : a === "max" ? [lo2] : [lo2, hi2];
|
|
428
616
|
};
|
|
429
617
|
if (anchor.x !== "free" && anchor.y !== "free") {
|
|
430
618
|
const c = drawn(movingEdge("x")[0], movingEdge("y")[0]);
|
|
@@ -432,25 +620,27 @@ function matchRotatedCorners(pose, anchor, originBox, rotation, candidates, tol)
|
|
|
432
620
|
if (m.activeX === null && m.activeY === null) return null;
|
|
433
621
|
const lx = m.dx * cos + m.dy * sin;
|
|
434
622
|
const ly = -m.dx * sin + m.dy * cos;
|
|
435
|
-
return { lx, ly, guides: activeList(m) };
|
|
623
|
+
return { lx, ly, guides: activeList(m), gaps: [] };
|
|
436
624
|
}
|
|
437
625
|
if (anchor.x === "free" && anchor.y === "free") return null;
|
|
438
626
|
const along = anchor.x === "free" ? "y" : "x";
|
|
439
627
|
const dir = along === "x" ? toWorld(1, 0) : toWorld(0, 1);
|
|
440
628
|
const edge = movingEdge(along)[0];
|
|
441
629
|
let best = null;
|
|
442
|
-
for (const
|
|
443
|
-
const c = along === "x" ? drawn(edge,
|
|
630
|
+
for (const across2 of movingEdge(along === "x" ? "y" : "x")) {
|
|
631
|
+
const c = along === "x" ? drawn(edge, across2) : drawn(across2, edge);
|
|
444
632
|
for (const g of candidates) {
|
|
445
633
|
const reach = g.axis === "x" ? dir.x : dir.y;
|
|
446
634
|
if (Math.abs(reach) < 1e-9) continue;
|
|
447
635
|
const t = (g.offset - (g.axis === "x" ? c.x : c.y)) / reach;
|
|
448
636
|
if (Math.abs(t * dir.x) > tol.x || Math.abs(t * dir.y) > tol.y) continue;
|
|
449
|
-
if (best === null || Math.abs(t) < Math.abs(best.t)) best = { t, guide: g };
|
|
637
|
+
if (best === null || Math.abs(t) < Math.abs(best.t)) best = { t, guide: g, at: c };
|
|
450
638
|
}
|
|
451
639
|
}
|
|
452
640
|
if (best === null) return null;
|
|
453
|
-
|
|
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)];
|
|
643
|
+
return along === "x" ? { lx: best.t, ly: 0, guides, gaps: [] } : { lx: 0, ly: best.t, guides, gaps: [] };
|
|
454
644
|
}
|
|
455
645
|
|
|
456
646
|
// src/layout/snaps.ts
|
|
@@ -546,103 +736,111 @@ function defaultCenterOf(pose) {
|
|
|
546
736
|
const p = pose;
|
|
547
737
|
return { x: (p.x ?? 0) + (p.width ?? 0) / 2, y: (p.y ?? 0) + (p.height ?? 0) / 2 };
|
|
548
738
|
}
|
|
549
|
-
function cellRectAt(bounds, cols, rows, gap, col, row) {
|
|
550
|
-
const cw = (bounds.width - gap * (cols - 1)) / cols;
|
|
551
|
-
const ch = (bounds.height - gap * (rows - 1)) / rows;
|
|
552
|
-
return {
|
|
553
|
-
x: bounds.x + col * (cw + gap),
|
|
554
|
-
y: bounds.y + row * (ch + gap),
|
|
555
|
-
width: cw,
|
|
556
|
-
height: ch
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
739
|
function tileGrid(opts) {
|
|
560
740
|
const { cols, rows } = opts;
|
|
561
741
|
const gap = opts.gap ?? 0;
|
|
742
|
+
const byColumn = opts.flow === "column";
|
|
743
|
+
const overflow = opts.overflow ?? "reject";
|
|
562
744
|
const snap = opts.snap ?? cellAt();
|
|
563
745
|
const capacity = cols * rows;
|
|
746
|
+
const perLine = byColumn ? rows : cols;
|
|
747
|
+
const minLines = byColumn ? cols : rows;
|
|
564
748
|
const cellToPose = opts.cellToPose ?? ((cell, dragged) => {
|
|
565
749
|
return { ...dragged, ...cell };
|
|
566
750
|
});
|
|
567
751
|
const centerOf = opts.centerOf ?? defaultCenterOf;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
752
|
+
const linesFor = (n) => Math.max(minLines, Math.ceil(n / perLine));
|
|
753
|
+
function gridOf(bounds, n) {
|
|
754
|
+
const lines = overflow === "grow" ? linesFor(n) : minLines;
|
|
755
|
+
const c = byColumn ? lines : cols;
|
|
756
|
+
const r = byColumn ? rows : lines;
|
|
757
|
+
const cw = (bounds.width - gap * (c - 1)) / c;
|
|
758
|
+
const ch = (bounds.height - gap * (r - 1)) / r;
|
|
759
|
+
function rectAt(index) {
|
|
760
|
+
const along = index % perLine;
|
|
761
|
+
const line = Math.floor(index / perLine);
|
|
762
|
+
const col = byColumn ? line : along;
|
|
763
|
+
const row = byColumn ? along : line;
|
|
764
|
+
return { x: bounds.x + col * (cw + gap), y: bounds.y + row * (ch + gap), width: cw, height: ch };
|
|
765
|
+
}
|
|
766
|
+
function indexOf(pose) {
|
|
767
|
+
const p = centerOf(pose);
|
|
768
|
+
const col = Math.floor((p.x - bounds.x) / (cw + gap));
|
|
769
|
+
const row = Math.floor((p.y - bounds.y) / (ch + gap));
|
|
770
|
+
if (col < 0 || row < 0) return null;
|
|
771
|
+
if (p.x - bounds.x - col * (cw + gap) >= cw) return null;
|
|
772
|
+
if (p.y - bounds.y - row * (ch + gap) >= ch) return null;
|
|
773
|
+
if (byColumn ? row >= rows : col >= cols) return null;
|
|
774
|
+
return byColumn ? col * rows + row : row * cols + col;
|
|
580
775
|
}
|
|
581
|
-
|
|
776
|
+
const cells = overflow === "reject" ? capacity : linesFor(n) * perLine;
|
|
777
|
+
function boundsFor(lineCount) {
|
|
778
|
+
return byColumn ? { ...bounds, width: lineCount * cw + gap * (lineCount - 1) } : { ...bounds, height: lineCount * ch + gap * (lineCount - 1) };
|
|
779
|
+
}
|
|
780
|
+
return { rectAt, indexOf, cells, boundsFor };
|
|
582
781
|
}
|
|
782
|
+
const metaOf = (index, cellRect) => {
|
|
783
|
+
const along = index % perLine;
|
|
784
|
+
const line = Math.floor(index / perLine);
|
|
785
|
+
return byColumn ? { col: line, row: along, cellRect } : { col: along, row: line, cellRect };
|
|
786
|
+
};
|
|
787
|
+
const indexOfMeta = (m) => byColumn ? m.col * rows + m.row : m.row * cols + m.col;
|
|
788
|
+
const heldBy = (container, children, dragged) => dragged && dragged.sourceContainerId !== container.id ? children.filter((c) => c.id !== dragged.id).length : children.length;
|
|
583
789
|
function computeSwap(container, children, dragged, target) {
|
|
584
790
|
if (target === null) return null;
|
|
585
791
|
if (dragged.sourceContainerId !== container.id) return null;
|
|
586
|
-
const
|
|
587
|
-
const idx =
|
|
588
|
-
const occupant = children.find(
|
|
589
|
-
(c) => c.id !== dragged.id && cellIndexOf(container.bounds, c.pose) === idx
|
|
590
|
-
);
|
|
792
|
+
const grid = gridOf(container.bounds, children.length);
|
|
793
|
+
const idx = indexOfMeta(target.meta);
|
|
794
|
+
const occupant = children.find((c) => c.id !== dragged.id && grid.indexOf(c.pose) === idx);
|
|
591
795
|
if (occupant === void 0) return null;
|
|
592
796
|
const self = children.find((c) => c.id === dragged.id);
|
|
593
|
-
const fromIdx =
|
|
797
|
+
const fromIdx = grid.indexOf(self ? self.pose : dragged.originPose);
|
|
594
798
|
if (fromIdx === null || fromIdx === idx) return null;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
799
|
+
return { occupant: occupant.id, from: occupant.pose, newPose: cellToPose(grid.rectAt(fromIdx), occupant.pose) };
|
|
800
|
+
}
|
|
801
|
+
function pack(from, into, children) {
|
|
802
|
+
const ordered = children.map((c) => ({ c, cell: from.indexOf(c.pose) })).sort((a, b) => {
|
|
803
|
+
if (a.cell !== b.cell) {
|
|
804
|
+
if (a.cell === null) return 1;
|
|
805
|
+
if (b.cell === null) return -1;
|
|
806
|
+
return a.cell - b.cell;
|
|
807
|
+
}
|
|
808
|
+
return a.c.id < b.c.id ? -1 : a.c.id > b.c.id ? 1 : 0;
|
|
809
|
+
});
|
|
810
|
+
const out = /* @__PURE__ */ new Map();
|
|
811
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
812
|
+
out.set(ordered[i].c.id, cellToPose(into.rectAt(i), ordered[i].c.pose));
|
|
813
|
+
}
|
|
814
|
+
return out;
|
|
604
815
|
}
|
|
605
816
|
return {
|
|
606
817
|
snap,
|
|
607
818
|
childPoses(container, children) {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
if (a.cell !== b.cell) {
|
|
611
|
-
if (a.cell === null) return 1;
|
|
612
|
-
if (b.cell === null) return -1;
|
|
613
|
-
return a.cell - b.cell;
|
|
614
|
-
}
|
|
615
|
-
return a.c.id < b.c.id ? -1 : a.c.id > b.c.id ? 1 : 0;
|
|
616
|
-
});
|
|
617
|
-
for (let i = 0; i < ordered.length && i < capacity; i++) {
|
|
618
|
-
const col = i % cols;
|
|
619
|
-
const row = Math.floor(i / cols);
|
|
620
|
-
out.set(ordered[i].c.id, cellPose(container.bounds, col, row, ordered[i].c.pose));
|
|
621
|
-
}
|
|
622
|
-
return out;
|
|
819
|
+
const grid = gridOf(container.bounds, children.length);
|
|
820
|
+
return pack(grid, grid, children);
|
|
623
821
|
},
|
|
624
822
|
getDropTargets(container, children, dragged) {
|
|
823
|
+
const grid = gridOf(container.bounds, heldBy(container, children, dragged));
|
|
625
824
|
const occupied = /* @__PURE__ */ new Set();
|
|
626
|
-
|
|
825
|
+
const fromOutside = dragged.sourceContainerId !== container.id;
|
|
826
|
+
if (fromOutside) {
|
|
627
827
|
for (const c of children) {
|
|
628
828
|
if (c.id === dragged.id) continue;
|
|
629
|
-
const idx =
|
|
829
|
+
const idx = grid.indexOf(c.pose);
|
|
630
830
|
if (idx !== null) occupied.add(idx);
|
|
631
831
|
}
|
|
632
832
|
}
|
|
633
|
-
const
|
|
634
|
-
for (let
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
return out;
|
|
833
|
+
const offered = [];
|
|
834
|
+
for (let i = 0; i < grid.cells; i++) if (!occupied.has(i)) offered.push(i);
|
|
835
|
+
if (fromOutside && offered.length === 0 && overflow !== "reject") offered.push(grid.cells);
|
|
836
|
+
return offered.map((i) => {
|
|
837
|
+
const cellRect = grid.rectAt(i);
|
|
838
|
+
return {
|
|
839
|
+
pose: cellToPose(cellRect, dragged.pose),
|
|
840
|
+
origin: { x: cellRect.x + cellRect.width / 2, y: cellRect.y + cellRect.height / 2 },
|
|
841
|
+
meta: metaOf(i, cellRect)
|
|
842
|
+
};
|
|
843
|
+
});
|
|
646
844
|
},
|
|
647
845
|
reflowPoses(container, children, dragged, target) {
|
|
648
846
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -681,6 +879,67 @@ function tileGrid(opts) {
|
|
|
681
879
|
})
|
|
682
880
|
);
|
|
683
881
|
return ops;
|
|
882
|
+
},
|
|
883
|
+
arrive(container, children, arrivals) {
|
|
884
|
+
const n = children.length;
|
|
885
|
+
if (overflow === "reject" && n > capacity) return null;
|
|
886
|
+
const grid = gridOf(container.bounds, n - arrivals.size);
|
|
887
|
+
const slots = overflow === "reject" ? capacity : linesFor(n) * perLine;
|
|
888
|
+
const taken = /* @__PURE__ */ new Set();
|
|
889
|
+
for (const c of children) {
|
|
890
|
+
if (arrivals.has(c.id)) continue;
|
|
891
|
+
const idx = grid.indexOf(c.pose);
|
|
892
|
+
if (idx !== null) taken.add(idx);
|
|
893
|
+
}
|
|
894
|
+
const poses = /* @__PURE__ */ new Map();
|
|
895
|
+
for (const c of children) {
|
|
896
|
+
if (!arrivals.has(c.id)) continue;
|
|
897
|
+
const own = grid.indexOf(c.pose);
|
|
898
|
+
let idx;
|
|
899
|
+
if (own !== null && own < slots && !taken.has(own)) {
|
|
900
|
+
idx = own;
|
|
901
|
+
} else {
|
|
902
|
+
const at = centerOf(c.pose);
|
|
903
|
+
let best = -1;
|
|
904
|
+
let bestD = Infinity;
|
|
905
|
+
for (let i = 0; i < slots; i++) {
|
|
906
|
+
if (taken.has(i)) continue;
|
|
907
|
+
const r = grid.rectAt(i);
|
|
908
|
+
const dx = r.x + r.width / 2 - at.x;
|
|
909
|
+
const dy = r.y + r.height / 2 - at.y;
|
|
910
|
+
const d = dx * dx + dy * dy;
|
|
911
|
+
if (d < bestD) {
|
|
912
|
+
bestD = d;
|
|
913
|
+
best = i;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (best < 0) return null;
|
|
917
|
+
idx = best;
|
|
918
|
+
}
|
|
919
|
+
taken.add(idx);
|
|
920
|
+
poses.set(c.id, cellToPose(grid.rectAt(idx), c.pose));
|
|
921
|
+
}
|
|
922
|
+
if (overflow !== "grow") return { poses };
|
|
923
|
+
const lines = linesFor(n);
|
|
924
|
+
if (lines === linesFor(n - arrivals.size)) return { poses };
|
|
925
|
+
return { poses, bounds: grid.boundsFor(lines) };
|
|
926
|
+
},
|
|
927
|
+
depart(container, children, departed) {
|
|
928
|
+
const n = children.length;
|
|
929
|
+
const before = gridOf(container.bounds, n + departed.size);
|
|
930
|
+
const lines = linesFor(n);
|
|
931
|
+
if (overflow !== "grow" || lines === linesFor(n + departed.size)) {
|
|
932
|
+
return { poses: pack(before, before, children) };
|
|
933
|
+
}
|
|
934
|
+
const bounds = before.boundsFor(lines);
|
|
935
|
+
return { poses: pack(before, gridOf(bounds, n), children), bounds };
|
|
936
|
+
},
|
|
937
|
+
contentExtent(container, children) {
|
|
938
|
+
const grid = gridOf(container.bounds, children.length);
|
|
939
|
+
const lines = Math.ceil(children.length / perLine);
|
|
940
|
+
if (lines <= minLines) return container.bounds;
|
|
941
|
+
const reach = grid.boundsFor(lines);
|
|
942
|
+
return overflow === "grow" ? container.bounds : { ...reach, width: Math.max(reach.width, container.bounds.width), height: Math.max(reach.height, container.bounds.height) };
|
|
684
943
|
}
|
|
685
944
|
};
|
|
686
945
|
}
|
|
@@ -751,6 +1010,6 @@ function snapPoint(opts) {
|
|
|
751
1010
|
};
|
|
752
1011
|
}
|
|
753
1012
|
|
|
754
|
-
export { MOVE_ANCHORS, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, cellAt, containedThenNearest, createCellHighlightLayer, createGridLayer, createGuidesLayer, deriveAlignmentGuides, freeform, matchAlignment, nearest, nearestWithin, none, roundToCell, snapPoint, tileGrid, useGridCellHover, useGuides };
|
|
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 };
|
|
755
1014
|
//# sourceMappingURL=index.js.map
|
|
756
1015
|
//# sourceMappingURL=index.js.map
|