@weasel-js/guides 1.9.2 → 1.9.4
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/{chunk-5BCCIJY3.js → chunk-JETW4ZA3.js} +6 -8
- package/dist/chunk-JETW4ZA3.js.map +1 -0
- package/dist/chunk-NIEZTECL.js +45 -0
- package/dist/chunk-NIEZTECL.js.map +1 -0
- package/dist/index.d.ts +28 -12
- package/dist/index.js +65 -24
- package/dist/index.js.map +1 -1
- package/dist/insert.js +12 -31
- package/dist/insert.js.map +1 -1
- package/dist/move.js +2 -2
- package/dist/resize.js +10 -32
- package/dist/resize.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-5BCCIJY3.js.map +0 -1
- package/dist/chunk-GEDAPTOM.js +0 -48
- package/dist/chunk-GEDAPTOM.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { roundToGridOn } from './chunk-NIEZTECL.js';
|
|
2
|
+
import { AUTO_ORIGIN_PROJECTION, resolveUnit } from '@weasel-js/core';
|
|
2
3
|
|
|
3
|
-
// src/strategies/grid.ts
|
|
4
4
|
function gridSnapStrategy(spacing, arg) {
|
|
5
5
|
const isOpts = typeof arg === "object" && arg !== null && ("origin" in arg || "debug" in arg || "unitSystem" in arg) && !("base" in arg);
|
|
6
6
|
const optsArg = isOpts ? arg : null;
|
|
@@ -11,10 +11,8 @@ function gridSnapStrategy(spacing, arg) {
|
|
|
11
11
|
return {
|
|
12
12
|
snap(pose, ctx) {
|
|
13
13
|
const o = proj.getOrigin(pose);
|
|
14
|
-
const
|
|
15
|
-
const
|
|
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, ctx?.plane);
|
|
15
|
+
const sy = roundToGridOn(o.y, "y", c, 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
|
}
|
|
@@ -29,5 +27,5 @@ function pointToGridCell(point, spacing, unitSystem, origin = { x: 0, y: 0 }) {
|
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
export { gridSnapStrategy, pointToGridCell };
|
|
32
|
-
//# sourceMappingURL=chunk-
|
|
33
|
-
//# sourceMappingURL=chunk-
|
|
30
|
+
//# sourceMappingURL=chunk-JETW4ZA3.js.map
|
|
31
|
+
//# sourceMappingURL=chunk-JETW4ZA3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/strategies/grid.ts"],"names":[],"mappings":";;;AAgCO,SAAS,gBAAA,CACd,SACA,GAAA,EACqB;AACrB,EAAA,MAAM,MAAA,GACJ,OAAO,GAAA,KAAQ,QAAA,IACf,GAAA,KAAQ,IAAA,KACP,QAAA,IAAY,GAAA,IAAO,OAAA,IAAW,GAAA,IAAO,YAAA,IAAgB,GAAA,CAAA,IACtD,EAAE,MAAA,IAAU,GAAA,CAAA;AACd,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,KAAK,aAAA,CAAc,CAAA,CAAE,GAAG,GAAA,EAAK,CAAA,EAAG,KAAK,KAAK,CAAA;AAChD,MAAA,MAAM,KAAK,aAAA,CAAc,CAAA,CAAE,GAAG,GAAA,EAAK,CAAA,EAAG,KAAK,KAAK,CAAA;AAChD,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;AAmBO,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,KAAK,IAAA,CAAK,KAAA,CAAA,CAAO,MAAM,CAAA,GAAI,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IACxC,KAAK,IAAA,CAAK,KAAA,CAAA,CAAO,MAAM,CAAA,GAAI,MAAA,CAAO,KAAK,CAAC;AAAA,GAC1C;AACF","file":"chunk-JETW4ZA3.js","sourcesContent":["import {\n AUTO_ORIGIN_PROJECTION,\n resolveUnit,\n type DebugSink,\n type OriginProjection,\n type SnapStrategy,\n type UnitSystem,\n type UnitValue,\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`). For non-rect TPose pass an\n * `OriginProjection` so the strategy knows how to read/write the origin. */\nexport function gridSnapStrategy<TPose>(\n spacing: UnitValue,\n unitSystem?: UnitSystem,\n): SnapStrategy<TPose>;\n/** As above, additionally reporting each candidate to a debug sink. */\nexport function gridSnapStrategy<TPose>(\n spacing: UnitValue,\n opts: { unitSystem?: UnitSystem; 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: UnitValue,\n opts: { unitSystem?: UnitSystem; 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: UnitValue,\n arg?: UnitSystem | { unitSystem?: UnitSystem; 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 const optsArg = isOpts\n ? (arg as { unitSystem?: UnitSystem; origin?: OriginProjection<TPose>; debug?: DebugSink })\n : null;\n const unitSystem = optsArg ? optsArg.unitSystem : (arg as UnitSystem | 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 = resolveUnit(spacing, unitSystem);\n return {\n snap(pose, ctx) {\n const o = proj.getOrigin(pose);\n const sx = roundToGridOn(o.x, 'x', c, ctx?.plane);\n const sy = roundToGridOn(o.y, 'y', c, 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 * Compute the integer cell `{col, row}` that contains `point`, given a grid\n * `spacing` and optional `origin` and `unitSystem`. Pair with\n * `createCellHighlightLayer` (its `getCell` callback) to draw a snap-target\n * preview that uses the same spacing as `gridSnapStrategy` — so the visual\n * and behavioral grids stay in lockstep:\n *\n * const SPACING = 20;\n * const snap = gridSnapStrategy(SPACING);\n * // visual grid:\n * createGridLayer({ spacing: SPACING, bounds: () => ... });\n * // hover-preview overlay:\n * createCellHighlightLayer({\n * spacing: SPACING,\n * getCell: () => hoverPoint && pointToGridCell(hoverPoint, SPACING),\n * });\n */\nexport function pointToGridCell(\n point: { x: number; y: number },\n spacing: UnitValue,\n unitSystem?: UnitSystem,\n origin: { x: number; y: number } = { x: 0, y: 0 },\n): { col: number; row: number } {\n const s = resolveUnit(spacing, unitSystem);\n return {\n col: Math.floor((point.x - origin.x) / s),\n row: Math.floor((point.y - origin.y) / s),\n };\n}\n"]}
|
|
@@ -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-NIEZTECL.js.map
|
|
45
|
+
//# sourceMappingURL=chunk-NIEZTECL.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;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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RefObject } from 'react';
|
|
2
|
-
import { ViewTransform, UnitValue, UnitSystem,
|
|
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';
|
|
3
3
|
export { p as pointSnapToGrid } from './pointSnapToGrid-BaVEjV23.js';
|
|
4
4
|
import { G as Guide, S as SpacingGap } from './types-DRSPiXfk.js';
|
|
5
5
|
|
|
@@ -97,9 +97,9 @@ interface GridLayerOpts {
|
|
|
97
97
|
subdivisions?: number;
|
|
98
98
|
/** Per-band stroke styles. Each is a FillStyle+width (etc.) — see `Stroke`. */
|
|
99
99
|
style?: {
|
|
100
|
-
line?:
|
|
101
|
-
accent?:
|
|
102
|
-
sub?:
|
|
100
|
+
line?: CommandStroke;
|
|
101
|
+
accent?: CommandStroke;
|
|
102
|
+
sub?: CommandStroke;
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
/** Build a `RenderLayer` that draws a world-space grid with optional accent lines and subdivisions. */
|
|
@@ -128,7 +128,7 @@ interface CellHighlightLayerOpts {
|
|
|
128
128
|
row: number;
|
|
129
129
|
} | null;
|
|
130
130
|
/** FillStyle strategy for the filled cell. Defaults to a soft green. */
|
|
131
|
-
fill?:
|
|
131
|
+
fill?: CommandFill;
|
|
132
132
|
}
|
|
133
133
|
/** Build a `RenderLayer` that fills a single grid cell — typically a snap-target preview. */
|
|
134
134
|
declare function createCellHighlightLayer(opts: CellHighlightLayerOpts): RenderLayer<unknown>;
|
|
@@ -275,8 +275,14 @@ interface SpacingMatchResult {
|
|
|
275
275
|
/** Which candidate lines to derive from a set of poses — edges, centers, or
|
|
276
276
|
* both, and whether the page box contributes its own. */
|
|
277
277
|
interface DeriveAlignmentGuidesOptions<TPose = Bounds> {
|
|
278
|
-
/** Include the document/page box's edges + center as candidates.
|
|
278
|
+
/** Include the document/page box's edges + center as candidates. It is in
|
|
279
|
+
* the camera's world, as every line derived is. */
|
|
279
280
|
page?: Bounds;
|
|
281
|
+
/** The parallax plane a target's pose is stored in, for a sibling on a
|
|
282
|
+
* `parallax` layer (`view.planeOf(layer)`). Its box is carried out to the
|
|
283
|
+
* camera's world, which is the world alignment candidates are in. Omit it,
|
|
284
|
+
* or answer null, for a target already there. */
|
|
285
|
+
planeOf?: (target: TPose, index: number) => PlaneMap | null | undefined;
|
|
280
286
|
/** Emit left/right (x) and top/bottom (y) edge guides. Default true. */
|
|
281
287
|
edges?: boolean;
|
|
282
288
|
/** Emit centerX (x) and centerY (y) guides. Default true. */
|
|
@@ -285,9 +291,17 @@ interface DeriveAlignmentGuidesOptions<TPose = Bounds> {
|
|
|
285
291
|
* gets, or the two sides disagree. Default `AUTO_POSE_DESCRIPTOR`. */
|
|
286
292
|
poseDescriptor?: PoseDescriptor<TPose>;
|
|
287
293
|
}
|
|
288
|
-
/** Common options shared by the three alignment behavior factories.
|
|
294
|
+
/** Common options shared by the three alignment behavior factories.
|
|
295
|
+
*
|
|
296
|
+
* Everything crossing this interface is in the camera's world, whatever
|
|
297
|
+
* layer the gesture edits: the candidate lines and spacing boxes handed in,
|
|
298
|
+
* and the lines and gaps published, which a layer the camera draws shows.
|
|
299
|
+
* A gesture on a `parallax` layer carries them into its plane to match. */
|
|
289
300
|
interface AlignmentBehaviorBase {
|
|
290
|
-
/** Live candidate lines — consumer derives from current siblings + page.
|
|
301
|
+
/** Live candidate lines — consumer derives from current siblings + page.
|
|
302
|
+
* A sibling on a parallax layer goes through `deriveAlignmentGuides`'
|
|
303
|
+
* `planeOf`, or its lines land where its plane's numbers say, not where
|
|
304
|
+
* it is drawn. */
|
|
291
305
|
getCandidates: () => readonly Guide[];
|
|
292
306
|
/** Publish the currently-matched line(s). Called every onMove; cleared
|
|
293
307
|
* (`[]`) on a miss and on onEnd. */
|
|
@@ -311,7 +325,9 @@ interface AlignmentBehaviorBase {
|
|
|
311
325
|
* edges and the center. Overlapping offsets collapse to one candidate, whose
|
|
312
326
|
* `span` covers every box that produced it.
|
|
313
327
|
* Poses go through the same descriptor `alignMoveBehavior` matches with, so
|
|
314
|
-
* a rotated sibling advertises its ink edges rather than its stored box.
|
|
328
|
+
* a rotated sibling advertises its ink edges rather than its stored box.
|
|
329
|
+
* The lines are in the camera's world: a sibling on a parallax layer needs
|
|
330
|
+
* `planeOf` to say which plane its pose is in. */
|
|
315
331
|
declare function deriveAlignmentGuides<TPose = Bounds>(targets: readonly TPose[], opts?: DeriveAlignmentGuidesOptions<TPose>): Guide[];
|
|
316
332
|
|
|
317
333
|
/** Move/insert test all three features per axis. */
|
|
@@ -378,10 +394,10 @@ interface AlignMoveArgs<TPose> extends AlignmentBehaviorBase {
|
|
|
378
394
|
* transform uniformly to every dragged id, so the selection shifts together
|
|
379
395
|
* and stays rigid. Single-select is the degenerate one-box union. Publishes
|
|
380
396
|
* the matched line(s); clears on miss, end and cancel. */
|
|
381
|
-
declare function alignMoveBehavior<TPose>(
|
|
397
|
+
declare function alignMoveBehavior<TPose>(given: AlignMoveArgs<TPose>): MoveBehavior<TPose>;
|
|
382
398
|
/** Insert behavior: snap the live `current` point to candidates (treating it
|
|
383
399
|
* as a zero-size box). Publishes the matched line(s). */
|
|
384
|
-
declare function alignInsertBehavior<TPose>(
|
|
400
|
+
declare function alignInsertBehavior<TPose>(given: AlignmentBehaviorBase): InsertBehavior<TPose>;
|
|
385
401
|
/** Resize constraint: snap the corner(s) the drag moves, as drawn, to
|
|
386
402
|
* candidates; the pinned corner stays fixed. On an unrotated pose that is
|
|
387
403
|
* the moving edge(s). On a rotated one the node's own axes set how the
|
|
@@ -389,7 +405,7 @@ declare function alignInsertBehavior<TPose>(args: AlignmentBehaviorBase): Insert
|
|
|
389
405
|
* slides its edge until the nearer of its two corners meets a line.
|
|
390
406
|
* `poseDescriptor` reads the origin pose's box and rotation, and must match
|
|
391
407
|
* the resize action's. Publishes the matched line(s). */
|
|
392
|
-
declare function alignResizeBehavior<TPose extends Bounds>(
|
|
408
|
+
declare function alignResizeBehavior<TPose extends Bounds>(given: AlignMoveArgs<TPose>): BoundsConstraint<TPose>;
|
|
393
409
|
|
|
394
410
|
/** Default snap tolerance (screen pixels). */
|
|
395
411
|
declare const DEFAULT_GUIDE_TOLERANCE_PX = 6;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { pointToGridCell } from './chunk-
|
|
2
|
-
export { gridSnapStrategy, pointToGridCell } from './chunk-
|
|
1
|
+
import { pointToGridCell } from './chunk-JETW4ZA3.js';
|
|
2
|
+
export { gridSnapStrategy, pointToGridCell } from './chunk-JETW4ZA3.js';
|
|
3
3
|
export { pointSnapToGrid } from './chunk-B25KFI3I.js';
|
|
4
|
-
|
|
4
|
+
import { guideOffsetOn } from './chunk-NIEZTECL.js';
|
|
5
|
+
export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy } from './chunk-NIEZTECL.js';
|
|
5
6
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
6
|
-
import { useLatest, resolveUnit, pxExtent, textCommandFromRuns, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, translatePoseViaDescriptor, unionBounds, fixedCornerOf, createTransformOp, resolveStrokeWidth, screenToWorld, screenTolerance } from '@weasel-js/core';
|
|
7
|
+
import { useLatest, resolveUnit, pxExtent, textCommandFromRuns, AUTO_POSE_DESCRIPTOR, visualBoundsViaDescriptor, rectFromPlane, translatePoseViaDescriptor, unionBounds, fixedCornerOf, createTransformOp, resolveStrokeWidth, screenToWorld, rectToPlane, screenTolerance } from '@weasel-js/core';
|
|
7
8
|
import { rectPath, PATH_M, PATH_L } from '@weasel-js/geom';
|
|
8
9
|
|
|
9
10
|
// src/grid/roundToCell.ts
|
|
@@ -88,11 +89,11 @@ function createGridLayer(opts) {
|
|
|
88
89
|
coords: new Float32Array([sx, sy, ex, ey]),
|
|
89
90
|
fillRule: "nonzero"
|
|
90
91
|
};
|
|
91
|
-
const
|
|
92
|
+
const across2 = sx === ex ? px.x : px.y;
|
|
92
93
|
children.push({
|
|
93
94
|
kind: "path",
|
|
94
95
|
path,
|
|
95
|
-
stroke: { ...s, width: resolveStrokeWidth(s.width ?? 1, 1) *
|
|
96
|
+
stroke: { ...s, width: resolveStrokeWidth(s.width ?? 1, 1) * across2 }
|
|
96
97
|
});
|
|
97
98
|
};
|
|
98
99
|
if (subdivisions && subdivisions > 1) {
|
|
@@ -291,7 +292,11 @@ function deriveAlignmentGuides(targets, opts = {}) {
|
|
|
291
292
|
add("y", b.y + b.height / 2, b);
|
|
292
293
|
}
|
|
293
294
|
};
|
|
294
|
-
|
|
295
|
+
targets.forEach((t, i) => {
|
|
296
|
+
const box = visualBoundsViaDescriptor(t, d);
|
|
297
|
+
const plane = opts.planeOf?.(t, i);
|
|
298
|
+
emit(plane ? rectFromPlane(plane, box) : box);
|
|
299
|
+
});
|
|
295
300
|
if (opts.page) emit(opts.page);
|
|
296
301
|
return [...seenX.values(), ...seenY.values()];
|
|
297
302
|
}
|
|
@@ -460,6 +465,39 @@ function applyEdges(b, dx, dy, moving) {
|
|
|
460
465
|
} else if (moving.y === "max") height += dy;
|
|
461
466
|
return { x, y, width, height };
|
|
462
467
|
}
|
|
468
|
+
var across = (axis) => axis === "x" ? "y" : "x";
|
|
469
|
+
var into = (m, axis, v) => v * m.scale[axis] + m.offset[axis];
|
|
470
|
+
var outOf = (m, axis, v) => (v - m.offset[axis]) / m.scale[axis];
|
|
471
|
+
function range(r, map) {
|
|
472
|
+
const a = map(r.min);
|
|
473
|
+
const b = map(r.max);
|
|
474
|
+
return { ...r, min: Math.min(a, b), max: Math.max(a, b) };
|
|
475
|
+
}
|
|
476
|
+
function guideToPlane(g, m) {
|
|
477
|
+
const span = g.span && range(g.span, (v) => into(m, across(g.axis), v));
|
|
478
|
+
return { ...g, offset: guideOffsetOn(g, m), ...span ? { span } : {} };
|
|
479
|
+
}
|
|
480
|
+
function guideFromPlane(g, m) {
|
|
481
|
+
const span = g.span && range(g.span, (v) => outOf(m, across(g.axis), v));
|
|
482
|
+
return { ...g, offset: outOf(m, g.axis, g.offset), ...span ? { span } : {} };
|
|
483
|
+
}
|
|
484
|
+
function gapFromPlane(g, m) {
|
|
485
|
+
return { ...range(g, (v) => outOf(m, g.axis, v)), at: outOf(m, across(g.axis), g.at) };
|
|
486
|
+
}
|
|
487
|
+
function alignmentOnPlane(args, plane) {
|
|
488
|
+
if (!plane) return args;
|
|
489
|
+
const spacing = args.getSpacingTargets;
|
|
490
|
+
const setGaps = args.setActiveGaps;
|
|
491
|
+
return {
|
|
492
|
+
...args,
|
|
493
|
+
getCandidates: () => args.getCandidates().map((g) => guideToPlane(g, plane)),
|
|
494
|
+
setActiveGuides: (guides) => args.setActiveGuides(guides.map((g) => guideFromPlane(g, plane))),
|
|
495
|
+
...spacing ? { getSpacingTargets: () => spacing().map((b) => rectToPlane(plane, b)) } : {},
|
|
496
|
+
...setGaps ? { setActiveGaps: (gaps) => setGaps(gaps.map((g) => gapFromPlane(g, plane))) } : {}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/guides/alignment/behaviors.ts
|
|
463
501
|
var activeList = (m) => [m.activeX, m.activeY].filter((g) => g !== null);
|
|
464
502
|
var EXACT = { x: 1e-6, y: 1e-6 };
|
|
465
503
|
function resolve(b, args, tol, anchors, edges) {
|
|
@@ -491,10 +529,11 @@ function publish(args, r) {
|
|
|
491
529
|
function worldTol(base, ctx) {
|
|
492
530
|
return screenTolerance(base.tolerance ?? 6, ctx);
|
|
493
531
|
}
|
|
494
|
-
function alignMoveBehavior(
|
|
495
|
-
const d =
|
|
532
|
+
function alignMoveBehavior(given) {
|
|
533
|
+
const d = given.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
|
|
496
534
|
return {
|
|
497
535
|
onMove(ctx, transform) {
|
|
536
|
+
const args = alignmentOnPlane(given, ctx.plane);
|
|
498
537
|
if (args.bypassKey && ctx.modifiers[args.bypassKey]) {
|
|
499
538
|
publish(args, null);
|
|
500
539
|
return;
|
|
@@ -517,17 +556,18 @@ function alignMoveBehavior(args) {
|
|
|
517
556
|
return { transform: { kind: "translate", dx: transform.dx + r.dx, dy: transform.dy + r.dy } };
|
|
518
557
|
},
|
|
519
558
|
onEnd() {
|
|
520
|
-
publish(
|
|
559
|
+
publish(given, null);
|
|
521
560
|
},
|
|
522
561
|
onCancel() {
|
|
523
|
-
publish(
|
|
562
|
+
publish(given, null);
|
|
524
563
|
}
|
|
525
564
|
};
|
|
526
565
|
}
|
|
527
|
-
function alignInsertBehavior(
|
|
566
|
+
function alignInsertBehavior(given) {
|
|
528
567
|
const pointAnchors = { x: ["min"], y: ["min"] };
|
|
529
568
|
return {
|
|
530
569
|
onMove(ctx, { current }) {
|
|
570
|
+
const args = alignmentOnPlane(given, ctx.plane);
|
|
531
571
|
if (args.bypassKey && ctx.modifiers[args.bypassKey]) {
|
|
532
572
|
args.setActiveGuides([]);
|
|
533
573
|
return;
|
|
@@ -542,17 +582,18 @@ function alignInsertBehavior(args) {
|
|
|
542
582
|
return { current: { x: current.x + m.dx, y: current.y + m.dy } };
|
|
543
583
|
},
|
|
544
584
|
onEnd() {
|
|
545
|
-
|
|
585
|
+
given.setActiveGuides([]);
|
|
546
586
|
},
|
|
547
587
|
onCancel() {
|
|
548
|
-
|
|
588
|
+
given.setActiveGuides([]);
|
|
549
589
|
}
|
|
550
590
|
};
|
|
551
591
|
}
|
|
552
|
-
function alignResizeBehavior(
|
|
553
|
-
const d =
|
|
592
|
+
function alignResizeBehavior(given) {
|
|
593
|
+
const d = given.poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
|
|
554
594
|
return {
|
|
555
595
|
onMove(ctx, { pose, anchor }) {
|
|
596
|
+
const args = alignmentOnPlane(given, ctx.plane);
|
|
556
597
|
if (args.bypassKey && ctx.modifiers[args.bypassKey]) {
|
|
557
598
|
publish(args, null);
|
|
558
599
|
return;
|
|
@@ -579,10 +620,10 @@ function alignResizeBehavior(args) {
|
|
|
579
620
|
return { pose: { ...pose, x, y, width, height } };
|
|
580
621
|
},
|
|
581
622
|
onEnd() {
|
|
582
|
-
publish(
|
|
623
|
+
publish(given, null);
|
|
583
624
|
},
|
|
584
625
|
onCancel() {
|
|
585
|
-
publish(
|
|
626
|
+
publish(given, null);
|
|
586
627
|
}
|
|
587
628
|
};
|
|
588
629
|
}
|
|
@@ -627,8 +668,8 @@ function matchRotatedCorners(pose, anchor, originBox, rotation, candidates, tol)
|
|
|
627
668
|
const dir = along === "x" ? toWorld(1, 0) : toWorld(0, 1);
|
|
628
669
|
const edge = movingEdge(along)[0];
|
|
629
670
|
let best = null;
|
|
630
|
-
for (const
|
|
631
|
-
const c = along === "x" ? drawn(edge,
|
|
671
|
+
for (const across3 of movingEdge(along === "x" ? "y" : "x")) {
|
|
672
|
+
const c = along === "x" ? drawn(edge, across3) : drawn(across3, edge);
|
|
632
673
|
for (const g of candidates) {
|
|
633
674
|
const reach = g.axis === "x" ? dir.x : dir.y;
|
|
634
675
|
if (Math.abs(reach) < 1e-9) continue;
|
|
@@ -638,8 +679,8 @@ function matchRotatedCorners(pose, anchor, originBox, rotation, candidates, tol)
|
|
|
638
679
|
}
|
|
639
680
|
}
|
|
640
681
|
if (best === null) return null;
|
|
641
|
-
const
|
|
642
|
-
const guides = [stretchSpan(best.guide,
|
|
682
|
+
const across2 = best.guide.axis === "x" ? best.at.y + best.t * dir.y : best.at.x + best.t * dir.x;
|
|
683
|
+
const guides = [stretchSpan(best.guide, across2, across2)];
|
|
643
684
|
return along === "x" ? { lx: best.t, ly: 0, guides, gaps: [] } : { lx: 0, ly: best.t, guides, gaps: [] };
|
|
644
685
|
}
|
|
645
686
|
|
|
@@ -798,7 +839,7 @@ function tileGrid(opts) {
|
|
|
798
839
|
if (fromIdx === null || fromIdx === idx) return null;
|
|
799
840
|
return { occupant: occupant.id, from: occupant.pose, newPose: cellToPose(grid.rectAt(fromIdx), occupant.pose) };
|
|
800
841
|
}
|
|
801
|
-
function pack(from,
|
|
842
|
+
function pack(from, into2, children) {
|
|
802
843
|
const ordered = children.map((c) => ({ c, cell: from.indexOf(c.pose) })).sort((a, b) => {
|
|
803
844
|
if (a.cell !== b.cell) {
|
|
804
845
|
if (a.cell === null) return 1;
|
|
@@ -809,7 +850,7 @@ function tileGrid(opts) {
|
|
|
809
850
|
});
|
|
810
851
|
const out = /* @__PURE__ */ new Map();
|
|
811
852
|
for (let i = 0; i < ordered.length; i++) {
|
|
812
|
-
out.set(ordered[i].c.id, cellToPose(
|
|
853
|
+
out.set(ordered[i].c.id, cellToPose(into2.rectAt(i), ordered[i].c.pose));
|
|
813
854
|
}
|
|
814
855
|
return out;
|
|
815
856
|
}
|