@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/README.md CHANGED
@@ -52,7 +52,7 @@ import { snapToGrid } from '@weasel-js/guides/insert';
52
52
  |---|---|
53
53
  | Grid | `createGridLayer`, `createCellHighlightLayer`, `useGridCellHover`, `gridSnapStrategy`, `pointToGridCell`, `pointSnapToGrid`, `roundToCell` |
54
54
  | Guides | `useGuides`, `createGuidesLayer`, `guideSnapStrategy`, `deriveAlignmentGuides`, `matchAlignment`, `alignMoveBehavior`, `alignResizeBehavior`, `alignInsertBehavior` |
55
- | Layout | `freeform`, `tileGrid`, `snapPoint`, and the drop-target pickers `none`, `nearest`, `nearestWithin`, `containedThenNearest`, `cellAt` |
55
+ | Layout | `freeform`, `tileGrid`, `snapPoint`, `slots` (with the `rowSlots`, `gridSlots`, and `ringSlots` arrangements), `pack`, `quadtree`, `perContainer` for a strategy that keeps state, and the drop-target pickers `none`, `nearest`, `nearestWithin`, `containedThenNearest`, `cellAt` |
56
56
 
57
57
  ## License
58
58
 
@@ -0,0 +1,45 @@
1
+ import { AUTO_ORIGIN_PROJECTION, screenTolerance } from '@weasel-js/core';
2
+
3
+ // src/strategies/guides.ts
4
+
5
+ // src/strategies/onPlane.ts
6
+ function guideOffsetOn(g, plane) {
7
+ return plane ? g.offset * plane.scale[g.axis] + plane.offset[g.axis] : g.offset;
8
+ }
9
+ function nearestGuideDelta(guides, axis, value, tol, plane) {
10
+ let best = null;
11
+ for (const g of guides) {
12
+ if (g.axis !== axis) continue;
13
+ const d = guideOffsetOn(g, plane) - value;
14
+ if (Math.abs(d) <= tol && (best === null || Math.abs(d) < Math.abs(best))) best = d;
15
+ }
16
+ return best;
17
+ }
18
+ function roundToGridOn(value, axis, spacing, plane) {
19
+ if (!plane) return Math.round(value / spacing) * spacing;
20
+ const camera = (value - plane.offset[axis]) / plane.scale[axis];
21
+ return Math.round(camera / spacing) * spacing * plane.scale[axis] + plane.offset[axis];
22
+ }
23
+
24
+ // src/strategies/guides.ts
25
+ var DEFAULT_GUIDE_TOLERANCE_PX = 6;
26
+ function guideSnapStrategy(getGuides, options = {}) {
27
+ const tolerance = options.tolerance ?? DEFAULT_GUIDE_TOLERANCE_PX;
28
+ const proj = options.origin ?? AUTO_ORIGIN_PROJECTION;
29
+ return {
30
+ snap(pose, ctx) {
31
+ const guides = getGuides();
32
+ if (guides.length === 0) return null;
33
+ const tol = screenTolerance(tolerance, ctx);
34
+ const o = proj.getOrigin(pose);
35
+ const dx = nearestGuideDelta(guides, "x", o.x, tol.x, ctx.plane) ?? 0;
36
+ const dy = nearestGuideDelta(guides, "y", o.y, tol.y, ctx.plane) ?? 0;
37
+ if (dx === 0 && dy === 0) return null;
38
+ return proj.translate(pose, dx, dy);
39
+ }
40
+ };
41
+ }
42
+
43
+ export { DEFAULT_GUIDE_TOLERANCE_PX, guideOffsetOn, guideSnapStrategy, nearestGuideDelta, roundToGridOn };
44
+ //# sourceMappingURL=chunk-LOFNN7EQ.js.map
45
+ //# sourceMappingURL=chunk-LOFNN7EQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/strategies/onPlane.ts","../src/strategies/guides.ts"],"names":[],"mappings":";;;;;AAQO,SAAS,aAAA,CAAc,GAAU,KAAA,EAAsB;AAC5D,EAAA,OAAO,KAAA,GAAQ,CAAA,CAAE,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,CAAA,CAAE,IAAI,CAAA,GAAI,KAAA,CAAM,MAAA,CAAO,CAAA,CAAE,IAAI,IAAI,CAAA,CAAE,MAAA;AAC3E;AAGO,SAAS,iBAAA,CACd,MAAA,EACA,IAAA,EACA,KAAA,EACA,KACA,KAAA,EACe;AACf,EAAA,IAAI,IAAA,GAAsB,IAAA;AAC1B,EAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACtB,IAAA,IAAI,CAAA,CAAE,SAAS,IAAA,EAAM;AACrB,IAAA,MAAM,CAAA,GAAI,aAAA,CAAc,CAAA,EAAG,KAAK,CAAA,GAAI,KAAA;AACpC,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,IAAK,QAAQ,IAAA,KAAS,IAAA,IAAQ,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,IAAI,IAAI,IAAA,GAAO,CAAA;AAAA,EACpF;AACA,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,aAAA,CAAc,KAAA,EAAe,IAAA,EAAY,OAAA,EAAiB,KAAA,EAAsB;AAC9F,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,KAAK,KAAA,CAAM,KAAA,GAAQ,OAAO,CAAA,GAAI,OAAA;AACjD,EAAA,MAAM,MAAA,GAAA,CAAU,QAAQ,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA,IAAK,KAAA,CAAM,MAAM,IAAI,CAAA;AAC9D,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,OAAO,CAAA,GAAI,OAAA,GAAU,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA,GAAI,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA;AACvF;;;ACxBO,IAAM,0BAAA,GAA6B;AAuCnC,SAAS,iBAAA,CACd,SAAA,EACA,OAAA,GAAmC,EAAC,EACf;AACrB,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,0BAAA;AACvC,EAAA,MAAM,IAAA,GACJ,QAAQ,MAAA,IAAW,sBAAA;AAErB,EAAA,OAAO;AAAA,IACL,IAAA,CAAK,MAAM,GAAA,EAAK;AACd,MAAA,MAAM,SAAS,SAAA,EAAU;AACzB,MAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAIhC,MAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,SAAA,EAAW,GAAG,CAAA;AAE1C,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAC7B,MAAA,MAAM,EAAA,GAAK,iBAAA,CAAkB,MAAA,EAAQ,GAAA,EAAK,CAAA,CAAE,GAAG,GAAA,CAAI,CAAA,EAAG,GAAA,CAAI,KAAK,CAAA,IAAK,CAAA;AACpE,MAAA,MAAM,EAAA,GAAK,iBAAA,CAAkB,MAAA,EAAQ,GAAA,EAAK,CAAA,CAAE,GAAG,GAAA,CAAI,CAAA,EAAG,GAAA,CAAI,KAAK,CAAA,IAAK,CAAA;AACpE,MAAA,IAAI,EAAA,KAAO,CAAA,IAAK,EAAA,KAAO,CAAA,EAAG,OAAO,IAAA;AACjC,MAAA,OAAO,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI,EAAE,CAAA;AAAA,IACpC;AAAA,GACF;AACF","file":"chunk-LOFNN7EQ.js","sourcesContent":["import type { GestureContext } from '@weasel-js/core';\nimport type { Guide } from '../guides/types';\n\n/** A gesture's `plane`: how the camera's world maps into the world its poses are in. */\ntype Plane = GestureContext<unknown>['plane'];\ntype Axis = 'x' | 'y';\n\n/** Where a guide the camera draws lies along its own axis in the world a gesture's poses are in. */\nexport function guideOffsetOn(g: Guide, plane: Plane): number {\n return plane ? g.offset * plane.scale[g.axis] + plane.offset[g.axis] : g.offset;\n}\n\n/** The signed distance from `value` to the nearest guide on `axis` within `tol`, or null when none is. */\nexport function nearestGuideDelta(\n guides: readonly Guide[],\n axis: Axis,\n value: number,\n tol: number,\n plane: Plane,\n): number | null {\n let best: number | null = null;\n for (const g of guides) {\n if (g.axis !== axis) continue;\n const d = guideOffsetOn(g, plane) - value;\n if (Math.abs(d) <= tol && (best === null || Math.abs(d) < Math.abs(best))) best = d;\n }\n return best;\n}\n\n/** `value` on `axis` moved to the nearest line of the camera's grid, in the world a gesture's poses are in. */\nexport function roundToGridOn(value: number, axis: Axis, spacing: number, plane: Plane): number {\n if (!plane) return Math.round(value / spacing) * spacing;\n const camera = (value - plane.offset[axis]) / plane.scale[axis];\n return Math.round(camera / spacing) * spacing * plane.scale[axis] + plane.offset[axis];\n}\n","import {\n type SnapStrategy,\n screenTolerance,\n type OriginProjection,\n AUTO_ORIGIN_PROJECTION,\n} from '@weasel-js/core';\nimport type { Guide } from '../guides/types';\nimport { nearestGuideDelta } from './onPlane';\n\n/** Default snap tolerance (screen pixels). */\nexport const DEFAULT_GUIDE_TOLERANCE_PX = 6;\n\n/** Options for `guideSnapStrategy`. */\nexport interface GuideSnapOptions<TPose> {\n /**\n * Tolerance in screen pixels, divided by the gesture view's scale so the\n * trigger zone stays the same on screen at every zoom. Defaults to\n * `DEFAULT_GUIDE_TOLERANCE_PX`.\n */\n tolerance?: number;\n /**\n * Projection used when `TPose` doesn't expose `{x, y}` directly (Path,\n * polygon, etc.) — same contract as `gridSnapStrategy`. Defaults to the\n * rect-pose identity projection.\n */\n origin?: OriginProjection<TPose>;\n}\n\n/**\n * Snap-strategy that snaps the pose's origin to the nearest guide line on\n * each axis when within tolerance. Each axis is considered independently:\n * a vertical guide (`axis: 'x'`) constrains X, a horizontal guide\n * (`axis: 'y'`) constrains Y. When multiple guides are within range on the\n * same axis, the closest wins.\n *\n * Pair with `useGuides` for storage and `createGuidesLayer` for rendering.\n * Combining with `gridSnapStrategy` is left to the consumer.\n */\nexport function guideSnapStrategy<TPose extends { x: number; y: number }>(\n getGuides: () => readonly Guide[],\n options?: Omit<GuideSnapOptions<TPose>, 'origin'>,\n): SnapStrategy<TPose>;\n/** As above, for a `TPose` that is not a rect: `origin` tells the strategy how\n * to read and write the pose's origin. */\nexport function guideSnapStrategy<TPose>(\n getGuides: () => readonly Guide[],\n options: GuideSnapOptions<TPose> & { origin: OriginProjection<TPose> },\n): SnapStrategy<TPose>;\n/** Snap a pose's origin to the nearest guide line within tolerance, per axis. */\nexport function guideSnapStrategy<TPose>(\n getGuides: () => readonly Guide[],\n options: GuideSnapOptions<TPose> = {},\n): SnapStrategy<TPose> {\n const tolerance = options.tolerance ?? DEFAULT_GUIDE_TOLERANCE_PX;\n const proj: OriginProjection<TPose> =\n options.origin ?? (AUTO_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n\n return {\n snap(pose, ctx) {\n const guides = getGuides();\n if (guides.length === 0) return null;\n\n // Per axis: a guide on the x axis is matched by a horizontal distance,\n // so it answers to `scale.x` alone.\n const tol = screenTolerance(tolerance, ctx);\n\n const o = proj.getOrigin(pose);\n const dx = nearestGuideDelta(guides, 'x', o.x, tol.x, ctx.plane) ?? 0;\n const dy = nearestGuideDelta(guides, 'y', o.y, tol.y, ctx.plane) ?? 0;\n if (dx === 0 && dy === 0) return null;\n return proj.translate(pose, dx, dy);\n },\n };\n}\n"]}
@@ -1,33 +1,31 @@
1
- import { AUTO_ORIGIN_PROJECTION, resolveUnit, fromPlane, toPlane } from '@weasel-js/core';
1
+ import { roundToGridOn } from './chunk-LOFNN7EQ.js';
2
+ import { AUTO_ORIGIN_PROJECTION, resolveAxes } from '@weasel-js/core';
2
3
 
