@weasel-js/core 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +266 -0
  2. package/README.md +24 -4
  3. package/dist/{depSchema-BhgZc0-Q.d.ts → builtins-DLn2lCr_.d.ts} +693 -53
  4. package/dist/{chunk-BJN5ZRIJ.js → chunk-3TY6FHAF.js} +7 -4
  5. package/dist/chunk-3TY6FHAF.js.map +1 -0
  6. package/dist/{chunk-H6N2JSZ3.js → chunk-DGWNNKGD.js} +3 -3
  7. package/dist/{chunk-H6N2JSZ3.js.map → chunk-DGWNNKGD.js.map} +1 -1
  8. package/dist/{chunk-THCSDHII.js → chunk-G4ZGQMY7.js} +2453 -1264
  9. package/dist/chunk-G4ZGQMY7.js.map +1 -0
  10. package/dist/{chunk-HPFJUUTF.js → chunk-LU4IJ52W.js} +5 -61
  11. package/dist/chunk-LU4IJ52W.js.map +1 -0
  12. package/dist/{chunk-D4AAWR64.js → chunk-M24CLARN.js} +4 -4
  13. package/dist/{chunk-D4AAWR64.js.map → chunk-M24CLARN.js.map} +1 -1
  14. package/dist/{chunk-4RJP2N2L.js → chunk-NE27EOLP.js} +8 -5
  15. package/dist/chunk-NE27EOLP.js.map +1 -0
  16. package/dist/{chunk-DOBSZOPR.js → chunk-UPZY4ART.js} +3 -3
  17. package/dist/{chunk-DOBSZOPR.js.map → chunk-UPZY4ART.js.map} +1 -1
  18. package/dist/{chunk-T3UQ3F6R.js → chunk-WPNA6L5O.js} +47 -13
  19. package/dist/chunk-WPNA6L5O.js.map +1 -0
  20. package/dist/{chunk-44EWMJV2.js → chunk-XMAWPGTK.js} +5 -5
  21. package/dist/{chunk-44EWMJV2.js.map → chunk-XMAWPGTK.js.map} +1 -1
  22. package/dist/clipboard.d.ts +1 -1
  23. package/dist/clipboard.js +3 -3
  24. package/dist/clone.d.ts +2 -2
  25. package/dist/clone.js +3 -3
  26. package/dist/grid-CY5UKlZh.d.ts +98 -0
  27. package/dist/index.css +11 -35
  28. package/dist/index.css.map +1 -1
  29. package/dist/index.d.ts +912 -1148
  30. package/dist/index.js +9 -9
  31. package/dist/insert.d.ts +2 -3
  32. package/dist/insert.js +1 -1
  33. package/dist/math.d.ts +1 -1
  34. package/dist/move.d.ts +5 -4
  35. package/dist/move.js +4 -4
  36. package/dist/move.js.map +1 -1
  37. package/dist/patterns-builtin.js +1 -1
  38. package/dist/{pointSnapToGrid-7mCcm6qG.d.ts → pointSnapToGrid-Dp2IxLGb.d.ts} +53 -60
  39. package/dist/renderer.css +11 -35
  40. package/dist/renderer.css.map +1 -1
  41. package/dist/renderer.d.ts +17 -8
  42. package/dist/renderer.js +9 -9
  43. package/dist/resize.d.ts +3 -3
  44. package/dist/resize.js +2 -2
  45. package/dist/routing.d.ts +1 -1
  46. package/dist/routing.js +1 -1
  47. package/dist/{types-CxC8P6W0.d.ts → types-D-9QWBy5.d.ts} +9 -6
  48. package/dist/{types-D_74YlWs.d.ts → types-qYpyTAiZ.d.ts} +4 -7
  49. package/package.json +15 -12
  50. package/dist/chunk-4RJP2N2L.js.map +0 -1
  51. package/dist/chunk-BJN5ZRIJ.js.map +0 -1
  52. package/dist/chunk-HPFJUUTF.js.map +0 -1
  53. package/dist/chunk-T3UQ3F6R.js.map +0 -1
  54. package/dist/chunk-THCSDHII.js.map +0 -1
  55. package/dist/grid-Dx2gQbNv.d.ts +0 -158
  56. package/dist/options-CzjxFoYZ.d.ts +0 -67
  57. package/dist/{math-DRa6HBBs.d.ts → math-MCQcf_gc.d.ts} +111 -111
@@ -1,62 +1,6 @@
1
1
  import { isPathLike, translatePath, boundsOfPath } from './chunk-L5YNZYEG.js';
2
+ import { resolveUnit } from '@weasel-js/quantity';
2
3
 
3
- // src/core/units.ts
4
- function unitScale(unitSystem, unit) {
5
- const entry = unitSystem.units[unit];
6
- if (entry === void 0) {
7
- const known = Object.keys(unitSystem.units).join(", ") || "(none)";
8
- throw new Error(
9
- `unknown unit '${unit}' (system base: '${unitSystem.base}', known units: ${known})`
10
- );
11
- }
12
- return typeof entry === "number" ? { factor: entry, offset: 0 } : { factor: entry.factor, offset: entry.offset ?? 0 };
13
- }
14
- function resolveUnit(v, unitSystem) {
15
- if (typeof v === "number") return v;
16
- if (!unitSystem) {
17
- throw new Error(
18
- `resolveUnit: tagged value { value: ${v.value}, unit: '${v.unit}' } requires a UnitSystem`
19
- );
20
- }
21
- const { factor, offset } = scaleOrThrow("resolveUnit", unitSystem, v.unit);
22
- return v.value * factor + offset;
23
- }
24
- function formatUnit(baseValue, displayUnit, unitSystem, opts) {
25
- const { factor, offset } = scaleOrThrow("formatUnit", unitSystem, displayUnit);
26
- const precision = opts?.precision ?? 2;
27
- const suffix = opts?.suffix ?? true;
28
- const display = (baseValue - offset) / factor;
29
- let s = display.toFixed(precision);
30
- if (s.includes(".")) {
31
- s = s.replace(/0+$/, "").replace(/\.$/, "");
32
- }
33
- return suffix ? `${s}${displayUnit}` : s;
34
- }
35
- function scaleOrThrow(caller, unitSystem, unit) {
36
- try {
37
- return unitScale(unitSystem, unit);
38
- } catch (e) {
39
- throw new Error(`${caller}: ${e instanceof Error ? e.message : String(e)}`);
40
- }
41
- }
42
- var IMPERIAL_INCHES = {
43
- base: "in",
44
- units: { in: 1, ft: 12, yd: 36, mi: 63360 }
45
- };
46
- var METRIC_MM = {
47
- base: "mm",
48
- units: { mm: 1, cm: 10, m: 1e3, km: 1e6 }
49
- };
50
- var ANGLE_RADIANS = {
51
- base: "rad",
52
- units: { rad: 1, deg: Math.PI / 180, turn: Math.PI * 2 }
53
- };
54
- var PIXELS = {
55
- base: "px",
56
- units: { px: 1 }
57
- };
58
-
59
- // src/interactions/gestures/shared/strategies/grid.ts
60
4
  var RECT_ORIGIN_PROJECTION = {
61
5
  getOrigin: (p) => ({ x: p.x, y: p.y }),
62
6
  translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy })
