@weasel-js/core 1.0.2 → 1.0.3
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 +61 -0
- package/dist/{DrawCommand-DCmiNkBj.d.ts → DrawCommand-BbPrSTra.d.ts} +24 -0
- package/dist/{chunk-Z3KNFTTS.js → chunk-3JYFAQIB.js} +3 -3
- package/dist/chunk-3JYFAQIB.js.map +1 -0
- package/dist/{chunk-UB6A6L77.js → chunk-3KGMJMOS.js} +3 -3
- package/dist/{chunk-UB6A6L77.js.map → chunk-3KGMJMOS.js.map} +1 -1
- package/dist/{chunk-IO5Z75X4.js → chunk-HGF5X6MZ.js} +2 -2
- package/dist/chunk-HGF5X6MZ.js.map +1 -0
- package/dist/{chunk-775XXAHR.js → chunk-JOSUVS6C.js} +2 -2
- package/dist/{chunk-775XXAHR.js.map → chunk-JOSUVS6C.js.map} +1 -1
- package/dist/{chunk-PI56ORHE.js → chunk-SMCZRSYB.js} +88 -59
- package/dist/chunk-SMCZRSYB.js.map +1 -0
- package/dist/{chunk-W2WB4352.js → chunk-TR3HZN34.js} +2 -2
- package/dist/chunk-TR3HZN34.js.map +1 -0
- package/dist/{chunk-6SHZHXXR.js → chunk-U45SODC2.js} +2 -2
- package/dist/{chunk-6SHZHXXR.js.map → chunk-U45SODC2.js.map} +1 -1
- package/dist/clipboard.d.ts +5 -2
- package/dist/clipboard.js +1 -1
- package/dist/clone.d.ts +2 -2
- package/dist/{geometry-CnISZ11w.d.ts → geometry-5X6TrAkX.d.ts} +1 -1
- package/dist/{grid-CBiLBnyc.d.ts → grid-sfilv5HP.d.ts} +7 -2
- package/dist/index.d.ts +546 -20
- package/dist/index.js +7 -7
- package/dist/insert.d.ts +6 -3
- package/dist/insert.js +1 -1
- package/dist/insert.js.map +1 -1
- package/dist/move.d.ts +26 -5
- package/dist/move.js +2 -2
- package/dist/move.js.map +1 -1
- package/dist/{options-DoOvx50U.d.ts → options-CbsIgQci.d.ts} +3 -1
- package/dist/patterns-builtin.d.ts +8 -0
- package/dist/patterns-builtin.js +1 -1
- package/dist/{pointSnapToGrid-Bj4r_lEQ.d.ts → pointSnapToGrid-C3wowsQy.d.ts} +9 -3
- package/dist/{registry-BQL4oyTg.d.ts → registry-CxnkXbG8.d.ts} +90 -6
- package/dist/renderer.d.ts +25 -4
- package/dist/renderer.js +7 -7
- package/dist/resize.d.ts +12 -5
- package/dist/resize.js +2 -2
- package/dist/routing.d.ts +7 -7
- package/dist/routing.js +1 -1
- package/dist/{types-Dtxe7jTS.d.ts → types-BHK2dkMu.d.ts} +23 -0
- package/dist/{types-Ct4n0Z38.d.ts → types-ByZxoD6P.d.ts} +1 -1
- package/dist/{types-Ch0Goo-j.d.ts → types-DpQ0cWYC.d.ts} +20 -0
- package/dist/{types-Kh_osAq9.d.ts → types-vqxpCFVp.d.ts} +31 -0
- package/dist/{viewToMat3-BvaWCN99.d.ts → viewToMat3-CQW_YqDw.d.ts} +1 -1
- package/package.json +6 -6
- package/dist/chunk-IO5Z75X4.js.map +0 -1
- package/dist/chunk-PI56ORHE.js.map +0 -1
- package/dist/chunk-W2WB4352.js.map +0 -1
- package/dist/chunk-Z3KNFTTS.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5d25a40: `@weasel-js/font`'s six reset seams — `_resetFontRegistryForTests`,
|
|
8
|
+
`_resetFallbackForTests`, `_getPagesForTests`, `_resetDynamicFontsForTests`,
|
|
9
|
+
`__setGlyphRasterizerForTests` and `_resetFontOutlinesForTests` — are no longer
|
|
10
|
+
exported from the package barrel. They now live at a new
|
|
11
|
+
`@weasel-js/font/test-seams` entry point.
|
|
12
|
+
|
|
13
|
+
Nothing loses the ability to reach them. They exist because font registration,
|
|
14
|
+
the fallback policy, the dynamic atlas and the outline registry are global
|
|
15
|
+
module state that changes what renders, so a test in another package that sets
|
|
16
|
+
one has to be able to put it back — which is why they were on the barrel in the
|
|
17
|
+
first place. A named test-seam entry serves that need without an application
|
|
18
|
+
finding a `_resetFontOutlinesForTests` by autocompleting the barrel. Both
|
|
19
|
+
entries share one chunk, so the registries remain single instances.
|
|
20
|
+
|
|
21
|
+
This is a breaking change for anything importing those six names from
|
|
22
|
+
`@weasel-js/font`; the import specifier is the only edit.
|
|
23
|
+
|
|
24
|
+
`evaluateEnabled` in `@weasel-js/core` is now marked `@experimental` at its
|
|
25
|
+
definition. An `@internal` block intended for it had come detached and sat above
|
|
26
|
+
three unrelated constants, so the function read as undocumented public API while
|
|
27
|
+
a stale marker said otherwise. It is genuinely public — `@weasel-js/ui`'s
|
|
28
|
+
`ActionBar` calls it — and `@experimental` matches the rest of the `enabled`
|
|
29
|
+
predicate surface.
|
|
30
|
+
|
|
31
|
+
- f7077f6: An image quad no longer mints and frees a vertex array and two buffers on every
|
|
32
|
+
draw. The renderer keeps a ring of quad geometry per image program instead, and
|
|
33
|
+
a draw writes its four corners into the next slot. On an M2 Max via ANGLE this
|
|
34
|
+
takes an image command from ~7.0 us to ~3.9 us, measured by
|
|
35
|
+
`tests/perf/image-quad.spec.ts`; nothing about the API or the pixels changes.
|
|
36
|
+
|
|
37
|
+
A ring rather than one buffer, because one is the worst of the three shapes. The
|
|
38
|
+
driver tracks a write hazard per buffer object, so rewriting a single quad
|
|
39
|
+
buffer before each draw waits on the draw still reading it — 40–80 us per quad
|
|
40
|
+
against 5.4 for the per-draw allocation this replaced and 0.3 for the ring.
|
|
41
|
+
Sixty-four slots put that many draws between one write of a buffer and the next.
|
|
42
|
+
|
|
43
|
+
The remaining gap to a pattern-filled rect of the same size (~2.3 us) is texture
|
|
44
|
+
state, not geometry: an image binds a different texture and sets its filter per
|
|
45
|
+
draw.
|
|
46
|
+
|
|
47
|
+
- 514c34a: Document every public export at its definition site
|
|
48
|
+
|
|
49
|
+
A JSDoc string now sits on each symbol reachable through a package's published
|
|
50
|
+
entry points, in every package except `@weasel-js/ui`. Documentation only — no
|
|
51
|
+
export was added, removed, renamed or reordered, and no behavior changed.
|
|
52
|
+
|
|
53
|
+
`npm run audit:jsdoc` enumerates the public exports and reports which lack a
|
|
54
|
+
docstring, so the claim can be re-derived rather than trusted.
|
|
55
|
+
|
|
56
|
+
- Updated dependencies [5d25a40]
|
|
57
|
+
- Updated dependencies [514c34a]
|
|
58
|
+
- @weasel-js/font@1.0.3
|
|
59
|
+
- @weasel-js/geom@1.0.3
|
|
60
|
+
- @weasel-js/gestures@1.0.3
|
|
61
|
+
- @weasel-js/history@1.0.3
|
|
62
|
+
- @weasel-js/modes@1.0.3
|
|
63
|
+
|
|
3
64
|
## 1.0.2
|
|
4
65
|
|
|
5
66
|
### Patch Changes
|
|
@@ -33,6 +33,10 @@ declare function apply(m: Mat3, x: number, y: number): [number, number];
|
|
|
33
33
|
* matches clip-down).
|
|
34
34
|
*/
|
|
35
35
|
declare function screenToClip(width: number, height: number): Mat3;
|
|
36
|
+
/** The renderer's 3x3 matrix operations, as one namespace. These work on the
|
|
37
|
+
* 9-element `Float32Array` form the GL uniform upload wants — distinct from
|
|
38
|
+
* `@weasel-js/geom`'s 6-element affine `Mat3`, though the logical element
|
|
39
|
+
* order is the same. */
|
|
36
40
|
declare const mat3: {
|
|
37
41
|
identity: typeof identity;
|
|
38
42
|
multiply: typeof multiply;
|
|
@@ -140,6 +144,9 @@ declare function fontString(s: ResolvedTextStyle): string;
|
|
|
140
144
|
* (300/500/900) are out of scope for slice 1.
|
|
141
145
|
*/
|
|
142
146
|
|
|
147
|
+
/** A span of text with its own styling, as authored. Fields left absent
|
|
148
|
+
* inherit from the node's text style — this is the difference between a run
|
|
149
|
+
* and a fully resolved one. */
|
|
143
150
|
interface StyledRun {
|
|
144
151
|
text: string;
|
|
145
152
|
bold?: boolean;
|
|
@@ -155,8 +162,13 @@ interface StyledRun {
|
|
|
155
162
|
underline?: boolean;
|
|
156
163
|
strikethrough?: boolean;
|
|
157
164
|
}
|
|
165
|
+
/** Normalize the two accepted spellings of text content — a plain string or
|
|
166
|
+
* an array of runs — to runs. Throws on a run with no string `text`. */
|
|
158
167
|
declare function toRuns(input: string | StyledRun[]): StyledRun[];
|
|
168
|
+
/** Concatenate runs, dropping all styling. */
|
|
159
169
|
declare function runsToPlainText(runs: readonly StyledRun[]): string;
|
|
170
|
+
/** Render runs as Markdown, preserving bold and italic. The flavor written to
|
|
171
|
+
* the clipboard alongside the plain-text one. */
|
|
160
172
|
declare function runsToMarkdown(runs: readonly StyledRun[]): string;
|
|
161
173
|
/**
|
|
162
174
|
* Parse a small markdown subset (`**bold**`, `*italic*`, `***both***`) into
|
|
@@ -184,6 +196,8 @@ declare function markdownToRuns(input: string): StyledRun[];
|
|
|
184
196
|
* `runs/rangeStyle.ts` for why the model collapses the tri-state.
|
|
185
197
|
*/
|
|
186
198
|
|
|
199
|
+
/** A run with every style resolved against the node's text style — no
|
|
200
|
+
* optional inheritance left. This is what layout and painting consume. */
|
|
187
201
|
interface ResolvedRun {
|
|
188
202
|
text: string;
|
|
189
203
|
fontFamily: string;
|
|
@@ -201,6 +215,8 @@ interface ResolvedRun {
|
|
|
201
215
|
/** Draw a rule through this run's x-height. Additive over the node style. */
|
|
202
216
|
strikethrough: boolean;
|
|
203
217
|
}
|
|
218
|
+
/** Resolve each run's styling against the node's text style, filling in
|
|
219
|
+
* everything the run left inherited. */
|
|
204
220
|
declare function resolveRuns(runs: readonly StyledRun[], style: ResolvedTextStyle): ResolvedRun[];
|
|
205
221
|
|
|
206
222
|
/**
|
|
@@ -210,6 +226,9 @@ declare function resolveRuns(runs: readonly StyledRun[], style: ResolvedTextStyl
|
|
|
210
226
|
* `height`) is the legacy behavior: offset 0.
|
|
211
227
|
*/
|
|
212
228
|
type TextVerticalAlign = 'top' | 'center' | 'bottom';
|
|
229
|
+
/** How far to shift laid-out text down inside a box of `boxHeight` to satisfy
|
|
230
|
+
* a vertical alignment. Zero when the box height is unknown or alignment is
|
|
231
|
+
* top. */
|
|
213
232
|
declare function verticalAlignOffset(align: TextVerticalAlign | undefined, boxHeight: number | undefined, textHeight: number): number;
|
|
214
233
|
|
|
215
234
|
/**
|
|
@@ -286,6 +305,8 @@ interface SolidPaint {
|
|
|
286
305
|
}
|
|
287
306
|
/** DrawCommand variants implemented through step 6. */
|
|
288
307
|
type DrawCommand = PathDrawCommand | GroupDrawCommand | TextDrawCommand | ImageDrawCommand | ShaderDrawCommand;
|
|
308
|
+
/** Draw a path, filled and/or stroked. The workhorse command: every shape the
|
|
309
|
+
* kit draws that is not text, an image, or a custom shader is one of these. */
|
|
289
310
|
interface PathDrawCommand {
|
|
290
311
|
kind: 'path';
|
|
291
312
|
path: Path;
|
|
@@ -308,6 +329,9 @@ interface PathDrawCommand {
|
|
|
308
329
|
*/
|
|
309
330
|
vertexColors?: number[];
|
|
310
331
|
}
|
|
332
|
+
/** Draw a list of commands under a shared transform, opacity, color matrix
|
|
333
|
+
* and clip. Groups nest, and their effects accumulate down the stack — this
|
|
334
|
+
* is how a container node's transform reaches its descendants. */
|
|
311
335
|
interface GroupDrawCommand {
|
|
312
336
|
kind: 'group';
|
|
313
337
|
transform?: Mat3;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_GUIDE_TOLERANCE_PX, pxExtent } from './chunk-
|
|
1
|
+
import { DEFAULT_GUIDE_TOLERANCE_PX, pxExtent } from './chunk-U45SODC2.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-3JYFAQIB.js.map
|
|
248
|
+
//# sourceMappingURL=chunk-3JYFAQIB.js.map
|
|
@@ -0,0 +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-3JYFAQIB.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,4 +1,4 @@
|
|
|
1
|
-
import { RECT_ORIGIN_PROJECTION } from './chunk-
|
|
1
|
+
import { RECT_ORIGIN_PROJECTION } from './chunk-U45SODC2.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-3KGMJMOS.js.map
|
|
103
|
+
//# sourceMappingURL=chunk-3KGMJMOS.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-UB6A6L77.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-3KGMJMOS.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"]}
|
|
@@ -163,5 +163,5 @@ function chunks(params) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
export { chunks, createTilePattern, crosshatch, dots, drawTileShapes, getTexture, hatch, registerTexture, tileGeometry, tileSize };
|
|
166
|
-
//# sourceMappingURL=chunk-
|
|
167
|
-
//# sourceMappingURL=chunk-
|
|
166
|
+
//# sourceMappingURL=chunk-HGF5X6MZ.js.map
|
|
167
|
+
//# sourceMappingURL=chunk-HGF5X6MZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/renderer/textures/registerTexture.ts","../src/features/patterns/tileGeometry.ts","../src/features/patterns/index.ts","../src/features/patterns/patterns-builtin.ts"],"names":[],"mappings":"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","file":"chunk-HGF5X6MZ.js","sourcesContent":["/**\n * registerTexture — accepts an image source, assigns an opaque id, stores in\n * a module-level registry. Actual GL upload happens lazily at draw time in\n * drawShader() via GLTextureCache.upload (which is idempotent).\n *\n * Lifecycle: textures live for the renderer's lifetime. No unregister in v1.\n *\n * Convention §9: this registry stores image data only — no per-renderer state.\n * Each WeaselRenderer's GLTextureCache does its own dedup via has(id).\n */\n\nexport interface TextureHandle {\n readonly id: string;\n}\n\nexport interface TextureEntry {\n source: HTMLImageElement | ImageBitmap;\n}\n\nlet counter = 0;\nlet registry = new Map<string, TextureEntry>();\n\n/** @internal Test helper — do not call from product code. */\nexport function _resetTextureRegistryForTests(): void {\n registry = new Map();\n counter = 0;\n}\n\nexport function getTexture(id: string): TextureEntry | null {\n return registry.get(id) ?? null;\n}\n\n/**\n * Register an image for use as a shader texture uniform.\n *\n * Returns a TextureHandle whose `id` can be passed as a ShaderUniform value.\n * The handle is valid for the lifetime of the renderer it will be used with.\n *\n * @param image The image to register. HTMLImageElement must be fully loaded.\n * @returns Opaque TextureHandle.\n */\nexport function registerTexture(\n image: HTMLImageElement | ImageBitmap,\n): TextureHandle {\n const id = `tex_${++counter}`;\n registry.set(id, { source: image });\n return { id };\n}\n","/**\n * The shapes a built-in tile is made of, as data.\n *\n * One description, two renderers: `patterns-builtin` strokes/fills these to\n * an `OffscreenCanvas` to build the GL texture, and `@weasel-js/svg` maps\n * them to `<pattern>` children. Duplicating the tile drawing in the\n * serializer would let the raster and vector forms drift apart silently.\n */\n\nimport type { TilePatternSpec } from '../../core/paint-types';\n\n/** One primitive within a pattern tile, in tile-local coordinates. */\nexport type TileShape =\n | { kind: 'line'; x1: number; y1: number; x2: number; y2: number; width: number; color: string }\n | { kind: 'circle'; cx: number; cy: number; r: number; color: string }\n | { kind: 'ellipse'; cx: number; cy: number; rx: number; ry: number; rotation: number; color: string }\n | { kind: 'rect'; x: number; y: number; width: number; height: number; color: string };\n\n/** A built-in tile's shapes plus the edge length they're drawn within. */\nexport interface TileGeometry {\n size: number;\n shapes: TileShape[];\n}\n\n/** Defaults per tile kind, matching each factory's signature. */\nexport function tileSize(spec: TilePatternSpec): number {\n if (spec.size !== undefined) return spec.size;\n switch (spec.tile) {\n case 'hatch': return 5;\n case 'crosshatch': return 6;\n case 'dots': return 6;\n case 'chunks': return 48;\n default: return 8;\n }\n}\n\n/** Seeded PRNG (mulberry32) — `chunks` places its scatter with this, so the\n * same `seed` yields the same tile in both the raster and vector paths. */\nfunction mulberry32(seed: number): () => number {\n let s = seed | 0;\n return () => {\n s = (s + 0x6D2B79F5) | 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/** Resolve a pattern spec to the shapes that make up one tile. Consumers that\n * want to draw a pattern swatch themselves — a picker, an SVG export — read\n * this rather than rasterizing a texture. */\nexport function tileGeometry(spec: TilePatternSpec): TileGeometry {\n const s = tileSize(spec);\n const { color } = spec;\n\n switch (spec.tile) {\n case 'hatch': {\n const width = spec.lineWidth ?? 1;\n return { size: s, shapes: forwardDiagonals(s, width, color) };\n }\n case 'crosshatch': {\n const width = spec.lineWidth ?? 0.8;\n return {\n size: s,\n shapes: [...forwardDiagonals(s, width, color), ...backwardDiagonals(s, width, color)],\n };\n }\n case 'dots': {\n const r = spec.radius ?? 1;\n return { size: s, shapes: [{ kind: 'circle', cx: s / 2, cy: s / 2, r, color }] };\n }\n case 'chunks': {\n const density = spec.density ?? 0.35;\n const chunkSize = spec.chunkSize ?? 3;\n const rand = mulberry32(spec.seed ?? 42);\n const count = Math.round((s * s * density) / (chunkSize * chunkSize));\n const shapes: TileShape[] = [];\n if (spec.bg !== undefined) {\n shapes.push({ kind: 'rect', x: 0, y: 0, width: s, height: s, color: spec.bg });\n }\n for (let i = 0; i < count; i++) {\n const cx = rand() * s;\n const cy = rand() * s;\n const w = chunkSize * (0.5 + rand());\n const h = chunkSize * (0.5 + rand());\n const rotation = rand() * Math.PI;\n shapes.push({ kind: 'ellipse', cx, cy, rx: w / 2, ry: h / 2, rotation, color });\n }\n return { size: s, shapes };\n }\n default:\n return { size: s, shapes: [] };\n }\n}\n\n/** The forward (`/`) diagonal, plus the two corner stubs that make it tile\n * seamlessly across the edges. */\nfunction forwardDiagonals(s: number, width: number, color: string): TileShape[] {\n return [\n { kind: 'line', x1: -1, y1: s + 1, x2: s + 1, y2: -1, width, color },\n { kind: 'line', x1: -1, y1: 1, x2: 1, y2: -1, width, color },\n { kind: 'line', x1: s - 1, y1: s + 1, x2: s + 1, y2: s - 1, width, color },\n ];\n}\n\n/** The backward (`\\`) diagonal and its two stubs. */\nfunction backwardDiagonals(s: number, width: number, color: string): TileShape[] {\n return [\n { kind: 'line', x1: -1, y1: -1, x2: s + 1, y2: s + 1, width, color },\n { kind: 'line', x1: s - 1, y1: -1, x2: s + 1, y2: 1, width, color },\n { kind: 'line', x1: -1, y1: s - 1, x2: 1, y2: s + 1, width, color },\n ];\n}\n\n/** Draw a tile's shapes to a 2D context — the raster half of the pair. */\nexport function drawTileShapes(oc: CanvasRenderingContext2D, geometry: TileGeometry): void {\n for (const shape of geometry.shapes) {\n if (shape.kind === 'line') {\n oc.strokeStyle = shape.color;\n oc.lineWidth = shape.width;\n oc.beginPath();\n oc.moveTo(shape.x1, shape.y1);\n oc.lineTo(shape.x2, shape.y2);\n oc.stroke();\n continue;\n }\n oc.fillStyle = shape.color;\n if (shape.kind === 'rect') {\n oc.fillRect(shape.x, shape.y, shape.width, shape.height);\n continue;\n }\n oc.beginPath();\n if (shape.kind === 'circle') {\n oc.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2);\n } else {\n oc.ellipse(shape.cx, shape.cy, shape.rx, shape.ry, shape.rotation, 0, Math.PI * 2);\n }\n oc.fill();\n }\n}\n","/**\n * Tile-pattern primitive — a small helper for building repeating texture\n * tiles from a draw callback. The result plugs into a `FillStyle` of\n * `{ fill: 'pattern', pattern }` (see `../../core/paint-types.ts`).\n *\n * GL-backed: the tile is rendered to an `OffscreenCanvas` (with a 2D fallback\n * to a regular `<canvas>` for environments without `OffscreenCanvas`),\n * converted to an `ImageBitmap`, and registered with `registerTexture()`.\n * The returned `TextureHandle` is what `FillStyle.pattern` accepts.\n *\n * For ready-to-use named patterns (hatch, crosshatch, dots, chunks),\n * see the `@weasel-js/core/patterns-builtin` subpath.\n */\n\nimport { registerTexture } from '../../renderer/textures/registerTexture';\nimport type { TextureHandle } from '../../renderer/textures/registerTexture';\n\n/** Options for `createTilePattern`. */\nexport interface TilePatternOpts {\n /** Edge length (in pixels) of the square offscreen tile. */\n size: number;\n /** FillStyle one tile at integer-pixel coordinates `(0, 0) .. (size, size)`. */\n draw: (oc: CanvasRenderingContext2D, size: number) => void;\n}\n\n/**\n * Render a single tile to an offscreen canvas and register it as a GL\n * texture, returning a `TextureHandle` you can wrap in a `FillStyle` of\n * `{ fill: 'pattern', pattern }`. Returns `null` only when an\n * `OffscreenCanvas`/2D context can't be acquired (very rare; same fallback\n * semantics as the prior `CanvasPattern`-based implementation).\n */\nexport function createTilePattern(opts: TilePatternOpts): TextureHandle | null {\n // Prefer OffscreenCanvas; fall back to <canvas> for environments without it\n // (tests in jsdom may need that fallback — try OffscreenCanvas first).\n let off: OffscreenCanvas | HTMLCanvasElement;\n try {\n off = new OffscreenCanvas(opts.size, opts.size);\n } catch {\n if (typeof document === 'undefined') return null;\n const c = document.createElement('canvas');\n c.width = opts.size;\n c.height = opts.size;\n off = c;\n }\n const oc = off.getContext('2d') as CanvasRenderingContext2D | null;\n if (!oc) return null;\n opts.draw(oc, opts.size);\n\n // Convert to ImageBitmap so registerTexture can store it.\n // OffscreenCanvas.transferToImageBitmap() is the fast path; the\n // synchronous path requires OffscreenCanvas. If we got here without it,\n // the env doesn't support OffscreenCanvas — fail gracefully.\n let bitmap: ImageBitmap;\n if (typeof (off as OffscreenCanvas).transferToImageBitmap === 'function') {\n bitmap = (off as OffscreenCanvas).transferToImageBitmap();\n } else {\n return null;\n }\n return registerTexture(bitmap);\n}\n","/**\n * Built-in pattern factories — generic line/dot/scatter textures. Each\n * returns a `TextureHandle` you can wrap in a `FillStyle` of\n * `{ fill: 'pattern', pattern }` and pass to anything that accepts a `FillStyle`\n * (`FillStyle`-aware layers, path fills, etc.).\n * Imported from a subpath so consumers that ship their own catalog can\n * avoid the bundle cost.\n *\n * All factories require an explicit `color` (and `bg` for `chunks`). No\n * defaults are baked in — the caller's design system is the source of truth\n * for palette decisions.\n *\n * Each is a thin wrapper over `tileGeometry`, which describes the tile as\n * shapes so `@weasel-js/svg` can emit the same tile as a `<pattern>`.\n * Prefer naming a tile in a `TilePatternSpec` over calling these directly —\n * a spec serializes, a `TextureHandle` does not.\n */\n\nexport { tileGeometry, tileSize, drawTileShapes } from './tileGeometry';\nexport type { TileShape, TileGeometry } from './tileGeometry';\n\nimport { createTilePattern } from '.';\nimport { tileGeometry, drawTileShapes, tileSize } from './tileGeometry';\nimport type { TilePatternSpec } from '../../core/paint-types';\nimport type { TextureHandle } from '../../renderer/textures/registerTexture';\n\nfunction build(spec: TilePatternSpec): TextureHandle | null {\n const geometry = tileGeometry(spec);\n return createTilePattern({\n size: tileSize(spec),\n draw: (oc) => drawTileShapes(oc, geometry),\n });\n}\n\n/** Diagonal hatch (forward slash). */\nexport interface HatchParams {\n color: string;\n size?: number;\n lineWidth?: number;\n}\n\n/** Build a diagonal-hatch pattern texture. */\nexport function hatch(params: HatchParams): TextureHandle | null {\n return build({ tile: 'hatch', ...params });\n}\n\n/** Diagonal hatch in both directions. */\nexport interface CrosshatchParams {\n color: string;\n size?: number;\n lineWidth?: number;\n}\n\n/** Build a crosshatch pattern texture. */\nexport function crosshatch(params: CrosshatchParams): TextureHandle | null {\n return build({ tile: 'crosshatch', ...params });\n}\n\n/** Regular grid of filled dots. */\nexport interface DotsParams {\n color: string;\n size?: number;\n radius?: number;\n}\n\n/** Build a dot-grid pattern texture. */\nexport function dots(params: DotsParams): TextureHandle | null {\n return build({ tile: 'dots', ...params });\n}\n\n/**\n * Random scatter of small ellipses on top of a solid background. Driven by a\n * seeded PRNG so the same `seed` produces a deterministic tile.\n *\n * Note: the `bg` field is optional — omit it for a transparent background\n * (the scatter shapes draw directly onto whatever sits beneath the overlay).\n */\nexport interface ChunksParams {\n color: string;\n bg?: string;\n size?: number;\n /** Coverage: `chunks * chunkSize²` per `size²` tile. Range ~[0, 1]. */\n density?: number;\n chunkSize?: number;\n seed?: number;\n}\n\n/** Build a seeded random-scatter pattern texture. */\nexport function chunks(params: ChunksParams): TextureHandle | null {\n return build({ tile: 'chunks', ...params });\n}\n"]}
|
|
@@ -212,5 +212,5 @@ async function writeOsClipboard(flavors) {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
export { PointerContextProvider, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, applyOpsTo, buildWeaselClipboardText, dispatchApplyBatch, dlog, dwarn, embedWeaselMetadataInSvg, extractWeaselClipboardFromSvg, isDebugEnabled, materializePaste, parseWeaselClipboardText, sniffWeaselClipboardText, useClipboardOps, usePointerContext };
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
216
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-JOSUVS6C.js.map
|
|
216
|
+
//# sourceMappingURL=chunk-JOSUVS6C.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/applyOps.ts","../src/debug/flag.ts","../src/features/pointer/PointerContext.tsx","../src/interactions/actions/clipboard/wireFormat.ts","../src/interactions/actions/clipboard/materializePaste.ts","../src/interactions/actions/clipboard/clipboardOps.ts"],"names":["useRef"],"mappings":"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","file":"chunk-775XXAHR.js","sourcesContent":["import type { Op } from './ops/types';\n\n/**\n * Default `applyOps` implementation: apply each op in order against\n * `adapter`. Used by hooks when an adapter omits `applyOps`. Apps that\n * need history integration (checkpoint + push entry) supply their own\n * `applyOps` instead.\n *\n * The `_label` parameter mirrors the `applyOps(ops, label)` signature so\n * call-sites can dispatch through `(adapter.applyOps ?? applyOpsTo)` with\n * matching arguments. The default ignores it.\n */\nexport function applyOpsTo<A>(adapter: A, ops: Op[], _label?: string): void {\n for (const op of ops) op.apply(adapter);\n}\n\n/**\n * Dispatcher: invokes `adapter.applyOps` if present, otherwise falls back\n * to `applyOpsTo`. Convenience for hooks so they don't repeat the\n * `?? applyOpsTo` pattern at every call site.\n */\nexport function dispatchApplyBatch<A>(\n adapter: A,\n ops: Op[],\n label?: string,\n): void {\n const a = adapter as { applyOps?: (ops: Op[], label?: string) => void };\n if (a.applyOps) a.applyOps(ops, label);\n else applyOpsTo(adapter, ops, label);\n}\n","/**\n * Namespaced, opt-in console tracing for the kit.\n *\n * Enable in the browser:\n *\n * localStorage.setItem('weasel.debug', '*') // all namespaces\n * localStorage.setItem('weasel.debug', 'tools,dispatch') // specific ones\n * localStorage.setItem('weasel.debug', '1') // legacy \"all\"\n *\n * Each call site picks a stable namespace string. Off by default; the flag\n * is read once and cached per page session — reload to pick up a change.\n *\n * Mirrors `packages/ui/src/dlog.ts`. The two impls live in separate\n * packages because weasel-ui doesn't depend on the kit, but both read the\n * same localStorage keys so behavior is uniform from a debugger's POV.\n *\n * Known kit namespaces:\n * - tools — active-tool + hotkey transitions (useTools)\n * - dispatch — gesture start / end / cancel (dispatcher)\n * - drag-rect — useDragRect lifecycle\n * - drag-radial — useDragRadial lifecycle\n * - scene — restored-op replay gaps (history persistence)\n * - scene-canvas — SceneCanvas mount / unmount; adapter paste-translation warnings\n * - history — applyOps push, no-op suppression, serialize/restore\n * - clipboard — OS clipboard write/read failures, malformed payloads\n */\n\nconst KEYS = ['weasel.debug', 'weaseldraw.debug'] as const;\n\ninterface Cached {\n all: boolean;\n set: Set<string>;\n}\n\nlet cached: Cached | null = null;\n\nfunction read(): Cached {\n const empty: Cached = { all: false, set: new Set() };\n if (typeof localStorage === 'undefined') return empty;\n try {\n for (const k of KEYS) {\n const v = localStorage.getItem(k);\n if (v == null || v === '') continue;\n if (v === '1' || v === 'true' || v === '*') return { all: true, set: new Set() };\n return { all: false, set: new Set(v.split(/[\\s,]+/).filter(Boolean)) };\n }\n return empty;\n } catch {\n return empty;\n }\n}\n\nfunction get(): Cached {\n if (cached === null) cached = read();\n return cached;\n}\n\n/** True when `namespace` (or `*`) is enabled. Pass no argument to test\n * whether *any* namespace is on (legacy global-flag semantics). */\nexport function isDebugEnabled(namespace?: string): boolean {\n const c = get();\n if (c.all) return true;\n if (namespace === undefined) return c.set.size > 0;\n return c.set.has(namespace);\n}\n\n/** Conditional `console.debug` — no-op unless `namespace` (or `*`) is enabled.\n * Each call is prefixed with `[namespace]` so the source is searchable. */\nexport function dlog(namespace: string, ...args: unknown[]): void {\n if (!isDebugEnabled(namespace)) return;\n // eslint-disable-next-line no-console\n console.debug(`[${namespace}]`, ...args);\n}\n\n/** Conditional `console.warn` — like `dlog` but at warn level. Use for\n * diagnostics that indicate something worth surfacing during debugging\n * (e.g. no-op op batches suppressed). */\nexport function dwarn(namespace: string, ...args: unknown[]): void {\n if (!isDebugEnabled(namespace)) return;\n // eslint-disable-next-line no-console\n console.warn(`[${namespace}]`, ...args);\n}\n","/**\n * @experimental\n * PointerContext — a tiny ambient context that publishes the world-space\n * position of the canvas pointer, refreshed on every `pointermove` over\n * the canvas. Cleared (set to `null`) on `pointerleave`.\n *\n * Why ref-based and not state-based: cursor moves fire dozens of times per\n * second; routing those through React state would re-render every consumer\n * in the tree. The context exposes a stable `pointerRef` whose `.current`\n * is mutated directly by the publisher, plus a thunk `getDropPoint()` that\n * reads it on demand. Consumers (e.g. `useClipboard`) pull via the thunk\n * inside their callbacks — no subscription, no re-render.\n *\n * `<SceneCanvas>` publishes automatically. `useClipboardOps` consumes when\n * the caller didn't pass an explicit `getDropPoint` option. Other future\n * hit-on-cursor consumers (drop-zone hover, context-menu anchor) can reuse\n * the same context.\n */\nimport { createContext, useContext, useMemo, useRef, type MutableRefObject, type ReactNode } from 'react';\n\n/** @experimental World-space pointer position, or `null` when the pointer\n * isn't over the publishing canvas. */\nexport type PointerWorldPos = { worldX: number; worldY: number } | null;\n\n/** @experimental */\nexport interface PointerContextValue {\n /** Live ref — mutate to publish, read for the latest snapshot. The\n * identity is stable for the lifetime of the provider. */\n readonly pointerRef: MutableRefObject<PointerWorldPos>;\n /** Convenience thunk equivalent to `() => pointerRef.current`. Stable\n * identity for the lifetime of the provider; safe to pass to hooks. */\n readonly getDropPoint: () => PointerWorldPos;\n}\n\nconst PointerContext = createContext<PointerContextValue | null>(null);\n\n/**\n * @experimental\n * Wrap the part of the React tree that should share a pointer-position\n * context. Usually placed at the demo / app root, alongside\n * `<ActionsProvider>` and `<SelectionContextProvider>`.\n *\n * Most consumers don't need to mount this directly — `<SceneCanvas>` mounts\n * an internal provider when no parent provider is in scope, so child hooks\n * (`useClipboard` without an explicit `getDropPoint`) read the canvas's\n * tracked pointer for free.\n */\nexport function PointerContextProvider({ children }: { children: ReactNode }): ReactNode {\n const pointerRef = useRef<PointerWorldPos>(null);\n const value = useMemo<PointerContextValue>(() => ({\n pointerRef,\n getDropPoint: () => pointerRef.current,\n }), []);\n return <PointerContext.Provider value={value}>{children}</PointerContext.Provider>;\n}\n\n/** @experimental Read the surrounding pointer-context value, or `null` when\n * no provider is in scope. */\nexport function usePointerContext(): PointerContextValue | null {\n return useContext(PointerContext);\n}\n","import { dwarn } from 'debug/flag';\n\nexport const WEASEL_CLIPBOARD_MIME = 'application/x-weasel-clipboard+json';\n/** Chromium's async-clipboard spelling for custom formats. */\nexport const WEASEL_CLIPBOARD_MIME_WEB = `web ${WEASEL_CLIPBOARD_MIME}`;\n\ninterface WeaselClipboardPayload { weaselClipboard: 1; nodes: unknown[] }\n\ntype Replacer = (key: string, value: unknown) => unknown;\ntype Reviver = (key: string, value: unknown) => unknown;\n\n/** Serialize snapshot items into the versioned wire text.\n *\n * Function-valued fields (e.g. a container's `clipFromPose`) are dropped by\n * JSON serialization — OS-pasted clip containers arrive unclipped.\n * Consumers who need clips to survive the OS round-trip can translate\n * function ↔ registry key in their `jsonReplacer`/reviver pair (the\n * scene-serialization `clipFromPoseKey` precedent). */\nexport function buildWeaselClipboardText(items: unknown[], replacer?: Replacer): string {\n const payload: WeaselClipboardPayload = { weaselClipboard: 1, nodes: items };\n return JSON.stringify(payload, replacer as Parameters<typeof JSON.stringify>[1]);\n}\n\n/** Cheap check: could this text be a weasel clipboard payload? Substring\n * pre-check, then a guarded parse + marker/version test. */\nexport function sniffWeaselClipboardText(text: string): boolean {\n if (!text.includes('\"weaselClipboard\"')) return false;\n try {\n const parsed = JSON.parse(text) as Partial<WeaselClipboardPayload> | null;\n return !!parsed && parsed.weaselClipboard === 1;\n } catch {\n return false;\n }\n}\n\n// Opening `<svg ...>` tag of a serialized document. `[^>]*` is safe for OUR\n// serializer's output (attribute values are XML-escaped, so no raw `>` can\n// appear inside the tag) — see embedWeaselMetadataInSvg's contract note.\nconst SVG_OPEN_TAG = /<svg(?:\\s[^>]*)?>/i;\n\n/**\n * Embed a weasel clipboard payload in an SVG document as a `<metadata>`\n * child of the root — legal SVG 1.1, ignored by external tools — so one\n * `text/plain` flavor serves both audiences: external tools parse the SVG,\n * weasel extracts the JSON for full fidelity on draw→draw DOM paste.\n *\n * The payload rides in CDATA so JSON's `<` / `&` need no entity escaping; a\n * literal `]]>` inside the payload is split across CDATA sections\n * (`]]]]><![CDATA[>`), which {@link extractWeaselClipboardFromSvg} rejoins.\n *\n * Intended for OUR serializer's output only (`serializeSvg` — single\n * `<svg ...>` open tag, XML-escaped attributes). When no open tag is found\n * the input is returned unchanged with a debug warning.\n */\nexport function embedWeaselMetadataInSvg(svgText: string, payloadText: string): string {\n const open = SVG_OPEN_TAG.exec(svgText);\n if (!open) {\n dwarn('clipboard', 'embedWeaselMetadataInSvg: no <svg ...> open tag found — returning input unchanged');\n return svgText;\n }\n const cdata = `<![CDATA[${payloadText.replaceAll(']]>', ']]]]><![CDATA[>')}]]>`;\n const at = open.index + open[0].length;\n return `${svgText.slice(0, at)}<metadata>${cdata}</metadata>${svgText.slice(at)}`;\n}\n\n/**\n * Recover an embedded weasel clipboard payload from SVG text: read the first\n * `<metadata>` block, rejoin its CDATA sections (concatenation restores the\n * `]]>` split by {@link embedWeaselMetadataInSvg}; plain non-CDATA content\n * passes through as-is), and return the payload text iff it sniffs as the\n * weasel wire format — else null (external/non-weasel metadata declines).\n *\n * A text scan, not an XML parse — the payload is small and ours.\n */\nexport function extractWeaselClipboardFromSvg(svgText: string): string | null {\n const block = /<metadata(?:\\s[^>]*)?>([\\s\\S]*?)<\\/metadata>/i.exec(svgText);\n if (!block) return null;\n const inner = block[1];\n const payload = inner.includes('<![CDATA[')\n ? Array.from(inner.matchAll(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g), (m) => m[1]).join('')\n : inner;\n return sniffWeaselClipboardText(payload) ? payload : null;\n}\n\n/** Parse wire text to snapshot items, or null when malformed/mismatched\n * (callers decline to the next content handler). */\nexport function parseWeaselClipboardText(text: string, reviver?: Reviver): unknown[] | null {\n try {\n const parsed = JSON.parse(text, reviver as Parameters<typeof JSON.parse>[1]) as\n Partial<WeaselClipboardPayload> | null;\n if (!parsed || parsed.weaselClipboard !== 1 || !Array.isArray(parsed.nodes)) return null;\n return parsed.nodes;\n } catch (err) {\n dwarn('clipboard', `weasel payload failed to parse: ${String(err)}`);\n return null;\n }\n}\n","/**\n * Shared paste materialization — the one place the paste recipe lives:\n * offset policy → `commitPaste` → the op batch (InsertOps parents-first +\n * a SetSelectionOp). Both `useClipboardOps.paste` (in-memory paste) and the\n * kit weasel-JSON content handler (OS-clipboard paste) dispatch exactly this\n * batch, so the two arrival paths stay indistinguishable in history.\n *\n * Pure: no dispatching here — callers own how the ops are applied\n * (`dispatchApplyBatch` vs `IngestCtx.applyOps`) and any post-paste\n * bookkeeping (the hook's cascade re-snapshot, the handler's selection set).\n */\nimport { createInsertOp } from 'core/ops/create';\nimport { createSetSelectionOp } from 'core/ops/select';\nimport type { Op } from 'core/ops/types';\nimport type { NodeId } from 'core/scene/types';\nimport type { ClipboardSnapshot, InsertAdapter } from 'core/adapters/types';\n\n/** Shared paste materialization: offset policy → commitPaste → the op batch\n * both the in-memory paste and the OS-paste handler dispatch. Returns null\n * when the adapter can't paste or nothing was created. */\nexport function materializePaste<TNode extends { id: string }>(\n adapter: InsertAdapter<TNode>,\n snapshot: ClipboardSnapshot,\n opts: {\n currentSelection: string[];\n dropPoint?: { worldX: number; worldY: number } | null;\n },\n): { ops: Op[]; newIds: string[]; created: TNode[] } | null {\n const commitPaste = adapter.commitPaste;\n if (!commitPaste) return null;\n const offset = adapter.getPasteOffset?.(snapshot) ?? { dx: 0, dy: 0 };\n const ctx = opts.dropPoint != null ? { dropPoint: opts.dropPoint } : undefined;\n const created = commitPaste.call(adapter, snapshot, offset, ctx);\n if (created.length === 0) return null;\n const newIds = created.map((o) => o.id);\n const ops: Op[] = [\n ...created.map((o) => createInsertOp({ node: o })),\n createSetSelectionOp({ from: opts.currentSelection as NodeId[], to: newIds as NodeId[] }),\n ];\n return { ops, newIds, created };\n}\n","import { useCallback, useRef } from 'react';\nimport type { NodeId } from 'core/scene/types';\nimport { dispatchApplyBatch } from 'core/applyOps';\nimport type { InsertAdapter } from 'core/adapters/types';\nimport type { ClipboardSnapshot } from './types';\nimport { usePointerContext } from 'features/pointer/PointerContext';\nimport { dwarn } from 'debug/flag';\nimport { materializePaste } from './materializePaste';\nimport { WEASEL_CLIPBOARD_MIME, buildWeaselClipboardText } from './wireFormat';\n\ntype Replacer = (key: string, value: unknown) => unknown;\n\n/** Options for `useClipboardOps`. */\nexport interface UseClipboardOpsOptions {\n /** How the hook reads \"current selection\" for copy. The kit doesn't assume\n * a global selection store; each consumer wires this. */\n getSelection: () => NodeId[];\n /** Called after a successful paste with the ids of the newly inserted objects. */\n onPaste?: (newIds: NodeId[]) => void;\n /** Label for the history entry produced by paste. Default 'Paste'. */\n pasteLabel?: string;\n /** Pulled once per paste() call. When non-null, threaded to commitPaste\n * as `ctx.dropPoint`; adapters typically use it as the cluster origin\n * (ignoring `offset`). When null/undefined, the hook falls back to the\n * existing cascade-offset behavior. */\n getDropPoint?: () => { worldX: number; worldY: number } | null;\n /** Produce the OS-clipboard flavor map for a copied snapshot. Keys are MIME\n * types, values the serialized payload. The kit default emits\n * `application/x-weasel-clipboard+json` plus `text/plain` carrying the\n * same JSON. Apps override to add richer flavors (e.g. real SVG) or to\n * replace the text flavor. Return an empty object to skip the OS write\n * entirely. */\n produceFlavors?: (snapshot: ClipboardSnapshot) => Record<string, string>;\n /** Replacer for the kit-default JSON flavor (typed arrays etc.). Ignored\n * when `produceFlavors` is supplied. */\n jsonReplacer?: Replacer;\n}\n\n/** Return shape of `useClipboardOps`: imperative `copy`, `paste`, and `isEmpty` functions. */\nexport interface UseClipboardOpsReturn {\n copy(): void;\n paste(): void;\n isEmpty(): boolean;\n}\n\nconst EMPTY: ClipboardSnapshot = { items: [] };\n\n/** In-memory copy/paste of selections via `InsertAdapter.snapshotSelection` / `commitPaste`. */\nexport function useClipboardOps<TNode extends { id: string }>(\n adapter: InsertAdapter<TNode>,\n options: UseClipboardOpsOptions,\n): UseClipboardOpsReturn {\n const { getSelection, onPaste, pasteLabel = 'Paste', getDropPoint, produceFlavors, jsonReplacer } = options;\n // Fall back to the ambient pointer context (auto-published by SceneCanvas)\n // when the caller didn't supply an explicit `getDropPoint`. Null context\n // (no provider in scope) and no caller-supplied thunk together mean\n // \"no drop point\" — paste uses the cascade offset instead.\n const pointerCtx = usePointerContext();\n const effectiveGetDropPoint = getDropPoint ?? pointerCtx?.getDropPoint;\n const clipboardRef = useRef<ClipboardSnapshot>(EMPTY);\n // Keep callbacks stable across renders.\n const adapterRef = useRef(adapter);\n adapterRef.current = adapter;\n const optsRef = useRef({\n getSelection, onPaste, pasteLabel, getDropPoint: effectiveGetDropPoint, produceFlavors, jsonReplacer,\n });\n optsRef.current = {\n getSelection, onPaste, pasteLabel, getDropPoint: effectiveGetDropPoint, produceFlavors, jsonReplacer,\n };\n\n const copy = useCallback(() => {\n const ids = optsRef.current.getSelection();\n if (ids.length === 0) return;\n const snap = adapterRef.current.snapshotSelection;\n if (!snap) return;\n clipboardRef.current = snap(ids);\n // Best-effort OS write. Never blocks or throws — the in-memory ref is\n // already set, and OS clipboard support varies (custom formats are\n // Chromium-only via the \"web \" prefix; jsdom/non-secure contexts have\n // no API at all). The flavor producer itself is untrusted consumer code\n // (`produceFlavors`), so guard its call too — a throwing producer must\n // not break `copy()`, it just skips the OS write.\n let flavors: Record<string, string> | null = null;\n try {\n flavors = optsRef.current.produceFlavors?.(clipboardRef.current)\n ?? defaultFlavors(clipboardRef.current, optsRef.current.jsonReplacer);\n } catch (err) {\n dwarn('clipboard', `flavor producer threw — skipping OS write: ${String(err)}`);\n }\n if (flavors) void writeOsClipboard(flavors);\n }, []);\n\n const paste = useCallback(() => {\n const cb = clipboardRef.current;\n if (cb.items.length === 0) return;\n const a = adapterRef.current;\n const result = materializePaste(a, cb, {\n currentSelection: optsRef.current.getSelection(),\n dropPoint: optsRef.current.getDropPoint?.(),\n });\n if (!result) return;\n dispatchApplyBatch(a, result.ops, optsRef.current.pasteLabel);\n // Use the just-created objects as the next cascade source. Re-snapshotting\n // via `adapter.snapshotSelection(newIds)` would read adapter state, but\n // React-state adapters haven't flushed yet — `itemsRef.current` still\n // points at the pre-insert array, so the snapshot would come back empty\n // and the next paste would no-op.\n clipboardRef.current = { items: result.created };\n optsRef.current.onPaste?.(result.newIds as NodeId[]);\n }, []);\n\n const isEmpty = useCallback(() => clipboardRef.current.items.length === 0, []);\n\n return { copy, paste, isEmpty };\n}\n\nfunction defaultFlavors(snapshot: ClipboardSnapshot, replacer?: Replacer): Record<string, string> {\n const text = buildWeaselClipboardText(snapshot.items, replacer);\n return { [WEASEL_CLIPBOARD_MIME]: text, 'text/plain': text };\n}\n\n/** MIME types the async Clipboard API accepts unprefixed. Every other type\n * (including our own custom MIME, and app-supplied flavors like\n * `image/svg+xml`) must carry Chromium's `web ` prefix or `ClipboardItem`'s\n * constructor throws. */\nconst WELL_KNOWN_CLIPBOARD_MIMES = new Set(['text/plain', 'text/html', 'image/png']);\n\n/** Degradation ladder: full map (every non-well-known MIME web-prefixed) →\n * well-known-only → give up with a dwarn.\n * @internal test seam — exported so unit tests can await the fire-and-forget\n * write directly; tests should still exercise this at least once through\n * the public `copy()` (see clipboardOps.test.tsx). */\nexport async function writeOsClipboard(flavors: Record<string, string>): Promise<void> {\n const entries = Object.entries(flavors);\n if (entries.length === 0) return;\n const cb = typeof navigator !== 'undefined' ? navigator.clipboard : undefined;\n if (!cb?.write || typeof ClipboardItem === 'undefined') return;\n const wireMime = (mime: string) => (WELL_KNOWN_CLIPBOARD_MIMES.has(mime) ? mime : `web ${mime}`);\n const toItem = (fs: [string, string][]) => new ClipboardItem(Object.fromEntries(\n fs.map(([mime, text]) => {\n const wire = wireMime(mime);\n return [wire, new Blob([text], { type: wire })];\n }),\n ));\n try {\n await cb.write([toItem(entries)]);\n } catch {\n const standard = entries.filter(([mime]) => WELL_KNOWN_CLIPBOARD_MIMES.has(mime));\n if (standard.length === 0) { dwarn('clipboard', 'OS write failed; no standard flavors to fall back to'); return; }\n try {\n await cb.write([toItem(standard)]);\n } catch (err) {\n dwarn('clipboard', `OS clipboard write failed twice — copy stays in-memory only: ${String(err)}`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/applyOps.ts","../src/debug/flag.ts","../src/features/pointer/PointerContext.tsx","../src/interactions/actions/clipboard/wireFormat.ts","../src/interactions/actions/clipboard/materializePaste.ts","../src/interactions/actions/clipboard/clipboardOps.ts"],"names":["useRef"],"mappings":"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","file":"chunk-JOSUVS6C.js","sourcesContent":["import type { Op } from './ops/types';\n\n/**\n * Default `applyOps` implementation: apply each op in order against\n * `adapter`. Used by hooks when an adapter omits `applyOps`. Apps that\n * need history integration (checkpoint + push entry) supply their own\n * `applyOps` instead.\n *\n * The `_label` parameter mirrors the `applyOps(ops, label)` signature so\n * call-sites can dispatch through `(adapter.applyOps ?? applyOpsTo)` with\n * matching arguments. The default ignores it.\n */\nexport function applyOpsTo<A>(adapter: A, ops: Op[], _label?: string): void {\n for (const op of ops) op.apply(adapter);\n}\n\n/**\n * Dispatcher: invokes `adapter.applyOps` if present, otherwise falls back\n * to `applyOpsTo`. Convenience for hooks so they don't repeat the\n * `?? applyOpsTo` pattern at every call site.\n */\nexport function dispatchApplyBatch<A>(\n adapter: A,\n ops: Op[],\n label?: string,\n): void {\n const a = adapter as { applyOps?: (ops: Op[], label?: string) => void };\n if (a.applyOps) a.applyOps(ops, label);\n else applyOpsTo(adapter, ops, label);\n}\n","/**\n * Namespaced, opt-in console tracing for the kit.\n *\n * Enable in the browser:\n *\n * localStorage.setItem('weasel.debug', '*') // all namespaces\n * localStorage.setItem('weasel.debug', 'tools,dispatch') // specific ones\n * localStorage.setItem('weasel.debug', '1') // legacy \"all\"\n *\n * Each call site picks a stable namespace string. Off by default; the flag\n * is read once and cached per page session — reload to pick up a change.\n *\n * Mirrors `packages/ui/src/dlog.ts`. The two impls live in separate\n * packages because weasel-ui doesn't depend on the kit, but both read the\n * same localStorage keys so behavior is uniform from a debugger's POV.\n *\n * Known kit namespaces:\n * - tools — active-tool + hotkey transitions (useTools)\n * - dispatch — gesture start / end / cancel (dispatcher)\n * - drag-rect — useDragRect lifecycle\n * - drag-radial — useDragRadial lifecycle\n * - scene — restored-op replay gaps (history persistence)\n * - scene-canvas — SceneCanvas mount / unmount; adapter paste-translation warnings\n * - history — applyOps push, no-op suppression, serialize/restore\n * - clipboard — OS clipboard write/read failures, malformed payloads\n */\n\nconst KEYS = ['weasel.debug', 'weaseldraw.debug'] as const;\n\ninterface Cached {\n all: boolean;\n set: Set<string>;\n}\n\nlet cached: Cached | null = null;\n\nfunction read(): Cached {\n const empty: Cached = { all: false, set: new Set() };\n if (typeof localStorage === 'undefined') return empty;\n try {\n for (const k of KEYS) {\n const v = localStorage.getItem(k);\n if (v == null || v === '') continue;\n if (v === '1' || v === 'true' || v === '*') return { all: true, set: new Set() };\n return { all: false, set: new Set(v.split(/[\\s,]+/).filter(Boolean)) };\n }\n return empty;\n } catch {\n return empty;\n }\n}\n\nfunction get(): Cached {\n if (cached === null) cached = read();\n return cached;\n}\n\n/** True when `namespace` (or `*`) is enabled. Pass no argument to test\n * whether *any* namespace is on (legacy global-flag semantics). */\nexport function isDebugEnabled(namespace?: string): boolean {\n const c = get();\n if (c.all) return true;\n if (namespace === undefined) return c.set.size > 0;\n return c.set.has(namespace);\n}\n\n/** Conditional `console.debug` — no-op unless `namespace` (or `*`) is enabled.\n * Each call is prefixed with `[namespace]` so the source is searchable. */\nexport function dlog(namespace: string, ...args: unknown[]): void {\n if (!isDebugEnabled(namespace)) return;\n // eslint-disable-next-line no-console\n console.debug(`[${namespace}]`, ...args);\n}\n\n/** Conditional `console.warn` — like `dlog` but at warn level. Use for\n * diagnostics that indicate something worth surfacing during debugging\n * (e.g. no-op op batches suppressed). */\nexport function dwarn(namespace: string, ...args: unknown[]): void {\n if (!isDebugEnabled(namespace)) return;\n // eslint-disable-next-line no-console\n console.warn(`[${namespace}]`, ...args);\n}\n","/**\n * @experimental\n * PointerContext — a tiny ambient context that publishes the world-space\n * position of the canvas pointer, refreshed on every `pointermove` over\n * the canvas. Cleared (set to `null`) on `pointerleave`.\n *\n * Why ref-based and not state-based: cursor moves fire dozens of times per\n * second; routing those through React state would re-render every consumer\n * in the tree. The context exposes a stable `pointerRef` whose `.current`\n * is mutated directly by the publisher, plus a thunk `getDropPoint()` that\n * reads it on demand. Consumers (e.g. `useClipboard`) pull via the thunk\n * inside their callbacks — no subscription, no re-render.\n *\n * `<SceneCanvas>` publishes automatically. `useClipboardOps` consumes when\n * the caller didn't pass an explicit `getDropPoint` option. Other future\n * hit-on-cursor consumers (drop-zone hover, context-menu anchor) can reuse\n * the same context.\n */\nimport { createContext, useContext, useMemo, useRef, type MutableRefObject, type ReactNode } from 'react';\n\n/** @experimental World-space pointer position, or `null` when the pointer\n * isn't over the publishing canvas. */\nexport type PointerWorldPos = { worldX: number; worldY: number } | null;\n\n/** @experimental */\nexport interface PointerContextValue {\n /** Live ref — mutate to publish, read for the latest snapshot. The\n * identity is stable for the lifetime of the provider. */\n readonly pointerRef: MutableRefObject<PointerWorldPos>;\n /** Convenience thunk equivalent to `() => pointerRef.current`. Stable\n * identity for the lifetime of the provider; safe to pass to hooks. */\n readonly getDropPoint: () => PointerWorldPos;\n}\n\nconst PointerContext = createContext<PointerContextValue | null>(null);\n\n/**\n * @experimental\n * Wrap the part of the React tree that should share a pointer-position\n * context. Usually placed at the demo / app root, alongside\n * `<ActionsProvider>` and `<SelectionContextProvider>`.\n *\n * Most consumers don't need to mount this directly — `<SceneCanvas>` mounts\n * an internal provider when no parent provider is in scope, so child hooks\n * (`useClipboard` without an explicit `getDropPoint`) read the canvas's\n * tracked pointer for free.\n */\nexport function PointerContextProvider({ children }: { children: ReactNode }): ReactNode {\n const pointerRef = useRef<PointerWorldPos>(null);\n const value = useMemo<PointerContextValue>(() => ({\n pointerRef,\n getDropPoint: () => pointerRef.current,\n }), []);\n return <PointerContext.Provider value={value}>{children}</PointerContext.Provider>;\n}\n\n/** @experimental Read the surrounding pointer-context value, or `null` when\n * no provider is in scope. */\nexport function usePointerContext(): PointerContextValue | null {\n return useContext(PointerContext);\n}\n","import { dwarn } from 'debug/flag';\n\n/** MIME type the kit writes its full-fidelity clipboard payload under, so a\n * copy/paste between two weasel canvases round-trips scene nodes rather than\n * the SVG or text flavors other apps read. */\nexport const WEASEL_CLIPBOARD_MIME = 'application/x-weasel-clipboard+json';\n/** Chromium's async-clipboard spelling for custom formats. */\nexport const WEASEL_CLIPBOARD_MIME_WEB = `web ${WEASEL_CLIPBOARD_MIME}`;\n\ninterface WeaselClipboardPayload { weaselClipboard: 1; nodes: unknown[] }\n\ntype Replacer = (key: string, value: unknown) => unknown;\ntype Reviver = (key: string, value: unknown) => unknown;\n\n/** Serialize snapshot items into the versioned wire text.\n *\n * Function-valued fields (e.g. a container's `clipFromPose`) are dropped by\n * JSON serialization — OS-pasted clip containers arrive unclipped.\n * Consumers who need clips to survive the OS round-trip can translate\n * function ↔ registry key in their `jsonReplacer`/reviver pair (the\n * scene-serialization `clipFromPoseKey` precedent). */\nexport function buildWeaselClipboardText(items: unknown[], replacer?: Replacer): string {\n const payload: WeaselClipboardPayload = { weaselClipboard: 1, nodes: items };\n return JSON.stringify(payload, replacer as Parameters<typeof JSON.stringify>[1]);\n}\n\n/** Cheap check: could this text be a weasel clipboard payload? Substring\n * pre-check, then a guarded parse + marker/version test. */\nexport function sniffWeaselClipboardText(text: string): boolean {\n if (!text.includes('\"weaselClipboard\"')) return false;\n try {\n const parsed = JSON.parse(text) as Partial<WeaselClipboardPayload> | null;\n return !!parsed && parsed.weaselClipboard === 1;\n } catch {\n return false;\n }\n}\n\n// Opening `<svg ...>` tag of a serialized document. `[^>]*` is safe for OUR\n// serializer's output (attribute values are XML-escaped, so no raw `>` can\n// appear inside the tag) — see embedWeaselMetadataInSvg's contract note.\nconst SVG_OPEN_TAG = /<svg(?:\\s[^>]*)?>/i;\n\n/**\n * Embed a weasel clipboard payload in an SVG document as a `<metadata>`\n * child of the root — legal SVG 1.1, ignored by external tools — so one\n * `text/plain` flavor serves both audiences: external tools parse the SVG,\n * weasel extracts the JSON for full fidelity on draw→draw DOM paste.\n *\n * The payload rides in CDATA so JSON's `<` / `&` need no entity escaping; a\n * literal `]]>` inside the payload is split across CDATA sections\n * (`]]]]><![CDATA[>`), which {@link extractWeaselClipboardFromSvg} rejoins.\n *\n * Intended for OUR serializer's output only (`serializeSvg` — single\n * `<svg ...>` open tag, XML-escaped attributes). When no open tag is found\n * the input is returned unchanged with a debug warning.\n */\nexport function embedWeaselMetadataInSvg(svgText: string, payloadText: string): string {\n const open = SVG_OPEN_TAG.exec(svgText);\n if (!open) {\n dwarn('clipboard', 'embedWeaselMetadataInSvg: no <svg ...> open tag found — returning input unchanged');\n return svgText;\n }\n const cdata = `<![CDATA[${payloadText.replaceAll(']]>', ']]]]><![CDATA[>')}]]>`;\n const at = open.index + open[0].length;\n return `${svgText.slice(0, at)}<metadata>${cdata}</metadata>${svgText.slice(at)}`;\n}\n\n/**\n * Recover an embedded weasel clipboard payload from SVG text: read the first\n * `<metadata>` block, rejoin its CDATA sections (concatenation restores the\n * `]]>` split by {@link embedWeaselMetadataInSvg}; plain non-CDATA content\n * passes through as-is), and return the payload text iff it sniffs as the\n * weasel wire format — else null (external/non-weasel metadata declines).\n *\n * A text scan, not an XML parse — the payload is small and ours.\n */\nexport function extractWeaselClipboardFromSvg(svgText: string): string | null {\n const block = /<metadata(?:\\s[^>]*)?>([\\s\\S]*?)<\\/metadata>/i.exec(svgText);\n if (!block) return null;\n const inner = block[1];\n const payload = inner.includes('<![CDATA[')\n ? Array.from(inner.matchAll(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g), (m) => m[1]).join('')\n : inner;\n return sniffWeaselClipboardText(payload) ? payload : null;\n}\n\n/** Parse wire text to snapshot items, or null when malformed/mismatched\n * (callers decline to the next content handler). */\nexport function parseWeaselClipboardText(text: string, reviver?: Reviver): unknown[] | null {\n try {\n const parsed = JSON.parse(text, reviver as Parameters<typeof JSON.parse>[1]) as\n Partial<WeaselClipboardPayload> | null;\n if (!parsed || parsed.weaselClipboard !== 1 || !Array.isArray(parsed.nodes)) return null;\n return parsed.nodes;\n } catch (err) {\n dwarn('clipboard', `weasel payload failed to parse: ${String(err)}`);\n return null;\n }\n}\n","/**\n * Shared paste materialization — the one place the paste recipe lives:\n * offset policy → `commitPaste` → the op batch (InsertOps parents-first +\n * a SetSelectionOp). Both `useClipboardOps.paste` (in-memory paste) and the\n * kit weasel-JSON content handler (OS-clipboard paste) dispatch exactly this\n * batch, so the two arrival paths stay indistinguishable in history.\n *\n * Pure: no dispatching here — callers own how the ops are applied\n * (`dispatchApplyBatch` vs `IngestCtx.applyOps`) and any post-paste\n * bookkeeping (the hook's cascade re-snapshot, the handler's selection set).\n */\nimport { createInsertOp } from 'core/ops/create';\nimport { createSetSelectionOp } from 'core/ops/select';\nimport type { Op } from 'core/ops/types';\nimport type { NodeId } from 'core/scene/types';\nimport type { ClipboardSnapshot, InsertAdapter } from 'core/adapters/types';\n\n/** Shared paste materialization: offset policy → commitPaste → the op batch\n * both the in-memory paste and the OS-paste handler dispatch. Returns null\n * when the adapter can't paste or nothing was created. */\nexport function materializePaste<TNode extends { id: string }>(\n adapter: InsertAdapter<TNode>,\n snapshot: ClipboardSnapshot,\n opts: {\n currentSelection: string[];\n dropPoint?: { worldX: number; worldY: number } | null;\n },\n): { ops: Op[]; newIds: string[]; created: TNode[] } | null {\n const commitPaste = adapter.commitPaste;\n if (!commitPaste) return null;\n const offset = adapter.getPasteOffset?.(snapshot) ?? { dx: 0, dy: 0 };\n const ctx = opts.dropPoint != null ? { dropPoint: opts.dropPoint } : undefined;\n const created = commitPaste.call(adapter, snapshot, offset, ctx);\n if (created.length === 0) return null;\n const newIds = created.map((o) => o.id);\n const ops: Op[] = [\n ...created.map((o) => createInsertOp({ node: o })),\n createSetSelectionOp({ from: opts.currentSelection as NodeId[], to: newIds as NodeId[] }),\n ];\n return { ops, newIds, created };\n}\n","import { useCallback, useRef } from 'react';\nimport type { NodeId } from 'core/scene/types';\nimport { dispatchApplyBatch } from 'core/applyOps';\nimport type { InsertAdapter } from 'core/adapters/types';\nimport type { ClipboardSnapshot } from './types';\nimport { usePointerContext } from 'features/pointer/PointerContext';\nimport { dwarn } from 'debug/flag';\nimport { materializePaste } from './materializePaste';\nimport { WEASEL_CLIPBOARD_MIME, buildWeaselClipboardText } from './wireFormat';\n\ntype Replacer = (key: string, value: unknown) => unknown;\n\n/** Options for `useClipboardOps`. */\nexport interface UseClipboardOpsOptions {\n /** How the hook reads \"current selection\" for copy. The kit doesn't assume\n * a global selection store; each consumer wires this. */\n getSelection: () => NodeId[];\n /** Called after a successful paste with the ids of the newly inserted objects. */\n onPaste?: (newIds: NodeId[]) => void;\n /** Label for the history entry produced by paste. Default 'Paste'. */\n pasteLabel?: string;\n /** Pulled once per paste() call. When non-null, threaded to commitPaste\n * as `ctx.dropPoint`; adapters typically use it as the cluster origin\n * (ignoring `offset`). When null/undefined, the hook falls back to the\n * existing cascade-offset behavior. */\n getDropPoint?: () => { worldX: number; worldY: number } | null;\n /** Produce the OS-clipboard flavor map for a copied snapshot. Keys are MIME\n * types, values the serialized payload. The kit default emits\n * `application/x-weasel-clipboard+json` plus `text/plain` carrying the\n * same JSON. Apps override to add richer flavors (e.g. real SVG) or to\n * replace the text flavor. Return an empty object to skip the OS write\n * entirely. */\n produceFlavors?: (snapshot: ClipboardSnapshot) => Record<string, string>;\n /** Replacer for the kit-default JSON flavor (typed arrays etc.). Ignored\n * when `produceFlavors` is supplied. */\n jsonReplacer?: Replacer;\n}\n\n/** Return shape of `useClipboardOps`: imperative `copy`, `paste`, and `isEmpty` functions. */\nexport interface UseClipboardOpsReturn {\n copy(): void;\n paste(): void;\n isEmpty(): boolean;\n}\n\nconst EMPTY: ClipboardSnapshot = { items: [] };\n\n/** In-memory copy/paste of selections via `InsertAdapter.snapshotSelection` / `commitPaste`. */\nexport function useClipboardOps<TNode extends { id: string }>(\n adapter: InsertAdapter<TNode>,\n options: UseClipboardOpsOptions,\n): UseClipboardOpsReturn {\n const { getSelection, onPaste, pasteLabel = 'Paste', getDropPoint, produceFlavors, jsonReplacer } = options;\n // Fall back to the ambient pointer context (auto-published by SceneCanvas)\n // when the caller didn't supply an explicit `getDropPoint`. Null context\n // (no provider in scope) and no caller-supplied thunk together mean\n // \"no drop point\" — paste uses the cascade offset instead.\n const pointerCtx = usePointerContext();\n const effectiveGetDropPoint = getDropPoint ?? pointerCtx?.getDropPoint;\n const clipboardRef = useRef<ClipboardSnapshot>(EMPTY);\n // Keep callbacks stable across renders.\n const adapterRef = useRef(adapter);\n adapterRef.current = adapter;\n const optsRef = useRef({\n getSelection, onPaste, pasteLabel, getDropPoint: effectiveGetDropPoint, produceFlavors, jsonReplacer,\n });\n optsRef.current = {\n getSelection, onPaste, pasteLabel, getDropPoint: effectiveGetDropPoint, produceFlavors, jsonReplacer,\n };\n\n const copy = useCallback(() => {\n const ids = optsRef.current.getSelection();\n if (ids.length === 0) return;\n const snap = adapterRef.current.snapshotSelection;\n if (!snap) return;\n clipboardRef.current = snap(ids);\n // Best-effort OS write. Never blocks or throws — the in-memory ref is\n // already set, and OS clipboard support varies (custom formats are\n // Chromium-only via the \"web \" prefix; jsdom/non-secure contexts have\n // no API at all). The flavor producer itself is untrusted consumer code\n // (`produceFlavors`), so guard its call too — a throwing producer must\n // not break `copy()`, it just skips the OS write.\n let flavors: Record<string, string> | null = null;\n try {\n flavors = optsRef.current.produceFlavors?.(clipboardRef.current)\n ?? defaultFlavors(clipboardRef.current, optsRef.current.jsonReplacer);\n } catch (err) {\n dwarn('clipboard', `flavor producer threw — skipping OS write: ${String(err)}`);\n }\n if (flavors) void writeOsClipboard(flavors);\n }, []);\n\n const paste = useCallback(() => {\n const cb = clipboardRef.current;\n if (cb.items.length === 0) return;\n const a = adapterRef.current;\n const result = materializePaste(a, cb, {\n currentSelection: optsRef.current.getSelection(),\n dropPoint: optsRef.current.getDropPoint?.(),\n });\n if (!result) return;\n dispatchApplyBatch(a, result.ops, optsRef.current.pasteLabel);\n // Use the just-created objects as the next cascade source. Re-snapshotting\n // via `adapter.snapshotSelection(newIds)` would read adapter state, but\n // React-state adapters haven't flushed yet — `itemsRef.current` still\n // points at the pre-insert array, so the snapshot would come back empty\n // and the next paste would no-op.\n clipboardRef.current = { items: result.created };\n optsRef.current.onPaste?.(result.newIds as NodeId[]);\n }, []);\n\n const isEmpty = useCallback(() => clipboardRef.current.items.length === 0, []);\n\n return { copy, paste, isEmpty };\n}\n\nfunction defaultFlavors(snapshot: ClipboardSnapshot, replacer?: Replacer): Record<string, string> {\n const text = buildWeaselClipboardText(snapshot.items, replacer);\n return { [WEASEL_CLIPBOARD_MIME]: text, 'text/plain': text };\n}\n\n/** MIME types the async Clipboard API accepts unprefixed. Every other type\n * (including our own custom MIME, and app-supplied flavors like\n * `image/svg+xml`) must carry Chromium's `web ` prefix or `ClipboardItem`'s\n * constructor throws. */\nconst WELL_KNOWN_CLIPBOARD_MIMES = new Set(['text/plain', 'text/html', 'image/png']);\n\n/** Degradation ladder: full map (every non-well-known MIME web-prefixed) →\n * well-known-only → give up with a dwarn.\n * @internal test seam — exported so unit tests can await the fire-and-forget\n * write directly; tests should still exercise this at least once through\n * the public `copy()` (see clipboardOps.test.tsx). */\nexport async function writeOsClipboard(flavors: Record<string, string>): Promise<void> {\n const entries = Object.entries(flavors);\n if (entries.length === 0) return;\n const cb = typeof navigator !== 'undefined' ? navigator.clipboard : undefined;\n if (!cb?.write || typeof ClipboardItem === 'undefined') return;\n const wireMime = (mime: string) => (WELL_KNOWN_CLIPBOARD_MIMES.has(mime) ? mime : `web ${mime}`);\n const toItem = (fs: [string, string][]) => new ClipboardItem(Object.fromEntries(\n fs.map(([mime, text]) => {\n const wire = wireMime(mime);\n return [wire, new Blob([text], { type: wire })];\n }),\n ));\n try {\n await cb.write([toItem(entries)]);\n } catch {\n const standard = entries.filter(([mime]) => WELL_KNOWN_CLIPBOARD_MIMES.has(mime));\n if (standard.length === 0) { dwarn('clipboard', 'OS write failed; no standard flavors to fall back to'); return; }\n try {\n await cb.write([toItem(standard)]);\n } catch (err) {\n dwarn('clipboard', `OS clipboard write failed twice — copy stays in-memory only: ${String(err)}`);\n }\n }\n}\n"]}
|