3
- // src/strategies/grid.ts
4
4
  function gridSnapStrategy(spacing, arg) {
5
- const isOpts = typeof arg === "object" && arg !== null && ("origin" in arg || "debug" in arg || "unitSystem" in arg) && !("base" in arg);
5
+ const isOpts = typeof arg === "object" && arg !== null && ("origin" in arg || "debug" in arg || "unitSystem" in arg) && !("base" in arg) && !("x" in arg);
6
6
  const optsArg = isOpts ? arg : null;
7
7
  const unitSystem = optsArg ? optsArg.unitSystem : arg;
8
8
  const proj = optsArg && optsArg.origin ? optsArg.origin : AUTO_ORIGIN_PROJECTION;
9
9
  const debug = optsArg ? optsArg.debug : void 0;
10
- const c = resolveUnit(spacing, unitSystem);
10
+ const c = resolveAxes(spacing, unitSystem);
11
11
  return {
12
12
  snap(pose, ctx) {
13
13
  const o = proj.getOrigin(pose);
14
- const m = ctx?.plane ?? null;
15
- const w = m ? fromPlane(m, o) : o;
16
- const cell = { x: Math.round(w.x / c) * c, y: Math.round(w.y / c) * c };
17
- const { x: sx, y: sy } = m ? toPlane(m, cell) : cell;
14
+ const sx = roundToGridOn(o.x, "x", c.x, ctx?.plane);
15
+ const sy = roundToGridOn(o.y, "y", c.y, ctx?.plane);
18
16
  debug?.recordSnapCandidate({ x: sx, y: sy }, true);
19
17
  return proj.translate(pose, sx - o.x, sy - o.y);
20
18
  }
21
19
  };
22
20
  }
