@weasel-js/core 1.2.0 → 1.3.0-pre.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 +1508 -0
- package/dist/{DrawCommand-uNkv5TjO.d.ts → DrawCommand-BnZyQaDc.d.ts} +6 -189
- package/dist/chunk-2KKYDDDD.js +121 -0
- package/dist/chunk-2KKYDDDD.js.map +1 -0
- package/dist/{chunk-J7LUY47N.js → chunk-3FB44DBC.js} +3 -3
- package/dist/{chunk-J7LUY47N.js.map → chunk-3FB44DBC.js.map} +1 -1
- package/dist/{chunk-CKADTCHK.js → chunk-3ZV53ZW2.js} +51 -14
- package/dist/chunk-3ZV53ZW2.js.map +1 -0
- package/dist/{chunk-ADWOB5K2.js → chunk-67KE7SDP.js} +4 -4
- package/dist/{chunk-ADWOB5K2.js.map → chunk-67KE7SDP.js.map} +1 -1
- package/dist/{chunk-CSMF654J.js → chunk-AQS4JGRA.js} +50 -7
- package/dist/chunk-AQS4JGRA.js.map +1 -0
- package/dist/{chunk-HGF5X6MZ.js → chunk-BJN5ZRIJ.js} +2 -2
- package/dist/chunk-BJN5ZRIJ.js.map +1 -0
- package/dist/{chunk-4Z6J4IVH.js → chunk-F74NVEHT.js} +14 -46
- package/dist/chunk-F74NVEHT.js.map +1 -0
- package/dist/{chunk-7KYLIC3E.js → chunk-IFOVTYAK.js} +16611 -15749
- package/dist/chunk-IFOVTYAK.js.map +1 -0
- package/dist/{chunk-UGFFCMQP.js → chunk-MXFSHJOM.js} +4 -4
- package/dist/{chunk-UGFFCMQP.js.map → chunk-MXFSHJOM.js.map} +1 -1
- package/dist/{chunk-BHVYVFGV.js → chunk-ZB7UYJVG.js} +3 -3
- package/dist/{chunk-BHVYVFGV.js.map → chunk-ZB7UYJVG.js.map} +1 -1
- package/dist/clipboard.d.ts +3 -3
- package/dist/clipboard.js +3 -3
- package/dist/clone.d.ts +2 -2
- package/dist/clone.js +3 -3
- package/dist/{geometry-C56YbnfD.d.ts → geometry-Dtt_k6Dq.d.ts} +1 -1
- package/dist/{grid-DqOi0Vgi.d.ts → grid-nnXU4VjN.d.ts} +1 -1
- package/dist/index.d.ts +1510 -1292
- package/dist/index.js +10 -10
- package/dist/insert.d.ts +3 -3
- package/dist/insert.js +1 -1
- package/dist/move.d.ts +4 -4
- package/dist/move.js +3 -3
- package/dist/{options-C6HYCKP7.d.ts → options-CdFl510T.d.ts} +1 -1
- package/dist/{path-B6MMiodD.d.ts → path-JEV2c5If.d.ts} +2 -13
- package/dist/patterns-builtin.d.ts +1 -1
- package/dist/patterns-builtin.js +1 -1
- package/dist/{pointSnapToGrid-BBtn5bus.d.ts → pointSnapToGrid-Dmthv94u.d.ts} +2 -2
- package/dist/{registry-l3JX0Z6b.d.ts → registry-BU1_3L_C.d.ts} +629 -34
- package/dist/renderer.d.ts +38 -37
- package/dist/renderer.js +10 -10
- package/dist/resize.d.ts +4 -4
- package/dist/resize.js +2 -2
- package/dist/routing.d.ts +35 -9
- package/dist/routing.js +1 -1
- package/dist/{stroke-DaxnQ-u7.d.ts → stroke-5pBwOm9s.d.ts} +41 -4
- package/dist/{types-C2R2bjxf.d.ts → types-DIQAisSG.d.ts} +99 -5
- package/dist/{types-DbysOO-2.d.ts → types-XBcDEp3Y.d.ts} +14 -9
- package/dist/{types-BhrifbZ1.d.ts → types-miXHGrZM.d.ts} +1 -1
- package/package.json +8 -6
- package/dist/chunk-4Z6J4IVH.js.map +0 -1
- package/dist/chunk-7KYLIC3E.js.map +0 -1
- package/dist/chunk-CKADTCHK.js.map +0 -1
- package/dist/chunk-CSMF654J.js.map +0 -1
- package/dist/chunk-GVCNT7UH.js +0 -47
- package/dist/chunk-GVCNT7UH.js.map +0 -1
- package/dist/chunk-HGF5X6MZ.js.map +0 -1
- package/dist/paint-types-1fUdZUaH.d.ts +0 -266
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { P as Path } from './path-
|
|
2
|
-
import {
|
|
1
|
+
import { P as Path } from './path-JEV2c5If.js';
|
|
2
|
+
import { TextureHandle, FillStyle, Stroke } from '@weasel-js/paint';
|
|
3
|
+
import { ResolvedRun, TextStyle, TextVerticalAlign } from '@weasel-js/text';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 2D affine matrix utilities. Column-major 9-element Float32Array, matching
|
|
@@ -55,190 +56,6 @@ declare const mat3: {
|
|
|
55
56
|
meanScaleOf: typeof meanScaleOf;
|
|
56
57
|
};
|
|
57
58
|
|
|
58
|
-
/**
|
|
59
|
-
* Typography for `TextPose` and friends. Every field is optional; consumers
|
|
60
|
-
* pass `{}` or override the few they care about. Defaults live in
|
|
61
|
-
* `DEFAULT_TEXT_STYLE` and are applied at render/measure time, never written
|
|
62
|
-
* back to the pose.
|
|
63
|
-
*
|
|
64
|
-
* `fill` follows the kit-wide `FillStyle` model — solid color or pattern. The
|
|
65
|
-
* contenteditable edit overlay flattens non-solid fills to `'#000'` for CSS
|
|
66
|
-
* since the browser can't paint with a texture handle.
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
|
-
/** User-facing text style. All fields optional; defaults applied at render time via `resolveTextStyle`. */
|
|
70
|
-
interface TextStyle {
|
|
71
|
-
/** Font size in world units. Default 16. */
|
|
72
|
-
fontSize?: number;
|
|
73
|
-
/** Default `'sans-serif'`. */
|
|
74
|
-
fontFamily?: string;
|
|
75
|
-
/** Default 400. */
|
|
76
|
-
fontWeight?: number | string;
|
|
77
|
-
/** Default `'normal'`. */
|
|
78
|
-
fontStyle?: 'normal' | 'italic';
|
|
79
|
-
/** Default `'left'`. */
|
|
80
|
-
align?: 'left' | 'center' | 'right';
|
|
81
|
-
/** Multiplier applied to `fontSize`. Default 1.2. */
|
|
82
|
-
lineHeight?: number;
|
|
83
|
-
/** Default `{ fill: 'solid', color: '#000' }`. */
|
|
84
|
-
fill?: FillStyle;
|
|
85
|
-
/**
|
|
86
|
-
* Caret color used by the edit overlay. Defaults to the text color when
|
|
87
|
-
* `fill` is solid; falls back to `#000` for non-solid paints.
|
|
88
|
-
*/
|
|
89
|
-
caretColor?: string;
|
|
90
|
-
/**
|
|
91
|
-
* Selection background color used by the edit overlay's `::selection`
|
|
92
|
-
* pseudo-element. Defaults to a 25%-opacity tint of `caretColor` via CSS
|
|
93
|
-
* `color-mix`. Pass `'none'` to fall back to the browser-native highlight.
|
|
94
|
-
*/
|
|
95
|
-
selectionBackground?: string;
|
|
96
|
-
/** Selection text color paired with `selectionBackground`. Default: inherits text color. */
|
|
97
|
-
selectionColor?: string;
|
|
98
|
-
/** Extra advance added after each glyph, in world units. Default 0. */
|
|
99
|
-
letterSpacing?: number;
|
|
100
|
-
/** Default `false`. */
|
|
101
|
-
underline?: boolean;
|
|
102
|
-
/** Default `false`. */
|
|
103
|
-
strikethrough?: boolean;
|
|
104
|
-
/**
|
|
105
|
-
* Outline painted over the glyph fill. Omitted (the default) means no
|
|
106
|
-
* outline — there is no such thing as a default text stroke.
|
|
107
|
-
*
|
|
108
|
-
* Only glyphs on the outline tier are stroked: above
|
|
109
|
-
* `textOutlineMinScreenSize` a glyph is a real `PolygonPath`, so it gets
|
|
110
|
-
* the ordinary tessellated ribbon with real joins, caps and miters, in any
|
|
111
|
-
* paint. Below it a glyph is a sampled distance field with no geometry to
|
|
112
|
-
* stroke, and it renders unstroked rather than approximated. `width` is in
|
|
113
|
-
* world units, like every other stroke in the kit — it does not scale with
|
|
114
|
-
* `fontSize`.
|
|
115
|
-
*/
|
|
116
|
-
stroke?: Stroke;
|
|
117
|
-
}
|
|
118
|
-
/** `TextStyle` with all fields filled in from defaults — what the renderer actually consumes. */
|
|
119
|
-
interface ResolvedTextStyle {
|
|
120
|
-
fontSize: number;
|
|
121
|
-
fontFamily: string;
|
|
122
|
-
fontWeight: number | string;
|
|
123
|
-
fontStyle: 'normal' | 'italic';
|
|
124
|
-
align: 'left' | 'center' | 'right';
|
|
125
|
-
lineHeight: number;
|
|
126
|
-
fill: FillStyle;
|
|
127
|
-
caretColor: string;
|
|
128
|
-
selectionBackground: string | null;
|
|
129
|
-
selectionColor: string | null;
|
|
130
|
-
letterSpacing: number;
|
|
131
|
-
underline: boolean;
|
|
132
|
-
strikethrough: boolean;
|
|
133
|
-
/** Absent means no outline — unlike the other fields, this one has no
|
|
134
|
-
* default to fall back to. See {@link TextStyle.stroke}. */
|
|
135
|
-
stroke?: Stroke;
|
|
136
|
-
}
|
|
137
|
-
/** Default resolved style used when a `TextPose` omits `style`. */
|
|
138
|
-
declare const DEFAULT_TEXT_STYLE: ResolvedTextStyle;
|
|
139
|
-
/** Fill in a partial `TextStyle` with defaults from `DEFAULT_TEXT_STYLE`. */
|
|
140
|
-
declare function resolveTextStyle(style?: TextStyle): ResolvedTextStyle;
|
|
141
|
-
/** Build a CSS `font` shorthand suitable for `ctx.font`. */
|
|
142
|
-
declare function fontString(s: ResolvedTextStyle): string;
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Canonical inline-styling primitive for text nodes. A node's text is
|
|
146
|
-
* either a plain `string` (treated as a single-run, default-styled fragment)
|
|
147
|
-
* or `StyledRun[]` for rich content. `toRuns` is the funnel that normalizes
|
|
148
|
-
* either form into the array shape used by the renderer.
|
|
149
|
-
*
|
|
150
|
-
* Every field except `text` is optional; missing fields fall back to the
|
|
151
|
-
* node-level `TextStyle`. `bold`/`italic` are toggles; richer weight axes
|
|
152
|
-
* (300/500/900) are out of scope for slice 1.
|
|
153
|
-
*/
|
|
154
|
-
|
|
155
|
-
/** A span of text with its own styling, as authored. Fields left absent
|
|
156
|
-
* inherit from the node's text style — this is the difference between a run
|
|
157
|
-
* and a fully resolved one. */
|
|
158
|
-
interface StyledRun {
|
|
159
|
-
text: string;
|
|
160
|
-
bold?: boolean;
|
|
161
|
-
italic?: boolean;
|
|
162
|
-
fontFamily?: string;
|
|
163
|
-
fontSize?: number;
|
|
164
|
-
fill?: FillStyle;
|
|
165
|
-
/** Outline over this run's glyphs. Overrides the node style's stroke;
|
|
166
|
-
* absent inherits it. Only painted on the outline tier — see
|
|
167
|
-
* {@link TextStyle.stroke}. */
|
|
168
|
-
stroke?: Stroke;
|
|
169
|
-
letterSpacing?: number;
|
|
170
|
-
underline?: boolean;
|
|
171
|
-
strikethrough?: boolean;
|
|
172
|
-
}
|
|
173
|
-
/** Normalize the two accepted spellings of text content — a plain string or
|
|
174
|
-
* an array of runs — to runs. Throws on a run with no string `text`. */
|
|
175
|
-
declare function toRuns(input: string | StyledRun[]): StyledRun[];
|
|
176
|
-
/** Concatenate runs, dropping all styling. */
|
|
177
|
-
declare function runsToPlainText(runs: readonly StyledRun[]): string;
|
|
178
|
-
/** Render runs as Markdown, preserving bold and italic. The flavor written to
|
|
179
|
-
* the clipboard alongside the plain-text one. */
|
|
180
|
-
declare function runsToMarkdown(runs: readonly StyledRun[]): string;
|
|
181
|
-
/**
|
|
182
|
-
* Parse a small markdown subset (`**bold**`, `*italic*`, `***both***`) into
|
|
183
|
-
* styled runs. Backslash escapes `\*` and `\\`. Newlines are preserved as
|
|
184
|
-
* literal characters inside a run — they're not run-boundary markers in
|
|
185
|
-
* this format.
|
|
186
|
-
*/
|
|
187
|
-
declare function markdownToRuns(input: string): StyledRun[];
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Apply node-level `ResolvedTextStyle` defaults to each `StyledRun`,
|
|
191
|
-
* producing a fully-resolved run with every styling field set. Downstream
|
|
192
|
-
* layout and draw never re-resolve defaults — `ResolvedRun` is the
|
|
193
|
-
* canonical shape the renderer consumes.
|
|
194
|
-
*
|
|
195
|
-
* `bold`/`italic` toggles on a run are folded into `fontWeight`/`fontStyle`:
|
|
196
|
-
* `bold: true` → fontWeight 700, `italic: true` → fontStyle 'italic'.
|
|
197
|
-
* Explicit `fontFamily` / `fontSize` / `fill` / `letterSpacing` on the run
|
|
198
|
-
* override the node-level value (`letterSpacing: 0` on a run is an override,
|
|
199
|
-
* not an absence — it zeroes inherited tracking).
|
|
200
|
-
*
|
|
201
|
-
* `underline` / `strikethrough` are *additive*, like `bold`/`italic`: a run
|
|
202
|
-
* can turn a decoration on but never off, so they resolve as
|
|
203
|
-
* `run.x || style.x` and not `run.x ?? style.x`. See the header of
|
|
204
|
-
* `runs/rangeStyle.ts` for why the model collapses the tri-state.
|
|
205
|
-
*/
|
|
206
|
-
|
|
207
|
-
/** A run with every style resolved against the node's text style — no
|
|
208
|
-
* optional inheritance left. This is what layout and painting consume. */
|
|
209
|
-
interface ResolvedRun {
|
|
210
|
-
text: string;
|
|
211
|
-
fontFamily: string;
|
|
212
|
-
fontSize: number;
|
|
213
|
-
fontWeight: number;
|
|
214
|
-
fontStyle: 'normal' | 'italic';
|
|
215
|
-
fill: FillStyle;
|
|
216
|
-
/** Outline over this run's glyphs, or absent for none. Painted only on the
|
|
217
|
-
* outline tier — a distance field has no geometry to stroke. */
|
|
218
|
-
stroke?: Stroke;
|
|
219
|
-
/** Extra advance added after each glyph of this run, in world units. */
|
|
220
|
-
letterSpacing: number;
|
|
221
|
-
/** Draw a rule below this run's baseline. Additive over the node style. */
|
|
222
|
-
underline: boolean;
|
|
223
|
-
/** Draw a rule through this run's x-height. Additive over the node style. */
|
|
224
|
-
strikethrough: boolean;
|
|
225
|
-
}
|
|
226
|
-
/** Resolve each run's styling against the node's text style, filling in
|
|
227
|
-
* everything the run left inherited. */
|
|
228
|
-
declare function resolveRuns(runs: readonly StyledRun[], style: ResolvedTextStyle): ResolvedRun[];
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Box vertical alignment for text draw commands. Given the command's box
|
|
232
|
-
* `height` and the laid-out text block's height, returns the Y offset to
|
|
233
|
-
* apply to every quad. `'top'` (or an undefined `align`, or a missing box
|
|
234
|
-
* `height`) is the legacy behavior: offset 0.
|
|
235
|
-
*/
|
|
236
|
-
type TextVerticalAlign = 'top' | 'center' | 'bottom';
|
|
237
|
-
/** How far to shift laid-out text down inside a box of `boxHeight` to satisfy
|
|
238
|
-
* a vertical alignment. Zero when the box height is unknown or alignment is
|
|
239
|
-
* top. */
|
|
240
|
-
declare function verticalAlignOffset(align: TextVerticalAlign | undefined, boxHeight: number | undefined, textHeight: number): number;
|
|
241
|
-
|
|
242
59
|
/**
|
|
243
60
|
* registerProgram — public API for registering custom shader programs.
|
|
244
61
|
*
|
|
@@ -247,7 +64,7 @@ declare function verticalAlignOffset(align: TextVerticalAlign | undefined, boxHe
|
|
|
247
64
|
* calls getProgramSource() and compiles the result. This keeps registerProgram
|
|
248
65
|
* GL-context-agnostic — identical pattern to registerFont storing ImageBitmap.
|
|
249
66
|
*
|
|
250
|
-
*
|
|
67
|
+
* Module-level state = source strings only; compiled GL
|
|
251
68
|
* programs live on each renderer's programRegistry (Map<id, ShaderProgram>).
|
|
252
69
|
*
|
|
253
70
|
* Lifecycle: program sources live for the module lifetime. No unregister in v1.
|
|
@@ -278,7 +95,7 @@ type ShaderUniform = number | [number, number] | [number, number, number] | [num
|
|
|
278
95
|
* (recommended). The kit's vertex shader exposes `v_uv`, `v_screen`, and
|
|
279
96
|
* `v_world` varyings plus `u_bounds` and `u_view` uniforms.
|
|
280
97
|
*
|
|
281
|
-
* **IMPORTANT — Premultiplied alpha
|
|
98
|
+
* **IMPORTANT — Premultiplied alpha:**
|
|
282
99
|
* Your fragment shader MUST output premultiplied alpha:
|
|
283
100
|
* `outColor = vec4(rgb * a, a);` ← correct
|
|
284
101
|
* `outColor = vec4(rgb, a);` ← WRONG — over-brightens translucent regions
|
|
@@ -436,4 +253,4 @@ interface ShaderDrawCommand {
|
|
|
436
253
|
};
|
|
437
254
|
}
|
|
438
255
|
|
|
439
|
-
export { type DrawCommand as D, type GroupDrawCommand as G, type ImageDrawCommand as I, type Mat3 as M, type PathDrawCommand as P, type
|
|
256
|
+
export { type DrawCommand as D, type GroupDrawCommand as G, type ImageDrawCommand as I, type Mat3 as M, type PathDrawCommand as P, type ShaderProgramHandle as S, type TextDrawCommand as T, type ShaderDrawCommand as a, type ShaderUniform as b, type SolidPaint as c, mat3 as m, registerProgram as r };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/core/ops/registry.ts
|
|
2
|
+
var FACTORIES = /* @__PURE__ */ new Map();
|
|
3
|
+
function registerOpFactory(name, build) {
|
|
4
|
+
FACTORIES.set(name, build);
|
|
5
|
+
}
|
|
6
|
+
function rebuildOp(name, args) {
|
|
7
|
+
const f = FACTORIES.get(name);
|
|
8
|
+
return f ? f(args) : null;
|
|
9
|
+
}
|
|
10
|
+
function registeredOpNames() {
|
|
11
|
+
return [...FACTORIES.keys()];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/core/ops/slot.ts
|
|
15
|
+
function captureSlot(a, parentId, id) {
|
|
16
|
+
const siblings = a.getChildren?.(parentId);
|
|
17
|
+
if (!siblings) return null;
|
|
18
|
+
const i = siblings.indexOf(id);
|
|
19
|
+
if (i < 0) return null;
|
|
20
|
+
return { index: i, before: i + 1 < siblings.length ? siblings[i + 1] : null };
|
|
21
|
+
}
|
|
22
|
+
function resolveSlot(siblings, slot) {
|
|
23
|
+
if (slot.before === null) return void 0;
|
|
24
|
+
if (slot.before !== void 0 && siblings) {
|
|
25
|
+
const at = siblings.indexOf(slot.before);
|
|
26
|
+
if (at >= 0) return at;
|
|
27
|
+
}
|
|
28
|
+
return slot.index >= 0 ? slot.index : void 0;
|
|
29
|
+
}
|
|
30
|
+
function slotFromIndex(index) {
|
|
31
|
+
return { index: index ?? -1 };
|
|
32
|
+
}
|
|
33
|
+
function parentOf(node) {
|
|
34
|
+
return node.parent ?? null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/core/ops/create.ts
|
|
38
|
+
function createInsertOp(args) {
|
|
39
|
+
const { node, label } = args;
|
|
40
|
+
const slot = args.slot ?? slotFromIndex(args.index);
|
|
41
|
+
return {
|
|
42
|
+
name: "insert",
|
|
43
|
+
args: { node, label, slot },
|
|
44
|
+
label,
|
|
45
|
+
apply(adapter) {
|
|
46
|
+
const a = adapter;
|
|
47
|
+
const parentId = parentOf(node);
|
|
48
|
+
a.insertNode(node, resolveSlot(a.getChildren?.(parentId), slot));
|
|
49
|
+
},
|
|
50
|
+
invert() {
|
|
51
|
+
return createDeleteOp({ node, label, slot });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
registerOpFactory("insert", (args) => createInsertOp(args));
|
|
56
|
+
|
|
57
|
+
// src/core/ops/delete.ts
|
|
58
|
+
function captureDescendants(a, rootId) {
|
|
59
|
+
const { getChildren, getNode } = a;
|
|
60
|
+
if (!getChildren || !getNode) return null;
|
|
61
|
+
const out = [];
|
|
62
|
+
const walk = (parentId) => {
|
|
63
|
+
const kids = getChildren.call(a, parentId);
|
|
64
|
+
for (let i = 0; i < kids.length; i++) {
|
|
65
|
+
const child = getNode.call(a, kids[i]);
|
|
66
|
+
if (!child) continue;
|
|
67
|
+
out.push({ node: child, index: i });
|
|
68
|
+
walk(kids[i]);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
walk(rootId);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
function createDeleteOp(args) {
|
|
75
|
+
const { node, label } = args;
|
|
76
|
+
let slot = args.slot ?? slotFromIndex(args.index);
|
|
77
|
+
const argsForSerial = { node, label, slot, descendants: args.descendants };
|
|
78
|
+
let captured = args.descendants ?? [];
|
|
79
|
+
return {
|
|
80
|
+
name: "delete",
|
|
81
|
+
args: argsForSerial,
|
|
82
|
+
label,
|
|
83
|
+
apply(adapter) {
|
|
84
|
+
const a = adapter;
|
|
85
|
+
const observed = captureSlot(a, parentOf(node), node.id);
|
|
86
|
+
if (observed) {
|
|
87
|
+
slot = observed;
|
|
88
|
+
argsForSerial.slot = observed;
|
|
89
|
+
}
|
|
90
|
+
const snapshot = captureDescendants(a, node.id);
|
|
91
|
+
if (snapshot) {
|
|
92
|
+
captured = snapshot;
|
|
93
|
+
argsForSerial.descendants = snapshot;
|
|
94
|
+
}
|
|
95
|
+
a.removeNode(node.id);
|
|
96
|
+
},
|
|
97
|
+
invert() {
|
|
98
|
+
const subtree = captured;
|
|
99
|
+
const restoredSlot = slot;
|
|
100
|
+
const reinsert = createInsertOp({ node, label, slot: restoredSlot });
|
|
101
|
+
if (subtree.length === 0) return reinsert;
|
|
102
|
+
return {
|
|
103
|
+
label,
|
|
104
|
+
apply(adapter) {
|
|
105
|
+
reinsert.apply(adapter);
|
|
106
|
+
const a = adapter;
|
|
107
|
+
for (const entry of subtree) {
|
|
108
|
+
if (a.getNode?.(entry.node.id)) continue;
|
|
109
|
+
a.insertNode(entry.node, entry.index);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
invert: () => createDeleteOp({ node, label, slot: restoredSlot, descendants: subtree })
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
registerOpFactory("delete", (args) => createDeleteOp(args));
|
|
118
|
+
|
|
119
|
+
export { captureSlot, createDeleteOp, createInsertOp, rebuildOp, registerOpFactory, registeredOpNames, resolveSlot, slotFromIndex };
|
|
120
|
+
//# sourceMappingURL=chunk-2KKYDDDD.js.map
|
|
121
|
+
//# sourceMappingURL=chunk-2KKYDDDD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/ops/registry.ts","../src/core/ops/slot.ts","../src/core/ops/create.ts","../src/core/ops/delete.ts"],"names":[],"mappings":"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","file":"chunk-2KKYDDDD.js","sourcesContent":["// Op-factory registry — pairs a stable `name` (stamped onto each op by its\n// factory) with a `(args) => Op` rebuilder so a `{ name, args }` snapshot\n// can be reconstituted into a live `Op` whose `apply`/`invert` closures\n// behave identically to the original.\n//\n// The kit calls `registerOpFactory` at module-init time for every built-in\n// factory (see `./create.ts`, `./delete.ts`, `./transform.ts`, etc.).\n// Consumers wanting their custom ops to survive `History.serialize()` /\n// `History.restore()` register their factories at app bootstrap, before\n// `createHistory` is called.\n\nimport type { Op } from './types';\n\ntype Builder = (args: unknown) => Op;\n\nconst FACTORIES = new Map<string, Builder>();\n\n/** Register an op-factory builder under a stable name. The builder receives\n * the serialized `args` and must return an `Op` equivalent to one freshly\n * constructed at the original call site (`apply` and `invert` behave the\n * same). Idempotent on the same `(name, build)` pair; a second registration\n * with a different builder silently overwrites — kit modules import each\n * factory exactly once at init, so this never races in practice. */\nexport function registerOpFactory<A>(name: string, build: (args: A) => Op): void {\n FACTORIES.set(name, build as Builder);\n}\n\n/** Rebuild an op from its serialized `(name, args)`. Returns `null` when\n * `name` isn't registered — callers handle this (typically by substituting\n * a no-op placeholder so stack ordering survives across kit version skew). */\nexport function rebuildOp(name: string, args: unknown): Op | null {\n const f = FACTORIES.get(name);\n return f ? f(args) : null;\n}\n\n/** Test-only: drop all registrations. Not exported from the kit barrel — the\n * built-in factories self-register at module init, so production code never\n * needs to clear the table. Exposed for unit tests that want to assert the\n * unknown-name fallback path. */\nexport function _clearOpFactoriesForTest(): void {\n FACTORIES.clear();\n}\n\n/** Every op name currently registered, in registration order. The kit's\n * built-ins self-register at module init, so importing the barrel is enough\n * to see all of them; consumer factories appear once their module has run. */\nexport function registeredOpNames(): readonly string[] {\n return [...FACTORIES.keys()];\n}\n","/**\n * A node's position among its siblings, recorded so an op can put it back.\n *\n * Recorded as an anchor (`before`) plus an ordinal (`index`). The anchor is\n * the source of truth: `History` replays a batch's inverses in reverse order,\n * so an ordinal captured before the mutation is replayed against a list that\n * has not been refilled yet and clamps to the end, while \"put this back before\n * `e`\" stays true however much else has already moved. The ordinal is the\n * fallback for the cases the anchor cannot cover — an adapter with no\n * `getChildren` seam, or an anchor that is itself gone by replay time.\n */\n\n/** Sibling-order read seam. Optional everywhere: a flat adapter has none. */\nexport interface OrderedReader {\n getChildren?(parentId: string | null): string[];\n}\n\nexport interface Slot {\n /** Ordinal among the siblings. `-1` means \"nowhere in particular\". */\n index: number;\n /** Id of the sibling that followed at capture time; `null` when the node\n * was last. Absent when nothing observed it — `null` survives JSON and\n * `undefined` does not, so the two stay distinguishable across a\n * serialize/restore round trip. */\n before?: string | null;\n}\n\n/** Observe `id`'s slot among `parentId`'s children. `null` when the adapter\n * has no ordering seam or `id` isn't there — callers keep whatever weaker\n * slot they already had rather than overwriting it with a guess. */\nexport function captureSlot(\n a: OrderedReader,\n parentId: string | null,\n id: string,\n): Slot | null {\n const siblings = a.getChildren?.(parentId);\n if (!siblings) return null;\n const i = siblings.indexOf(id);\n if (i < 0) return null;\n return { index: i, before: i + 1 < siblings.length ? siblings[i + 1]! : null };\n}\n\n/**\n * Index `slot` denotes in `siblings`, which must NOT contain the node being\n * placed. `undefined` means \"no position\" — the caller appends, or lets the\n * adapter's default placement stand.\n */\nexport function resolveSlot(\n siblings: readonly string[] | undefined,\n slot: Slot,\n): number | undefined {\n if (slot.before === null) return undefined;\n if (slot.before !== undefined && siblings) {\n const at = siblings.indexOf(slot.before);\n if (at >= 0) return at;\n }\n return slot.index >= 0 ? slot.index : undefined;\n}\n\n/** Normalize the `index` sugar every op factory accepts into a `Slot`. */\nexport function slotFromIndex(index: number | undefined): Slot {\n return { index: index ?? -1 };\n}\n\n/** The parent a node claims. Nodes in a flat adapter have none. */\nexport function parentOf(node: object): string | null {\n return ((node as { parent?: unknown }).parent ?? null) as string | null;\n}\n","import type { Op } from './types';\nimport { createDeleteOp } from './delete';\nimport { registerOpFactory } from './registry';\nimport { parentOf, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface InsertAdapter<TNode> extends OrderedReader {\n /** When `index` is supplied, insert at that position; adapters that ignore\n * the second arg (or don't expose it) fall back to their default\n * placement (typically append-to-end). */\n insertNode(node: TNode, index?: number): void;\n}\n\n/** Type alias for ops produced by `createInsertOp`. Carries no extra type info today;\n * exists so consumers can name the op type when needed. */\nexport type InsertOp = Op;\n\n/** @internal */\ninterface InsertArgs<TNode extends { id: string }> {\n node: TNode;\n label?: string;\n /** Sibling ordinal to insert at. Sugar for `slot: { index }` — the weaker\n * of the two forms, and all a fresh-insert callsite can know. */\n index?: number;\n /** Full slot, anchor included. Produced only by `captureSlot` (via a\n * delete op's `invert`), and supersedes `index` when present. */\n slot?: Slot;\n}\n\n/** Op: insert `node` into the scene; inverts to a delete of the same id. */\nexport function createInsertOp<TNode extends { id: string }>(args: InsertArgs<TNode>): InsertOp {\n const { node, label } = args;\n const slot: Slot = args.slot ?? slotFromIndex(args.index);\n return {\n name: 'insert',\n args: { node, label, slot },\n label,\n apply(adapter) {\n const a = adapter as InsertAdapter<TNode>;\n const parentId = parentOf(node);\n a.insertNode(node, resolveSlot(a.getChildren?.(parentId), slot));\n },\n invert() {\n return createDeleteOp({ node, label, slot });\n },\n };\n}\n\nregisterOpFactory<InsertArgs<{ id: string }>>('insert', (args) => createInsertOp(args));\n","import type { Op } from './types';\nimport { createInsertOp } from './create';\nimport { registerOpFactory } from './registry';\nimport { captureSlot, parentOf, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface DeleteAdapter<TNode> extends OrderedReader {\n removeNode(id: string): void;\n /** Subtree reads, used to snapshot descendants before the cascade. Optional\n * — a flat adapter has neither, and a delete there is a single node. */\n getNode?(id: string): TNode | undefined;\n}\n\ninterface InsertAdapter<TNode> {\n insertNode(node: TNode, index?: number): void;\n getNode?(id: string): TNode | undefined;\n}\n\n/** A captured node plus its slot in its own parent's child list. */\ninterface Placed<TNode> {\n node: TNode;\n index: number;\n}\n\n/** @internal */\ninterface DeleteArgs<TNode extends { id: string }> {\n node: TNode;\n label?: string;\n /** Sibling ordinal the node occupies at the moment of delete. Sugar for\n * `slot: { index }`, and only a seed: `apply` re-observes the node's full\n * slot through the adapter and that observation supersedes this. Kept\n * because it is the whole story for a flat adapter with no `getChildren`,\n * where nothing can be observed. */\n index?: number;\n /** Full slot, anchor included. Written by `apply`; supersedes `index`. */\n slot?: Slot;\n /** Descendants of `node`, preorder (parents before children). `removeNode`\n * cascades the whole subtree, so an inverse that re-inserts `node` alone\n * brings the container back empty and drops every child on the floor.\n * Captured on apply; mirrored into the serialized args so a rebuilt op\n * can invert without having run. */\n descendants?: Placed<TNode>[];\n}\n\n/** Walk `rootId`'s descendants preorder. Returns `null` when the adapter\n * can't enumerate children, which leaves any previously captured snapshot\n * in place rather than clobbering it with an empty one. */\nfunction captureDescendants<TNode extends { id: string }>(\n a: DeleteAdapter<TNode>,\n rootId: string,\n): Placed<TNode>[] | null {\n const { getChildren, getNode } = a;\n if (!getChildren || !getNode) return null;\n const out: Placed<TNode>[] = [];\n const walk = (parentId: string): void => {\n const kids = getChildren.call(a, parentId);\n for (let i = 0; i < kids.length; i++) {\n const child = getNode.call(a, kids[i]);\n if (!child) continue;\n out.push({ node: child, index: i });\n walk(kids[i]);\n }\n };\n walk(rootId);\n return out;\n}\n\n/** Op: remove `node` and its subtree from the scene; inverts to a re-insert\n * of the whole subtree at its captured slots. */\nexport function createDeleteOp<TNode extends { id: string }>(args: DeleteArgs<TNode>): Op {\n const { node, label } = args;\n let slot: Slot = args.slot ?? slotFromIndex(args.index);\n const argsForSerial: DeleteArgs<TNode> = { node, label, slot, descendants: args.descendants };\n let captured: Placed<TNode>[] = args.descendants ?? [];\n return {\n name: 'delete',\n args: argsForSerial,\n label,\n apply(adapter) {\n const a = adapter as DeleteAdapter<TNode>;\n const observed = captureSlot(a, parentOf(node), node.id);\n if (observed) {\n slot = observed;\n argsForSerial.slot = observed;\n }\n const snapshot = captureDescendants(a, node.id);\n if (snapshot) {\n captured = snapshot;\n argsForSerial.descendants = snapshot;\n }\n a.removeNode(node.id);\n },\n invert() {\n const subtree = captured;\n const restoredSlot = slot;\n const reinsert = createInsertOp<TNode>({ node, label, slot: restoredSlot });\n if (subtree.length === 0) return reinsert;\n return {\n label,\n apply(adapter) {\n reinsert.apply(adapter);\n const a = adapter as InsertAdapter<TNode>;\n for (const entry of subtree) {\n // A flat adapter's removeNode doesn't cascade, so its children\n // were never gone — re-inserting them would duplicate them.\n if (a.getNode?.(entry.node.id)) continue;\n a.insertNode(entry.node, entry.index);\n }\n },\n invert: () =>\n createDeleteOp<TNode>({ node, label, slot: restoredSlot, descendants: subtree }),\n };\n },\n };\n}\n\nregisterOpFactory<DeleteArgs<{ id: string }>>('delete', (args) => createDeleteOp(args));\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_GUIDE_TOLERANCE_PX, pxExtent } from './chunk-
|
|
1
|
+
import { DEFAULT_GUIDE_TOLERANCE_PX, pxExtent } from './chunk-F74NVEHT.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-3FB44DBC.js.map
|
|
248
|
+
//# sourceMappingURL=chunk-3FB44DBC.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-J7LUY47N.js","sourcesContent":["import type { BoundsConstraint, ResizePose } from '../../../gestures/types';\n\n/** Resize constraint that stops a node shrinking past a minimum. The anchored\n * edge stays put, so clamping pushes the dragged edge back rather than moving\n * the node. */\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\n/** Resize constraint that snaps the moving edges to a grid, leaving the\n * anchored ones where they are. By default a node already smaller than one\n * grid cell on an axis is left unsnapped on that axis, so it stays resizable\n * at sub-grid sizes. */\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 { pxExtent } from 'core/viewport/pxExtent';\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 // Per axis: a vertical guide is matched by a horizontal distance, so it\n // answers to `scale.x` alone.\n const tol = getView ? pxExtent(tolerance, getView().scale) : { x: tolerance, y: 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 <= tol.x && 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 <= tol.y && 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\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 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-3FB44DBC.js","sourcesContent":["import type { BoundsConstraint, ResizePose } from '../../../gestures/types';\n\n/** Resize constraint that stops a node shrinking past a minimum. The anchored\n * edge stays put, so clamping pushes the dragged edge back rather than moving\n * the node. */\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\n/** Resize constraint that snaps the moving edges to a grid, leaving the\n * anchored ones where they are. By default a node already smaller than one\n * grid cell on an axis is left unsnapped on that axis, so it stays resizable\n * at sub-grid sizes. */\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 { pxExtent } from 'core/viewport/pxExtent';\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 // Per axis: a vertical guide is matched by a horizontal distance, so it\n // answers to `scale.x` alone.\n const tol = getView ? pxExtent(tolerance, getView().scale) : { x: tolerance, y: 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 <= tol.x && 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 <= tol.y && 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\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 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,6 +1,6 @@
|
|
|
1
1
|
import { createContext, useRef, useMemo, useContext, useEffect } from 'react';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { isKnownGestureName, getGestureDescriptor, canonicalModifiers, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES
|
|
3
|
+
import { isKnownGestureName, getGestureDescriptor, formatRoute, canonicalModifiers, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from '@weasel-js/gestures';
|
|
4
4
|
export { GESTURE_DESCRIPTORS, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatPhaseAtom, formatRoute, getGestureDescriptor, isKnownGestureName, parseRoute } from '@weasel-js/gestures';
|
|
5
5
|
|
|
6
6
|
// src/interactions/actions/depRegistry.tsx
|
|
@@ -254,13 +254,27 @@ function ActionsProvider({ children }) {
|
|
|
254
254
|
}, []);
|
|
255
255
|
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: registry, children });
|
|
256
256
|
}
|
|
257
|
+
var IS_DEV = (() => {
|
|
258
|
+
try {
|
|
259
|
+
return Boolean(import.meta.env?.DEV);
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
})();
|
|
257
264
|
function useActionsRegistry() {
|
|
258
265
|
return useContext(ActionsContext);
|
|
259
266
|
}
|
|
260
267
|
function useAction(action) {
|
|
261
268
|
const reg = useActionsRegistry();
|
|
262
269
|
useEffect(() => {
|
|
263
|
-
if (!reg)
|
|
270
|
+
if (!reg) {
|
|
271
|
+
if (IS_DEV) {
|
|
272
|
+
console.warn(
|
|
273
|
+
`useAction("${action.id}"): no <ActionsProvider> is in scope, so the action was not registered and its bindings will never fire.`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
264
278
|
return reg.register(action);
|
|
265
279
|
}, [reg, action]);
|
|
266
280
|
}
|
|
@@ -292,6 +306,20 @@ function buildRouteRegistry(tools) {
|
|
|
292
306
|
}
|
|
293
307
|
return out;
|
|
294
308
|
}
|
|
309
|
+
function routeGestureForSpecKind(kind) {
|
|
310
|
+
const gesture = SPEC_KIND_TO_GESTURE[kind];
|
|
311
|
+
return gesture && isKnownGestureName(gesture) ? gesture : void 0;
|
|
312
|
+
}
|
|
313
|
+
function routesForSpec(spec) {
|
|
314
|
+
const gesture = routeGestureForSpecKind(spec.kind);
|
|
315
|
+
if (!gesture) return [];
|
|
316
|
+
const descriptor = getGestureDescriptor(gesture);
|
|
317
|
+
const modifiers = parseModSpec("mods" in spec ? spec.mods : void 0);
|
|
318
|
+
const phases = phaseAtomsForSpec("phase" in spec ? spec.phase : void 0);
|
|
319
|
+
const target = descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) ?? "*" : void 0;
|
|
320
|
+
const args = descriptor.arg ? routeArgsForSpec(spec, descriptor.arg.default) : [void 0];
|
|
321
|
+
return args.map((arg) => formatRoute({ phases, gesture, arg, target, modifiers }));
|
|
322
|
+
}
|
|
295
323
|
function entryFor(toolId, spec, actionId) {
|
|
296
324
|
const gesture = SPEC_KIND_TO_GESTURE[spec.kind];
|
|
297
325
|
if (!gesture || !isKnownGestureName(gesture)) return null;
|
|
@@ -303,7 +331,7 @@ function entryFor(toolId, spec, actionId) {
|
|
|
303
331
|
phase: phaseOf("phase" in spec ? spec.phase : void 0),
|
|
304
332
|
modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
|
|
305
333
|
arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
|
|
306
|
-
target: descriptor.hasTarget ?
|
|
334
|
+
target: descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) : void 0,
|
|
307
335
|
spec
|
|
308
336
|
};
|
|
309
337
|
}
|
|
@@ -324,18 +352,27 @@ function parseModSpec(mods) {
|
|
|
324
352
|
}
|
|
325
353
|
return out;
|
|
326
354
|
}
|
|
327
|
-
function
|
|
355
|
+
function phaseAtomsForSpec(phase) {
|
|
356
|
+
if (phase === void 0) return [{ channel: "&", phase: "*" }];
|
|
357
|
+
if (phase === "initial" || phase === "engaged" || phase === "*") {
|
|
358
|
+
return [{ channel: "&", phase }];
|
|
359
|
+
}
|
|
360
|
+
return phase;
|
|
361
|
+
}
|
|
362
|
+
function routeTargetForSpec(target) {
|
|
328
363
|
if (target === void 0) return void 0;
|
|
329
364
|
return typeof target === "string" ? target : PREDICATE_TARGET;
|
|
330
365
|
}
|
|
366
|
+
function routeArgsForSpec(spec, fallback) {
|
|
367
|
+
if ("key" in spec) return Array.isArray(spec.key) ? spec.key : [spec.key];
|
|
368
|
+
if ("fingers" in spec) return [String(spec.fingers)];
|
|
369
|
+
if ("direction" in spec) return [spec.direction ?? fallback];
|
|
370
|
+
if ("types" in spec) return [spec.types?.length ? spec.types.join("|") : fallback];
|
|
371
|
+
return [fallback];
|
|
372
|
+
}
|
|
331
373
|
function argOf(spec, fallback) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
if ("fingers" in spec) return String(spec.fingers);
|
|
336
|
-
if ("direction" in spec) return spec.direction ?? fallback;
|
|
337
|
-
if ("types" in spec) return spec.types?.length ? spec.types.join("|") : fallback;
|
|
338
|
-
return fallback;
|
|
374
|
+
const args = routeArgsForSpec(spec, fallback);
|
|
375
|
+
return args.length > 1 ? args.join("|") : args[0];
|
|
339
376
|
}
|
|
340
377
|
|
|
341
378
|
// src/tools/routing/reflection/conflicts.ts
|
|
@@ -429,6 +466,6 @@ function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
|
|
|
429
466
|
return conflicts;
|
|
430
467
|
}
|
|
431
468
|
|
|
432
|
-
export { ActionDisabledReason, ActionsProvider, DepRegistryProvider, actionBindings, buildDepsFromRequires, buildRouteRegistry, evaluateEnabled, findConflicts, reportRouteConflicts, useAction, useActionsRegistry, useDepRegistry, useDepSource, useOptionalDepRegistry };
|
|
433
|
-
//# sourceMappingURL=chunk-
|
|
434
|
-
//# sourceMappingURL=chunk-
|
|
469
|
+
export { ActionDisabledReason, ActionsProvider, DepRegistryProvider, PREDICATE_TARGET, actionBindings, buildDepsFromRequires, buildRouteRegistry, evaluateEnabled, findConflicts, reportRouteConflicts, routeGestureForSpecKind, routesForSpec, useAction, useActionsRegistry, useDepRegistry, useDepSource, useOptionalDepRegistry };
|
|
470
|
+
//# sourceMappingURL=chunk-3ZV53ZW2.js.map
|
|
471
|
+
//# sourceMappingURL=chunk-3ZV53ZW2.js.map
|