@weasel-js/guides 1.7.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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/chunk-5BCCIJY3.js +33 -0
- package/dist/chunk-5BCCIJY3.js.map +1 -0
- package/dist/chunk-B25KFI3I.js +18 -0
- package/dist/chunk-B25KFI3I.js.map +1 -0
- package/dist/chunk-GEDAPTOM.js +48 -0
- package/dist/chunk-GEDAPTOM.js.map +1 -0
- package/dist/index.d.ts +463 -0
- package/dist/index.js +756 -0
- package/dist/index.js.map +1 -0
- package/dist/insert.d.ts +30 -0
- package/dist/insert.js +77 -0
- package/dist/insert.js.map +1 -0
- package/dist/move.d.ts +42 -0
- package/dist/move.js +18 -0
- package/dist/move.js.map +1 -0
- package/dist/pointSnapToGrid-BaVEjV23.d.ts +13 -0
- package/dist/resize.d.ts +38 -0
- package/dist/resize.js +111 -0
- package/dist/resize.js.map +1 -0
- package/dist/types-D2tTKEU0.d.ts +18 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 orochi235
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @weasel-js/guides
|
|
2
|
+
|
|
3
|
+
Placement aids for weasel: a visible grid and snapping to it, guide lines and
|
|
4
|
+
alignment to sibling edges, and the layout strategies a container arranges its
|
|
5
|
+
children with.
|
|
6
|
+
|
|
7
|
+
Everything here plugs into a seam `@weasel-js/core` already has. A snap
|
|
8
|
+
strategy is a `SnapStrategy`, the select tool's `snap` option takes one, and
|
|
9
|
+
core's `snap()` turns one into a move behavior. The move, resize and insert
|
|
10
|
+
behaviors go in the actions' `behaviors` lists. The grid and guide overlays are
|
|
11
|
+
`RenderLayer`s for a canvas layer slot, and a layout strategy implements core's
|
|
12
|
+
`LayoutStrategy`.
|
|
13
|
+
|
|
14
|
+
Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
|
|
15
|
+
scene-graph canvas kit for React. See the
|
|
16
|
+
[API reference](https://orochi235.github.io/weasel/api/).
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install @weasel-js/guides
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
A grid you can see and snap to:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { SceneCanvas, type RectPose } from '@weasel-js/core';
|
|
30
|
+
import { createGridLayer, gridSnapStrategy } from '@weasel-js/guides';
|
|
31
|
+
|
|
32
|
+
const grid = createGridLayer({ spacing: 20, bounds: () => ({ x: 0, y: 0, width: 800, height: 600 }) });
|
|
33
|
+
|
|
34
|
+
<SceneCanvas
|
|
35
|
+
/* … */
|
|
36
|
+
selectTool={{ snap: gridSnapStrategy<RectPose>(20) }}
|
|
37
|
+
layers={{ grid: { layer: grid } }}
|
|
38
|
+
/>;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`snapToGrid` and `snapToGuides` exist for move, resize and insert, and each
|
|
42
|
+
has the shape its action expects, so they are imported from a per-action
|
|
43
|
+
subpath:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { snapToGrid } from '@weasel-js/guides/move';
|
|
47
|
+
import { snapToGrid, pointSnapToGrid } from '@weasel-js/guides/resize';
|
|
48
|
+
import { snapToGrid } from '@weasel-js/guides/insert';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Area | Exports |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Grid | `createGridLayer`, `createCellHighlightLayer`, `useGridCellHover`, `gridSnapStrategy`, `pointToGridCell`, `pointSnapToGrid`, `roundToCell` |
|
|
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` |
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AUTO_ORIGIN_PROJECTION, resolveUnit, fromPlane, toPlane } from '@weasel-js/core';
|
|
2
|
+
|
|
3
|
+
// src/strategies/grid.ts
|
|
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);
|
|
6
|
+
const optsArg = isOpts ? arg : null;
|
|
7
|
+
const unitSystem = optsArg ? optsArg.unitSystem : arg;
|
|
8
|
+
const proj = optsArg && optsArg.origin ? optsArg.origin : AUTO_ORIGIN_PROJECTION;
|
|
9
|
+
const debug = optsArg ? optsArg.debug : void 0;
|
|
10
|
+
const c = resolveUnit(spacing, unitSystem);
|
|
11
|
+
return {
|
|
12
|
+
snap(pose, ctx) {
|
|
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;
|
|
18
|
+
debug?.recordSnapCandidate({ x: sx, y: sy }, true);
|
|
19
|
+
return proj.translate(pose, sx - o.x, sy - o.y);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function pointToGridCell(point, spacing, unitSystem, origin = { x: 0, y: 0 }) {
|
|
24
|
+
const s = resolveUnit(spacing, unitSystem);
|
|
25
|
+
return {
|
|
26
|
+
col: Math.floor((point.x - origin.x) / s),
|
|
27
|
+
row: Math.floor((point.y - origin.y) / s)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { gridSnapStrategy, pointToGridCell };
|
|
32
|
+
//# sourceMappingURL=chunk-5BCCIJY3.js.map
|
|
33
|
+
//# sourceMappingURL=chunk-5BCCIJY3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/strategies/grid.ts"],"names":[],"mappings":";;;AAiCO,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;AAG7B,MAAA,MAAM,CAAA,GAAI,KAAK,KAAA,IAAS,IAAA;AACxB,MAAA,MAAM,CAAA,GAAI,CAAA,GAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA,GAAI,CAAA;AAChC,MAAA,MAAM,OAAO,EAAE,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,EAAE,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA,EAAG,GAAG,IAAA,CAAK,KAAA,CAAM,EAAE,CAAA,GAAI,CAAC,IAAI,CAAA,EAAE;AACtE,MAAA,MAAM,EAAE,CAAA,EAAG,EAAA,EAAI,CAAA,EAAG,EAAA,KAAO,CAAA,GAAI,OAAA,CAAQ,CAAA,EAAG,IAAI,CAAA,GAAI,IAAA;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-5BCCIJY3.js","sourcesContent":["import {\n AUTO_ORIGIN_PROJECTION,\n fromPlane,\n resolveUnit,\n toPlane,\n type DebugSink,\n type OriginProjection,\n type SnapStrategy,\n type UnitSystem,\n type UnitValue,\n} from '@weasel-js/core';\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 // The grid is the camera's: on a plane, round where the origin lies in\n // the camera's world and carry the lattice point back.\n const m = ctx?.plane ?? null;\n const w = m ? fromPlane(m, o) : o;\n const cell = { x: Math.round(w.x / c) * c, y: Math.round(w.y / c) * c };\n const { x: sx, y: sy } = m ? toPlane(m, cell) : cell;\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,18 @@
|
|
|
1
|
+
// src/behaviors/resize/pointSnapToGrid.ts
|
|
2
|
+
function pointSnapToGrid(args) {
|
|
3
|
+
const { spacing, frame = "dragged-corner", bypassKey } = args;
|
|
4
|
+
const round = (v) => Math.round(v / spacing) * spacing;
|
|
5
|
+
return {
|
|
6
|
+
id: `pointSnapToGrid:${frame}`,
|
|
7
|
+
onMove(ctx) {
|
|
8
|
+
if (bypassKey && ctx.modifiers[bypassKey]) return null;
|
|
9
|
+
const src = frame === "dragged-corner" ? ctx.draggedCorner : frame === "fixed-corner" ? ctx.fixedCorner : frame === "center" ? ctx.center : ctx.origin;
|
|
10
|
+
if (!src) return null;
|
|
11
|
+
return { frame, worldX: round(src.worldX), worldY: round(src.worldY) };
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { pointSnapToGrid };
|
|
17
|
+
//# sourceMappingURL=chunk-B25KFI3I.js.map
|
|
18
|
+
//# sourceMappingURL=chunk-B25KFI3I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/behaviors/resize/pointSnapToGrid.ts"],"names":[],"mappings":";AAaO,SAAS,gBAAsC,IAAA,EAIzB;AAC3B,EAAA,MAAM,EAAE,OAAA,EAAS,KAAA,GAAQ,gBAAA,EAAkB,WAAU,GAAI,IAAA;AACzD,EAAA,MAAM,QAAQ,CAAC,CAAA,KAAc,KAAK,KAAA,CAAM,CAAA,GAAI,OAAO,CAAA,GAAI,OAAA;AAEvD,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,mBAAmB,KAAK,CAAA,CAAA;AAAA,IAC5B,OAAO,GAAA,EAAsD;AAC3D,MAAA,IAAI,SAAA,IAAa,GAAA,CAAI,SAAA,CAAU,SAAS,GAAG,OAAO,IAAA;AAClD,MAAA,MAAM,GAAA,GACJ,KAAA,KAAU,gBAAA,GAAmB,GAAA,CAAI,aAAA,GACjC,KAAA,KAAU,cAAA,GAAiB,GAAA,CAAI,WAAA,GAC/B,KAAA,KAAU,QAAA,GAAW,GAAA,CAAI,SACzB,GAAA,CAAI,MAAA;AACN,MAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,MAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA,EAAG,MAAA,EAAQ,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA,EAAE;AAAA,IACvE;AAAA,GACF;AACF","file":"chunk-B25KFI3I.js","sourcesContent":["import type {\n ModifierState,\n PointSnapBehavior,\n PointSnapContext,\n PointSnapFrame,\n PointSnapResult,\n Bounds,\n} from '@weasel-js/core';\n\n/** Resize behavior that snaps one reference point of the resize — the dragged\n * corner, the fixed corner, or the center, per `frame` — to a grid, moving\n * the whole pose by the same delta. Unlike the bounds-level `snapToGrid`,\n * this preserves the node's dimensions. */\nexport function pointSnapToGrid<TPose extends Bounds>(args: {\n spacing: number;\n frame?: PointSnapFrame;\n bypassKey?: keyof ModifierState;\n}): PointSnapBehavior<TPose> {\n const { spacing, frame = 'dragged-corner', bypassKey } = args;\n const round = (v: number) => Math.round(v / spacing) * spacing;\n\n return {\n id: `pointSnapToGrid:${frame}`,\n onMove(ctx: PointSnapContext<TPose>): PointSnapResult | null {\n if (bypassKey && ctx.modifiers[bypassKey]) return null;\n const src =\n frame === 'dragged-corner' ? ctx.draggedCorner :\n frame === 'fixed-corner' ? ctx.fixedCorner :\n frame === 'center' ? ctx.center :\n ctx.origin;\n if (!src) return null;\n return { frame, worldX: round(src.worldX), worldY: round(src.worldY) };\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AUTO_ORIGIN_PROJECTION, screenTolerance } from '@weasel-js/core';
|
|
2
|
+
|
|
3
|
+
// src/strategies/guides.ts
|
|
4
|
+
var DEFAULT_GUIDE_TOLERANCE_PX = 6;
|
|
5
|
+
function guideSnapStrategy(getGuides, options = {}) {
|
|
6
|
+
const tolerance = options.tolerance ?? DEFAULT_GUIDE_TOLERANCE_PX;
|
|
7
|
+
const proj = options.origin ?? AUTO_ORIGIN_PROJECTION;
|
|
8
|
+
return {
|
|
9
|
+
snap(pose, ctx) {
|
|
10
|
+
const guides = getGuides();
|
|
11
|
+
if (guides.length === 0) return null;
|
|
12
|
+
const tol = screenTolerance(tolerance, ctx);
|
|
13
|
+
const o = proj.getOrigin(pose);
|
|
14
|
+
let bestDx = 0;
|
|
15
|
+
let bestAbsDx = Infinity;
|
|
16
|
+
let bestDy = 0;
|
|
17
|
+
let bestAbsDy = Infinity;
|
|
18
|
+
const m = ctx.plane ?? null;
|
|
19
|
+
const at = (g) => m === null ? g.offset : g.axis === "x" ? g.offset * m.scale.x + m.offset.x : g.offset * m.scale.y + m.offset.y;
|
|
20
|
+
for (const g of guides) {
|
|
21
|
+
if (g.axis === "x") {
|
|
22
|
+
const d = at(g) - o.x;
|
|
23
|
+
const ad = Math.abs(d);
|
|
24
|
+
if (ad <= tol.x && ad < bestAbsDx) {
|
|
25
|
+
bestAbsDx = ad;
|
|
26
|
+
bestDx = d;
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
const d = at(g) - o.y;
|
|
30
|
+
const ad = Math.abs(d);
|
|
31
|
+
if (ad <= tol.y && ad < bestAbsDy) {
|
|
32
|
+
bestAbsDy = ad;
|
|
33
|
+
bestDy = d;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (bestAbsDx === Infinity && bestAbsDy === Infinity) return null;
|
|
38
|
+
const dx = bestAbsDx === Infinity ? 0 : bestDx;
|
|
39
|
+
const dy = bestAbsDy === Infinity ? 0 : bestDy;
|
|
40
|
+
if (dx === 0 && dy === 0) return null;
|
|
41
|
+
return proj.translate(pose, dx, dy);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { DEFAULT_GUIDE_TOLERANCE_PX, guideSnapStrategy };
|
|
47
|
+
//# sourceMappingURL=chunk-GEDAPTOM.js.map
|
|
48
|
+
//# sourceMappingURL=chunk-GEDAPTOM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/strategies/guides.ts"],"names":[],"mappings":";;;AASO,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;AAE7B,MAAA,IAAI,MAAA,GAAS,CAAA;AACb,MAAA,IAAI,SAAA,GAAY,QAAA;AAChB,MAAA,IAAI,MAAA,GAAS,CAAA;AACb,MAAA,IAAI,SAAA,GAAY,QAAA;AAIhB,MAAA,MAAM,CAAA,GAAI,IAAI,KAAA,IAAS,IAAA;AACvB,MAAA,MAAM,EAAA,GAAK,CAAC,CAAA,KAAc,CAAA,KAAM,IAAA,GAAO,EAAE,MAAA,GACrC,CAAA,CAAE,IAAA,KAAS,GAAA,GAAM,CAAA,CAAE,MAAA,GAAS,EAAE,KAAA,CAAM,CAAA,GAAI,CAAA,CAAE,MAAA,CAAO,CAAA,GACjD,CAAA,CAAE,SAAS,CAAA,CAAE,KAAA,CAAM,CAAA,GAAI,CAAA,CAAE,MAAA,CAAO,CAAA;AAEpC,MAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACtB,QAAA,IAAI,CAAA,CAAE,SAAS,GAAA,EAAK;AAClB,UAAA,MAAM,CAAA,GAAI,EAAA,CAAG,CAAC,CAAA,GAAI,CAAA,CAAE,CAAA;AACpB,UAAA,MAAM,EAAA,GAAK,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA;AACrB,UAAA,IAAI,EAAA,IAAM,GAAA,CAAI,CAAA,IAAK,EAAA,GAAK,SAAA,EAAW;AACjC,YAAA,SAAA,GAAY,EAAA;AACZ,YAAA,MAAA,GAAS,CAAA;AAAA,UACX;AAAA,QACF,CAAA,MAAO;AACL,UAAA,MAAM,CAAA,GAAI,EAAA,CAAG,CAAC,CAAA,GAAI,CAAA,CAAE,CAAA;AACpB,UAAA,MAAM,EAAA,GAAK,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA;AACrB,UAAA,IAAI,EAAA,IAAM,GAAA,CAAI,CAAA,IAAK,EAAA,GAAK,SAAA,EAAW;AACjC,YAAA,SAAA,GAAY,EAAA;AACZ,YAAA,MAAA,GAAS,CAAA;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAEA,MAAA,IAAI,SAAA,KAAc,QAAA,IAAY,SAAA,KAAc,QAAA,EAAU,OAAO,IAAA;AAC7D,MAAA,MAAM,EAAA,GAAK,SAAA,KAAc,QAAA,GAAW,CAAA,GAAI,MAAA;AACxC,MAAA,MAAM,EAAA,GAAK,SAAA,KAAc,QAAA,GAAW,CAAA,GAAI,MAAA;AACxC,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-GEDAPTOM.js","sourcesContent":["import {\n type SnapStrategy,\n screenTolerance,\n type OriginProjection,\n AUTO_ORIGIN_PROJECTION,\n} from '@weasel-js/core';\nimport type { Guide } from '../guides/types';\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\n let bestDx = 0;\n let bestAbsDx = Infinity;\n let bestDy = 0;\n let bestAbsDy = Infinity;\n\n // Guides are drawn by the camera; on a plane each is carried into the\n // pose's world, which on one axis is a scale and an offset.\n const m = ctx.plane ?? null;\n const at = (g: Guide) => (m === null ? g.offset\n : g.axis === 'x' ? g.offset * m.scale.x + m.offset.x\n : g.offset * m.scale.y + m.offset.y);\n\n for (const g of guides) {\n if (g.axis === 'x') {\n const d = at(g) - o.x;\n const ad = Math.abs(d);\n if (ad <= tol.x && ad < bestAbsDx) {\n bestAbsDx = ad;\n bestDx = d;\n }\n } else {\n const d = at(g) - o.y;\n const ad = Math.abs(d);\n if (ad <= tol.y && ad < bestAbsDy) {\n bestAbsDy = ad;\n bestDy = d;\n }\n }\n }\n\n if (bestAbsDx === Infinity && bestAbsDy === Infinity) return null;\n const dx = bestAbsDx === Infinity ? 0 : bestDx;\n const dy = bestAbsDy === Infinity ? 0 : bestDy;\n if (dx === 0 && dy === 0) return null;\n return proj.translate(pose, dx, dy);\n },\n };\n}\n"]}
|