@weasel-js/core 0.7.2 → 0.8.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/CHANGELOG.md +128 -0
- package/dist/{DrawCommand-CJtqqt8H.d.ts → DrawCommand-BKJ5JPkC.d.ts} +24 -1
- package/dist/{chunk-SYM6RAM4.js → chunk-6DAUIMTO.js} +3 -2
- package/dist/chunk-6DAUIMTO.js.map +1 -0
- package/dist/{chunk-VOVKONXA.js → chunk-BPRHGTLQ.js} +3 -3
- package/dist/{chunk-VOVKONXA.js.map → chunk-BPRHGTLQ.js.map} +1 -1
- package/dist/{chunk-BGGZ4CVF.js → chunk-CMBE45MT.js} +3 -3
- package/dist/{chunk-BGGZ4CVF.js.map → chunk-CMBE45MT.js.map} +1 -1
- package/dist/{chunk-5F3PIERN.js → chunk-DLV5F4NY.js} +520 -120
- package/dist/chunk-DLV5F4NY.js.map +1 -0
- package/dist/{chunk-CQNKCG34.js → chunk-GLZLSPGJ.js} +3 -3
- package/dist/chunk-GLZLSPGJ.js.map +1 -0
- package/dist/clipboard.d.ts +2 -2
- package/dist/clone.d.ts +1 -1
- package/dist/{geometry-D9BDMiQi.d.ts → geometry-DVeZ2i-c.d.ts} +1 -1
- package/dist/{grid-CaSK9bHV.d.ts → grid-XcFQVS0f.d.ts} +1 -1
- package/dist/{index-DOYRTfP0.d.ts → index-Clf_koyR.d.ts} +178 -6
- package/dist/index.d.ts +135 -31
- package/dist/index.js +5 -5
- package/dist/insert.d.ts +2 -2
- package/dist/insert.js +1 -1
- package/dist/move.d.ts +3 -3
- package/dist/move.js +2 -2
- package/dist/{options-DMWeTELe.d.ts → options-wAvCnOZd.d.ts} +1 -1
- package/dist/{pointSnapToGrid-C3EruUwt.d.ts → pointSnapToGrid-BuDZWBrL.d.ts} +2 -2
- package/dist/renderer.d.ts +5 -5
- package/dist/renderer.js +5 -5
- package/dist/resize.d.ts +3 -3
- package/dist/resize.js +2 -2
- package/dist/routing.d.ts +6 -6
- package/dist/routing.js +1 -1
- package/dist/{types-Dcaa0tPq.d.ts → types-BhJJ2OCM.d.ts} +9 -1
- package/dist/{types-Cpb4hii1.d.ts → types-DUpI7Apc.d.ts} +3 -95
- package/dist/{viewToMat3-D4lrBigW.d.ts → viewToMat3-DFMdZvVl.d.ts} +1 -1
- package/package.json +6 -6
- package/dist/chunk-5F3PIERN.js.map +0 -1
- package/dist/chunk-CQNKCG34.js.map +0 -1
- package/dist/chunk-SYM6RAM4.js.map +0 -1
- /package/dist/{types-B6MMiodD.d.ts → path-B6MMiodD.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,133 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bdcdfe5: Clipboard keyboard actions, and two bugs the wiring flushed out.
|
|
8
|
+
|
|
9
|
+
`clipboard.copy` (Cmd/Ctrl+C) and `clipboard.cut` (Cmd/Ctrl+X) are kit-standard
|
|
10
|
+
descriptors. Publish the imperative surface `useClipboardOps` returns as the new
|
|
11
|
+
`clipboard` dep and both work; the buttons a consumer already has and the
|
|
12
|
+
shortcut then route through one implementation instead of two. Cut is copy plus
|
|
13
|
+
the same batched delete `deleteAction` performs — one undo entry.
|
|
14
|
+
|
|
15
|
+
There is deliberately no `clipboard.paste`. Cmd/Ctrl+V already arrives as a DOM
|
|
16
|
+
`paste` event, which the dispatcher routes to `ingest` and the content-handler
|
|
17
|
+
registry — the path that reaches the OS payload. A key binding would fire
|
|
18
|
+
alongside it and paste twice.
|
|
19
|
+
|
|
20
|
+
Two older bugs, both found by actually pressing the keys:
|
|
21
|
+
|
|
22
|
+
- **Deleting a container together with its children threw mid-batch.**
|
|
23
|
+
`removeNode` cascades the subtree, so a group's op already takes its members
|
|
24
|
+
with it and the members' own ops then hit `unknown node id`. Selecting a group
|
|
25
|
+
and its contents at once — what Cmd+A does — was enough. `deleteAction` and
|
|
26
|
+
`clipboard.cut` now share one op builder that skips any id with a selected
|
|
27
|
+
ancestor.
|
|
28
|
+
- **Cmd+D did nothing at all.** Two faults stacked. `duplicate` threw before
|
|
29
|
+
its `enabled` gate could answer, because the gate reads `deps.selection` and
|
|
30
|
+
the descriptor never declared it — an undeclared read the dev-build deps
|
|
31
|
+
Proxy treats as an error. Declared now, with a sweep test over every
|
|
32
|
+
`requiresSelection`-gated descriptor so the next one can't ship the same way.
|
|
33
|
+
And underneath that, the invoker was a stub whose body was `void params`,
|
|
34
|
+
deferring to a "legacy bridge" that no longer exists. `duplicateAction` now
|
|
35
|
+
really duplicates: each selected node with its whole subtree (so a duplicated
|
|
36
|
+
group comes out populated, not empty), offset by the same 12 units a paste
|
|
37
|
+
gets, as one undoable batch, with the copies selected afterward. Descendants
|
|
38
|
+
are offset only for absolute-pose scenes — with a `poseComposition`
|
|
39
|
+
registered, poses are relative and moving the root already carries them.
|
|
40
|
+
|
|
41
|
+
`polygonHitTestRect` moved from `features/paths/` to `core/geometry/`: it is
|
|
42
|
+
pure geometry, `core/adapters/arrayAdapter.ts` needs it, and core may not import
|
|
43
|
+
from features. Same exports from the package barrel.
|
|
44
|
+
|
|
45
|
+
- e0ab60e: Device profile: the kit stops assuming a mouse.
|
|
46
|
+
|
|
47
|
+
`DeviceProfile` is one object holding pointer coarseness, hover capability and
|
|
48
|
+
pixel density, resolved once per `<SceneCanvas>` and published to its subtree.
|
|
49
|
+
Two things read it. The chrome-caps rule layer gains `coarsePointer:` and
|
|
50
|
+
`canHover:` selectors (plus matching fluent atoms), so consumers can gate
|
|
51
|
+
chrome on the device. And every handle size and hit radius — six independent
|
|
52
|
+
literal `8`s and one `24` before this — now derives from one base module times
|
|
53
|
+
`DeviceProfile.targetScale`, so a coarse pointer gets 14px handles and a 42px
|
|
54
|
+
rotation distance without paint and hit-test ever drifting apart. The public
|
|
55
|
+
`DEFAULT_HANDLE_SIZE` / `DEFAULT_ROTATION_HANDLE_DISTANCE` constants keep
|
|
56
|
+
their unscaled values.
|
|
57
|
+
|
|
58
|
+
`longPress` is a real gesture kind: spec, event, matcher, and route grammar.
|
|
59
|
+
The dispatcher synthesizes it for touch and pen presses held 500ms without
|
|
60
|
+
crossing the drag threshold — never for a mouse, and cancelled by movement,
|
|
61
|
+
release, cancel, or a second finger landing. An unmatched long-press
|
|
62
|
+
re-dispatches as `contextmenu`, so existing `contextMenu` bindings become
|
|
63
|
+
reachable by touch with no consumer change.
|
|
64
|
+
|
|
65
|
+
Also fixes a density bug: `useCanvasSize` read `devicePixelRatio` only inside
|
|
66
|
+
its `ResizeObserver` callback, so moving a window to a different-density
|
|
67
|
+
display without resizing it left the snapshot stale. Density now comes from
|
|
68
|
+
the profile, which watches a re-armed resolution media query.
|
|
69
|
+
|
|
70
|
+
Override any of it with the new `<SceneCanvas device={{ coarsePointer: true }}>`
|
|
71
|
+
prop — for tests, for demos that want touch-sized chrome on a desktop, and for
|
|
72
|
+
hybrid devices where the media query guesses wrong.
|
|
73
|
+
|
|
74
|
+
- 3d693c7: Underline and strikethrough shortcuts, and the core boundary goes strict.
|
|
75
|
+
|
|
76
|
+
Cmd/Ctrl+U toggles underline and Cmd/Ctrl+Shift+X toggles strikethrough, both
|
|
77
|
+
through `toggleFlagInRange` like bold and italic — so they toggle _off_, a mixed
|
|
78
|
+
range turns fully on, and a collapsed caret gets a pending style that styles the
|
|
79
|
+
next character only. `rangeStyle.ts` had listed both flags all along; only
|
|
80
|
+
`useTextEdit`'s `StyleFlag` and its keydown switch were narrower.
|
|
81
|
+
|
|
82
|
+
Underline in particular had to be intercepted rather than merely supported.
|
|
83
|
+
Left alone, the browser ran its own `formatUnderline` and `domToRuns`' `<u>`
|
|
84
|
+
flattening made that look like it had worked while bypassing the run algebra
|
|
85
|
+
entirely. The flattening stays — it's what lets pasted decoration survive — but
|
|
86
|
+
it was never the mechanism. Bare Cmd+X is deliberately left to the browser so
|
|
87
|
+
cutting text mid-edit still works; only Cmd+Shift+X is claimed.
|
|
88
|
+
|
|
89
|
+
The `core/` ← `features/`/`interactions/` lint rule no longer exempts type
|
|
90
|
+
imports. Three types core named across the boundary moved down to where core
|
|
91
|
+
can own them — `Path` to `core/geometry/path.ts`, `RectPose` to
|
|
92
|
+
`core/scene/types.ts` (whose doc comment already claimed it lived in core), and
|
|
93
|
+
`ModifierState` to `core/modifierState.ts` — each with a re-export left at its
|
|
94
|
+
old address, so no importer changes.
|
|
95
|
+
|
|
96
|
+
- e264d62: Stroked text.
|
|
97
|
+
|
|
98
|
+
`TextStyle.stroke` and `StyledRun.stroke` carry a real `Stroke`, and the
|
|
99
|
+
outline tier paints it as a second batched draw call over the group's merged
|
|
100
|
+
geometry — so a glyph above `textOutlineMinScreenSize` gets real joins, caps
|
|
101
|
+
and miters in any paint, because by then it is an ordinary `PolygonPath`.
|
|
102
|
+
Width stays a world measure: it crosses into the cached em-space tessellation
|
|
103
|
+
by dividing by the glyph's scale, so it does not grow with `fontSize`. Below
|
|
104
|
+
the threshold a glyph is a sampled distance field with no geometry to stroke,
|
|
105
|
+
and renders unstroked rather than approximated.
|
|
106
|
+
|
|
107
|
+
`kit:text` also reads the kit-native `data.stroke` / `data.strokeWidth` leaf
|
|
108
|
+
fields that `kit:shape` already honors, so one pair of stroke controls means
|
|
109
|
+
the same thing on a text node as on a rect.
|
|
110
|
+
|
|
111
|
+
`@weasel-js/svg` round-trips all of it — node-level and per-`<tspan>` — where
|
|
112
|
+
it previously parsed a text stroke into a warning and dropped it.
|
|
113
|
+
|
|
114
|
+
Two older bugs fell out of building it, both invisible to fills and both
|
|
115
|
+
fixed: `extractPolylines` kept a closed contour's duplicate final point
|
|
116
|
+
(zero-length closing segment, dropped wrap-around join), and glyph path data
|
|
117
|
+
whose contours carried no `Z` stroked as open polylines — a missing closing
|
|
118
|
+
edge with a cap at each loose end. A fill closes a contour implicitly; only a
|
|
119
|
+
stroke reads the difference.
|
|
120
|
+
|
|
121
|
+
### Patch Changes
|
|
122
|
+
|
|
123
|
+
- Updated dependencies [e0ab60e]
|
|
124
|
+
- Updated dependencies [e264d62]
|
|
125
|
+
- @weasel-js/gestures@0.8.0
|
|
126
|
+
- @weasel-js/font@0.8.0
|
|
127
|
+
- @weasel-js/geom@0.8.0
|
|
128
|
+
- @weasel-js/history@0.8.0
|
|
129
|
+
- @weasel-js/modes@0.8.0
|
|
130
|
+
|
|
3
131
|
## 0.7.2
|
|
4
132
|
|
|
5
133
|
### Patch Changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as Path } from './
|
|
1
|
+
import { P as Path } from './path-B6MMiodD.js';
|
|
2
2
|
import { T as TextureHandle } from './registerTexture-BzHTLhD9.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -248,6 +248,19 @@ interface TextStyle {
|
|
|
248
248
|
underline?: boolean;
|
|
249
249
|
/** Default `false`. */
|
|
250
250
|
strikethrough?: boolean;
|
|
251
|
+
/**
|
|
252
|
+
* Outline painted over the glyph fill. Omitted (the default) means no
|
|
253
|
+
* outline — there is no such thing as a default text stroke.
|
|
254
|
+
*
|
|
255
|
+
* Only glyphs on the outline tier are stroked: above
|
|
256
|
+
* `textOutlineMinScreenSize` a glyph is a real `PolygonPath`, so it gets
|
|
257
|
+
* the ordinary tessellated ribbon with real joins, caps and miters, in any
|
|
258
|
+
* paint. Below it a glyph is a sampled distance field with no geometry to
|
|
259
|
+
* stroke, and it renders unstroked rather than approximated. `width` is in
|
|
260
|
+
* world units, like every other stroke in the kit — it does not scale with
|
|
261
|
+
* `fontSize`.
|
|
262
|
+
*/
|
|
263
|
+
stroke?: Stroke;
|
|
251
264
|
}
|
|
252
265
|
/** `TextStyle` with all fields filled in from defaults — what the renderer actually consumes. */
|
|
253
266
|
interface ResolvedTextStyle {
|
|
@@ -264,6 +277,9 @@ interface ResolvedTextStyle {
|
|
|
264
277
|
letterSpacing: number;
|
|
265
278
|
underline: boolean;
|
|
266
279
|
strikethrough: boolean;
|
|
280
|
+
/** Absent means no outline — unlike the other fields, this one has no
|
|
281
|
+
* default to fall back to. See {@link TextStyle.stroke}. */
|
|
282
|
+
stroke?: Stroke;
|
|
267
283
|
}
|
|
268
284
|
/** Default resolved style used when a `TextPose` omits `style`. */
|
|
269
285
|
declare const DEFAULT_TEXT_STYLE: ResolvedTextStyle;
|
|
@@ -290,6 +306,10 @@ interface StyledRun {
|
|
|
290
306
|
fontFamily?: string;
|
|
291
307
|
fontSize?: number;
|
|
292
308
|
fill?: FillStyle;
|
|
309
|
+
/** Outline over this run's glyphs. Overrides the node style's stroke;
|
|
310
|
+
* absent inherits it. Only painted on the outline tier — see
|
|
311
|
+
* {@link TextStyle.stroke}. */
|
|
312
|
+
stroke?: Stroke;
|
|
293
313
|
letterSpacing?: number;
|
|
294
314
|
underline?: boolean;
|
|
295
315
|
strikethrough?: boolean;
|
|
@@ -330,6 +350,9 @@ interface ResolvedRun {
|
|
|
330
350
|
fontWeight: number;
|
|
331
351
|
fontStyle: 'normal' | 'italic';
|
|
332
352
|
fill: FillStyle;
|
|
353
|
+
/** Outline over this run's glyphs, or absent for none. Painted only on the
|
|
354
|
+
* outline tier — a distance field has no geometry to stroke. */
|
|
355
|
+
stroke?: Stroke;
|
|
333
356
|
/** Extra advance added after each glyph of this run, in world units. */
|
|
334
357
|
letterSpacing: number;
|
|
335
358
|
/** Draw a rule below this run's baseline. Additive over the node style. */
|
|
@@ -81,6 +81,7 @@ var SPEC_KIND_TO_GESTURE = {
|
|
|
81
81
|
click: "click",
|
|
82
82
|
doubleClick: "dblTap",
|
|
83
83
|
contextMenu: "contextMenu",
|
|
84
|
+
longPress: "longPress",
|
|
84
85
|
drag: "drag",
|
|
85
86
|
pointerDown: "pointerDown",
|
|
86
87
|
multiTouch: void 0,
|
|
@@ -228,5 +229,5 @@ function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
export { PREDICATE_TARGET, buildRouteRegistry, defineTool, defineViewportTool, findConflicts, findScopedConflicts, formatConflict, reportRouteConflicts, routing_exports };
|
|
231
|
-
//# sourceMappingURL=chunk-
|
|
232
|
-
//# sourceMappingURL=chunk-
|
|
232
|
+
//# sourceMappingURL=chunk-6DAUIMTO.js.map
|
|
233
|
+
//# sourceMappingURL=chunk-6DAUIMTO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/routing/index.ts","../src/tools/routing/defineTool.ts","../src/tools/routing/defineViewportTool.ts","../src/tools/routing/reflection/registry.ts","../src/tools/routing/reflection/conflicts.ts"],"names":[],"mappings":"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","file":"chunk-6DAUIMTO.js","sourcesContent":["export type { ToolDef, ViewportToolDef, ToolKeybinding } from './types';\nexport { parseRoute, formatRoute, formatPhaseAtom, collapseShiftPairs, describeRoute, describeRouteParts, canonicalModifiers, ROUTE_TERMS, ROUTE_FIELD_DEFINITIONS, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from './routeGrammar';\nexport type { ParsedRoute, ParsedModifiers, ModifierKey, ModRequirement, PhaseAtom, ChannelRef, DescribeRouteOptions, RouteDescriptionPart, RouteTermLabel, RouteFieldName } from './routeGrammar';\nexport { getGestureDescriptor, isKnownGestureName, GESTURE_DESCRIPTORS } from './gestures';\nexport type { GestureName, GestureDescriptor, GestureArgSpec } from './gestures';\nexport { defineTool } from './defineTool';\nexport { defineViewportTool } from './defineViewportTool';\n\n// Reflection consumers — registry / conflict checker / debug overlay.\nexport * from './reflection';\n","// src/tools/routing/defineTool.ts\nimport type { Tool, ToolCtx } from '../types';\nimport type { ToolDef } from './types';\nimport { RESERVED_ID_NAMES, RESERVED_ID_PREFIXES } from './routeGrammar';\n\n/** Validate that `id` is usable as a tool / channel id in the route\n * grammar. Rejects ids that start with a reserved sigil (would shadow\n * future grammar extensions) and ids that collide with phase keywords\n * (would parse as the bare-phase shorthand). */\nfunction validateId(id: string, kind: 'tool' | 'action'): void {\n if (id.length === 0) {\n throw new Error(`weasel: ${kind} id may not be empty`);\n }\n if (RESERVED_ID_PREFIXES.has(id[0]!)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" starts with reserved sigil \"${id[0]}\" ` +\n `(reserved set: ${[...RESERVED_ID_PREFIXES].join(' ')})`,\n );\n }\n if (RESERVED_ID_NAMES.has(id)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" collides with a reserved phase keyword ` +\n `(reserved: ${[...RESERVED_ID_NAMES].join(', ')})`,\n );\n }\n}\n\n/**\n * Build a `Tool<TScratch>` from a declarative `ToolDef<TScratch>`.\n *\n * This used to be a translator: `ToolDef` carried `initial` / `engaged` phase\n * tables of hit-keyed route handlers, and `defineTool` compiled them into the\n * imperative `pointer` / `drag` / `keyboard` / `wheel` handlers the\n * tool-routing dispatcher called. Both ends of that are gone — tools declare\n * `bindings` and the gesture dispatcher routes them — so what remains is\n * identity plumbing plus the id check and the cursor resolver.\n *\n * It stays a function rather than becoming a spread because the id validation\n * and the `initScratch` / `cursor` defaults are worth applying uniformly, and\n * because `Tool.def` gives reflection a handle on the authored form.\n */\nexport function defineTool<TScratch = void>(\n def: ToolDef<TScratch>,\n): Tool<TScratch> {\n validateId(def.id, 'tool');\n\n // Normalize `string | ((ctx) => string)` to the function form so callers\n // have one shape to deal with. Returns '' (not undefined) so the signature\n // satisfies `Tool.cursor: (ctx) => string`.\n const resolveCursor = (ctx: ToolCtx<TScratch>): string => {\n if (def.cursor == null) return '';\n return typeof def.cursor === 'function' ? def.cursor(ctx) : def.cursor;\n };\n\n return {\n id: def.id,\n capabilities: def.capabilities,\n actions: def.actions,\n def,\n presentation: def.presentation,\n keybinding: def.keybinding,\n onActivate: def.onActivate,\n onDeactivate: def.onDeactivate,\n initScratch: def.initScratch ?? (() => null as unknown as TScratch),\n cursor: resolveCursor,\n bindings: def.bindings,\n overlay: def.overlay,\n };\n}\n","import type { Tool } from '../types';\nimport type { ViewportToolDef } from './types';\nimport { defineTool } from './defineTool';\n\n/**\n * Define a tool that acts on the viewport rather than the scene.\n *\n * This used to do real work: `ViewportPhaseDef` was a narrowed `PhaseDef`\n * (no click routes, drag restricted to the function form), and the factory\n * lifted it back to the permissive shape before handing it to `defineTool`.\n * With phase tables gone there is no shape left to narrow — a viewport tool\n * declares `bindings` like any other, pointing at `viewport.*` actions.\n *\n * It survives as an authoring signal. `defineViewportTool` at the top of a\n * hook says \"this tool moves the camera, not the drawing\", which is worth\n * more than the type gymnastics it replaced.\n */\nexport function defineViewportTool<TScratch = void>(\n def: ViewportToolDef<TScratch>,\n): Tool<TScratch> {\n return defineTool<TScratch>(def);\n}\n","import type { Tool } from '../../types';\nimport type { GestureSpec, ModSpec, TargetSpec, PhaseSpec } from '@weasel-js/gestures';\nimport type { ParsedModifiers, ModifierKey } from '../routeGrammar';\nimport { getGestureDescriptor, isKnownGestureName, type GestureName } from '../gestures';\n\n/**\n * One row in the route registry — a single `GestureBinding` on one tool,\n * flattened into the route grammar's vocabulary so the inspector, the\n * conflict checker, and `describeRoute` can all read the same shape.\n *\n * Multiple rows can share (gesture, arg, target, modifiers) if different tools\n * declare them; consumers walk the list and group client-side.\n */\nexport interface RegistryEntry {\n toolId: string;\n /** Which phase the binding's `phase` spec restricts it to. `'any'` when it\n * declares none, declares `'*'`, or declares an atom list whose atoms\n * don't agree on one phase — such a binding fires in either phase, and\n * reporting it as `'initial'` made it collide with genuinely-initial\n * bindings in `findConflicts`' bucket key. */\n phase: 'initial' | 'engaged' | 'any';\n /** Structured v3 modifier requirements. Empty object = \"no modifiers\n * held\" (the strict default). */\n modifiers: ParsedModifiers;\n gesture: GestureName;\n /** Resolved arg value for arg-bearing gestures (wheel direction,\n * key name, multiTouchTap fingers). Undefined for gestures whose\n * descriptor has no `arg`. */\n arg: string | undefined;\n /** Target class for hit-testing gestures. `undefined` when the descriptor\n * has `hasTarget: false` or the spec declares no target;\n * {@link PREDICATE_TARGET} when the spec uses a `kindOf` predicate, which\n * the route grammar has no notation for. */\n target: string | undefined;\n /** The action this binding fires. */\n actionId: string;\n /** The `GestureSpec` this row was flattened from, by reference. Reflection\n * consumers that need something the grammar doesn't capture — the\n * specificity tuple, a `kindOf` predicate identity — read it here rather\n * than re-walking `Tool.bindings`. */\n spec: GestureSpec;\n}\n\n/**\n * Stand-in target for a `{ kindOf }` predicate spec.\n *\n * The route grammar can name target *classes* (`empty`, `selected-body`,\n * `kind:rect`) but not arbitrary predicates, so three distinct select-tool\n * bindings all render as this one token. Narrowing it would mean giving the\n * grammar a way to describe a function, which is a real design question and\n * not one this reflection layer should answer by inventing syntax.\n */\nexport const PREDICATE_TARGET = 'predicate';\n\n/** `GestureSpec.kind` → route-grammar gesture name. `multiTouch` has no\n * route-grammar gesture (only its tap synthesis does), and `drop` / `paste`\n * shipped without grammar names, so specs of those kinds are skipped. */\nconst SPEC_KIND_TO_GESTURE: Record<GestureSpec['kind'], GestureName | undefined> = {\n key: 'keyDown',\n 'key-held': 'keyHeld',\n wheel: 'wheel',\n click: 'click',\n doubleClick: 'dblTap',\n contextMenu: 'contextMenu',\n longPress: 'longPress',\n drag: 'drag',\n pointerDown: 'pointerDown',\n multiTouch: undefined,\n multiTouchTap: 'multiTouchTap',\n drop: undefined,\n paste: undefined,\n};\n\n/**\n * Flatten every tool's `bindings` into route-registry rows.\n *\n * This was `buildActionRegistry`, and it walked `ToolDef.initial` /\n * `.engaged` phase tables — the grammar that no longer exists. It was also\n * blind to `Tool.bindings` the entire time the two lived side by side, which\n * is why the inspector under-reported select by more than half. The rename\n * fixes a second thing: it never had anything to do with the Actions\n * Registry, and reading `buildActionRegistry` next to `ActionsRegistry`\n * suggested otherwise.\n */\nexport function buildRouteRegistry(\n tools: readonly Tool<unknown>[],\n): RegistryEntry[] {\n const out: RegistryEntry[] = [];\n for (const tool of tools) {\n for (const binding of tool.bindings ?? []) {\n const entry = entryFor(tool.id, binding.spec, binding.actionId);\n if (entry) out.push(entry);\n }\n }\n return out;\n}\n\nfunction entryFor(\n toolId: string,\n spec: GestureSpec,\n actionId: string,\n): RegistryEntry | null {\n const gesture = SPEC_KIND_TO_GESTURE[spec.kind];\n if (!gesture || !isKnownGestureName(gesture)) return null;\n const descriptor = getGestureDescriptor(gesture);\n return {\n toolId,\n actionId,\n gesture,\n phase: phaseOf('phase' in spec ? spec.phase : undefined),\n modifiers: parseModSpec('mods' in spec ? spec.mods : undefined),\n arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : undefined,\n target: descriptor.hasTarget\n ? targetOf('target' in spec ? spec.target : undefined)\n : undefined,\n spec,\n };\n}\n\n/** Collapse a `PhaseSpec` to the single phase a binding is restricted to, or\n * `'any'` when it isn't restricted to one.\n *\n * `PhaseAtom.channel` says which channel's phase state the binding reads,\n * not which phase it fires in, so it plays no part in this collapse — only\n * the set of distinct `atom.phase` values matters. */\nfunction phaseOf(phase: PhaseSpec | undefined): 'initial' | 'engaged' | 'any' {\n if (phase === undefined || phase === '*') return 'any';\n if (phase === 'initial' || phase === 'engaged') return phase;\n const distinct = new Set(phase.map((atom) => atom.phase));\n if (distinct.size !== 1) return 'any';\n const only = [...distinct][0];\n return only === 'initial' || only === 'engaged' ? only : 'any';\n}\n\n/** `ModSpec` (per-key tri-state) → `ParsedModifiers`. `false` and absent both\n * mean \"must not be held\", which the parsed form spells as an absent key. */\nfunction parseModSpec(mods: ModSpec | undefined): ParsedModifiers {\n if (!mods) return {};\n const out: ParsedModifiers = {};\n for (const [name, req] of Object.entries(mods)) {\n if (req === true) out[name as ModifierKey] = 'required';\n else if (req === 'optional') out[name as ModifierKey] = 'optional';\n }\n return out;\n}\n\nfunction targetOf(target: TargetSpec | undefined): string | undefined {\n if (target === undefined) return undefined;\n return typeof target === 'string' ? target : PREDICATE_TARGET;\n}\n\nfunction argOf(spec: GestureSpec, fallback: string | undefined): string | undefined {\n if ('key' in spec) {\n return Array.isArray(spec.key) ? spec.key.join('|') : spec.key;\n }\n if ('fingers' in spec) return String(spec.fingers);\n if ('direction' in spec) return spec.direction ?? fallback;\n return fallback;\n}\n\n// Re-export for downstream consumers.\nexport { getGestureDescriptor };\nexport type { GestureName };\n","import type { Tool } from '../../types';\nimport type { ParsedModifiers } from '../routeGrammar';\nimport { canonicalModifiers, formatRoute } from '../routeGrammar';\nimport { buildRouteRegistry, PREDICATE_TARGET, type RegistryEntry, type GestureName } from './registry';\n\n/** Two or more tools declare the same exact (phase, gesture, arg, target,\n * modifiers) tuple — the dispatcher's slot precedence picks one\n * arbitrarily (well, deterministically by slot order, but the author\n * probably didn't intend the duplication). */\nexport interface Conflict {\n phase: 'initial' | 'engaged' | 'any';\n gesture: GestureName;\n arg: string | undefined;\n target: string | undefined;\n modifiers: ParsedModifiers;\n /** All tool ids that registered the same tuple. At least 2 by\n * construction. Order matches the input tools[] order. */\n toolIds: string[];\n}\n\n/** Detect exact-tuple overlaps across a tool registration set.\n *\n * Intentionally NOT flagged:\n * - Broad vs. narrow targets (e.g. an untargeted `click` alongside\n * `click` on `empty`) — the dispatcher's specificity ordering resolves\n * those cleanly, and the broad one is usually the intended fallback.\n * - Different modifier requirements on the same target — they fire on\n * different inputs.\n * - A binding whose action declines via `enabled()` so a lower-priority\n * one can take the gesture. Detecting that intent would mean evaluating\n * the action; consumers can suppress known-intentional compositions in\n * their UI layer.\n *\n * - Two `{ kindOf }` predicate targets. The route grammar renders every\n * predicate as the single token {@link PREDICATE_TARGET}, so bucketing on\n * the rendered target alone reported select's `resize` / `rotate` / `move`\n * drags — three genuinely different predicates — as a three-way conflict.\n * Predicate entries bucket by function identity instead, which means two\n * *separately written but equivalent* predicates go unflagged. That's the\n * right way to be wrong here: this check has to be silent when nothing is\n * wrong or nobody will keep it on.\n *\n * Note that two bindings sharing a tuple on the SAME tool are now possible\n * (bindings are an array, where phase tables were objects with unique\n * keys) — so a conflict may name one tool twice.\n *\n * This is the raw same-tuple detector. Feeding it a whole tool *registry*\n * over-reports, because registry tools take turns in the active slot and\n * can't collide with each other — see {@link findScopedConflicts}.\n */\nexport function findConflicts(\n tools: readonly Tool<unknown>[],\n): Conflict[] {\n const entries = buildRouteRegistry(tools);\n const groups = new Map<string, RegistryEntry[]>();\n for (const entry of entries) {\n const key = `${entry.phase}|${entry.gesture}|${entry.arg ?? ''}|${targetKey(entry)}|${canonicalModifiers(entry.modifiers)}`;\n const bucket = groups.get(key);\n if (bucket) bucket.push(entry);\n else groups.set(key, [entry]);\n }\n const conflicts: Conflict[] = [];\n for (const bucket of groups.values()) {\n if (bucket.length < 2) continue;\n const first = bucket[0];\n conflicts.push({\n phase: first.phase,\n gesture: first.gesture,\n arg: first.arg,\n target: first.target,\n modifiers: first.modifiers,\n toolIds: bucket.map((e) => e.toolId),\n });\n }\n return conflicts;\n}\n\n/** Bucket key for an entry's target slot. Predicate targets all render as\n * the same grammar token, so they're keyed by the predicate's identity —\n * two different functions are two different targets. */\nfunction targetKey(entry: RegistryEntry): string {\n if (entry.target !== PREDICATE_TARGET) return entry.target ?? '';\n const spec = entry.spec as { target?: unknown };\n const pred = spec.target;\n if (typeof pred !== 'object' || pred === null) return PREDICATE_TARGET;\n return `${PREDICATE_TARGET}#${predicateId(pred)}`;\n}\n\nconst PREDICATE_IDS = new WeakMap<object, number>();\nlet nextPredicateId = 0;\n\nfunction predicateId(pred: object): number {\n let id = PREDICATE_IDS.get(pred);\n if (id === undefined) {\n id = nextPredicateId++;\n PREDICATE_IDS.set(pred, id);\n }\n return id;\n}\n\n/**\n * The tool scopes as the dispatcher sees them — which is what decides whether\n * two same-tuple bindings can actually collide.\n */\nexport interface ToolScopes {\n /** Tools eligible for the active slot, keyed by id or as a flat list. */\n registry: readonly Tool<unknown>[] | Readonly<Record<string, Tool<unknown>>>;\n /** Always-on tools. Every one of these is live at once. */\n ambient?: readonly Tool<unknown>[];\n}\n\n/**\n * Detect the same-tuple overlaps that are *reachable* — the ones where the\n * dispatcher really does fall back on declaration order.\n *\n * `matchSorted` walks scopes in strict priority (hotkey > active > ambient)\n * and only sorts by specificity *within* a scope. So a cross-scope tie isn't\n * a tie at all: an ambient tool losing a tuple to the active tool is the\n * documented design, not an accident. Likewise two registry tools sharing a\n * tuple — `rect` and `ellipse` both binding a bare `drag` — can never both be\n * in the active slot, so they never compete.\n *\n * What's left, and what this reports:\n * - a single tool colliding with **itself** (possible since bindings became\n * an array), which is ambiguous in whichever slot it occupies;\n * - two **ambient** tools, all of which are live simultaneously and are\n * ordered only by registration;\n * - two **hotkey-capable** tools, which can stack.\n *\n * Not covered: the actions registry's `defaultBinding`s, which the dispatcher\n * also folds into ambient/hotkey scope. They're assembled somewhere else\n * entirely (`ActionsRegistry`, not `useTools`), so catching tool-vs-action\n * collisions means giving this function a second input it doesn't have yet.\n */\nexport function findScopedConflicts(scopes: ToolScopes): Conflict[] {\n const registry = Array.isArray(scopes.registry)\n ? (scopes.registry as readonly Tool<unknown>[])\n : Object.values(scopes.registry as Readonly<Record<string, Tool<unknown>>>);\n const ambient = scopes.ambient ?? [];\n\n const out: Conflict[] = [];\n const seen = new Set<string>();\n const add = (conflicts: readonly Conflict[]): void => {\n for (const c of conflicts) {\n const key = `${c.phase}|${c.gesture}|${c.arg ?? ''}|${c.target ?? ''}`\n + `|${canonicalModifiers(c.modifiers)}|${c.toolIds.join(',')}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(c);\n }\n };\n\n // Self-collisions: every tool, whichever slot it lands in.\n for (const tool of [...registry, ...ambient]) add(findConflicts([tool]));\n // Ambient tools are all live together.\n if (ambient.length > 1) add(findConflicts(ambient));\n // Hotkey-capable tools can stack on each other. `hotkey` is declared on the\n // authored `ToolDef`, not carried onto the runtime `Tool` — `Tool.def` is\n // the reflection handle for exactly this kind of read, and it's typed\n // `unknown` so consumers cast at the use site.\n const hotkeyTools = registry.filter(\n (t) => (t.def as { hotkey?: unknown } | undefined)?.hotkey !== undefined,\n );\n if (hotkeyTools.length > 1) add(findConflicts(hotkeyTools));\n\n return out;\n}\n\n/**\n * Render a conflict as one line of human-readable text.\n *\n * The tuple is printed through {@link formatRoute}, so the message names the\n * collision in the same grammar the author wrote the binding in — modulo the\n * phase slot, which prints as a bare `'&'`-channel atom because the bucket key\n * collapses channel-bearing phase specs (`sel:engaged` and `&:engaged` collide\n * with each other, and the collapse is what made them collide).\n */\nexport function formatConflict(conflict: Conflict): string {\n const route = formatRoute({\n phases: [{ channel: '&', phase: conflict.phase === 'any' ? '*' : conflict.phase }],\n gesture: conflict.gesture,\n arg: conflict.arg,\n target: conflict.target,\n modifiers: conflict.modifiers,\n });\n return `${route} — declared by ${conflict.toolIds.join(', ')}`;\n}\n\n/**\n * Detect reachable route conflicts in an assembled tool set and report each one.\n *\n * This is the wiring `findConflicts` spent its first life without: the kit\n * could detect the one class of genuine routing ambiguity it has and never\n * looked. Call it once wherever a tool set is assembled (`useTools` does),\n * behind a `process.env.NODE_ENV !== 'production'` guard.\n *\n * **Warn, never throw.** A conflict between a consumer's tool and a kit tool\n * is a design question — sometimes deliberate, since the loser can still take\n * the gesture by declining through `enabled()` — and exploding in a running\n * app is the wrong way to raise it. A conflict between two *kit* tools is\n * always a bug, but the place to fail on that is the kit's own test suite\n * (`canvas/SceneCanvas.routeConflicts.test.tsx`), not a consumer's console.\n *\n * @returns The conflicts found, so callers can dedupe repeat reports.\n */\nexport function reportRouteConflicts(\n scopes: ToolScopes,\n warn: (message: string) => void = (m) => console.warn(m),\n): Conflict[] {\n const conflicts = findScopedConflicts(scopes);\n for (const c of conflicts) {\n warn(\n `[weasel] route conflict: two bindings declare the same (phase, gesture, arg, target, modifiers) tuple `\n + `in the same scope, so declaration order alone decides which one fires.\\n ${formatConflict(c)}`,\n );\n }\n return conflicts;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RECT_ORIGIN_PROJECTION } from './chunk-
|
|
1
|
+
import { RECT_ORIGIN_PROJECTION } from './chunk-GLZLSPGJ.js';
|
|
2
2
|
import { registerOpFactory } from './chunk-GVCNT7UH.js';
|
|
3
3
|
|
|
4
4
|
// src/core/ops/transform.ts
|
|
@@ -99,5 +99,5 @@ function snap(strategy, opts = {}) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export { createReparentOp, createTransformOp, deleteScratch, getScratch, scratchKey, setScratch, snap };
|
|
102
|
-
//# sourceMappingURL=chunk-
|
|
103
|
-
//# sourceMappingURL=chunk-
|
|
102
|
+
//# sourceMappingURL=chunk-BPRHGTLQ.js.map
|
|
103
|
+
//# sourceMappingURL=chunk-BPRHGTLQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/ops/transform.ts","../src/core/ops/reparent.ts","../src/interactions/scratchKey.ts","../src/interactions/gestures/shared/snap.ts"],"names":[],"mappings":"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","file":"chunk-VOVKONXA.js","sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface TransformAdapter<TPose> {\n setPose(id: string, pose: TPose): void;\n}\n\n/** @internal */\ninterface TransformArgs<TPose> {\n id: string;\n from: TPose;\n to: TPose;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Op: set an object's pose, inverting back to `from`. `coalesceKey` defaults to\n * `transform:${id}` so successive pose writes to the same node (drag-move,\n * resize, repeated nudges) merge into one undo entry within the history's\n * coalesce window. Pass an explicit key (or a unique one) to opt out. */\nexport function createTransformOp<TPose>(args: TransformArgs<TPose>): Op {\n const { id, from, to, label, coalesceKey = `transform:${id}` } = args;\n return {\n name: 'transform',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPose so consumers that inspect op behavior (tests,\n // overlays) see a consistent \"this op writes (id, to)\" signal. When\n // from and to are structurally identical, additionally report a\n // no-op to history so the entry can be skipped from the undo stack.\n // setPose(id, to) is idempotent in this branch — adapters write the\n // same pose they already had.\n (adapter as TransformAdapter<TPose>).setPose(id, to);\n if (poseShallowEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createTransformOp<TPose>({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<TransformArgs<unknown>>('transform', (args) => createTransformOp(args));\n\nfunction poseShallowEqual<TPose>(a: TPose, b: TPose): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n","import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface ReparentAdapter {\n setParent(id: string, parentId: string | null): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n label?: string;\n coalesceKey?: string;\n}\n\n/**\n * Op: change `id`'s parent, inverting back to the prior parent.\n *\n * `coalesceKey` defaults to `reparent:${id}` so successive reparents of the\n * same id batch-merge cleanly.\n */\nexport function createReparentOp(args: ReparentArgs): Op {\n const { id, fromParentId, toParentId, label = 'Reparent', coalesceKey = `reparent:${id}` } = args;\n return {\n name: 'reparent',\n args: { id, fromParentId, toParentId, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n (adapter as ReparentAdapter).setParent(id, toParentId);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\n","/**\n * Typed keys for the gesture/behavior scratch store.\n *\n * Gestures expose `ctx.scratch: Record<string, unknown>` as a per-gesture\n * mutable bag that behaviors use to stash state across `onDown` / `onMove`\n * / `onEnd` calls. The store is `unknown`-valued because behaviors are\n * tool-agnostic — a single behavior may run under different tools with\n * different scratch shapes. Without a typing convention every read site\n * has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the\n * shape can silently break readers with no compile-time signal.\n *\n * `ScratchKey<T>` is a phantom-typed string that carries its payload type\n * via a branded interface. `getScratch` and `setScratch` use the brand to\n * narrow / constrain at the typing layer; at runtime the key is just its\n * underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level (or behavior-level) constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n *\n * // Writer (a tool's onMove):\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n *\n * // Reader (a behavior's onEnd):\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\n * // ^? readonly { x: number; y: number }[] | undefined\n * ```\n */\n\n/** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom\n * field carries the value type without occupying any runtime memory. */\nexport interface ScratchKey<T> {\n readonly name: string;\n /** Phantom — never read at runtime. Only present so TypeScript can\n * recover `T` at use sites. */\n readonly __scratchKeyPayload?: (_: T) => T;\n}\n\n/** Compatible scratch store shape. The kit's gesture context's `scratch`\n * field already satisfies this; consumers don't need to construct one. */\nexport type ScratchStore = Record<string, unknown>;\n\n/**\n * Make a typed scratch key. Use module-level constants so the same key\n * is shared by writer and reader.\n *\n * Namespace the name by behavior or feature to avoid collisions\n * (`'behavior.field'` is a good convention). Two keys with the same\n * `name` refer to the same slot at runtime — the type parameter is a\n * compile-time contract, not a uniqueness guarantee.\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n","import type { MoveBehavior, ModifierState, SnapStrategy } from '../types';\nimport {\n RECT_ORIGIN_PROJECTION,\n type OriginProjection,\n} from './strategies/grid';\n\ntype ModKey = keyof ModifierState;\n\n/**\n * Generic snap behavior factory: wraps a `SnapStrategy` and returns a\n * `MoveBehavior` whose `onMove` returns a uniform `GroupTransform`.\n *\n * Implementation:\n * 1. Compute the primary's proposed pose by applying the gesture's\n * `transform` (translate) to `origin.get(primaryId)`.\n * 2. Call `strategy.snap(proposed)`. If null, no-op.\n * 3. Derive the snapped delta via the `OriginProjection`:\n * dx = origin(snapped).x - origin(originPose).x\n * ...so the gesture applies the same delta to every dragged id.\n *\n * The pose-shape-aware delta extraction lives here — gestures stay pose-shape\n * agnostic. Default projection is `{x, y}`-bearing (rect / path / polygon).\n * Pass `origin` for exotic poses.\n */\nexport function snap<TPose>(\n strategy: SnapStrategy<TPose>,\n opts: { bypassKey?: ModKey; origin?: OriginProjection<TPose> } = {},\n): MoveBehavior<TPose> {\n const { bypassKey } = opts;\n const proj: OriginProjection<TPose> = opts.origin\n ?? (RECT_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n return {\n onMove(ctx, transform) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n // The new contract: behaviors see a GroupTransform, not a pose. But\n // strategies still operate on poses. Reconstruct the proposed pose\n // by applying the (translate) transform to the primary's origin.\n if (transform.kind !== 'translate') return;\n const primaryId = ctx.draggedIds[0];\n if (primaryId === undefined) return;\n const originPose = ctx.origin.get(primaryId);\n if (originPose === undefined) return;\n const proposed = proj.translate(originPose, transform.dx, transform.dy);\n const snapped = strategy.snap(proposed, ctx);\n if (snapped === null) return;\n const o0 = proj.getOrigin(originPose);\n const o1 = proj.getOrigin(snapped);\n return {\n transform: { kind: 'translate', dx: o1.x - o0.x, dy: o1.y - o0.y },\n };\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/ops/transform.ts","../src/core/ops/reparent.ts","../src/interactions/scratchKey.ts","../src/interactions/gestures/shared/snap.ts"],"names":[],"mappings":"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","file":"chunk-BPRHGTLQ.js","sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface TransformAdapter<TPose> {\n setPose(id: string, pose: TPose): void;\n}\n\n/** @internal */\ninterface TransformArgs<TPose> {\n id: string;\n from: TPose;\n to: TPose;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Op: set an object's pose, inverting back to `from`. `coalesceKey` defaults to\n * `transform:${id}` so successive pose writes to the same node (drag-move,\n * resize, repeated nudges) merge into one undo entry within the history's\n * coalesce window. Pass an explicit key (or a unique one) to opt out. */\nexport function createTransformOp<TPose>(args: TransformArgs<TPose>): Op {\n const { id, from, to, label, coalesceKey = `transform:${id}` } = args;\n return {\n name: 'transform',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPose so consumers that inspect op behavior (tests,\n // overlays) see a consistent \"this op writes (id, to)\" signal. When\n // from and to are structurally identical, additionally report a\n // no-op to history so the entry can be skipped from the undo stack.\n // setPose(id, to) is idempotent in this branch — adapters write the\n // same pose they already had.\n (adapter as TransformAdapter<TPose>).setPose(id, to);\n if (poseShallowEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createTransformOp<TPose>({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<TransformArgs<unknown>>('transform', (args) => createTransformOp(args));\n\nfunction poseShallowEqual<TPose>(a: TPose, b: TPose): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n","import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface ReparentAdapter {\n setParent(id: string, parentId: string | null): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n label?: string;\n coalesceKey?: string;\n}\n\n/**\n * Op: change `id`'s parent, inverting back to the prior parent.\n *\n * `coalesceKey` defaults to `reparent:${id}` so successive reparents of the\n * same id batch-merge cleanly.\n */\nexport function createReparentOp(args: ReparentArgs): Op {\n const { id, fromParentId, toParentId, label = 'Reparent', coalesceKey = `reparent:${id}` } = args;\n return {\n name: 'reparent',\n args: { id, fromParentId, toParentId, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n (adapter as ReparentAdapter).setParent(id, toParentId);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\n","/**\n * Typed keys for the gesture/behavior scratch store.\n *\n * Gestures expose `ctx.scratch: Record<string, unknown>` as a per-gesture\n * mutable bag that behaviors use to stash state across `onDown` / `onMove`\n * / `onEnd` calls. The store is `unknown`-valued because behaviors are\n * tool-agnostic — a single behavior may run under different tools with\n * different scratch shapes. Without a typing convention every read site\n * has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the\n * shape can silently break readers with no compile-time signal.\n *\n * `ScratchKey<T>` is a phantom-typed string that carries its payload type\n * via a branded interface. `getScratch` and `setScratch` use the brand to\n * narrow / constrain at the typing layer; at runtime the key is just its\n * underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level (or behavior-level) constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n *\n * // Writer (a tool's onMove):\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n *\n * // Reader (a behavior's onEnd):\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\n * // ^? readonly { x: number; y: number }[] | undefined\n * ```\n */\n\n/** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom\n * field carries the value type without occupying any runtime memory. */\nexport interface ScratchKey<T> {\n readonly name: string;\n /** Phantom — never read at runtime. Only present so TypeScript can\n * recover `T` at use sites. */\n readonly __scratchKeyPayload?: (_: T) => T;\n}\n\n/** Compatible scratch store shape. The kit's gesture context's `scratch`\n * field already satisfies this; consumers don't need to construct one. */\nexport type ScratchStore = Record<string, unknown>;\n\n/**\n * Make a typed scratch key. Use module-level constants so the same key\n * is shared by writer and reader.\n *\n * Namespace the name by behavior or feature to avoid collisions\n * (`'behavior.field'` is a good convention). Two keys with the same\n * `name` refer to the same slot at runtime — the type parameter is a\n * compile-time contract, not a uniqueness guarantee.\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n","import type { MoveBehavior, ModifierState, SnapStrategy } from '../types';\nimport {\n RECT_ORIGIN_PROJECTION,\n type OriginProjection,\n} from './strategies/grid';\n\ntype ModKey = keyof ModifierState;\n\n/**\n * Generic snap behavior factory: wraps a `SnapStrategy` and returns a\n * `MoveBehavior` whose `onMove` returns a uniform `GroupTransform`.\n *\n * Implementation:\n * 1. Compute the primary's proposed pose by applying the gesture's\n * `transform` (translate) to `origin.get(primaryId)`.\n * 2. Call `strategy.snap(proposed)`. If null, no-op.\n * 3. Derive the snapped delta via the `OriginProjection`:\n * dx = origin(snapped).x - origin(originPose).x\n * ...so the gesture applies the same delta to every dragged id.\n *\n * The pose-shape-aware delta extraction lives here — gestures stay pose-shape\n * agnostic. Default projection is `{x, y}`-bearing (rect / path / polygon).\n * Pass `origin` for exotic poses.\n */\nexport function snap<TPose>(\n strategy: SnapStrategy<TPose>,\n opts: { bypassKey?: ModKey; origin?: OriginProjection<TPose> } = {},\n): MoveBehavior<TPose> {\n const { bypassKey } = opts;\n const proj: OriginProjection<TPose> = opts.origin\n ?? (RECT_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n return {\n onMove(ctx, transform) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n // The new contract: behaviors see a GroupTransform, not a pose. But\n // strategies still operate on poses. Reconstruct the proposed pose\n // by applying the (translate) transform to the primary's origin.\n if (transform.kind !== 'translate') return;\n const primaryId = ctx.draggedIds[0];\n if (primaryId === undefined) return;\n const originPose = ctx.origin.get(primaryId);\n if (originPose === undefined) return;\n const proposed = proj.translate(originPose, transform.dx, transform.dy);\n const snapped = strategy.snap(proposed, ctx);\n if (snapped === null) return;\n const o0 = proj.getOrigin(originPose);\n const o1 = proj.getOrigin(snapped);\n return {\n transform: { kind: 'translate', dx: o1.x - o0.x, dy: o1.y - o0.y },\n };\n },\n };\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_GUIDE_TOLERANCE_PX, meanScale } from './chunk-
|
|
1
|
+
import { DEFAULT_GUIDE_TOLERANCE_PX, meanScale } from './chunk-GLZLSPGJ.js';
|
|
2
2
|
|
|
3
3
|
// src/interactions/actions/resize/behaviors/clampMinSize.ts
|
|
4
4
|
function clampMinSize(args) {
|
|
@@ -244,5 +244,5 @@ function fixedCornerOf(bounds, anchor) {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
export { CORNER_ANCHORS, DEFAULT_RESIZE_BEHAVIORS, clampMinSize, cornerPoint, cornerResizeHandles, fixedCornerOf, hitCornerHandle, lockAspectWithModifier, pointSnapToGrid, snapToGrid, snapToGuides };
|
|
247
|
-
//# sourceMappingURL=chunk-
|
|
248
|
-
//# sourceMappingURL=chunk-
|
|
247
|
+
//# sourceMappingURL=chunk-CMBE45MT.js.map
|
|
248
|
+
//# sourceMappingURL=chunk-CMBE45MT.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/interactions/actions/resize/behaviors/clampMinSize.ts","../src/interactions/actions/resize/behaviors/lockAspect.ts","../src/interactions/actions/resize/behaviors/snapToGrid.ts","../src/interactions/actions/resize/behaviors/snapToGuides.ts","../src/interactions/actions/resize/behaviors/pointSnapToGrid.ts","../src/interactions/actions/resize/behaviors/index.ts","../src/interactions/actions/resize/cornerHandles.ts"],"names":[],"mappings":"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","file":"chunk-BGGZ4CVF.js","sourcesContent":["import type { BoundsConstraint, ResizePose } from '../../../gestures/types';\n\nexport function clampMinSize<TPose extends ResizePose>(args: {\n minWidth: number;\n minHeight: number;\n}): BoundsConstraint<TPose> {\n const { minWidth, minHeight } = args;\n return {\n onMove(_ctx, { pose, anchor }) {\n let { x, y, width, height } = pose;\n let changed = false;\n if (anchor.x !== 'free' && width < minWidth) {\n if (anchor.x === 'min') {\n // West edge is anchor; east edge was dragged. Hold x; widen to min.\n width = minWidth;\n } else {\n // East edge is anchor at originalRight = x + width. Hold the right;\n // shift x left so width = minWidth.\n const right = x + width;\n x = right - minWidth;\n width = minWidth;\n }\n changed = true;\n }\n if (anchor.y !== 'free' && height < minHeight) {\n if (anchor.y === 'min') {\n height = minHeight;\n } else {\n const bottom = y + height;\n y = bottom - minHeight;\n height = minHeight;\n }\n changed = true;\n }\n if (!changed) return;\n return { pose: { ...pose, x, y, width, height } };\n },\n };\n}\n","import type {\n ModifierState,\n BoundsConstraint,\n ResizePose,\n} from '../../../gestures/types';\n\ntype ModKey = keyof ModifierState;\n\n/** Aspect-ratio lock: drag a corner/edge with `key` (default `'shift'`) held\n * to keep the resized bounds at the start-pose's `width / height` ratio.\n *\n * Strategy:\n * - Compute the proposed deltas in width and height (signed, relative to\n * origin). Whichever axis has the larger absolute delta \"wins\"; the other\n * is recomputed from `(originW / originH) * winnerDelta`.\n * - The opposite corner/edge stays anchored — for the corner case we infer\n * the anchor from the proposed bounds; for an edge handle we resize the\n * orthogonal axis symmetrically around the dragged edge's anchor (the\n * handle's own line stays put, the orthogonal axis grows/shrinks centered\n * on the un-dragged edge — see notes below).\n * - No-op when the modifier is not held, when the origin has zero width or\n * height (no defined ratio), or when the proposed pose hasn't moved.\n *\n * Edge-handle handling: with `anchor.x !== 'free' && anchor.y === 'free'`\n * (an `e` or `w` handle), only the width was driven by the pointer. We pick\n * the new height as `width / ratio` and pin it to the un-dragged y axis by\n * anchoring at the origin's top edge (`y` stays at `origin.y`). Symmetric\n * for `n` / `s`.\n */\nexport function lockAspectWithModifier<TPose extends ResizePose>(opts: {\n key?: ModKey;\n} = {}): BoundsConstraint<TPose> {\n const { key = 'shift' } = opts;\n\n return {\n onMove(ctx, { pose, anchor }) {\n if (!ctx.modifiers[key]) return;\n const origin = ctx.origin.get(ctx.draggedIds[0]) as ResizePose | undefined;\n if (!origin) return;\n if (origin.width === 0 || origin.height === 0) return;\n const ratio = origin.width / origin.height; // w / h\n\n // Signed deltas relative to origin. Negative deltas mean the user has\n // dragged through the anchor and flipped the rect; we preserve sign on\n // the recomputed axis so the flipped quadrant stays consistent.\n const dw = pose.width - origin.width;\n const dh = pose.height - origin.height;\n\n let nw: number;\n let nh: number;\n\n if (anchor.x === 'free' && anchor.y === 'free') {\n // No driven axis — nothing to lock against.\n return;\n } else if (anchor.x === 'free') {\n // Edge handle: n or s. height is driven; width follows.\n nh = pose.height;\n nw = nh * ratio;\n // sign-preserve width if the original height delta caused a flip.\n // (height-driven only; width direction follows ratio absolutely.)\n if (nw < 0) nw = -nw;\n } else if (anchor.y === 'free') {\n // Edge handle: e or w. width is driven; height follows.\n nw = pose.width;\n nh = nw / ratio;\n if (nh < 0) nh = -nh;\n } else {\n // Corner handle. Pick the dominant axis by absolute delta.\n if (Math.abs(dw) >= Math.abs(dh)) {\n nw = pose.width;\n // Match height sign to width sign so the rect stays in the same\n // quadrant the pointer chose.\n nh = (Math.abs(nw) / ratio) * Math.sign(pose.height || 1);\n } else {\n nh = pose.height;\n nw = (Math.abs(nh) * ratio) * Math.sign(pose.width || 1);\n }\n }\n\n // Recompute x/y from the anchor (the un-dragged edge stays pinned).\n let nx = pose.x;\n let ny = pose.y;\n if (anchor.x === 'min') {\n // West edge anchored at origin.x.\n nx = origin.x;\n } else if (anchor.x === 'max') {\n // East edge anchored at origin.x + origin.width.\n nx = origin.x + origin.width - nw;\n } else {\n // Edge handle (n/s): width grew/shrunk via ratio. Center horizontally\n // about the origin's center so the rect stays visually centered on\n // the dragged edge's midline.\n nx = origin.x + origin.width / 2 - nw / 2;\n }\n if (anchor.y === 'min') {\n ny = origin.y;\n } else if (anchor.y === 'max') {\n ny = origin.y + origin.height - nh;\n } else {\n ny = origin.y + origin.height / 2 - nh / 2;\n }\n\n return {\n pose: { ...pose, x: nx, y: ny, width: nw, height: nh },\n };\n },\n };\n}\n","import type {\n ModifierState,\n BoundsConstraint,\n ResizePose,\n} from '../../../gestures/types';\n\ntype ModKey = keyof ModifierState;\n\nexport function snapToGrid<TPose extends ResizePose>(args: {\n spacing: number;\n bypassKey?: ModKey;\n suspendBelowDim?: boolean;\n}): BoundsConstraint<TPose> {\n const { spacing, bypassKey, suspendBelowDim = true } = args;\n const round = (v: number) => Math.round(v / spacing) * spacing;\n\n return {\n onMove(ctx, { pose, anchor }) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n const origin = ctx.origin.get(ctx.draggedIds[0])!;\n const subX = suspendBelowDim && origin.width < spacing;\n const subY = suspendBelowDim && origin.height < spacing;\n\n let { x, y, width, height } = pose;\n let changed = false;\n\n if (anchor.x !== 'free' && !subX) {\n if (anchor.x === 'min') {\n // East edge moves; west (x) stays.\n const east = round(x + width);\n width = east - x;\n } else {\n // West edge moves; east (x+width) stays.\n const right = origin.x + origin.width;\n const newX = round(x);\n width = right - newX;\n x = newX;\n }\n changed = true;\n }\n if (anchor.y !== 'free' && !subY) {\n if (anchor.y === 'min') {\n const south = round(y + height);\n height = south - y;\n } else {\n const bottom = origin.y + origin.height;\n const newY = round(y);\n height = bottom - newY;\n y = newY;\n }\n changed = true;\n }\n if (!changed) return;\n return { pose: { ...pose, x, y, width, height } };\n },\n };\n}\n","import type {\n ModifierState,\n BoundsConstraint,\n ResizePose,\n} from '../../../gestures/types';\nimport type { Guide } from 'features/guides/types';\nimport type { View } from 'core/viewport/view';\nimport { meanScale } from 'core/viewport/meanScale';\nimport { DEFAULT_GUIDE_TOLERANCE_PX } from '../../../gestures/shared/strategies/guides';\n\ntype ModKey = keyof ModifierState;\n\n/** Options for the resize-flavored `snapToGuides`. */\nexport interface SnapToGuidesResizeArgs {\n /** Stable getter into the live guide list (typically from `useGuides`). */\n getGuides: () => readonly Guide[];\n /** Snap tolerance (screen px when `getView` is set, world units otherwise). */\n tolerance?: number;\n /** Read the active view; required for screen-pixel tolerance. */\n getView?: () => View;\n /** Modifier key that bypasses snapping while held. */\n bypassKey?: ModKey;\n}\n\n/**\n * Resize behavior that snaps the active edge of the dragged rect to the\n * nearest guide on the corresponding axis when within `tolerance`. The\n * \"free\" axis is left untouched. The fixed (anchor) edge stays pinned;\n * only the moving edge snaps.\n *\n * On the X axis, vertical guides (`axis: 'x'`) constrain the moving vertical\n * edge — east when `anchor.x === 'min'` (west pinned), west when `'max'`.\n * Likewise on Y for horizontal guides.\n */\nexport function snapToGuides<TPose extends ResizePose>(\n args: SnapToGuidesResizeArgs,\n): BoundsConstraint<TPose> {\n const tolerance = args.tolerance ?? DEFAULT_GUIDE_TOLERANCE_PX;\n const getView = args.getView;\n const bypassKey = args.bypassKey;\n\n return {\n onMove(ctx, { pose, anchor }) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n const guides = args.getGuides();\n if (guides.length === 0) return;\n\n const worldTolerance = getView\n ? tolerance / Math.max(1e-9, meanScale(getView().scale))\n : tolerance;\n\n let { x, y, width, height } = pose;\n let changed = false;\n\n // X-axis: snap the moving vertical edge to the nearest 'x' guide.\n if (anchor.x !== 'free') {\n const movingX = anchor.x === 'min' ? x + width : x;\n let bestD = 0;\n let bestAbs = Infinity;\n for (const g of guides) {\n if (g.axis !== 'x') continue;\n const d = g.offset - movingX;\n const ad = Math.abs(d);\n if (ad <= worldTolerance && ad < bestAbs) {\n bestAbs = ad;\n bestD = d;\n }\n }\n if (bestAbs !== Infinity && bestD !== 0) {\n if (anchor.x === 'min') {\n // East edge moves; west (x) stays.\n width = width + bestD;\n } else {\n // West edge moves; east (x+width) stays.\n x = x + bestD;\n width = width - bestD;\n }\n changed = true;\n }\n }\n\n // Y-axis: snap the moving horizontal edge to the nearest 'y' guide.\n if (anchor.y !== 'free') {\n const movingY = anchor.y === 'min' ? y + height : y;\n let bestD = 0;\n let bestAbs = Infinity;\n for (const g of guides) {\n if (g.axis !== 'y') continue;\n const d = g.offset - movingY;\n const ad = Math.abs(d);\n if (ad <= worldTolerance && ad < bestAbs) {\n bestAbs = ad;\n bestD = d;\n }\n }\n if (bestAbs !== Infinity && bestD !== 0) {\n if (anchor.y === 'min') {\n height = height + bestD;\n } else {\n y = y + bestD;\n height = height - bestD;\n }\n changed = true;\n }\n }\n\n if (!changed) return;\n return { pose: { ...pose, x, y, width, height } };\n },\n };\n}\n","import type {\n ModifierState,\n PointSnapBehavior,\n PointSnapContext,\n PointSnapFrame,\n PointSnapResult,\n ResizePose,\n} from '../../../gestures/types';\n\nexport function pointSnapToGrid<TPose extends ResizePose>(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","export { clampMinSize } from './clampMinSize';\nexport { lockAspectWithModifier } from './lockAspect';\nexport { snapToGrid } from './snapToGrid';\n\nimport type { BoundsConstraint, ResizePose } from '../../../gestures/types';\nimport { lockAspectWithModifier } from './lockAspect';\n\n/** The kit's standard resize behaviors, applied when a consumer doesn't\n * supply its own list: shift-drag locks the start pose's aspect ratio.\n * Pass an explicit `behaviors: []` to opt out. Stateless, so one shared\n * frozen instance is safe across every gesture. */\nexport const DEFAULT_RESIZE_BEHAVIORS: readonly BoundsConstraint<ResizePose>[] =\n Object.freeze([lockAspectWithModifier()]);\nexport { snapToGuides } from './snapToGuides';\nexport { pointSnapToGrid } from './pointSnapToGrid';\n","import type { ResizeAnchor } from '../../gestures/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\n\n/** Corner resize-handle: world-space center plus the anchor that pins the opposite corner during resize. */\nexport interface CornerHandle {\n cx: number;\n cy: number;\n anchor: ResizeAnchor;\n}\n\n/** Which AABB edge a corner sits on, per axis. `'min'` = the x/y origin\n * edge; `'max'` = the origin + extent edge. */\nexport type CornerEdge = 'min' | 'max';\n\n/** One entry of the canonical 4-corner resize layout. The single source of\n * truth for \"which corner maps to which anchor / handle-kind\". */\nexport interface CornerAnchor {\n /** Which x-edge the corner sits on (`'min'` = left, `'max'` = right). */\n xEdge: CornerEdge;\n /** Which y-edge the corner sits on (`'min'` = top, `'max'` = bottom). */\n yEdge: CornerEdge;\n /** Anchor that pins the diagonally-opposite (fixed) corner. Always the\n * axis-wise opposite of `{xEdge, yEdge}`. */\n anchor: ResizeAnchor;\n /** Affordance/handle discriminator string for this corner. */\n kind: string;\n /** Short positional tag (`'<xEdge>-<yEdge>'`), e.g. `'min-max'`. */\n tag: string;\n}\n\n/**\n * The single ordered corner→anchor table. Order is **load-bearing**: every\n * consumer iterates it in this sequence (TL, TR, BL, BR) so positional\n * meaning (the i-th handle / `kind` / `tag`) stays consistent across the\n * resize-handle affordance, the affordance-hit classifier, and\n * `cornerResizeHandles`. Each corner's `anchor` names the diagonally\n * opposite corner, so dragging the corner scales the box from that fixed\n * point.\n */\nexport const CORNER_ANCHORS: readonly CornerAnchor[] = [\n { xEdge: 'min', yEdge: 'min', anchor: { x: 'max', y: 'max' }, kind: 'handle:top-left', tag: 'min-min' },\n { xEdge: 'max', yEdge: 'min', anchor: { x: 'min', y: 'max' }, kind: 'handle:top-right', tag: 'max-min' },\n { xEdge: 'min', yEdge: 'max', anchor: { x: 'max', y: 'min' }, kind: 'handle:bottom-left', tag: 'min-max' },\n { xEdge: 'max', yEdge: 'max', anchor: { x: 'min', y: 'min' }, kind: 'handle:bottom-right', tag: 'max-max' },\n];\n\n/** Resolve a corner's world-space position for the given bounds. `xEdge`\n * `'max'` → `x + width`; `yEdge` `'max'` → `y + height`. */\nexport function cornerPoint(\n bounds: Bounds,\n entry: Pick<CornerAnchor, 'xEdge' | 'yEdge'>,\n): { x: number; y: number } {\n return {\n x: entry.xEdge === 'max' ? bounds.x + bounds.width : bounds.x,\n y: entry.yEdge === 'max' ? bounds.y + bounds.height : bounds.y,\n };\n}\n\n/** Standard 4-corner resize-handle layout: each corner pins the opposite\n * corner via an anchor of `{x: 'min'|'max', y: 'min'|'max'}` so dragging it\n * scales the box from the fixed opposite corner. Decodes {@link CORNER_ANCHORS}\n * against `bounds`. */\nexport function cornerResizeHandles(bounds: Bounds): CornerHandle[] {\n return CORNER_ANCHORS.map((c) => {\n const p = cornerPoint(bounds, c);\n return { cx: p.x, cy: p.y, anchor: c.anchor };\n });\n}\n\n/** Square hit-test for a handle: returns true if `(px, py)` is within\n * `radius` of the handle center on both axes. */\nexport function hitCornerHandle(\n handle: CornerHandle,\n px: number,\n py: number,\n radius: number,\n): boolean {\n return Math.abs(px - handle.cx) <= radius\n && Math.abs(py - handle.cy) <= radius;\n}\n\n/** The corner that does NOT move under a resize gesture with the given\n * anchor. Used by the rotated-resize math to pin a world-space invariant.\n *\n * Convention: `anchor.x === 'min'` means the min-x (left) edge is the\n * fixed anchor, so the fixed corner sits at `x = bounds.x` (min-x).\n * `anchor.x === 'max'` means the max-x (right) edge is fixed, so the\n * fixed corner sits at `x = bounds.x + bounds.width`. `'free'` on an\n * axis means that axis doesn't move; the fixed coord is the bounds\n * origin on that axis. */\nexport function fixedCornerOf(\n bounds: Bounds,\n anchor: ResizeAnchor,\n): { x: number; y: number } {\n return {\n x: anchor.x === 'max' ? bounds.x + bounds.width : bounds.x,\n y: anchor.y === 'max' ? bounds.y + bounds.height : bounds.y,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/interactions/actions/resize/behaviors/clampMinSize.ts","../src/interactions/actions/resize/behaviors/lockAspect.ts","../src/interactions/actions/resize/behaviors/snapToGrid.ts","../src/interactions/actions/resize/behaviors/snapToGuides.ts","../src/interactions/actions/resize/behaviors/pointSnapToGrid.ts","../src/interactions/actions/resize/behaviors/index.ts","../src/interactions/actions/resize/cornerHandles.ts"],"names":[],"mappings":"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","file":"chunk-CMBE45MT.js","sourcesContent":["import type { BoundsConstraint, ResizePose } from '../../../gestures/types';\n\nexport function clampMinSize<TPose extends ResizePose>(args: {\n minWidth: number;\n minHeight: number;\n}): BoundsConstraint<TPose> {\n const { minWidth, minHeight } = args;\n return {\n onMove(_ctx, { pose, anchor }) {\n let { x, y, width, height } = pose;\n let changed = false;\n if (anchor.x !== 'free' && width < minWidth) {\n if (anchor.x === 'min') {\n // West edge is anchor; east edge was dragged. Hold x; widen to min.\n width = minWidth;\n } else {\n // East edge is anchor at originalRight = x + width. Hold the right;\n // shift x left so width = minWidth.\n const right = x + width;\n x = right - minWidth;\n width = minWidth;\n }\n changed = true;\n }\n if (anchor.y !== 'free' && height < minHeight) {\n if (anchor.y === 'min') {\n height = minHeight;\n } else {\n const bottom = y + height;\n y = bottom - minHeight;\n height = minHeight;\n }\n changed = true;\n }\n if (!changed) return;\n return { pose: { ...pose, x, y, width, height } };\n },\n };\n}\n","import type {\n ModifierState,\n BoundsConstraint,\n ResizePose,\n} from '../../../gestures/types';\n\ntype ModKey = keyof ModifierState;\n\n/** Aspect-ratio lock: drag a corner/edge with `key` (default `'shift'`) held\n * to keep the resized bounds at the start-pose's `width / height` ratio.\n *\n * Strategy:\n * - Compute the proposed deltas in width and height (signed, relative to\n * origin). Whichever axis has the larger absolute delta \"wins\"; the other\n * is recomputed from `(originW / originH) * winnerDelta`.\n * - The opposite corner/edge stays anchored — for the corner case we infer\n * the anchor from the proposed bounds; for an edge handle we resize the\n * orthogonal axis symmetrically around the dragged edge's anchor (the\n * handle's own line stays put, the orthogonal axis grows/shrinks centered\n * on the un-dragged edge — see notes below).\n * - No-op when the modifier is not held, when the origin has zero width or\n * height (no defined ratio), or when the proposed pose hasn't moved.\n *\n * Edge-handle handling: with `anchor.x !== 'free' && anchor.y === 'free'`\n * (an `e` or `w` handle), only the width was driven by the pointer. We pick\n * the new height as `width / ratio` and pin it to the un-dragged y axis by\n * anchoring at the origin's top edge (`y` stays at `origin.y`). Symmetric\n * for `n` / `s`.\n */\nexport function lockAspectWithModifier<TPose extends ResizePose>(opts: {\n key?: ModKey;\n} = {}): BoundsConstraint<TPose> {\n const { key = 'shift' } = opts;\n\n return {\n onMove(ctx, { pose, anchor }) {\n if (!ctx.modifiers[key]) return;\n const origin = ctx.origin.get(ctx.draggedIds[0]) as ResizePose | undefined;\n if (!origin) return;\n if (origin.width === 0 || origin.height === 0) return;\n const ratio = origin.width / origin.height; // w / h\n\n // Signed deltas relative to origin. Negative deltas mean the user has\n // dragged through the anchor and flipped the rect; we preserve sign on\n // the recomputed axis so the flipped quadrant stays consistent.\n const dw = pose.width - origin.width;\n const dh = pose.height - origin.height;\n\n let nw: number;\n let nh: number;\n\n if (anchor.x === 'free' && anchor.y === 'free') {\n // No driven axis — nothing to lock against.\n return;\n } else if (anchor.x === 'free') {\n // Edge handle: n or s. height is driven; width follows.\n nh = pose.height;\n nw = nh * ratio;\n // sign-preserve width if the original height delta caused a flip.\n // (height-driven only; width direction follows ratio absolutely.)\n if (nw < 0) nw = -nw;\n } else if (anchor.y === 'free') {\n // Edge handle: e or w. width is driven; height follows.\n nw = pose.width;\n nh = nw / ratio;\n if (nh < 0) nh = -nh;\n } else {\n // Corner handle. Pick the dominant axis by absolute delta.\n if (Math.abs(dw) >= Math.abs(dh)) {\n nw = pose.width;\n // Match height sign to width sign so the rect stays in the same\n // quadrant the pointer chose.\n nh = (Math.abs(nw) / ratio) * Math.sign(pose.height || 1);\n } else {\n nh = pose.height;\n nw = (Math.abs(nh) * ratio) * Math.sign(pose.width || 1);\n }\n }\n\n // Recompute x/y from the anchor (the un-dragged edge stays pinned).\n let nx = pose.x;\n let ny = pose.y;\n if (anchor.x === 'min') {\n // West edge anchored at origin.x.\n nx = origin.x;\n } else if (anchor.x === 'max') {\n // East edge anchored at origin.x + origin.width.\n nx = origin.x + origin.width - nw;\n } else {\n // Edge handle (n/s): width grew/shrunk via ratio. Center horizontally\n // about the origin's center so the rect stays visually centered on\n // the dragged edge's midline.\n nx = origin.x + origin.width / 2 - nw / 2;\n }\n if (anchor.y === 'min') {\n ny = origin.y;\n } else if (anchor.y === 'max') {\n ny = origin.y + origin.height - nh;\n } else {\n ny = origin.y + origin.height / 2 - nh / 2;\n }\n\n return {\n pose: { ...pose, x: nx, y: ny, width: nw, height: nh },\n };\n },\n };\n}\n","import type {\n ModifierState,\n BoundsConstraint,\n ResizePose,\n} from '../../../gestures/types';\n\ntype ModKey = keyof ModifierState;\n\nexport function snapToGrid<TPose extends ResizePose>(args: {\n spacing: number;\n bypassKey?: ModKey;\n suspendBelowDim?: boolean;\n}): BoundsConstraint<TPose> {\n const { spacing, bypassKey, suspendBelowDim = true } = args;\n const round = (v: number) => Math.round(v / spacing) * spacing;\n\n return {\n onMove(ctx, { pose, anchor }) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n const origin = ctx.origin.get(ctx.draggedIds[0])!;\n const subX = suspendBelowDim && origin.width < spacing;\n const subY = suspendBelowDim && origin.height < spacing;\n\n let { x, y, width, height } = pose;\n let changed = false;\n\n if (anchor.x !== 'free' && !subX) {\n if (anchor.x === 'min') {\n // East edge moves; west (x) stays.\n const east = round(x + width);\n width = east - x;\n } else {\n // West edge moves; east (x+width) stays.\n const right = origin.x + origin.width;\n const newX = round(x);\n width = right - newX;\n x = newX;\n }\n changed = true;\n }\n if (anchor.y !== 'free' && !subY) {\n if (anchor.y === 'min') {\n const south = round(y + height);\n height = south - y;\n } else {\n const bottom = origin.y + origin.height;\n const newY = round(y);\n height = bottom - newY;\n y = newY;\n }\n changed = true;\n }\n if (!changed) return;\n return { pose: { ...pose, x, y, width, height } };\n },\n };\n}\n","import type {\n ModifierState,\n BoundsConstraint,\n ResizePose,\n} from '../../../gestures/types';\nimport type { Guide } from 'features/guides/types';\nimport type { View } from 'core/viewport/view';\nimport { meanScale } from 'core/viewport/meanScale';\nimport { DEFAULT_GUIDE_TOLERANCE_PX } from '../../../gestures/shared/strategies/guides';\n\ntype ModKey = keyof ModifierState;\n\n/** Options for the resize-flavored `snapToGuides`. */\nexport interface SnapToGuidesResizeArgs {\n /** Stable getter into the live guide list (typically from `useGuides`). */\n getGuides: () => readonly Guide[];\n /** Snap tolerance (screen px when `getView` is set, world units otherwise). */\n tolerance?: number;\n /** Read the active view; required for screen-pixel tolerance. */\n getView?: () => View;\n /** Modifier key that bypasses snapping while held. */\n bypassKey?: ModKey;\n}\n\n/**\n * Resize behavior that snaps the active edge of the dragged rect to the\n * nearest guide on the corresponding axis when within `tolerance`. The\n * \"free\" axis is left untouched. The fixed (anchor) edge stays pinned;\n * only the moving edge snaps.\n *\n * On the X axis, vertical guides (`axis: 'x'`) constrain the moving vertical\n * edge — east when `anchor.x === 'min'` (west pinned), west when `'max'`.\n * Likewise on Y for horizontal guides.\n */\nexport function snapToGuides<TPose extends ResizePose>(\n args: SnapToGuidesResizeArgs,\n): BoundsConstraint<TPose> {\n const tolerance = args.tolerance ?? DEFAULT_GUIDE_TOLERANCE_PX;\n const getView = args.getView;\n const bypassKey = args.bypassKey;\n\n return {\n onMove(ctx, { pose, anchor }) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n const guides = args.getGuides();\n if (guides.length === 0) return;\n\n const worldTolerance = getView\n ? tolerance / Math.max(1e-9, meanScale(getView().scale))\n : tolerance;\n\n let { x, y, width, height } = pose;\n let changed = false;\n\n // X-axis: snap the moving vertical edge to the nearest 'x' guide.\n if (anchor.x !== 'free') {\n const movingX = anchor.x === 'min' ? x + width : x;\n let bestD = 0;\n let bestAbs = Infinity;\n for (const g of guides) {\n if (g.axis !== 'x') continue;\n const d = g.offset - movingX;\n const ad = Math.abs(d);\n if (ad <= worldTolerance && ad < bestAbs) {\n bestAbs = ad;\n bestD = d;\n }\n }\n if (bestAbs !== Infinity && bestD !== 0) {\n if (anchor.x === 'min') {\n // East edge moves; west (x) stays.\n width = width + bestD;\n } else {\n // West edge moves; east (x+width) stays.\n x = x + bestD;\n width = width - bestD;\n }\n changed = true;\n }\n }\n\n // Y-axis: snap the moving horizontal edge to the nearest 'y' guide.\n if (anchor.y !== 'free') {\n const movingY = anchor.y === 'min' ? y + height : y;\n let bestD = 0;\n let bestAbs = Infinity;\n for (const g of guides) {\n if (g.axis !== 'y') continue;\n const d = g.offset - movingY;\n const ad = Math.abs(d);\n if (ad <= worldTolerance && ad < bestAbs) {\n bestAbs = ad;\n bestD = d;\n }\n }\n if (bestAbs !== Infinity && bestD !== 0) {\n if (anchor.y === 'min') {\n height = height + bestD;\n } else {\n y = y + bestD;\n height = height - bestD;\n }\n changed = true;\n }\n }\n\n if (!changed) return;\n return { pose: { ...pose, x, y, width, height } };\n },\n };\n}\n","import type {\n ModifierState,\n PointSnapBehavior,\n PointSnapContext,\n PointSnapFrame,\n PointSnapResult,\n ResizePose,\n} from '../../../gestures/types';\n\nexport function pointSnapToGrid<TPose extends ResizePose>(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","export { clampMinSize } from './clampMinSize';\nexport { lockAspectWithModifier } from './lockAspect';\nexport { snapToGrid } from './snapToGrid';\n\nimport type { BoundsConstraint, ResizePose } from '../../../gestures/types';\nimport { lockAspectWithModifier } from './lockAspect';\n\n/** The kit's standard resize behaviors, applied when a consumer doesn't\n * supply its own list: shift-drag locks the start pose's aspect ratio.\n * Pass an explicit `behaviors: []` to opt out. Stateless, so one shared\n * frozen instance is safe across every gesture. */\nexport const DEFAULT_RESIZE_BEHAVIORS: readonly BoundsConstraint<ResizePose>[] =\n Object.freeze([lockAspectWithModifier()]);\nexport { snapToGuides } from './snapToGuides';\nexport { pointSnapToGrid } from './pointSnapToGrid';\n","import type { ResizeAnchor } from '../../gestures/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\n\n/** Corner resize-handle: world-space center plus the anchor that pins the opposite corner during resize. */\nexport interface CornerHandle {\n cx: number;\n cy: number;\n anchor: ResizeAnchor;\n}\n\n/** Which AABB edge a corner sits on, per axis. `'min'` = the x/y origin\n * edge; `'max'` = the origin + extent edge. */\nexport type CornerEdge = 'min' | 'max';\n\n/** One entry of the canonical 4-corner resize layout. The single source of\n * truth for \"which corner maps to which anchor / handle-kind\". */\nexport interface CornerAnchor {\n /** Which x-edge the corner sits on (`'min'` = left, `'max'` = right). */\n xEdge: CornerEdge;\n /** Which y-edge the corner sits on (`'min'` = top, `'max'` = bottom). */\n yEdge: CornerEdge;\n /** Anchor that pins the diagonally-opposite (fixed) corner. Always the\n * axis-wise opposite of `{xEdge, yEdge}`. */\n anchor: ResizeAnchor;\n /** Affordance/handle discriminator string for this corner. */\n kind: string;\n /** Short positional tag (`'<xEdge>-<yEdge>'`), e.g. `'min-max'`. */\n tag: string;\n}\n\n/**\n * The single ordered corner→anchor table. Order is **load-bearing**: every\n * consumer iterates it in this sequence (TL, TR, BL, BR) so positional\n * meaning (the i-th handle / `kind` / `tag`) stays consistent across the\n * resize-handle affordance, the affordance-hit classifier, and\n * `cornerResizeHandles`. Each corner's `anchor` names the diagonally\n * opposite corner, so dragging the corner scales the box from that fixed\n * point.\n */\nexport const CORNER_ANCHORS: readonly CornerAnchor[] = [\n { xEdge: 'min', yEdge: 'min', anchor: { x: 'max', y: 'max' }, kind: 'handle:top-left', tag: 'min-min' },\n { xEdge: 'max', yEdge: 'min', anchor: { x: 'min', y: 'max' }, kind: 'handle:top-right', tag: 'max-min' },\n { xEdge: 'min', yEdge: 'max', anchor: { x: 'max', y: 'min' }, kind: 'handle:bottom-left', tag: 'min-max' },\n { xEdge: 'max', yEdge: 'max', anchor: { x: 'min', y: 'min' }, kind: 'handle:bottom-right', tag: 'max-max' },\n];\n\n/** Resolve a corner's world-space position for the given bounds. `xEdge`\n * `'max'` → `x + width`; `yEdge` `'max'` → `y + height`. */\nexport function cornerPoint(\n bounds: Bounds,\n entry: Pick<CornerAnchor, 'xEdge' | 'yEdge'>,\n): { x: number; y: number } {\n return {\n x: entry.xEdge === 'max' ? bounds.x + bounds.width : bounds.x,\n y: entry.yEdge === 'max' ? bounds.y + bounds.height : bounds.y,\n };\n}\n\n/** Standard 4-corner resize-handle layout: each corner pins the opposite\n * corner via an anchor of `{x: 'min'|'max', y: 'min'|'max'}` so dragging it\n * scales the box from the fixed opposite corner. Decodes {@link CORNER_ANCHORS}\n * against `bounds`. */\nexport function cornerResizeHandles(bounds: Bounds): CornerHandle[] {\n return CORNER_ANCHORS.map((c) => {\n const p = cornerPoint(bounds, c);\n return { cx: p.x, cy: p.y, anchor: c.anchor };\n });\n}\n\n/** Square hit-test for a handle: returns true if `(px, py)` is within\n * `radius` of the handle center on both axes. */\nexport function hitCornerHandle(\n handle: CornerHandle,\n px: number,\n py: number,\n radius: number,\n): boolean {\n return Math.abs(px - handle.cx) <= radius\n && Math.abs(py - handle.cy) <= radius;\n}\n\n/** The corner that does NOT move under a resize gesture with the given\n * anchor. Used by the rotated-resize math to pin a world-space invariant.\n *\n * Convention: `anchor.x === 'min'` means the min-x (left) edge is the\n * fixed anchor, so the fixed corner sits at `x = bounds.x` (min-x).\n * `anchor.x === 'max'` means the max-x (right) edge is fixed, so the\n * fixed corner sits at `x = bounds.x + bounds.width`. `'free'` on an\n * axis means that axis doesn't move; the fixed coord is the bounds\n * origin on that axis. */\nexport function fixedCornerOf(\n bounds: Bounds,\n anchor: ResizeAnchor,\n): { x: number; y: number } {\n return {\n x: anchor.x === 'max' ? bounds.x + bounds.width : bounds.x,\n y: anchor.y === 'max' ? bounds.y + bounds.height : bounds.y,\n };\n}\n"]}
|