23
21
  function pointToGridCell(point, spacing, unitSystem, origin = { x: 0, y: 0 }) {
24
- const s = resolveUnit(spacing, unitSystem);
22
+ const s = resolveAxes(spacing, unitSystem);
25
23
  return {
26
- col: Math.floor((point.x - origin.x) / s),
27
- row: Math.floor((point.y - origin.y) / s)
24
+ col: Math.floor((point.x - origin.x) / s.x),
25
+ row: Math.floor((point.y - origin.y) / s.y)
28
26
  };
29
27
  }
30
28
 
31
29
  export { gridSnapStrategy, pointToGridCell };
32
- //# sourceMappingURL=chunk-5BCCIJY3.js.map
33
- //# sourceMappingURL=chunk-5BCCIJY3.js.map
30
+ //# sourceMappingURL=chunk-TLFMAZ6M.js.map
31
+ //# sourceMappingURL=chunk-TLFMAZ6M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/strategies/grid.ts"],"names":[],"mappings":";;;AAkCO,SAAS,gBAAA,CACd,SACA,GAAA,EACqB;AACrB,EAAA,MAAM,SACJ,OAAO,GAAA,KAAQ,QAAA,IACf,GAAA,KAAQ,SACP,QAAA,IAAY,GAAA,IAAO,OAAA,IAAW,GAAA,IAAO,gBAAgB,GAAA,CAAA,IACtD,EAAE,MAAA,IAAU,GAAA,CAAA,IACZ,EAAE,GAAA,IAAO,GAAA,CAAA;AACX,EAAA,MAAM,OAAA,GAAU,SACX,GAAA,GACD,IAAA;AACJ,EAAA,MAAM,UAAA,GAAa,OAAA,GAAU,OAAA,CAAQ,UAAA,GAAc,GAAA;AACnD,EAAA,MAAM,IAAA,GAAgC,OAAA,IAAW,OAAA,CAAQ,MAAA,GACrD,QAAQ,MAAA,GACP,sBAAA;AACL,EAAA,MAAM,KAAA,GAA+B,OAAA,GAAU,OAAA,CAAQ,KAAA,GAAQ,MAAA;AAC/D,EAAA,MAAM,CAAA,GAAI,WAAA,CAAY,OAAA,EAAS,UAAU,CAAA;AACzC,EAAA,OAAO;AAAA,IACL,IAAA,CAAK,MAAM,GAAA,EAAK;AACd,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAC7B,MAAA,MAAM,EAAA,GAAK,cAAc,CAAA,CAAE,CAAA,EAAG,KAAK,CAAA,CAAE,CAAA,EAAG,KAAK,KAAK,CAAA;AAClD,MAAA,MAAM,EAAA,GAAK,cAAc,CAAA,CAAE,CAAA,EAAG,KAAK,CAAA,CAAE,CAAA,EAAG,KAAK,KAAK,CAAA;AAClD,MAAA,KAAA,EAAO,oBAAoB,EAAE,CAAA,EAAG,IAAI,CAAA,EAAG,EAAA,IAAM,IAAI,CAAA;AACjD,MAAA,OAAO,IAAA,CAAK,UAAU,IAAA,EAAM,EAAA,GAAK,EAAE,CAAA,EAAG,EAAA,GAAK,EAAE,CAAC,CAAA;AAAA,IAChD;AAAA,GACF;AACF;AAOO,SAAS,eAAA,CACd,KAAA,EACA,OAAA,EACA,UAAA,EACA,MAAA,GAAmC,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAE,EAClB;AAC9B,EAAA,MAAM,CAAA,GAAI,WAAA,CAAY,OAAA,EAAS,UAAU,CAAA;AACzC,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,KAAK,KAAA,CAAA,CAAO,KAAA,CAAM,IAAI,MAAA,CAAO,CAAA,IAAK,EAAE,CAAC,CAAA;AAAA,IAC1C,GAAA,EAAK,KAAK,KAAA,CAAA,CAAO,KAAA,CAAM,IAAI,MAAA,CAAO,CAAA,IAAK,EAAE,CAAC;AAAA,GAC5C;AACF","file":"chunk-TLFMAZ6M.js","sourcesContent":["import {\n AUTO_ORIGIN_PROJECTION,\n resolveAxes,\n type DebugSink,\n type OriginProjection,\n type SnapStrategy,\n type PlaneUnits,\n type PlaneValue,\n} from '@weasel-js/core';\nimport { roundToGridOn } from './onPlane';\n\n/** Snap-strategy that rounds the pose's origin to the nearest multiple of\n * `spacing` (resolved through `unitSystem`). Either may be per axis —\n * `{ x, y }` — for a grid whose axes have different pitches or units. For\n * non-rect TPose pass an `OriginProjection` so the strategy knows how to\n * read/write the origin. */\nexport function gridSnapStrategy<TPose>(\n spacing: PlaneValue,\n unitSystem?: PlaneUnits,\n): SnapStrategy<TPose>;\n/** As above, additionally reporting each candidate to a debug sink. */\nexport function gridSnapStrategy<TPose>(\n spacing: PlaneValue,\n opts: { unitSystem?: PlaneUnits; debug?: DebugSink },\n): SnapStrategy<TPose>;\n/** As above, for a `TPose` that is not a rect: `origin` tells the strategy how\n * to read and write the pose's origin. */\nexport function gridSnapStrategy<TPose>(\n spacing: PlaneValue,\n opts: { unitSystem?: PlaneUnits; origin: OriginProjection<TPose>; debug?: DebugSink },\n): SnapStrategy<TPose>;\n/** Snap-strategy that rounds the pose's origin to the nearest multiple of\n * `spacing` (resolved through `unitSystem`). For non-rect TPose pass an\n * `OriginProjection` so the strategy knows how to read/write the origin. */\nexport function gridSnapStrategy<TPose>(\n spacing: PlaneValue,\n arg?: PlaneUnits | { unitSystem?: PlaneUnits; origin?: OriginProjection<TPose>; debug?: DebugSink },\n): SnapStrategy<TPose> {\n const isOpts =\n typeof arg === 'object' &&\n arg !== null &&\n ('origin' in arg || 'debug' in arg || 'unitSystem' in arg) &&\n !('base' in arg) &&\n !('x' in arg);\n const optsArg = isOpts\n ? (arg as { unitSystem?: PlaneUnits; origin?: OriginProjection<TPose>; debug?: DebugSink })\n : null;\n const unitSystem = optsArg ? optsArg.unitSystem : (arg as PlaneUnits | undefined);\n const proj: OriginProjection<TPose> = optsArg && optsArg.origin\n ? optsArg.origin\n : (AUTO_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n const debug: DebugSink | undefined = optsArg ? optsArg.debug : undefined;\n const c = resolveAxes(spacing, unitSystem);\n return {\n snap(pose, ctx) {\n const o = proj.getOrigin(pose);\n const sx = roundToGridOn(o.x, 'x', c.x, ctx?.plane);\n const sy = roundToGridOn(o.y, 'y', c.y, ctx?.plane);\n debug?.recordSnapCandidate({ x: sx, y: sy }, true);\n return proj.translate(pose, sx - o.x, sy - o.y);\n },\n };\n}\n\n/**\n * The integer cell `{col, row}` that contains `point`. Pair with\n * `createCellHighlightLayer` (its `getCell` callback) to draw a snap-target\n * preview that uses the same spacing as `gridSnapStrategy`.\n */\nexport function pointToGridCell(\n point: { x: number; y: number },\n spacing: PlaneValue,\n unitSystem?: PlaneUnits,\n origin: { x: number; y: number } = { x: 0, y: 0 },\n): { col: number; row: number } {\n const s = resolveAxes(spacing, unitSystem);\n return {\n col: Math.floor((point.x - origin.x) / s.x),\n row: Math.floor((point.y - origin.y) / s.y),\n };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,21 +1,15 @@
1
1
  import { RefObject } from 'react';