@@ -125,7 +69,7 @@ var DEFAULT_GUIDE_TOLERANCE_PX = 6;
125
69
  function guideSnapStrategy(getGuides, options = {}) {
126
70
  const tolerance = options.tolerance ?? DEFAULT_GUIDE_TOLERANCE_PX;
127
71
  const getView = options.getView;
128
- const proj = options.origin ?? RECT_ORIGIN_PROJECTION;
72
+ const proj = options.origin ?? AUTO_ORIGIN_PROJECTION;
129
73
  return {
130
74
  snap(pose) {
131
75
  const guides = getGuides();
@@ -162,6 +106,6 @@ function guideSnapStrategy(getGuides, options = {}) {
162
106
  };
163
107
  }
164
108
 
165
- export { ANGLE_RADIANS, DEFAULT_GUIDE_TOLERANCE_PX, IMPERIAL_INCHES, METRIC_MM, PIXELS, RECT_ORIGIN_PROJECTION, formatUnit, gridSnapStrategy, guideSnapStrategy, pointToGridCell, pxExtent, resolveUnit, scaleDelta, unitScale, withinPxBox, withinPxRadius };
166
- //# sourceMappingURL=chunk-HPFJUUTF.js.map
167
- //# sourceMappingURL=chunk-HPFJUUTF.js.map
109
+ export { AUTO_ORIGIN_PROJECTION, DEFAULT_GUIDE_TOLERANCE_PX, RECT_ORIGIN_PROJECTION, gridSnapStrategy, guideSnapStrategy, pointToGridCell, pxExtent, scaleDelta, withinPxBox, withinPxRadius };
110
+ //# sourceMappingURL=chunk-LU4IJ52W.js.map
111
+ //# sourceMappingURL=chunk-LU4IJ52W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/interactions/gestures/shared/strategies/grid.ts","../src/core/viewport/pxExtent.ts","../src/interactions/gestures/shared/strategies/guides.ts"],"names":[],"mappings":"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","file":"chunk-LU4IJ52W.js","sourcesContent":["import type { SnapStrategy } from '../../types';\nimport { resolveUnit, type UnitSystem, type UnitValue } from '@weasel-js/quantity';\nimport type { DebugSink } from '../../../../debug/types';\nimport { isPathLike } from 'interactions/actions/resize/autoPoseDescriptor';\nimport { boundsOfPath } from 'features/paths/bounds';\nimport { translatePath } from 'features/paths/transform';\nimport type { Path } from 'features/paths/types';\n\n/**\n * Projection used by `gridSnapStrategy` when `TPose` doesn't expose `{x,y}`\n * directly (Path, polygon, etc.). The strategy reads the snap point via\n * `getOrigin`, rounds it to the grid, then asks `translate` to move the\n * pose by the resulting delta.\n */\nexport interface OriginProjection<TPose> {\n getOrigin(pose: TPose): { x: number; y: number };\n translate(pose: TPose, dx: number, dy: number): TPose;\n}\n\n/** Identity projection for `TPose extends { x; y }`. */\nexport const RECT_ORIGIN_PROJECTION: OriginProjection<{ x: number; y: number }> = {\n getOrigin: (p) => ({ x: p.x, y: p.y }),\n translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy }),\n};\n\n/** Per-call dispatch: routes Path-shaped poses to the path origin/translate\n * helpers and everything else to `RECT_ORIGIN_PROJECTION`. Default for\n * `gridSnapStrategy` so consumers with Path TPose don't have to thread\n * `pathOriginProjection` explicitly. */\nexport const AUTO_ORIGIN_PROJECTION: OriginProjection<unknown> = {\n getOrigin: (p) => {\n if (isPathLike(p)) {\n const b = boundsOfPath(p as Path);\n return { x: b.x, y: b.y };\n }\n const r = p as { x: number; y: number };\n return { x: r.x, y: r.y };\n },\n translate: (p, dx, dy) => {\n if (isPathLike(p)) return translatePath(p as Path, dx, dy);\n const r = p as { x: number; y: number };\n return { ...r, x: r.x + dx, y: r.y + dy };\n },\n};\n\n/** Snap-strategy that rounds the pose's origin to the nearest multiple of\n * `spacing` (resolved through `unitSystem`). For non-rect TPose pass an\n * `OriginProjection` so the strategy knows how to read/write the origin. */\nexport function gridSnapStrategy<TPose>(\n spacing: UnitValue,\n unitSystem?: UnitSystem,\n): SnapStrategy<TPose>;\n/** As above, additionally reporting each candidate to a debug sink. */\nexport function gridSnapStrategy<TPose>(\n spacing: UnitValue,\n opts: { unitSystem?: UnitSystem; debug?: DebugSink },\n): SnapStrategy<TPose>;\n/** As above, for a `TPose` that is not a rect: `origin` tells the strategy how\n * to read and write the pose's origin. */\nexport function gridSnapStrategy<TPose>(\n spacing: UnitValue,\n opts: { unitSystem?: UnitSystem; origin: OriginProjection<TPose>; debug?: DebugSink },\n): SnapStrategy<TPose>;\n/** Snap-strategy that rounds the pose's origin to the nearest multiple of\n * `spacing` (resolved through `unitSystem`). For non-rect TPose pass an\n * `OriginProjection` so the strategy knows how to read/write the origin. */\nexport function gridSnapStrategy<TPose>(\n spacing: UnitValue,\n arg?: UnitSystem | { unitSystem?: UnitSystem; origin?: OriginProjection<TPose>; debug?: DebugSink },\n): SnapStrategy<TPose> {\n const isOpts =\n typeof arg === 'object' &&\n arg !== null &&\n ('origin' in arg || 'debug' in arg || 'unitSystem' in arg) &&\n !('base' in arg);\n const optsArg = isOpts\n ? (arg as { unitSystem?: UnitSystem; origin?: OriginProjection<TPose>; debug?: DebugSink })\n : null;\n const unitSystem = optsArg ? optsArg.unitSystem : (arg as UnitSystem | undefined);\n const proj: OriginProjection<TPose> = optsArg && optsArg.origin\n ? optsArg.origin\n : (AUTO_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n const debug: DebugSink | undefined = optsArg ? optsArg.debug : undefined;\n const c = resolveUnit(spacing, unitSystem);\n return {\n snap(pose) {\n const o = proj.getOrigin(pose);\n const sx = Math.round(o.x / c) * c;\n const sy = Math.round(o.y / c) * c;\n debug?.recordSnapCandidate({ x: sx, y: sy }, true);\n return proj.translate(pose, sx - o.x, sy - o.y);\n },\n };\n}\n\n/**\n * Compute the integer cell `{col, row}` that contains `point`, given a grid\n * `spacing` and optional `origin` and `unitSystem`. Pair with\n * `createCellHighlightLayer` (its `getCell` callback) to draw a snap-target\n * preview that uses the same spacing as `gridSnapStrategy` — so the visual\n * and behavioral grids stay in lockstep:\n *\n * const SPACING = 20;\n * const snap = gridSnapStrategy(SPACING);\n * // visual grid:\n * createGridLayer({ spacing: SPACING, bounds: () => ... });\n * // hover-preview overlay:\n * createCellHighlightLayer({\n * spacing: SPACING,\n * getCell: () => hoverPoint && pointToGridCell(hoverPoint, SPACING),\n * });\n */\nexport function pointToGridCell(\n point: { x: number; y: number },\n spacing: UnitValue,\n unitSystem?: UnitSystem,\n origin: { x: number; y: number } = { x: 0, y: 0 },\n): { col: number; row: number } {\n const s = resolveUnit(spacing, unitSystem);\n return {\n col: Math.floor((point.x - origin.x) / s),\n row: Math.floor((point.y - origin.y) / s),\n };\n}\n","/**\n * Screen-pixel lengths in world units, per axis.\n *\n * Chrome declares its hit zones in screen pixels — an 8px handle stays 8px at\n * every zoom, because it paints in screen space. Converting one such length to\n * world units with a single scalar (see `meanScale`) is exact only under\n * uniform zoom; per-axis it is too generous on one axis and too mean on the\n * other, so the pickable region stops matching the painted one.\n *\n * Prefer {@link withinPxBox} / {@link withinPxRadius}, which compare in screen\n * space and so can't drift from the paint at all. {@link pxExtent} is for the\n * cases that must stay in world units — a tolerance handed to geometry that\n * doesn't know about the view.\n */\n\nexport interface Scale2 {\n x: number;\n y: number;\n}\n\n/** One screen-pixel length as world-space extents, per axis. `scale` is a\n * view's, so neither axis is zero (see `View`). */\nexport function pxExtent(px: number, scale: Scale2): { x: number; y: number } {\n return {\n x: px / Math.abs(scale.x),\n y: px / Math.abs(scale.y),\n };\n}\n\n/** World delta → screen delta. Only the scale participates: translation\n * cancels in a difference, and the kit's views carry no skew. */\nexport function scaleDelta(dx: number, dy: number, scale: Scale2): { x: number; y: number } {\n return { x: dx * scale.x, y: dy * scale.y };\n}\n\n/**\n * Is a world-space delta inside a screen-space square of half-extent `px`?\n *\n * The square is axis-aligned **on screen**, which is what a handle painted in\n * screen space actually is — so this stays true under non-uniform zoom and\n * under a rotated target, where an axis-aligned world-space test is a rotated\n * rectangle on screen.\n */\nexport function withinPxBox(dx: number, dy: number, px: number, scale: Scale2): boolean {\n const s = scaleDelta(dx, dy, scale);\n return Math.abs(s.x) <= px && Math.abs(s.y) <= px;\n}\n\n/** Is a world-space delta inside a screen-space circle of radius `px`? */\nexport function withinPxRadius(dx: number, dy: number, px: number, scale: Scale2): boolean {\n const s = scaleDelta(dx, dy, scale);\n return s.x * s.x + s.y * s.y <= px * px;\n}\n","import type { SnapStrategy } from '../../types';\nimport type { Guide } from 'features/guides/types';\nimport type { View } from 'core/viewport/view';\nimport { pxExtent } from 'core/viewport/pxExtent';\nimport type { OriginProjection } from './grid';\nimport { AUTO_ORIGIN_PROJECTION } from './grid';\n\n/** Default snap tolerance (screen pixels). */\nexport const DEFAULT_GUIDE_TOLERANCE_PX = 6;\n\n/** Options for `guideSnapStrategy`. */\nexport interface GuideSnapOptions<TPose> {\n /**\n * Tolerance, in screen pixels (CSS px) when `getView` is provided, or in\n * world units otherwise. Defaults to `DEFAULT_GUIDE_TOLERANCE_PX`.\n */\n tolerance?: number;\n /**\n * Read the active view transform. When supplied, `tolerance` is interpreted\n * in screen pixels and divided by `view.scale` so the trigger zone stays\n * the same on screen as the user zooms in/out. Without it, `tolerance`\n * is treated as world units.\n */\n getView?: () => View;\n /**\n * Projection used when `TPose` doesn't expose `{x, y}` directly (Path,\n * polygon, etc.) — same contract as `gridSnapStrategy`. Defaults to the\n * rect-pose identity projection.\n */\n origin?: OriginProjection<TPose>;\n}\n\n/**\n * Snap-strategy that snaps the pose's origin to the nearest guide line on\n * each axis when within tolerance. Each axis is considered independently:\n * a vertical guide (`axis: 'x'`) constrains X, a horizontal guide\n * (`axis: 'y'`) constrains Y. When multiple guides are within range on the\n * same axis, the closest wins.\n *\n * Pair with `useGuides` for storage and `createGuidesLayer` for rendering.\n *\n * **Composition:** combining with `gridSnapStrategy` is left to the\n * consumer in v1 — typically by picking one based on a modifier key, or\n * by stacking two move/resize/insert behaviors in priority order.\n */\nexport function guideSnapStrategy<TPose extends { x: number; y: number }>(\n getGuides: () => readonly Guide[],\n options?: Omit<GuideSnapOptions<TPose>, 'origin'>,\n): SnapStrategy<TPose>;\n/** As above, for a `TPose` that is not a rect: `origin` tells the strategy how\n * to read and write the pose's origin. */\nexport function guideSnapStrategy<TPose>(\n getGuides: () => readonly Guide[],\n options: GuideSnapOptions<TPose> & { origin: OriginProjection<TPose> },\n): SnapStrategy<TPose>;\n/** Snap a pose's origin to the nearest guide line within tolerance, per axis. */\nexport function guideSnapStrategy<TPose>(\n getGuides: () => readonly Guide[],\n options: GuideSnapOptions<TPose> = {},\n): SnapStrategy<TPose> {\n const tolerance = options.tolerance ?? DEFAULT_GUIDE_TOLERANCE_PX;\n const getView = options.getView;\n const proj: OriginProjection<TPose> =\n options.origin ?? (AUTO_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n\n return {\n snap(pose) {\n const guides = getGuides();\n if (guides.length === 0) return null;\n\n // Convert tolerance to world units, per axis: a guide on the x axis is\n // matched by a horizontal distance, so it answers to `scale.x` alone.\n // With no view, treat tolerance as already-world.\n const tol = getView ? pxExtent(tolerance, getView().scale) : { x: tolerance, y: tolerance };\n\n const o = proj.getOrigin(pose);\n\n let bestDx = 0;\n let bestAbsDx = Infinity;\n let bestDy = 0;\n let bestAbsDy = Infinity;\n\n for (const g of guides) {\n if (g.axis === 'x') {\n const d = g.offset - o.x;\n const ad = Math.abs(d);\n if (ad <= tol.x && ad < bestAbsDx) {\n bestAbsDx = ad;\n bestDx = d;\n }\n } else {\n const d = g.offset - o.y;\n const ad = Math.abs(d);\n if (ad <= tol.y && ad < bestAbsDy) {\n bestAbsDy = ad;\n bestDy = d;\n }\n }\n }\n\n if (bestAbsDx === Infinity && bestAbsDy === Infinity) return null;\n const dx = bestAbsDx === Infinity ? 0 : bestDx;\n const dy = bestAbsDy === Infinity ? 0 : bestDy;\n if (dx === 0 && dy === 0) return null;\n return proj.translate(pose, dx, dy);\n },\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { createSetSelectionOp } from './chunk-DOBSZOPR.js';
2
- import { createInsertOp } from './chunk-4RJP2N2L.js';
1
+ import { createSetSelectionOp } from './chunk-UPZY4ART.js';
2
+ import { createInsertOp } from './chunk-NE27EOLP.js';
3
3
 
4
4
  // src/interactions/actions/clone/behaviors/cloneByAltDrag.ts
5
5
  function cloneByAltDrag() {
@@ -24,5 +24,5 @@ function cloneByAltDrag() {
24
24
  }
25
25
 
26
26
  export { cloneByAltDrag };
27
- //# sourceMappingURL=chunk-D4AAWR64.js.map
28
- //# sourceMappingURL=chunk-D4AAWR64.js.map
27
+ //# sourceMappingURL=chunk-M24CLARN.js.map
28
+ //# sourceMappingURL=chunk-M24CLARN.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/interactions/actions/clone/behaviors/cloneByAltDrag.ts"],"names":[],"mappings":";;;;AAMO,SAAS,cAAA,GAAgC;AAC9C,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,gBAAA;AAAA,IACJ,SAAA,EAAW,CAAC,IAAA,KAAS,IAAA,CAAK,GAAA,KAAQ,IAAA;AAAA,IAClC,KAAA,CAAM,MAAM,GAAA,EAAK;AACf,MAAA,IAAI,CAAC,IAAI,OAAA,CAAQ,iBAAA,IAAqB,CAAC,GAAA,CAAI,OAAA,CAAQ,WAAA,EAAa,OAAO,EAAC;AACxE,MAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,iBAAA,CAAkB,KAAK,GAAG,CAAA;AACnD,MAAA,MAAM,UAAU,GAAA,CAAI,OAAA,CAAQ,WAAA,CAAY,IAAA,EAAM,KAAK,MAAA,EAAQ;AAAA,QACzD,WAAW,EAAE,MAAA,EAAQ,KAAK,MAAA,EAAQ,MAAA,EAAQ,KAAK,MAAA;AAAO,OACvD,CAAA;AACD,MAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,EAAC;AAClC,MAAA,MAAM,SAAS,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAsB,EAAE,EAAE,CAAA;AACtD,MAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,YAAA,QAAoB,EAAC;AAC9C,MAAA,OAAO;AAAA,QACL,GAAG,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,eAAe,EAAE,IAAA,EAAM,CAAA,EAAG,CAAC,CAAA;AAAA,QACjD,oBAAA,CAAqB,EAAE,IAAA,EAAwB,EAAA,EAAI,QAAoB;AAAA,OACzE;AAAA,IACF;AAAA,GACF;AACF","file":"chunk-D4AAWR64.js","sourcesContent":["import { createInsertOp } from 'core/ops/create';\nimport { createSetSelectionOp } from 'core/ops/select';\nimport type { NodeId } from 'core/scene/types';\nimport type { CloneBehavior } from '../../../gestures/types';\n\n/** Clone-on-alt-drag behavior for `useClone`; activates when Alt/Option is held at drag start. */\nexport function cloneByAltDrag(): CloneBehavior {\n return {\n id: 'cloneByAltDrag',\n activates: (mods) => mods.alt === true,\n onEnd(pose, ctx) {\n if (!ctx.adapter.snapshotSelection || !ctx.adapter.commitPaste) return [];\n const snap = ctx.adapter.snapshotSelection(pose.ids);\n const created = ctx.adapter.commitPaste(snap, pose.offset, {\n dropPoint: { worldX: pose.worldX, worldY: pose.worldY },\n });\n if (created.length === 0) return [];\n const newIds = created.map((o: { id: string }) => o.id);\n const from = ctx.adapter.getSelection?.() ?? [];\n return [\n ...created.map((o) => createInsertOp({ node: o })),\n createSetSelectionOp({ from: from as NodeId[], to: newIds as NodeId[] }),\n ];\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/interactions/actions/clone/behaviors/cloneByAltDrag.ts"],"names":[],"mappings":";;;;AAMO,SAAS,cAAA,GAAgC;AAC9C,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,gBAAA;AAAA,IACJ,SAAA,EAAW,CAAC,IAAA,KAAS,IAAA,CAAK,GAAA,KAAQ,IAAA;AAAA,IAClC,KAAA,CAAM,MAAM,GAAA,EAAK;AACf,MAAA,IAAI,CAAC,IAAI,OAAA,CAAQ,iBAAA,IAAqB,CAAC,GAAA,CAAI,OAAA,CAAQ,WAAA,EAAa,OAAO,EAAC;AACxE,MAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,iBAAA,CAAkB,KAAK,GAAG,CAAA;AACnD,MAAA,MAAM,UAAU,GAAA,CAAI,OAAA,CAAQ,WAAA,CAAY,IAAA,EAAM,KAAK,MAAA,EAAQ;AAAA,QACzD,WAAW,EAAE,MAAA,EAAQ,KAAK,MAAA,EAAQ,MAAA,EAAQ,KAAK,MAAA;AAAO,OACvD,CAAA;AACD,MAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,EAAC;AAClC,MAAA,MAAM,SAAS,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAsB,EAAE,EAAE,CAAA;AACtD,MAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,YAAA,QAAoB,EAAC;AAC9C,MAAA,OAAO;AAAA,QACL,GAAG,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,eAAe,EAAE,IAAA,EAAM,CAAA,EAAG,CAAC,CAAA;AAAA,QACjD,oBAAA,CAAqB,EAAE,IAAA,EAAwB,EAAA,EAAI,QAAoB;AAAA,OACzE;AAAA,IACF;AAAA,GACF;AACF","file":"chunk-M24CLARN.js","sourcesContent":["import { createInsertOp } from 'core/ops/create';\nimport { createSetSelectionOp } from 'core/ops/select';\nimport type { NodeId } from 'core/scene/types';\nimport type { CloneBehavior } from '../../../gestures/types';\n\n/** Clone-on-alt-drag behavior for `useClone`; activates when Alt/Option is held at drag start. */\nexport function cloneByAltDrag(): CloneBehavior {\n return {\n id: 'cloneByAltDrag',\n activates: (mods) => mods.alt === true,\n onEnd(pose, ctx) {\n if (!ctx.adapter.snapshotSelection || !ctx.adapter.commitPaste) return [];\n const snap = ctx.adapter.snapshotSelection(pose.ids);\n const created = ctx.adapter.commitPaste(snap, pose.offset, {\n dropPoint: { worldX: pose.worldX, worldY: pose.worldY },\n });\n if (created.length === 0) return [];\n const newIds = created.map((o: { id: string }) => o.id);\n const from = ctx.adapter.getSelection?.() ?? [];\n return [\n ...created.map((o) => createInsertOp({ node: o })),\n createSetSelectionOp({ from: from as NodeId[], to: newIds as NodeId[] }),\n ];\n },\n };\n}\n"]}
@@ -1,5 +1,8 @@
1
+ import { createReflectable } from '@weasel-js/registry';
2
+
1
3
  // src/core/ops/registry.ts
2
- var FACTORIES = /* @__PURE__ */ new Map();
4
+ var FACTORIES = createReflectable();
5
+ var opFactoryRegistry = FACTORIES.reflection;
3
6
  function registerOpFactory(name, build) {
4
7
  FACTORIES.set(name, build);
5
8
  }
@@ -8,7 +11,7 @@ function rebuildOp(name, args) {
8
11
  return f ? f(args) : null;
9
12
  }
10
13
  function registeredOpNames() {
11
- return [...FACTORIES.keys()];
14
+ return FACTORIES.entries().map((e) => e.key);
12
15
  }
13
16
 
14
17
  // src/core/ops/slot.ts
@@ -138,6 +141,6 @@ function createDeleteOp(args) {
138
141
  }
139
142
  registerOpFactory("delete", (args) => createDeleteOp(args));
140
143
 
141
- export { captureSlot, createDeleteOp, createInsertOp, rebuildOp, registerOpFactory, registeredOpNames, resolveSlot, slotFromIndex };
142
- //# sourceMappingURL=chunk-4RJP2N2L.js.map
143
- //# sourceMappingURL=chunk-4RJP2N2L.js.map
144
+ export { captureSlot, createDeleteOp, createInsertOp, opFactoryRegistry, rebuildOp, registerOpFactory, registeredOpNames, resolveSlot, slotFromIndex };
145
+ //# sourceMappingURL=chunk-NE27EOLP.js.map
146
+ //# sourceMappingURL=chunk-NE27EOLP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/ops/registry.ts","../src/core/ops/slot.ts","../src/core/ops/create.ts","../src/core/ops/delete.ts"],"names":[],"mappings":"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","file":"chunk-NE27EOLP.js","sourcesContent":["// Op-factory registry — pairs a stable `name` (stamped onto each op by its\n// factory) with a `(args) => Op` rebuilder so a `{ name, args }` snapshot\n// can be reconstituted into a live `Op` whose `apply`/`invert` closures\n// behave identically to the original.\n//\n// The kit calls `registerOpFactory` at module-init time for every built-in\n// factory (see `./create.ts`, `./delete.ts`, `./transform.ts`, etc.).\n// Consumers wanting their custom ops to survive `History.serialize()` /\n// `History.restore()` register their factories at app bootstrap, before\n// `createHistory` is called.\n\nimport { createReflectable, type Reflection } from '@weasel-js/registry';\nimport type { Op } from './types';\n\n/** Rebuilds an op from its serialized `args`. */\nexport type OpFactory = (args: unknown) => Op;\n\nconst FACTORIES = createReflectable<OpFactory>();\n\n/** Every registered op factory, keyed by op name, in registration order. */\nexport const opFactoryRegistry: Reflection<OpFactory> = FACTORIES.reflection;\n\n/** Register an op-factory builder under a stable name. The builder receives\n * the serialized `args` and must return an `Op` equivalent to one freshly\n * constructed at the original call site (`apply` and `invert` behave the\n * same). Idempotent on the same `(name, build)` pair; a second registration\n * with a different builder silently overwrites — kit modules import each\n * factory exactly once at init, so this never races in practice. */\nexport function registerOpFactory<A>(name: string, build: (args: A) => Op): void {\n FACTORIES.set(name, build as OpFactory);\n}\n\n/** Rebuild an op from its serialized `(name, args)`. Returns `null` when\n * `name` isn't registered — callers handle this (typically by substituting\n * a no-op placeholder so stack ordering survives across kit version skew). */\nexport function rebuildOp(name: string, args: unknown): Op | null {\n const f = FACTORIES.get(name);\n return f ? f(args) : null;\n}\n\n/** Test-only: drop all registrations. Not exported from the kit barrel — the\n * built-in factories self-register at module init, so production code never\n * needs to clear the table. Exposed for unit tests that want to assert the\n * unknown-name fallback path. */\nexport function _clearOpFactoriesForTest(): void {\n FACTORIES.clear();\n}\n\n/** Every op name currently registered, in registration order. The kit's\n * built-ins self-register at module init, so importing the barrel is enough\n * to see all of them; consumer factories appear once their module has run. */\nexport function registeredOpNames(): readonly string[] {\n return FACTORIES.entries().map((e) => e.key);\n}\n","/**\n * A node's position among its siblings, recorded so an op can put it back.\n *\n * Recorded as an anchor (`before`) plus an ordinal (`index`). The anchor is\n * the source of truth: `History` replays a batch's inverses in reverse order,\n * so an ordinal captured before the mutation is replayed against a list that\n * has not been refilled yet and clamps to the end, while \"put this back before\n * `e`\" stays true however much else has already moved. The ordinal is the\n * fallback for the cases the anchor cannot cover — an adapter with no\n * `getChildren` seam, or an anchor that is itself gone by replay time.\n */\n\n/** Sibling-order read seam. Optional everywhere: a flat adapter has none. */\nexport interface OrderedReader {\n getChildren?(parentId: string | null): string[];\n}\n\nexport interface Slot {\n /** Ordinal among the siblings. `-1` means \"nowhere in particular\". */\n index: number;\n /** Id of the sibling that followed at capture time; `null` when the node\n * was last. Absent when nothing observed it — `null` survives JSON and\n * `undefined` does not, so the two stay distinguishable across a\n * serialize/restore round trip. */\n before?: string | null;\n}\n\n/** Observe `id`'s slot among `parentId`'s children. `null` when the adapter\n * has no ordering seam or `id` isn't there — callers keep whatever weaker\n * slot they already had rather than overwriting it with a guess. */\nexport function captureSlot(\n a: OrderedReader,\n parentId: string | null,\n id: string,\n): Slot | null {\n const siblings = a.getChildren?.(parentId);\n if (!siblings) return null;\n const i = siblings.indexOf(id);\n if (i < 0) return null;\n return { index: i, before: i + 1 < siblings.length ? siblings[i + 1]! : null };\n}\n\n/**\n * Index `slot` denotes in `siblings`, which must NOT contain the node being\n * placed. `undefined` means \"no position\" — the caller appends, or lets the\n * adapter's default placement stand.\n */\nexport function resolveSlot(\n siblings: readonly string[] | undefined,\n slot: Slot,\n): number | undefined {\n if (slot.before === null) return undefined;\n if (slot.before !== undefined && siblings) {\n const at = siblings.indexOf(slot.before);\n if (at >= 0) return at;\n }\n return slot.index >= 0 ? slot.index : undefined;\n}\n\n/** Normalize the `index` sugar every op factory accepts into a `Slot`. */\nexport function slotFromIndex(index: number | undefined): Slot {\n return { index: index ?? -1 };\n}\n\n/** The parent a node claims. Nodes in a flat adapter have none. */\nexport function parentOf(node: object): string | null {\n return ((node as { parent?: unknown }).parent ?? null) as string | null;\n}\n","import type { Op } from './types';\nimport { createDeleteOp } from './delete';\nimport { registerOpFactory } from './registry';\nimport { parentOf, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface InsertAdapter<TNode> extends OrderedReader {\n /** When `index` is supplied, insert at that position; adapters that ignore\n * the second arg (or don't expose it) fall back to their default\n * placement (typically append-to-end). */\n insertNode(node: TNode, index?: number): void;\n}\n\n/** Type alias for ops produced by `createInsertOp`. Carries no extra type info today;\n * exists so consumers can name the op type when needed. */\nexport type InsertOp = Op;\n\n/** @internal */\ninterface InsertArgs<TNode extends { id: string }> {\n node: TNode;\n label?: string;\n /** Sibling ordinal to insert at. Sugar for `slot: { index }` — the weaker\n * of the two forms, and all a fresh-insert callsite can know. */\n index?: number;\n /** Full slot, anchor included. Produced only by `captureSlot` (via a\n * delete op's `invert`), and supersedes `index` when present. */\n slot?: Slot;\n}\n\n/** Op: insert `node` into the scene; inverts to a delete of the same id. */\nexport function createInsertOp<TNode extends { id: string }>(args: InsertArgs<TNode>): InsertOp {\n const { node, label } = args;\n const slot: Slot = args.slot ?? slotFromIndex(args.index);\n return {\n name: 'insert',\n args: { node, label, slot },\n label,\n apply(adapter) {\n const a = adapter as InsertAdapter<TNode>;\n const parentId = parentOf(node);\n a.insertNode(node, resolveSlot(a.getChildren?.(parentId), slot));\n },\n invert() {\n return createDeleteOp({ node, label, slot });\n },\n };\n}\n\nregisterOpFactory<InsertArgs<{ id: string }>>('insert', (args) => createInsertOp(args));\n","import type { Op } from './types';\nimport { createInsertOp } from './create';\nimport { registerOpFactory } from './registry';\nimport { captureSlot, parentOf, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface DeleteAdapter<TNode> extends OrderedReader {\n removeNode(id: string): void;\n /** Subtree reads, used to snapshot the cascade before it runs. Optional\n * — a flat adapter has neither, and a delete there is a single node. */\n getNode?(id: string): TNode | undefined;\n /** Every id `removeNode` would take with this one. Optional: a store whose\n * removal cascades along nothing but the subtree needs no such answer, and\n * `getChildren` is enough. A store that cascades further — the kit's scene\n * takes a deleted node's dependents too — must answer here, or the nodes it\n * cascades away are absent from the snapshot and undo brings back the named\n * node alone. */\n getRemovalClosure?(ids: readonly string[]): readonly string[];\n}\n\ninterface InsertAdapter<TNode> {\n insertNode(node: TNode, index?: number): void;\n getNode?(id: string): TNode | undefined;\n}\n\n/** A captured node plus its slot in its own parent's child list. */\ninterface Placed<TNode> {\n node: TNode;\n index: number;\n}\n\n/** @internal */\ninterface DeleteArgs<TNode extends { id: string }> {\n node: TNode;\n label?: string;\n /** Sibling ordinal the node occupies at the moment of delete. Sugar for\n * `slot: { index }`, and only a seed: `apply` re-observes the node's full\n * slot through the adapter and that observation supersedes this. Kept\n * because it is the whole story for a flat adapter with no `getChildren`,\n * where nothing can be observed. */\n index?: number;\n /** Full slot, anchor included. Written by `apply`; supersedes `index`. */\n slot?: Slot;\n /** Everything `removeNode` takes, `node` itself included — its subtree, and\n * whatever else the adapter cascades. An inverse that re-inserts `node`\n * alone brings a container back empty and leaves every dependent deleted.\n * Ordered so a node's parent precedes it, which is the order they have to\n * be re-inserted in. Captured on apply; mirrored into the serialized args so\n * a rebuilt op can invert without having run. */\n cascaded?: Placed<TNode>[];\n}\n\n/** `id`'s ordinal among `parentId`'s children, or `-1` when unobservable. */\nfunction indexIn(a: OrderedReader, parentId: string | null, id: string): number {\n return a.getChildren?.(parentId)?.indexOf(id) ?? -1;\n}\n\n/**\n * Snapshot everything removing `rootId` takes with it, `rootId` included.\n *\n * Returns `null` when the adapter can't enumerate, which leaves any previously\n * captured snapshot in place rather than clobbering it with an empty one.\n *\n * The order is the whole point. A cascade unlinks several disjoint subtrees, so\n * the captured set is walked as a forest: a node whose parent is not also going\n * is a detach root, and each one is emitted — ascending sibling index first, so\n * every slot still means what it meant — followed by its captured descendants\n * preorder. The closure's own order will not do: it reaches a dependent before\n * the parent it sits under whenever that parent is in the closure as a\n * dependent too, and re-inserting a child under a parent that is not back yet\n * puts it in the wrong place.\n */\nfunction captureCascade<TNode extends { id: string }>(\n a: DeleteAdapter<TNode>,\n rootId: string,\n): Placed<TNode>[] | null {\n const { getChildren, getNode } = a;\n if (!getChildren || !getNode) return null;\n\n const closure = a.getRemovalClosure?.([rootId]);\n const taken = new Map<string, TNode>();\n if (closure) {\n for (const id of closure) {\n const node = getNode.call(a, id);\n if (node) taken.set(id, node);\n }\n } else {\n // No closure read: the cascade is the subtree, discovered here.\n const walk = (id: string): void => {\n const node = getNode.call(a, id);\n if (!node) return;\n taken.set(id, node);\n for (const kid of getChildren.call(a, id)) walk(kid);\n };\n walk(rootId);\n }\n\n const detachRoots = [...taken.values()]\n .filter((n) => { const p = parentOf(n); return p === null || !taken.has(p); })\n .map((n) => ({ node: n, index: indexIn(a, parentOf(n), n.id) }))\n .sort((l, r) => l.index - r.index);\n\n const out: Placed<TNode>[] = [];\n const emitSubtree = (entry: Placed<TNode>): void => {\n out.push(entry);\n const kids = getChildren.call(a, entry.node.id);\n for (let i = 0; i < kids.length; i++) {\n const child = taken.get(kids[i]!);\n if (child) emitSubtree({ node: child, index: i });\n }\n };\n for (const entry of detachRoots) emitSubtree(entry);\n return out;\n}\n\n/** Op: remove `node` and everything the adapter cascades with it; inverts to a\n * re-insert of the whole set at its captured slots. */\nexport function createDeleteOp<TNode extends { id: string }>(args: DeleteArgs<TNode>): Op {\n const { node, label } = args;\n let slot: Slot = args.slot ?? slotFromIndex(args.index);\n const argsForSerial: DeleteArgs<TNode> = { node, label, slot, cascaded: args.cascaded };\n let captured: Placed<TNode>[] = args.cascaded ?? [];\n return {\n name: 'delete',\n args: argsForSerial,\n label,\n apply(adapter) {\n const a = adapter as DeleteAdapter<TNode>;\n const observed = captureSlot(a, parentOf(node), node.id);\n if (observed) {\n slot = observed;\n argsForSerial.slot = observed;\n }\n const snapshot = captureCascade(a, node.id);\n if (snapshot) {\n captured = snapshot;\n argsForSerial.cascaded = snapshot;\n }\n a.removeNode(node.id);\n },\n invert() {\n const cascaded = captured;\n const restoredSlot = slot;\n // The root's own placement keeps the anchor-bearing slot; the rest have\n // only an ordinal, which is all `getChildren` can observe of them.\n const reinsertRoot = createInsertOp<TNode>({ node, label, slot: restoredSlot });\n if (cascaded.length === 0) return reinsertRoot;\n return {\n label,\n apply(adapter) {\n const a = adapter as InsertAdapter<TNode>;\n for (const entry of cascaded) {\n // A flat adapter's removeNode doesn't cascade, so its children\n // were never gone — re-inserting them would duplicate them.\n if (a.getNode?.(entry.node.id)) continue;\n if (entry.node.id === node.id) reinsertRoot.apply(adapter);\n else a.insertNode(entry.node, entry.index);\n }\n },\n invert: () =>\n createDeleteOp<TNode>({ node, label, slot: restoredSlot, cascaded }),\n };\n },\n };\n}\n\nregisterOpFactory<DeleteArgs<{ id: string }>>('delete', (args) => createDeleteOp(args));\n"]}
@@ -1,4 +1,4 @@
1
- import { registerOpFactory } from './chunk-4RJP2N2L.js';
1
+ import { registerOpFactory } from './chunk-NE27EOLP.js';
2
2
 
3
3
  // src/core/ops/select.ts
4
4
  function createSetSelectionOp(args) {
@@ -25,5 +25,5 @@ function sequenceEqual(a, b) {
25
25
  }
26
26
 
27
27
  export { createSetSelectionOp };
28
- //# sourceMappingURL=chunk-DOBSZOPR.js.map
29
- //# sourceMappingURL=chunk-DOBSZOPR.js.map
28
+ //# sourceMappingURL=chunk-UPZY4ART.js.map
29
+ //# sourceMappingURL=chunk-UPZY4ART.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/ops/select.ts"],"names":[],"mappings":";;;AAgBO,SAAS,qBAAqB,IAAA,EAA4B;AAC/D,EAAA,MAAM,EAAE,IAAA,EAAM,EAAA,EAAI,KAAA,EAAM,GAAI,IAAA;AAC5B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,cAAA;AAAA,IACN,IAAA,EAAM,EAAE,IAAA,EAAM,EAAA,EAAI,KAAA,EAAM;AAAA,IACxB,KAAA;AAAA,IACA,MAAM,OAAA,EAAS;AAMb,MAAC,OAAA,CAA6B,YAAA,CAAa,CAAC,GAAG,EAAE,CAAC,CAAA;AAClD,MAAA,IAAI,aAAA,CAAc,IAAA,EAAM,EAAE,CAAA,EAAG,OAAO,KAAA;AACpC,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAA,GAAS;AACP,MAAA,OAAO,qBAAqB,EAAE,IAAA,EAAM,IAAI,EAAA,EAAI,IAAA,EAAM,OAAO,CAAA;AAAA,IAC3D;AAAA,GACF;AACF;AAEA,iBAAA,CAAoC,cAAA,EAAgB,CAAC,IAAA,KAAS,oBAAA,CAAqB,IAAI,CAAC,CAAA;AAExF,SAAS,aAAA,CAAc,GAAsB,CAAA,EAA+B;AAC1E,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAI,CAAA,CAAE,CAAC,CAAA,KAAM,CAAA,CAAE,CAAC,GAAG,OAAO,KAAA;AAC7D,EAAA,OAAO,IAAA;AACT","file":"chunk-DOBSZOPR.js","sourcesContent":["import type { NodeId } from '../scene/types';\nimport type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface SelectionAdapter {\n setSelection(ids: NodeId[]): void;\n}\n\n/** @internal */\ninterface SetSelectionArgs {\n from: readonly NodeId[];\n to: readonly NodeId[];\n label?: string;\n}\n\n/** Op: replace the current selection with `to`; inverts back to `from`. */\nexport function createSetSelectionOp(args: SetSelectionArgs): Op {\n const { from, to, label } = args;\n return {\n name: 'setSelection',\n args: { from, to, label },\n label,\n apply(adapter) {\n // Always call setSelection so consumers that inspect op behavior\n // (tests, overlays) see a consistent \"this op writes `to`\" signal.\n // When from and to are sequence-equal, additionally report a no-op\n // to history so the entry can be skipped from the undo stack.\n // setSelection with the same value is idempotent.\n (adapter as SelectionAdapter).setSelection([...to]);\n if (sequenceEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createSetSelectionOp({ from: to, to: from, label });\n },\n };\n}\n\nregisterOpFactory<SetSelectionArgs>('setSelection', (args) => createSetSelectionOp(args));\n\nfunction sequenceEqual(a: readonly NodeId[], b: readonly NodeId[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n"]}
1
+ {"version":3,"sources":["../src/core/ops/select.ts"],"names":[],"mappings":";;;AAgBO,SAAS,qBAAqB,IAAA,EAA4B;AAC/D,EAAA,MAAM,EAAE,IAAA,EAAM,EAAA,EAAI,KAAA,EAAM,GAAI,IAAA;AAC5B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,cAAA;AAAA,IACN,IAAA,EAAM,EAAE,IAAA,EAAM,EAAA,EAAI,KAAA,EAAM;AAAA,IACxB,KAAA;AAAA,IACA,MAAM,OAAA,EAAS;AAMb,MAAC,OAAA,CAA6B,YAAA,CAAa,CAAC,GAAG,EAAE,CAAC,CAAA;AAClD,MAAA,IAAI,aAAA,CAAc,IAAA,EAAM,EAAE,CAAA,EAAG,OAAO,KAAA;AACpC,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAA,GAAS;AACP,MAAA,OAAO,qBAAqB,EAAE,IAAA,EAAM,IAAI,EAAA,EAAI,IAAA,EAAM,OAAO,CAAA;AAAA,IAC3D;AAAA,GACF;AACF;AAEA,iBAAA,CAAoC,cAAA,EAAgB,CAAC,IAAA,KAAS,oBAAA,CAAqB,IAAI,CAAC,CAAA;AAExF,SAAS,aAAA,CAAc,GAAsB,CAAA,EAA+B;AAC1E,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAI,CAAA,CAAE,CAAC,CAAA,KAAM,CAAA,CAAE,CAAC,GAAG,OAAO,KAAA;AAC7D,EAAA,OAAO,IAAA;AACT","file":"chunk-UPZY4ART.js","sourcesContent":["import type { NodeId } from '../scene/types';\nimport type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface SelectionAdapter {\n setSelection(ids: NodeId[]): void;\n}\n\n/** @internal */\ninterface SetSelectionArgs {\n from: readonly NodeId[];\n to: readonly NodeId[];\n label?: string;\n}\n\n/** Op: replace the current selection with `to`; inverts back to `from`. */\nexport function createSetSelectionOp(args: SetSelectionArgs): Op {\n const { from, to, label } = args;\n return {\n name: 'setSelection',\n args: { from, to, label },\n label,\n apply(adapter) {\n // Always call setSelection so consumers that inspect op behavior\n // (tests, overlays) see a consistent \"this op writes `to`\" signal.\n // When from and to are sequence-equal, additionally report a no-op\n // to history so the entry can be skipped from the undo stack.\n // setSelection with the same value is idempotent.\n (adapter as SelectionAdapter).setSelection([...to]);\n if (sequenceEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createSetSelectionOp({ from: to, to: from, label });\n },\n };\n}\n\nregisterOpFactory<SetSelectionArgs>('setSelection', (args) => createSetSelectionOp(args));\n\nfunction sequenceEqual(a: readonly NodeId[], b: readonly NodeId[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n"]}
@@ -1,6 +1,6 @@
1
- import { createSetSelectionOp } from './chunk-DOBSZOPR.js';
2
- import { createInsertOp } from './chunk-4RJP2N2L.js';
3
- import { createContext, useRef, useMemo, useContext, useCallback } from 'react';
1
+ import { createSetSelectionOp } from './chunk-UPZY4ART.js';
2
+ import { createInsertOp } from './chunk-NE27EOLP.js';
3
+ import { createContext, useMemo, useContext, useSyncExternalStore, useRef, useCallback } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
 
6
6
  // src/core/applyOps.ts
@@ -49,18 +49,52 @@ function dwarn(namespace, ...args) {
49
49
  if (!isDebugEnabled(namespace)) return;
50
50
  console.warn(`[${namespace}]`, ...args);
51
51
  }
52
+ function samePos(a, b) {
53
+ if (a === null || b === null) return a === b;
54
+ return a.worldX === b.worldX && a.worldY === b.worldY && a.viewId === b.viewId;
55
+ }
56
+ function createPointerStore() {
57
+ let current = null;
58
+ let version = 0;
59
+ const listeners = /* @__PURE__ */ new Set();
60
+ const get2 = () => current;
61
+ return {
62
+ get: get2,
63
+ set(next) {
64
+ if (samePos(current, next)) return;
65
+ current = next;
66
+ version++;
67
+ for (const fn of [...listeners]) fn();
68
+ },
69
+ subscribe(fn) {
70
+ listeners.add(fn);
71
+ return () => {
72
+ listeners.delete(fn);
73
+ };
74
+ },
75
+ getVersion: () => version,
76
+ getDropPoint: get2
77
+ };
78
+ }
52
79
  var PointerContext = createContext(null);
53
- function PointerContextProvider({ children }) {
54
- const pointerRef = useRef(null);
55
- const value = useMemo(() => ({
56
- pointerRef,
57
- getDropPoint: () => pointerRef.current
58
- }), []);
59
- return /* @__PURE__ */ jsx(PointerContext.Provider, { value, children });
80
+ function PointerContextProvider({ children, store }) {
81
+ const own = useMemo(createPointerStore, []);
82
+ return /* @__PURE__ */ jsx(PointerContext.Provider, { value: store ?? own, children });
60
83
  }
61
84
  function usePointerContext() {
62
85
  return useContext(PointerContext);
63
86
  }
87
+ var noSubscribe = () => () => {
88
+ };
89
+ var noPointer = () => null;
90
+ function usePointerPosition() {
91
+ const store = usePointerContext();
92
+ return useSyncExternalStore(
93
+ store ? store.subscribe : noSubscribe,
94
+ store ? store.get : noPointer,
95
+ store ? store.get : noPointer
96
+ );
97
+ }
64
98
 
65
99
  // src/interactions/actions/clipboard/wireFormat.ts
66
100
  var WEASEL_CLIPBOARD_MIME = "application/x-weasel-clipboard+json";
@@ -211,6 +245,6 @@ async function writeOsClipboard(flavors) {
211
245
  }
212
246
  }
213
247
 
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-T3UQ3F6R.js.map
216
- //# sourceMappingURL=chunk-T3UQ3F6R.js.map
248
+ export { PointerContextProvider, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, applyOpsTo, buildWeaselClipboardText, createPointerStore, dispatchApplyBatch, dlog, dwarn, embedWeaselMetadataInSvg, extractWeaselClipboardFromSvg, isDebugEnabled, materializePaste, parseWeaselClipboardText, sniffWeaselClipboardText, useClipboardOps, usePointerContext, usePointerPosition };
249
+ //# sourceMappingURL=chunk-WPNA6L5O.js.map
250
+ //# sourceMappingURL=chunk-WPNA6L5O.js.map
@@ -0,0 +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":["get"],"mappings":"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","file":"chunk-WPNA6L5O.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 console.warn(`[${namespace}]`, ...args);\n}\n","/**\n * @experimental\n * PointerContext — the world-space position of the canvas pointer, and the\n * view it is over, refreshed on every `pointermove` and cleared on\n * `pointerleave`.\n *\n * It is an external store rather than React state: cursor moves fire dozens of\n * times per second, and a consumer that only reads on demand (paste at the\n * pointer) must not re-render for each one. Readers that do need to follow it\n * — a linked cursor — subscribe, or use `usePointerPosition()`.\n *\n * `<SceneCanvas>` publishes automatically, and defers to a provider already in\n * scope, so several surfaces under one provider share one pointer: a detached\n * minimap and its main canvas each see where the other's pointer is.\n */\nimport {\n createContext, useContext, useMemo, useSyncExternalStore, type ReactNode,\n} from 'react';\n\n/** @experimental World-space pointer position and the view it is over — a\n * view id, or `null` for a surface's own camera. `null` as a whole when the\n * pointer is over no publishing surface. */\nexport type PointerWorldPos = { worldX: number; worldY: number; viewId: string | null } | null;\n\n/** @experimental */\nexport interface PointerContextValue {\n /** The latest position. */\n get(): PointerWorldPos;\n /** Publish a position. Subscribers hear only a changed value. */\n set(next: PointerWorldPos): void;\n /** Called after every change; returns the unsubscribe. */\n subscribe(fn: () => void): () => void;\n /** Bumps on every change. */\n getVersion(): number;\n /** `get()` as a stable thunk, for hooks taking a drop-point option. */\n readonly getDropPoint: () => PointerWorldPos;\n}\n\nfunction samePos(a: PointerWorldPos, b: PointerWorldPos): boolean {\n if (a === null || b === null) return a === b;\n return a.worldX === b.worldX && a.worldY === b.worldY && a.viewId === b.viewId;\n}\n\n/** @experimental A pointer store with no provider around it. */\nexport function createPointerStore(): PointerContextValue {\n let current: PointerWorldPos = null;\n let version = 0;\n const listeners = new Set<() => void>();\n const get = (): PointerWorldPos => current;\n return {\n get,\n set(next) {\n if (samePos(current, next)) return;\n current = next;\n version++;\n for (const fn of [...listeners]) fn();\n },\n subscribe(fn) {\n listeners.add(fn);\n return () => { listeners.delete(fn); };\n },\n getVersion: () => version,\n getDropPoint: get,\n };\n}\n\nconst PointerContext = createContext<PointerContextValue | null>(null);\n\n/**\n * @experimental\n * Wrap the part of the React tree that should share one pointer. Usually at\n * the app root, alongside `<ActionsProvider>` and `<SelectionContextProvider>`.\n *\n * Most consumers don't need to mount this directly — `<SceneCanvas>` mounts\n * one when none is in scope. Mount it yourself when two surfaces should share\n * a pointer. Pass `store` to publish into one you hold elsewhere\n * (`createPointerStore()`).\n */\nexport function PointerContextProvider(\n { children, store }: { children: ReactNode; store?: PointerContextValue },\n): ReactNode {\n const own = useMemo(createPointerStore, []);\n return <PointerContext.Provider value={store ?? own}>{children}</PointerContext.Provider>;\n}\n\n/** @experimental The surrounding pointer store, or `null` when no provider is\n * in scope. */\nexport function usePointerContext(): PointerContextValue | null {\n return useContext(PointerContext);\n}\n\nconst noSubscribe = (): (() => void) => () => {};\nconst noPointer = (): PointerWorldPos => null;\n\n/** @experimental The pointer, re-rendering the caller whenever it changes.\n * `null` outside a provider. */\nexport function usePointerPosition(): PointerWorldPos {\n const store = usePointerContext();\n return useSyncExternalStore(\n store ? store.subscribe : noSubscribe,\n store ? store.get : noPointer,\n store ? store.get : noPointer,\n );\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"]}
@@ -1,5 +1,5 @@
1
- import { RECT_ORIGIN_PROJECTION } from './chunk-HPFJUUTF.js';
2
- import { registerOpFactory, captureSlot, slotFromIndex, resolveSlot } from './chunk-4RJP2N2L.js';
1
+ import { AUTO_ORIGIN_PROJECTION } from './chunk-LU4IJ52W.js';
2
+ import { registerOpFactory, captureSlot, slotFromIndex, resolveSlot } from './chunk-NE27EOLP.js';
3
3
 
4
4
  // src/core/ops/transform.ts
5
5
  function createTransformOp(args) {
@@ -102,7 +102,7 @@ registerOpFactory("reparent", (args) => createReparentOp(args));
102
102
  // src/interactions/gestures/shared/snap.ts
103
103
  function snap(strategy, opts = {}) {
104
104
  const { bypassKey } = opts;
105
- const proj = opts.origin ?? RECT_ORIGIN_PROJECTION;
105
+ const proj = opts.origin ?? AUTO_ORIGIN_PROJECTION;
106
106
  return {
107
107
  onMove(ctx, transform) {
108
108
  if (bypassKey && ctx.modifiers[bypassKey]) return;
@@ -124,5 +124,5 @@ function snap(strategy, opts = {}) {
124
124
  }
125
125
 
126
126
  export { createReparentOp, createTransformOp, snap };
127
- //# sourceMappingURL=chunk-44EWMJV2.js.map
128
- //# sourceMappingURL=chunk-44EWMJV2.js.map
127
+ //# sourceMappingURL=chunk-XMAWPGTK.js.map
128
+ //# sourceMappingURL=chunk-XMAWPGTK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/ops/transform.ts","../src/core/ops/reparent.ts","../src/interactions/gestures/shared/snap.ts"],"names":[],"mappings":"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","file":"chunk-44EWMJV2.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';\nimport { captureSlot, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface ReparentAdapter extends OrderedReader {\n setParent(id: string, parentId: string | null): void;\n /** Sibling-order write, paired with `getChildren`. Optional: an adapter\n * without both silently ignores slots and appends. */\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n /** Sibling ordinal under the old parent. Sugar for `fromSlot: { index }`,\n * and only a seed: `apply` re-observes the node's full slot through the\n * adapter and that observation supersedes this. */\n fromIndex?: number;\n /** Sibling ordinal under the new parent. Sugar for `toSlot: { index }`.\n * Omit to append. */\n toIndex?: number;\n /** Slot under the old parent, anchor included. `invert()` forwards it as\n * the destination slot; without it undo re-parents with no position and\n * the node lands last, quietly changing paint order. */\n fromSlot?: Slot;\n /** Slot under the new parent, anchor included. Omit to append. */\n toSlot?: Slot;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Move `id` to `slot` among `parentId`'s children. No-op when the adapter\n * has no ordering seam, when `id` isn't there, or when it's already in\n * place. */\nfunction placeAt(\n a: ReparentAdapter,\n parentId: string | null,\n id: string,\n slot: Slot,\n): void {\n if (!a.getChildren || !a.setChildOrder) return;\n const current = a.getChildren(parentId);\n const from = current.indexOf(id);\n if (from < 0) return;\n const next = current.slice();\n next.splice(from, 1);\n const resolved = resolveSlot(next, slot);\n const to = resolved === undefined ? next.length : Math.max(0, Math.min(resolved, next.length));\n if (from === to) return;\n next.splice(to, 0, id);\n a.setChildOrder(parentId, next);\n}\n\n/** Present when the caller named a destination at all; `undefined` leaves\n * placement to the adapter. */\nfunction optionalSlot(slot: Slot | undefined, index: number | undefined): Slot | undefined {\n if (slot) return slot;\n return index === undefined ? undefined : slotFromIndex(index);\n}\n\n/**\n * Op: change `id`'s parent and sibling slot, inverting back to the prior\n * parent and slot.\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 {\n id, fromParentId, toParentId,\n label = 'Reparent', coalesceKey = `reparent:${id}`,\n } = args;\n let fromSlot = optionalSlot(args.fromSlot, args.fromIndex);\n const toSlot = optionalSlot(args.toSlot, args.toIndex);\n const argsForSerial: ReparentArgs = {\n id, fromParentId, toParentId, fromSlot, toSlot, label, coalesceKey,\n };\n return {\n name: 'reparent',\n args: argsForSerial,\n label,\n coalesceKey,\n apply(adapter) {\n const a = adapter as ReparentAdapter;\n const observed = captureSlot(a, fromParentId, id);\n if (observed) {\n fromSlot = observed;\n argsForSerial.fromSlot = observed;\n }\n a.setParent(id, toParentId);\n if (toSlot) placeAt(a, toParentId, id, toSlot);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n fromSlot: toSlot,\n toSlot: fromSlot,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\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/gestures/shared/snap.ts"],"names":[],"mappings":";;;;AAoBO,SAAS,kBAAyB,IAAA,EAAgC;AACvE,EAAA,MAAM,EAAE,IAAI,IAAA,EAAM,EAAA,EAAI,OAAO,WAAA,GAAc,CAAA,UAAA,EAAa,EAAE,CAAA,CAAA,EAAG,GAAI,IAAA;AACjE,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,WAAA;AAAA,IACN,MAAM,EAAE,EAAA,EAAI,IAAA,EAAM,EAAA,EAAI,OAAO,WAAA,EAAY;AAAA,IACzC,KAAA;AAAA,IACA,WAAA;AAAA,IACA,MAAM,OAAA,EAAS;AAOb,MAAC,OAAA,CAAoC,OAAA,CAAQ,EAAA,EAAI,EAAE,CAAA;AACnD,MAAA,IAAI,gBAAA,CAAiB,IAAA,EAAM,EAAE,CAAA,EAAG,OAAO,KAAA;AACvC,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAA,GAAS;AACP,MAAA,OAAO,iBAAA,CAAyB,EAAE,EAAA,EAAI,IAAA,EAAM,IAAI,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,WAAA,EAAa,CAAA;AAAA,IAChF;AAAA,GACF;AACF;AAEA,iBAAA,CAA0C,WAAA,EAAa,CAAC,IAAA,KAAS,iBAAA,CAAkB,IAAI,CAAC,CAAA;AAExF,SAAS,gBAAA,CAAwB,GAAU,CAAA,EAAmB;AAC5D,EAAA,IAAI,CAAA,KAAM,GAAG,OAAO,IAAA;AACpB,EAAA,IAAI,CAAA,KAAM,IAAA,IAAQ,CAAA,KAAM,IAAA,IAAQ,OAAO,MAAM,QAAA,IAAY,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,KAAA;AACvF,EAAA,MAAM,EAAA,GAAK,MAAA,CAAO,IAAA,CAAK,CAAW,CAAA;AAClC,EAAA,MAAM,EAAA,GAAK,MAAA,CAAO,IAAA,CAAK,CAAW,CAAA;AAClC,EAAA,IAAI,EAAA,CAAG,MAAA,KAAW,EAAA,CAAG,MAAA,EAAQ,OAAO,KAAA;AACpC,EAAA,KAAA,MAAW,KAAK,EAAA,EAAI;AAClB,IAAA,IAAK,EAA8B,CAAC,CAAA,KAAO,CAAA,CAA8B,CAAC,GAAG,OAAO,KAAA;AAAA,EACtF;AACA,EAAA,OAAO,IAAA;AACT;;;ACpBA,SAAS,OAAA,CACP,CAAA,EACA,QAAA,EACA,EAAA,EACA,IAAA,EACM;AACN,EAAA,IAAI,CAAC,CAAA,CAAE,WAAA,IAAe,CAAC,EAAE,aAAA,EAAe;AACxC,EAAA,MAAM,OAAA,GAAU,CAAA,CAAE,WAAA,CAAY,QAAQ,CAAA;AACtC,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,EAAE,CAAA;AAC/B,EAAA,IAAI,OAAO,CAAA,EAAG;AACd,EAAA,MAAM,IAAA,GAAO,QAAQ,KAAA,EAAM;AAC3B,EAAA,IAAA,CAAK,MAAA,CAAO,MAAM,CAAC,CAAA;AACnB,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,IAAA,EAAM,IAAI,CAAA;AACvC,EAAA,MAAM,EAAA,GAAK,QAAA,KAAa,MAAA,GAAY,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,QAAA,EAAU,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7F,EAAA,IAAI,SAAS,EAAA,EAAI;AACjB,EAAA,IAAA,CAAK,MAAA,CAAO,EAAA,EAAI,CAAA,EAAG,EAAE,CAAA;AACrB,EAAA,CAAA,CAAE,aAAA,CAAc,UAAU,IAAI,CAAA;AAChC;AAIA,SAAS,YAAA,CAAa,MAAwB,KAAA,EAA6C;AACzF,EAAA,IAAI,MAAM,OAAO,IAAA;AACjB,EAAA,OAAO,KAAA,KAAU,MAAA,GAAY,MAAA,GAAY,aAAA,CAAc,KAAK,CAAA;AAC9D;AASO,SAAS,iBAAiB,IAAA,EAAwB;AACvD,EAAA,MAAM;AAAA,IACJ,EAAA;AAAA,IAAI,YAAA;AAAA,IAAc,UAAA;AAAA,IAClB,KAAA,GAAQ,UAAA;AAAA,IAAY,WAAA,GAAc,YAAY,EAAE,CAAA;AAAA,GAClD,GAAI,IAAA;AACJ,EAAA,IAAI,QAAA,GAAW,YAAA,CAAa,IAAA,CAAK,QAAA,EAAU,KAAK,SAAS,CAAA;AACzD,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,IAAA,CAAK,MAAA,EAAQ,KAAK,OAAO,CAAA;AACrD,EAAA,MAAM,aAAA,GAA8B;AAAA,IAClC,EAAA;AAAA,IAAI,YAAA;AAAA,IAAc,UAAA;AAAA,IAAY,QAAA;AAAA,IAAU,MAAA;AAAA,IAAQ,KAAA;AAAA,IAAO;AAAA,GACzD;AACA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,UAAA;AAAA,IACN,IAAA,EAAM,aAAA;AAAA,IACN,KAAA;AAAA,IACA,WAAA;AAAA,IACA,MAAM,OAAA,EAAS;AACb,MAAA,MAAM,CAAA,GAAI,OAAA;AACV,MAAA,MAAM,QAAA,GAAW,WAAA,CAAY,CAAA,EAAG,YAAA,EAAc,EAAE,CAAA;AAChD,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,QAAA,GAAW,QAAA;AACX,QAAA,aAAA,CAAc,QAAA,GAAW,QAAA;AAAA,MAC3B;AACA,MAAA,CAAA,CAAE,SAAA,CAAU,IAAI,UAAU,CAAA;AAC1B,MAAA,IAAI,MAAA,EAAQ,OAAA,CAAQ,CAAA,EAAG,UAAA,EAAY,IAAI,MAAM,CAAA;AAAA,IAC/C,CAAA;AAAA,IACA,MAAA,GAAS;AACP,MAAA,OAAO,gBAAA,CAAiB;AAAA,QACtB,EAAA;AAAA,QACA,YAAA,EAAc,UAAA;AAAA,QACd,UAAA,EAAY,YAAA;AAAA,QACZ,QAAA,EAAU,MAAA;AAAA,QACV,MAAA,EAAQ,QAAA;AAAA,QACR,KAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,IACH;AAAA,GACF;AACF;AAEA,iBAAA,CAAgC,UAAA,EAAY,CAAC,IAAA,KAAS,gBAAA,CAAiB,IAAI,CAAC,CAAA;;;ACnFrE,SAAS,IAAA,CACd,QAAA,EACA,IAAA,GAAiE,EAAC,EAC7C;AACrB,EAAA,MAAM,EAAE,WAAU,GAAI,IAAA;AACtB,EAAA,MAAM,IAAA,GAAgC,KAAK,MAAA,IACrC,sBAAA;AACN,EAAA,OAAO;AAAA,IACL,MAAA,CAAO,KAAK,SAAA,EAAW;AACrB,MAAA,IAAI,SAAA,IAAa,GAAA,CAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AAI3C,MAAA,IAAI,SAAA,CAAU,SAAS,WAAA,EAAa;AACpC,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA;AAClC,MAAA,IAAI,cAAc,MAAA,EAAW;AAC7B,MAAA,MAAM,UAAA,GAAa,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,SAAS,CAAA;AAC3C,MAAA,IAAI,eAAe,MAAA,EAAW;AAC9B,MAAA,MAAM,WAAW,IAAA,CAAK,SAAA,CAAU,YAAY,SAAA,CAAU,EAAA,EAAI,UAAU,EAAE,CAAA;AACtE,MAAA,MAAM,OAAA,GAAU,QAAA,CAAS,IAAA,CAAK,QAAA,EAAU,GAAG,CAAA;AAC3C,MAAA,IAAI,YAAY,IAAA,EAAM;AACtB,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,SAAA,CAAU,UAAU,CAAA;AACpC,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA;AACjC,MAAA,OAAO;AAAA,QACL,SAAA,EAAW,EAAE,IAAA,EAAM,WAAA,EAAa,EAAA,EAAI,EAAA,CAAG,CAAA,GAAI,EAAA,CAAG,CAAA,EAAG,EAAA,EAAI,EAAA,CAAG,CAAA,GAAI,GAAG,CAAA;AAAE,OACnE;AAAA,IACF;AAAA,GACF;AACF","file":"chunk-XMAWPGTK.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';\nimport { captureSlot, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface ReparentAdapter extends OrderedReader {\n setParent(id: string, parentId: string | null): void;\n /** Sibling-order write, paired with `getChildren`. Optional: an adapter\n * without both silently ignores slots and appends. */\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n /** Sibling ordinal under the old parent. Sugar for `fromSlot: { index }`,\n * and only a seed: `apply` re-observes the node's full slot through the\n * adapter and that observation supersedes this. */\n fromIndex?: number;\n /** Sibling ordinal under the new parent. Sugar for `toSlot: { index }`.\n * Omit to append. */\n toIndex?: number;\n /** Slot under the old parent, anchor included. `invert()` forwards it as\n * the destination slot; without it undo re-parents with no position and\n * the node lands last, quietly changing paint order. */\n fromSlot?: Slot;\n /** Slot under the new parent, anchor included. Omit to append. */\n toSlot?: Slot;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Move `id` to `slot` among `parentId`'s children. No-op when the adapter\n * has no ordering seam, when `id` isn't there, or when it's already in\n * place. */\nfunction placeAt(\n a: ReparentAdapter,\n parentId: string | null,\n id: string,\n slot: Slot,\n): void {\n if (!a.getChildren || !a.setChildOrder) return;\n const current = a.getChildren(parentId);\n const from = current.indexOf(id);\n if (from < 0) return;\n const next = current.slice();\n next.splice(from, 1);\n const resolved = resolveSlot(next, slot);\n const to = resolved === undefined ? next.length : Math.max(0, Math.min(resolved, next.length));\n if (from === to) return;\n next.splice(to, 0, id);\n a.setChildOrder(parentId, next);\n}\n\n/** Present when the caller named a destination at all; `undefined` leaves\n * placement to the adapter. */\nfunction optionalSlot(slot: Slot | undefined, index: number | undefined): Slot | undefined {\n if (slot) return slot;\n return index === undefined ? undefined : slotFromIndex(index);\n}\n\n/**\n * Op: change `id`'s parent and sibling slot, inverting back to the prior\n * parent and slot.\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 {\n id, fromParentId, toParentId,\n label = 'Reparent', coalesceKey = `reparent:${id}`,\n } = args;\n let fromSlot = optionalSlot(args.fromSlot, args.fromIndex);\n const toSlot = optionalSlot(args.toSlot, args.toIndex);\n const argsForSerial: ReparentArgs = {\n id, fromParentId, toParentId, fromSlot, toSlot, label, coalesceKey,\n };\n return {\n name: 'reparent',\n args: argsForSerial,\n label,\n coalesceKey,\n apply(adapter) {\n const a = adapter as ReparentAdapter;\n const observed = captureSlot(a, fromParentId, id);\n if (observed) {\n fromSlot = observed;\n argsForSerial.fromSlot = observed;\n }\n a.setParent(id, toParentId);\n if (toSlot) placeAt(a, toParentId, id, toSlot);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n fromSlot: toSlot,\n toSlot: fromSlot,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\n","import type { MoveBehavior, ModifierState, SnapStrategy } from '../types';\nimport {\n AUTO_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 reads a rect's `{x, y}` or a Path's bounds\n * origin, matching `gridSnapStrategy`'s own default.\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 ?? (AUTO_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,5 +1,5 @@
1
1
  import { NodeId } from '@weasel-js/routing';
2
- import { C as ClipboardSnapshot, I as InsertAdapter } from './types-CxC8P6W0.js';
2
+ import { C as ClipboardSnapshot, I as InsertAdapter } from './types-D-9QWBy5.js';
3
3
  import '@weasel-js/history';
4
4
  import './path-JEV2c5If.js';
5
5
 
package/dist/clipboard.js CHANGED
@@ -1,5 +1,5 @@
1
- export { WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, buildWeaselClipboardText, embedWeaselMetadataInSvg, extractWeaselClipboardFromSvg, parseWeaselClipboardText, sniffWeaselClipboardText, useClipboardOps } from './chunk-T3UQ3F6R.js';
2
- import './chunk-DOBSZOPR.js';
3
- import './chunk-4RJP2N2L.js';
1
+ export { WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, buildWeaselClipboardText, embedWeaselMetadataInSvg, extractWeaselClipboardFromSvg, parseWeaselClipboardText, sniffWeaselClipboardText, useClipboardOps } from './chunk-WPNA6L5O.js';
2
+ import './chunk-UPZY4ART.js';
3
+ import './chunk-NE27EOLP.js';
4
4
  //# sourceMappingURL=clipboard.js.map
5
5
  //# sourceMappingURL=clipboard.js.map
package/dist/clone.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { C as CloneBehavior } from './types-D_74YlWs.js';
1
+ import { C as CloneBehavior } from './types-qYpyTAiZ.js';
2
2
  import '@weasel-js/history';
3
- import './types-CxC8P6W0.js';
3
+ import './types-D-9QWBy5.js';
4
4
  import './path-JEV2c5If.js';
5
5
  import '@weasel-js/routing';
6
6
 
package/dist/clone.js CHANGED
@@ -1,5 +1,5 @@
1
- export { cloneByAltDrag } from './chunk-D4AAWR64.js';
2
- import './chunk-DOBSZOPR.js';
3
- import './chunk-4RJP2N2L.js';
1
+ export { cloneByAltDrag } from './chunk-M24CLARN.js';
2
+ import './chunk-UPZY4ART.js';
3
+ import './chunk-NE27EOLP.js';
4
4
  //# sourceMappingURL=clone.js.map
5
5
  //# sourceMappingURL=clone.js.map