@weasel-js/guides 1.9.4 → 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
 
@@ -41,5 +41,5 @@ function guideSnapStrategy(getGuides, options = {}) {
41
41
  }
42
42
 
43
43
  export { DEFAULT_GUIDE_TOLERANCE_PX, guideOffsetOn, guideSnapStrategy, nearestGuideDelta, roundToGridOn };
44
- //# sourceMappingURL=chunk-NIEZTECL.js.map
45
- //# sourceMappingURL=chunk-NIEZTECL.js.map
44
+ //# sourceMappingURL=chunk-LOFNN7EQ.js.map
45
+ //# sourceMappingURL=chunk-LOFNN7EQ.js.map
@@ -1 +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;AA0CnC,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-NIEZTECL.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 *\n * **Composition:** combining with `gridSnapStrategy` is left to the\n * consumer in v1 — typically by picking one based on a modifier key, or\n * by stacking two move/resize/insert behaviors in priority order.\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
+ {"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,31 +1,31 @@
1
- import { roundToGridOn } from './chunk-NIEZTECL.js';
2
- import { AUTO_ORIGIN_PROJECTION, resolveUnit } from '@weasel-js/core';
1
+ import { roundToGridOn } from './chunk-LOFNN7EQ.js';
2
+ import { AUTO_ORIGIN_PROJECTION, resolveAxes } from '@weasel-js/core';
3
3
 
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 sx = roundToGridOn(o.x, "x", c, ctx?.plane);
15
- const sy = roundToGridOn(o.y, "y", c, ctx?.plane);
14
+ const sx = roundToGridOn(o.x, "x", c.x, ctx?.plane);
15
+ const sy = roundToGridOn(o.y, "y", c.y, ctx?.plane);
16
16
  debug?.recordSnapCandidate({ x: sx, y: sy }, true);
17
17
  return proj.translate(pose, sx - o.x, sy - o.y);
18
18
  }
19
19
  };
20
20
  }
21
21
  function pointToGridCell(point, spacing, unitSystem, origin = { x: 0, y: 0 }) {
22
- const s = resolveUnit(spacing, unitSystem);
22
+ const s = resolveAxes(spacing, unitSystem);
23
23
  return {
24
- col: Math.floor((point.x - origin.x) / s),
25
- 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)
26
26
  };
27
27
  }
28
28
 
29
29
  export { gridSnapStrategy, pointToGridCell };
30
- //# sourceMappingURL=chunk-JETW4ZA3.js.map
31
- //# sourceMappingURL=chunk-JETW4ZA3.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, CommandStroke, RenderLayer, CommandFill, SnapStrategy, DebugSink, OriginProjection, ModifierState, Bounds, PlaneMap, 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,7 +90,7 @@ 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
95
  line?: CommandStroke;
101
96
  accent?: CommandStroke;
@@ -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. */
125
+ /** Fill for the highlighted cell. Defaults to a soft green. */
131
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
  }): {
@@ -344,9 +329,8 @@ declare const MOVE_ANCHORS: {
344
329
  * from the aligned siblings to the box being dragged.
345
330
  *
346
331
  * `worldTolerance` is per axis because a screen-pixel tolerance is not one
347
- * world distance under non-uniform zoom — and each axis here is matched by a
348
- * distance along that axis alone, so there is an exact answer rather than an
349
- * 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.
350
334
  */
351
335
  declare function matchAlignment(bounds: Bounds, candidates: readonly Guide[], worldTolerance: {
352
336
  x: number;
@@ -356,14 +340,13 @@ declare function matchAlignment(bounds: Bounds, candidates: readonly Guide[], wo
356
340
  y: readonly AlignAnchor[];
357
341
  }): AlignMatchResult;
358
342
 
359
- type Axis = 'x' | 'y';
360
343
  /**
361
344
  * The visible gaps between neighboring boxes along `axis`: for `'x'`, each box
362
345
  * and the nearest box to its right that shares some of its vertical range, as
363
346
  * long as no third box stands in the strip between them. These are the gaps an
364
347
  * equal-spacing snap can reproduce.
365
348
  */
366
- declare function measureGaps(targets: readonly Bounds[], axis: Axis): SpacingGap[];
349
+ declare function measureGaps(targets: readonly Bounds[], axis: 'x' | 'y'): SpacingGap[];
367
350
  /**
368
351
  * Match a box against the gaps between `targets`, Figma's "equal spacing":
369
352
  * on each axis, find where the box's gap to a neighbor equals a gap that
@@ -432,10 +415,7 @@ interface GuideSnapOptions<TPose> {
432
415
  * same axis, the closest wins.
433
416
  *
434
417
  * Pair with `useGuides` for storage and `createGuidesLayer` for rendering.
435
- *
436
- * **Composition:** combining with `gridSnapStrategy` is left to the
437
- * consumer in v1 — typically by picking one based on a modifier key, or
438
- * by stacking two move/resize/insert behaviors in priority order.
418
+ * Combining with `gridSnapStrategy` is left to the consumer.
439
419
  */
440
420
  declare function guideSnapStrategy<TPose extends {
441
421
  x: number;
@@ -476,6 +456,34 @@ declare function containedThenNearest<TPose>(): LayoutSnap<TPose>;
476
456
  */
477
457
  declare function cellAt<TPose>(): LayoutSnap<TPose>;
478
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
+
479
487
  /** Options for `freeform`. */
480
488
  interface FreeformOptions<TPose> {
481
489
  snap?: LayoutSnap<TPose>;
@@ -488,12 +496,6 @@ interface FreeformOptions<TPose> {
488
496
  */
489
497
  declare function freeform<TPose>(opts?: FreeformOptions<TPose>): LayoutStrategy<TPose>;
490
498
 
491
- type Rect = {
492
- x: number;
493
- y: number;
494
- width: number;
495
- height: number;
496
- };
497
499
  /**
498
500
  * What a `tileGrid` does with a child past its `cols × rows` cells.
499
501
  *
@@ -524,10 +526,8 @@ interface TileGridOptions<TPose> {
524
526
  snap?: LayoutSnap<TPose>;
525
527
  /** Optional: map a cell rect + the dragged pose to the new pose. Default
526
528
  * writes `{ x, y, width, height }` into the dragged pose (assumes
527
- * `RectPose`). Override for point-only poses or other shapes. The
528
- * `dragged` argument is the pre-drop pose of the child being placed —
529
- * use it to preserve fields the cell rect doesn't carry (e.g. rotation,
530
- * 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). */
531
531
  cellToPose?(cellRect: Rect, dragged: TPose): TPose;
532
532
  /** Optional: the point that decides which cell a child occupies — a child
533
533
  * occupies the cell containing it. Default reads `x`/`y` plus half of
@@ -566,4 +566,194 @@ interface SnapPointOptions<TPose> {
566
566
  * child to land on. */
567
567
  declare function snapPoint<TPose extends Vec2>(opts: SnapPointOptions<TPose>): LayoutStrategy<TPose>;
568
568
 
569
- 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 };