2
- import { ViewTransform, UnitValue, UnitSystem, Stroke, RenderLayer, FillStyle, SnapStrategy, DebugSink, OriginProjection, ModifierState, Bounds, PoseDescriptor, InsertBehavior, MoveBehavior, BoundsConstraint, LayoutSnap, LayoutStrategy, Vec2 } from '@weasel-js/core';
2
+ import { ViewTransform, PlaneValue, PlaneUnits, CommandStroke, RenderLayer, CommandFill, SnapStrategy, DebugSink, OriginProjection, ModifierState, Bounds, PlaneMap, PoseDescriptor, InsertBehavior, MoveBehavior, BoundsConstraint, LayoutSnap, LayoutStrategy, LayoutContainer, Vec2, ContainerBounds, LayoutChild } from '@weasel-js/core';
3
3
  export { p as pointSnapToGrid } from './pointSnapToGrid-BaVEjV23.js';
4
4
  import { G as Guide, S as SpacingGap } from './types-DRSPiXfk.js';
5
+ import { Rect } from '@weasel-js/geom';
5
6
 
6
7
  /** Round `value` to the nearest multiple of `cellSize`. Returns 0 when the result would be -0. */
7
8
  declare function roundToCell(value: number, cellSize: number): number;
8
9
 
9
10
  /**
10
11
  * Pointer-tracking glue that turns hover position into a grid cell, suitable
11
- * for dropping straight into `createCellHighlightLayer({ getCell })`. Pairs
12
- * with `gridSnapStrategy` / `createGridLayer` so the visual hover preview
13
- * uses the exact same spacing as the snap behavior.
14
- *
15
- * Wires three things you'd otherwise glue by hand:
16
- * 1. pointermove on a target element → client coords
17
- * 2. client coords → world coords via your `ViewTransform`
18
- * 3. world coords → integer `{col, row}` via `pointToGridCell`
12
+ * for dropping straight into `createCellHighlightLayer({ getCell })`.
19
13
  *
20
14
  * State updates only fire when the cell actually changes (not on every
21
15
  * pointer tick). Pass `onChange` to trigger a canvas redraw on each transition.
@@ -30,9 +24,9 @@ interface UseGridCellHoverOptions {
30
24
  view: () => ViewTransform;
31
25
  /** Cell spacing in world units. Same value you pass to `createGridLayer`
32
26
  * and `createCellHighlightLayer`. */
33
- spacing: UnitValue;
27
+ spacing: PlaneValue;
34
28
  /** Required if `spacing` is tagged (e.g. `{ value: 1, unit: 'ft' }`). */
35
- unitSystem?: UnitSystem;
29
+ unitSystem?: PlaneUnits;
36
30
  /** Origin the cell grid is anchored to, in world units. Default `(0, 0)`. */
37
31
  origin?: {
38
32
  x: number;
@@ -80,10 +74,11 @@ interface GridLayerOpts {
80
74
  * Distance between adjacent grid lines (also known as grid pitch),
81
75
  * in world (base) units. Accepts a bare number or a tagged
82
76
  * `{ value, unit }`. Tagged values require `unitSystem` to be supplied.
77
+ * Either may be per axis — `{ x, y }`.
83
78
  */
84
- spacing: UnitValue;
79
+ spacing: PlaneValue;
85
80
  /** Optional unit system for resolving tagged `spacing` values. */
86
- unitSystem?: UnitSystem;
81
+ unitSystem?: PlaneUnits;
87
82
  /** Bounds of the area to cover, in world units. */
88
83
  bounds: () => {
89
84
  x: number;
@@ -95,11 +90,11 @@ interface GridLayerOpts {
95
90
  accentEvery?: number;
96
91
  /** Optional finer subdivisions per cell. e.g. 4 -> 4 sub-lines per cell. */
97
92
  subdivisions?: number;
98
- /** Per-band stroke styles. Each is a FillStyle+width (etc.) — see `Stroke`. */
93
+ /** Per-band stroke styles. Each is a Paint+width (etc.) — see `Stroke`. */
99
94
  style?: {
100
- line?: Stroke;
101
- accent?: Stroke;
102
- sub?: Stroke;
95
+ line?: CommandStroke;
96
+ accent?: CommandStroke;
97
+ sub?: CommandStroke;
103
98
  };
104
99
  }
105
100
  /** Build a `RenderLayer` that draws a world-space grid with optional accent lines and subdivisions. */
@@ -114,9 +109,9 @@ declare function createGridLayer(opts: GridLayerOpts): RenderLayer<unknown>;
114
109
  /** Options for `createCellHighlightLayer`. */
115
110
  interface CellHighlightLayerOpts {
116
111
  /** Cell size in world units (matches the grid's `spacing`). */
117
- spacing: UnitValue;
112
+ spacing: PlaneValue;
118
113
  /** Optional unit system for resolving tagged `spacing` values. */
119
- unitSystem?: UnitSystem;
114
+ unitSystem?: PlaneUnits;
120
115
  /** Origin the cell grid is anchored to, in world units. Defaults to (0,0). */
121
116
  origin?: () => {
122
117
  x: number;
@@ -127,49 +122,39 @@ interface CellHighlightLayerOpts {
127
122
  col: number;
128
123
  row: number;
129
124
  } | null;
130
- /** FillStyle strategy for the filled cell. Defaults to a soft green. */
131
- fill?: FillStyle;
125
+ /** Fill for the highlighted cell. Defaults to a soft green. */
126
+ fill?: CommandFill;
132
127
  }
133
128
  /** Build a `RenderLayer` that fills a single grid cell — typically a snap-target preview. */
134
129
  declare function createCellHighlightLayer(opts: CellHighlightLayerOpts): RenderLayer<unknown>;
135
130
 
136
131
  /** Snap-strategy that rounds the pose's origin to the nearest multiple of
137
- * `spacing` (resolved through `unitSystem`). For non-rect TPose pass an
138
- * `OriginProjection` so the strategy knows how to read/write the origin. */
139
- declare function gridSnapStrategy<TPose>(spacing: UnitValue, unitSystem?: UnitSystem): SnapStrategy<TPose>;
132
+ * `spacing` (resolved through `unitSystem`). Either may be per axis —
133
+ * `{ x, y }` — for a grid whose axes have different pitches or units. For
134
+ * non-rect TPose pass an `OriginProjection` so the strategy knows how to
135
+ * read/write the origin. */
136
+ declare function gridSnapStrategy<TPose>(spacing: PlaneValue, unitSystem?: PlaneUnits): SnapStrategy<TPose>;
140
137
  /** As above, additionally reporting each candidate to a debug sink. */
141
- declare function gridSnapStrategy<TPose>(spacing: UnitValue, opts: {
142
- unitSystem?: UnitSystem;
138
+ declare function gridSnapStrategy<TPose>(spacing: PlaneValue, opts: {
139
+ unitSystem?: PlaneUnits;
143
140
  debug?: DebugSink;
144
141
  }): SnapStrategy<TPose>;
145
142
  /** As above, for a `TPose` that is not a rect: `origin` tells the strategy how
146
143
  * to read and write the pose's origin. */
147
- declare function gridSnapStrategy<TPose>(spacing: UnitValue, opts: {
148
- unitSystem?: UnitSystem;
144
+ declare function gridSnapStrategy<TPose>(spacing: PlaneValue, opts: {
145
+ unitSystem?: PlaneUnits;
149
146
  origin: OriginProjection<TPose>;
150
147
  debug?: DebugSink;
151
148
  }): SnapStrategy<TPose>;
152
149
  /**
153
- * Compute the integer cell `{col, row}` that contains `point`, given a grid
154
- * `spacing` and optional `origin` and `unitSystem`. Pair with
150
+ * The integer cell `{col, row}` that contains `point`. Pair with
155
151
  * `createCellHighlightLayer` (its `getCell` callback) to draw a snap-target
156
- * preview that uses the same spacing as `gridSnapStrategy` — so the visual
157
- * and behavioral grids stay in lockstep:
158
- *
159
- * const SPACING = 20;
160
- * const snap = gridSnapStrategy(SPACING);
161
- * // visual grid:
162
- * createGridLayer({ spacing: SPACING, bounds: () => ... });
163
- * // hover-preview overlay:
164
- * createCellHighlightLayer({
165
- * spacing: SPACING,
166
- * getCell: () => hoverPoint && pointToGridCell(hoverPoint, SPACING),
167
- * });
152
+ * preview that uses the same spacing as `gridSnapStrategy`.
168
153
  */
169
154
  declare function pointToGridCell(point: {
170
155
  x: number;
171
156
  y: number;
172
- }, spacing: UnitValue, unitSystem?: UnitSystem, origin?: {
157
+ }, spacing: PlaneValue, unitSystem?: PlaneUnits, origin?: {
173
158
  x: number;
174
159
  y: number;
175
160
  }): {
@@ -275,8 +260,14 @@ interface SpacingMatchResult {
275
260
  /** Which candidate lines to derive from a set of poses — edges, centers, or
276
261
  * both, and whether the page box contributes its own. */
277
262
  interface DeriveAlignmentGuidesOptions<TPose = Bounds> {
278
- /** Include the document/page box's edges + center as candidates. */
263
+ /** Include the document/page box's edges + center as candidates. It is in
264
+ * the camera's world, as every line derived is. */
279
265
  page?: Bounds;
266
+ /** The parallax plane a target's pose is stored in, for a sibling on a
267
+ * `parallax` layer (`view.planeOf(layer)`). Its box is carried out to the
268
+ * camera's world, which is the world alignment candidates are in. Omit it,
269
+ * or answer null, for a target already there. */
270
+ planeOf?: (target: TPose, index: number) => PlaneMap | null | undefined;
280
271
  /** Emit left/right (x) and top/bottom (y) edge guides. Default true. */
281
272
  edges?: boolean;
282
273
  /** Emit centerX (x) and centerY (y) guides. Default true. */
@@ -285,9 +276,17 @@ interface DeriveAlignmentGuidesOptions<TPose = Bounds> {
285
276
  * gets, or the two sides disagree. Default `AUTO_POSE_DESCRIPTOR`. */
286
277
  poseDescriptor?: PoseDescriptor<TPose>;
287
278
  }
288
- /** Common options shared by the three alignment behavior factories. */
279
+ /** Common options shared by the three alignment behavior factories.
280
+ *
281
+ * Everything crossing this interface is in the camera's world, whatever
282
+ * layer the gesture edits: the candidate lines and spacing boxes handed in,
283
+ * and the lines and gaps published, which a layer the camera draws shows.
284
+ * A gesture on a `parallax` layer carries them into its plane to match. */
289
285
  interface AlignmentBehaviorBase {
290
- /** Live candidate lines — consumer derives from current siblings + page. */
286
+ /** Live candidate lines — consumer derives from current siblings + page.
287
+ * A sibling on a parallax layer goes through `deriveAlignmentGuides`'
288
+ * `planeOf`, or its lines land where its plane's numbers say, not where
289
+ * it is drawn. */
291
290
  getCandidates: () => readonly Guide[];
292
291
  /** Publish the currently-matched line(s). Called every onMove; cleared
293
292
  * (`[]`) on a miss and on onEnd. */
@@ -311,7 +310,9 @@ interface AlignmentBehaviorBase {
311
310
  * edges and the center. Overlapping offsets collapse to one candidate, whose
312
311
  * `span` covers every box that produced it.
313
312
  * Poses go through the same descriptor `alignMoveBehavior` matches with, so
314
- * a rotated sibling advertises its ink edges rather than its stored box. */
313
+ * a rotated sibling advertises its ink edges rather than its stored box.
314
+ * The lines are in the camera's world: a sibling on a parallax layer needs
315
+ * `planeOf` to say which plane its pose is in. */
315
316
  declare function deriveAlignmentGuides<TPose = Bounds>(targets: readonly TPose[], opts?: DeriveAlignmentGuidesOptions<TPose>): Guide[];
316
317
 
317
318
  /** Move/insert test all three features per axis. */
@@ -328,9 +329,8 @@ declare const MOVE_ANCHORS: {
328
329
  * from the aligned siblings to the box being dragged.
329
330
  *
330
331
  * `worldTolerance` is per axis because a screen-pixel tolerance is not one
331
- * world distance under non-uniform zoom — and each axis here is matched by a
332
- * distance along that axis alone, so there is an exact answer rather than an
333
- * approximation. Pass the same number twice for a world-space tolerance.
332
+ * world distance under non-uniform zoom. Pass the same number twice for a
333
+ * world-space tolerance.
334
334
  */
335
335
  declare function matchAlignment(bounds: Bounds, candidates: readonly Guide[], worldTolerance: {
336
336
  x: number;
@@ -340,14 +340,13 @@ declare function matchAlignment(bounds: Bounds, candidates: readonly Guide[], wo
340
340
  y: readonly AlignAnchor[];
341
341
  }): AlignMatchResult;
342
342
 
343
- type Axis = 'x' | 'y';
344
343
  /**
345
344
  * The visible gaps between neighboring boxes along `axis`: for `'x'`, each box
346
345
  * and the nearest box to its right that shares some of its vertical range, as
347
346
  * long as no third box stands in the strip between them. These are the gaps an
348
347
  * equal-spacing snap can reproduce.
349
348
  */
350
- declare function measureGaps(targets: readonly Bounds[], axis: Axis): SpacingGap[];
349
+ declare function measureGaps(targets: readonly Bounds[], axis: 'x' | 'y'): SpacingGap[];
351
350
  /**
352
351
  * Match a box against the gaps between `targets`, Figma's "equal spacing":
353
352
  * on each axis, find where the box's gap to a neighbor equals a gap that
@@ -378,10 +377,10 @@ interface AlignMoveArgs<TPose> extends AlignmentBehaviorBase {
378
377
  * transform uniformly to every dragged id, so the selection shifts together
379
378
  * and stays rigid. Single-select is the degenerate one-box union. Publishes
380
379
  * the matched line(s); clears on miss, end and cancel. */
381
- declare function alignMoveBehavior<TPose>(args: AlignMoveArgs<TPose>): MoveBehavior<TPose>;
380
+ declare function alignMoveBehavior<TPose>(given: AlignMoveArgs<TPose>): MoveBehavior<TPose>;
382
381
  /** Insert behavior: snap the live `current` point to candidates (treating it
383
382
  * as a zero-size box). Publishes the matched line(s). */
384
- declare function alignInsertBehavior<TPose>(args: AlignmentBehaviorBase): InsertBehavior<TPose>;
383
+ declare function alignInsertBehavior<TPose>(given: AlignmentBehaviorBase): InsertBehavior<TPose>;
385
384
  /** Resize constraint: snap the corner(s) the drag moves, as drawn, to
386
385
  * candidates; the pinned corner stays fixed. On an unrotated pose that is
387
386
  * the moving edge(s). On a rotated one the node's own axes set how the
@@ -389,7 +388,7 @@ declare function alignInsertBehavior<TPose>(args: AlignmentBehaviorBase): Insert
389
388
  * slides its edge until the nearer of its two corners meets a line.
390
389
  * `poseDescriptor` reads the origin pose's box and rotation, and must match
391
390
  * the resize action's. Publishes the matched line(s). */
392
- declare function alignResizeBehavior<TPose extends Bounds>(args: AlignMoveArgs<TPose>): BoundsConstraint<TPose>;
391
+ declare function alignResizeBehavior<TPose extends Bounds>(given: AlignMoveArgs<TPose>): BoundsConstraint<TPose>;
393
392
 
394
393
  /** Default snap tolerance (screen pixels). */
395
394
  declare const DEFAULT_GUIDE_TOLERANCE_PX = 6;
@@ -416,10 +415,7 @@ interface GuideSnapOptions<TPose> {
416
415
  * same axis, the closest wins.
417
416
  *
418
417
  * Pair with `useGuides` for storage and `createGuidesLayer` for rendering.
419
- *
420
- * **Composition:** combining with `gridSnapStrategy` is left to the
421
- * consumer in v1 — typically by picking one based on a modifier key, or
422
- * by stacking two move/resize/insert behaviors in priority order.
418
+ * Combining with `gridSnapStrategy` is left to the consumer.
423
419
  */
424
420
  declare function guideSnapStrategy<TPose extends {
425
421
  x: number;
@@ -460,6 +456,34 @@ declare function containedThenNearest<TPose>(): LayoutSnap<TPose>;
460
456
  */
461
457
  declare function cellAt<TPose>(): LayoutSnap<TPose>;
462
458
 
459
+ /** Options for `perContainer`. */
460
+ interface PerContainerOptions<TPose> {
461
+ /** The snap every container shares: a strategy's `snap` is read with no
462
+ * container in hand, so it cannot come from the one the factory builds. */
463
+ snap: LayoutSnap<TPose>;
464
+ /** Shared for the same reason. Absent, a container's body is its bounds. */
465
+ contains?: LayoutStrategy<TPose>['contains'];
466
+ /** How many containers' strategies to keep. Past it, the one used longest
467
+ * ago is dropped and built again when next asked for. Default 256. */
468
+ max?: number;
469
+ }
470
+ /** A strategy `perContainer` built, and the way to drop one container's state. */
471
+ type PerContainerStrategy<TPose> = LayoutStrategy<TPose> & {
472
+ /** Drop what was built for `containerId`: for a container that is gone, or
473
+ * whose cached state no longer describes it. */
474
+ forget(containerId: string): void;
475
+ };
476
+ /**
477
+ * A layout strategy that keeps state, as one strategy value: `factory` runs
478
+ * once for each container the layout is asked about, and what it returns
479
+ * serves that container from then on, closing over whatever it caches — a
480
+ * spatial index, the last arrangement, a solver's warm start.
481
+ *
482
+ * Declare the result on as many containers as you like. A method the built
483
+ * strategy leaves out behaves as its absence would.
484
+ */
485
+ declare function perContainer<TPose>(factory: (container: LayoutContainer) => LayoutStrategy<TPose>, opts: PerContainerOptions<TPose>): PerContainerStrategy<TPose>;
486
+
463
487
  /** Options for `freeform`. */
464
488
  interface FreeformOptions<TPose> {
465
489
  snap?: LayoutSnap<TPose>;
@@ -472,12 +496,6 @@ interface FreeformOptions<TPose> {
472
496
  */
473
497
  declare function freeform<TPose>(opts?: FreeformOptions<TPose>): LayoutStrategy<TPose>;
474
498
 
475
- type Rect = {
476
- x: number;
477
- y: number;
478
- width: number;
479
- height: number;
480
- };
481
499
  /**
482
500
  * What a `tileGrid` does with a child past its `cols × rows` cells.
483
501
  *
@@ -508,10 +526,8 @@ interface TileGridOptions<TPose> {
508
526
  snap?: LayoutSnap<TPose>;
509
527
  /** Optional: map a cell rect + the dragged pose to the new pose. Default
510
528
  * writes `{ x, y, width, height }` into the dragged pose (assumes
511
- * `RectPose`). Override for point-only poses or other shapes. The
512
- * `dragged` argument is the pre-drop pose of the child being placed —
513
- * use it to preserve fields the cell rect doesn't carry (e.g. rotation,
514
- * domain metadata). */
529
+ * `RectPose`). `dragged` is the pre-drop pose of the child being placed —
530
+ * use it to preserve fields the cell rect doesn't carry (e.g. rotation). */
515
531
  cellToPose?(cellRect: Rect, dragged: TPose): TPose;
516
532
  /** Optional: the point that decides which cell a child occupies — a child
517
533
  * occupies the cell containing it. Default reads `x`/`y` plus half of
@@ -550,4 +566,194 @@ interface SnapPointOptions<TPose> {
550
566
  * child to land on. */
551
567
  declare function snapPoint<TPose extends Vec2>(opts: SnapPointOptions<TPose>): LayoutStrategy<TPose>;
552
568
 
553
- export { type AlignAnchor, type AlignMatchResult, type AlignMoveArgs, type AlignmentBehaviorBase, type CellHighlightLayerOpts, DEFAULT_GUIDE_TOLERANCE_PX, type DeriveAlignmentGuidesOptions, type FreeformOptions, type GridLayerOpts, Guide, type GuideSnapOptions, type GuidesLayerOpts, MOVE_ANCHORS, type SnapPattern, type SnapPointOptions, type SpacingEdge, SpacingGap, type SpacingMatchResult, type TileGridOptions, type TileGridOverflow, type UseGridCellHoverOptions, type UseGridCellHoverReturn, type UseGuidesReturn, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, cellAt, containedThenNearest, createCellHighlightLayer, createGridLayer, createGuidesLayer, deriveAlignmentGuides, freeform, gridSnapStrategy, guideSnapStrategy, matchAlignment, matchSpacing, measureGaps, nearest, nearestWithin, none, pointToGridCell, roundToCell, snapPoint, tileGrid, useGridCellHover, useGuides };
569
+ /** One place a child's center goes. */
570
+ interface Slot {
571
+ x: number;
572
+ y: number;
573
+ /** The direction the slot faces, in radians, where the arrangement has one
574
+ * — outward from the center on a ring. `slotToPose` decides what to do
575
+ * with it; the default ignores it. */
576
+ angle?: number;
577
+ }
578
+ /**
579
+ * Where a container's slots are when it holds `count` children, in fill
580
+ * order. Returning fewer than `count` caps the container: `slots` refuses
581
+ * whatever would not fit.
582
+ */
583
+ type Arrangement = (bounds: ContainerBounds, count: number) => Slot[];
584
+ /** Options for `rowSlots`. */
585
+ interface RowSlotsOptions {
586
+ /** The axis the row runs along. Default `'x'`. */
587
+ axis?: 'x' | 'y';
588
+ /** Distance between neighboring slots, in world units, the row centered in
589
+ * the container. Absent, the slots divide the container's length evenly. */
590
+ pitch?: number;
591
+ }
592
+ /** Slots in a line, centered across the container. */
593
+ declare function rowSlots(opts?: RowSlotsOptions): Arrangement;
594
+ /** Options for `gridSlots`. */
595
+ interface GridSlotsOptions {
596
+ cols: number;
597
+ /** Rows the grid has before any child needs one. Default 1. */
598
+ rows?: number;
599
+ }
600
+ /** Slots at the centers of a grid that fills row by row, adding a row when the
601
+ * last fills; the rows always divide the container's height. */
602
+ declare function gridSlots(opts: GridSlotsOptions): Arrangement;
603
+ /** Options for `ringSlots`. */
604
+ interface RingSlotsOptions {
605
+ /** Angle of the first slot, in radians, 0 pointing along +x and positive
606
+ * turning toward +y. Default −π/2: the top, on a y-down canvas. */
607
+ start?: number;
608
+ /** How far round the slots run. Default a full turn, which spaces them
609
+ * evenly; anything less puts a slot on each end of the arc. */
610
+ sweep?: number;
611
+ /** Distance to pull the ring in from the container's edges. Default 0. */
612
+ inset?: number;
613
+ }
614
+ /** Slots around the ellipse inscribed in the container, each facing outward. */
615
+ declare function ringSlots(opts?: RingSlotsOptions): Arrangement;
616
+
617
+ /** Options for `slots`. */
618
+ interface SlotsOptions<TPose> {
619
+ /** Where the slots are for a given child count: `rowSlots`, `gridSlots`,
620
+ * `ringSlots`, or a function of your own. */
621
+ arrangement: Arrangement;
622
+ /** Which slot a drag means. Default `nearest()`. */
623
+ snap?: LayoutSnap<TPose>;
624
+ /** The pose a child takes in a slot. Default centers the child's own
625
+ * `width`/`height` on the slot and keeps every other field. */
626
+ slotToPose?(slot: Slot, pose: TPose): TPose;
627
+ /** The point that decides which slot a child sits in. Default is the
628
+ * center of a `RectPose`, and the point itself for a point pose. */
629
+ centerOf?(pose: TPose): {
630
+ x: number;
631
+ y: number;
632
+ };
633
+ }
634
+ /**
635
+ * Layout strategy that keeps its children in a sequence of slots — a row, a
636
+ * grid, a ring — whose positions depend on how many children there are, so
637
+ * the arrangement re-spaces itself as children come and go.
638
+ *
639
+ * A child's place in the sequence is the slot it sits nearest. Dropping a
640
+ * drag on a slot inserts it there and shifts the children between; a drag
641
+ * from outside is offered one slot more than the container holds. An insert
642
+ * or reparent lands at the slot nearest where the child was put.
643
+ */
644
+ declare function slots<TPose>(opts: SlotsOptions<TPose>): LayoutStrategy<TPose>;
645
+
646
+ /**
647
+ * What `pack` does when its shelves run past the container's height.
648
+ *
649
+ * - `'reject'` (default) — refuse the child that would not fit, by any route.
650
+ * - `'grow'` — the container's height follows its content, never below
651
+ * `minHeight`.
652
+ * - `'scroll'` — the container keeps its size and the shelves run on below
653
+ * it; `contentExtent` reports the region they cover.
654
+ */
655
+ type PackOverflow = 'reject' | 'grow' | 'scroll';
656
+ /** Options for `pack`. */
657
+ interface PackOptions<TPose> {
658
+ /** Space between neighbors on a shelf and between shelves, in world units. Default 0. */
659
+ gap?: number;
660
+ /** What happens past the container's height. Default `'reject'`. */
661
+ overflow?: PackOverflow;
662
+ /** The least height a `'grow'` container shrinks to. Default 0. */
663
+ minHeight?: number;
664
+ /** Which index a drag means. Default `nearest()`. */
665
+ snap?: LayoutSnap<TPose>;
666
+ /** A child's box. Default reads `x`, `y`, `width`, and `height` off the pose. */
667
+ rectOf?(pose: TPose): Rect;
668
+ /** `pose` with its box's top-left at `at`. Default writes `x` and `y`. */
669
+ moveTo?(pose: TPose, at: {
670
+ x: number;
671
+ y: number;
672
+ }): TPose;
673
+ }
674
+ /**
675
+ * Layout strategy that packs children of any size onto shelves: left to
676
+ * right at their own sizes, wrapping at the container's width, each shelf as
677
+ * tall as its tallest child. A child wider than the container is refused.
678
+ *
679
+ * The children's order is the order they read in — shelf by shelf, left to
680
+ * right. A drop inserts at the index nearest the pointer and everything after
681
+ * it reflows.
682
+ */
683
+ declare function pack<TPose>(opts?: PackOptions<TPose>): LayoutStrategy<TPose>;
684
+
685
+ /** Options for `quadtree`. */
686
+ interface QuadtreeOptions<TPose> {
687
+ /** How many times the container may be quartered. Default 4, a 16 × 16 grid at its finest. */
688
+ maxDepth?: number;
689
+ /** Which free cell a drag means. Default `containedThenNearest()`. */
690
+ snap?: LayoutSnap<TPose>;
691
+ /** The length a cell's shorter side must reach to hold the child. Default
692
+ * is the larger of the pose's `width` and `height`. */
693
+ sizeOf?(pose: TPose): number;
694
+ /** The pose a child takes in a cell. Default centers the child's own size
695
+ * in the cell and keeps every other field. */
696
+ cellToPose?(cell: Rect, pose: TPose): TPose;
697
+ /** The point that decides which cell a child sits in. Default is the
698
+ * center of a `RectPose`, and the point itself for a point pose. */
699
+ centerOf?(pose: TPose): {
700
+ x: number;
701
+ y: number;
702
+ };
703
+ }
704
+ /** One cell a child holds: {@link QuadtreeStrategy.cells}. */
705
+ interface QuadtreeCell {
706
+ id: string;
707
+ /** How many times the container was quartered to reach the cell. */
708
+ depth: number;
709
+ rect: Rect;
710
+ }
711
+ /** A `quadtree` layout, which also reports the cells it has given out. */
712
+ interface QuadtreeStrategy<TPose> extends LayoutStrategy<TPose> {
713
+ /** The cell each child holds, largest first: what a debug overlay draws. */
714
+ cells(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>): QuadtreeCell[];
715
+ }
716
+ /**
717
+ * Layout strategy that quarters its container as far as each child needs: a
718
+ * child holds the smallest cell that fits it, so large and small children
719
+ * share one container without a fixed grid. A cell is free while it, the
720
+ * cells it sits inside, and the cells inside it hold nobody.
721
+ *
722
+ * A drag is offered the free cells of its own size. Children never move to
723
+ * make room: a child that finds its cell covered goes to the nearest free
724
+ * one, and an arrival with no free cell of its size is refused.
725
+ */
726
+ declare function quadtree<TPose>(opts?: QuadtreeOptions<TPose>): QuadtreeStrategy<TPose>;
727
+
728
+ /** What makes a sequence layout a row of slots or a shelf-packed flow. */
729
+ interface Sequencer<TPose> {
730
+ /** `children` in sequence, read off the poses they were given when the
731
+ * container was last arranged for `count` children. */
732
+ order(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>, count: number): LayoutChild<TPose>[];
733
+ /** A pose for every child of `sequence`, or `null` when the container
734
+ * cannot hold them all. */
735
+ place(container: LayoutContainer, sequence: ReadonlyArray<LayoutChild<TPose>>): Map<string, TPose> | null;
736
+ /** The bounds the container takes to hold `sequence`, for one that sizes
737
+ * itself to its children. */
738
+ resize?(container: LayoutContainer, sequence: ReadonlyArray<LayoutChild<TPose>>): ContainerBounds | undefined;
739
+ centerOf(pose: TPose): {
740
+ x: number;
741
+ y: number;
742
+ };
743
+ snap: LayoutSnap<TPose>;
744
+ /** Labels for the two kinds of op a drop writes. */
745
+ labels: {
746
+ drop: string;
747
+ shift: string;
748
+ };
749
+ }
750
+ /**
751
+ * The behavior every layout that keeps its children in one ordered sequence
752
+ * shares: a drop inserts at an index and shifts the children after it, an
753
+ * arrival goes in at the index that lands it nearest where it was put, and a
754
+ * departure closes the gap. The `Sequencer` says what the order is and where
755
+ * a sequence sits.
756
+ */
757
+ declare function sequenceLayout<TPose>(seq: Sequencer<TPose>): LayoutStrategy<TPose>;
758
+
759
+ export { type AlignAnchor, type AlignMatchResult, type AlignMoveArgs, type AlignmentBehaviorBase, type Arrangement, type CellHighlightLayerOpts, DEFAULT_GUIDE_TOLERANCE_PX, type DeriveAlignmentGuidesOptions, type FreeformOptions, type GridLayerOpts, type GridSlotsOptions, Guide, type GuideSnapOptions, type GuidesLayerOpts, MOVE_ANCHORS, type PackOptions, type PackOverflow, type PerContainerOptions, type PerContainerStrategy, type QuadtreeCell, type QuadtreeOptions, type QuadtreeStrategy, type RingSlotsOptions, type RowSlotsOptions, type Sequencer, type Slot, type SlotsOptions, type SnapPattern, type SnapPointOptions, type SpacingEdge, SpacingGap, type SpacingMatchResult, type TileGridOptions, type TileGridOverflow, type UseGridCellHoverOptions, type UseGridCellHoverReturn, type UseGuidesReturn, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, cellAt, containedThenNearest, createCellHighlightLayer, createGridLayer, createGuidesLayer, deriveAlignmentGuides, freeform, gridSlots, gridSnapStrategy, guideSnapStrategy, matchAlignment, matchSpacing, measureGaps, nearest, nearestWithin, none, pack, perContainer, pointToGridCell, quadtree, ringSlots, roundToCell, rowSlots, sequenceLayout, slots, snapPoint, tileGrid, useGridCellHover, useGuides };