@weasel-js/core 1.14.2 → 1.15.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 (137) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/dist/affordances/pathAnchors.js +0 -1
  3. package/dist/affordances/pathAnchors.js.map +1 -1
  4. package/dist/barrel/paths.js +1 -1
  5. package/dist/barrel/renderer.js +2 -2
  6. package/dist/barrel/routing.js +6 -4
  7. package/dist/barrel/text.js +5 -3
  8. package/dist/canvas/Canvas.js +5 -1
  9. package/dist/canvas/Canvas.js.map +1 -1
  10. package/dist/canvas/DrawCanvas.js +5 -1
  11. package/dist/canvas/DrawCanvas.js.map +1 -1
  12. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +4 -0
  13. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  14. package/dist/canvas/SceneCanvas/builtinToolSets.js +6 -3
  15. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -1
  16. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +1 -1
  17. package/dist/canvas/SceneCanvas/presets/paths.js +6 -6
  18. package/dist/canvas/SceneCanvas/presets/paths.js.map +1 -1
  19. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +1 -1
  20. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -1
  21. package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -0
  22. package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
  23. package/dist/canvas/SceneCanvas/useContributionRoles.js +2 -0
  24. package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/useSceneChrome.js +17 -5
  26. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  27. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +3 -0
  28. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  29. package/dist/canvas/SceneCanvas.js +5 -4
  30. package/dist/canvas/SceneCanvas.js.map +1 -1
  31. package/dist/canvas/SceneViewCanvas.js +5 -1
  32. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  33. package/dist/canvas/affordanceAt.js +0 -1
  34. package/dist/canvas/affordanceAt.js.map +1 -1
  35. package/dist/canvas/deps/chromeRules.js +37 -0
  36. package/dist/canvas/deps/chromeRules.js.map +1 -0
  37. package/dist/canvas/deps/editAnchors.js +0 -6
  38. package/dist/canvas/deps/editAnchors.js.map +1 -1
  39. package/dist/canvas/deps/hitTestArea.js +3 -1
  40. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  41. package/dist/canvas/deps/modeDefaultTool.js +23 -0
  42. package/dist/canvas/deps/modeDefaultTool.js.map +1 -0
  43. package/dist/canvas/deps/pathEditCommits.js +1 -1
  44. package/dist/canvas/deps/selectionSnapshot.js +23 -0
  45. package/dist/canvas/deps/selectionSnapshot.js.map +1 -0
  46. package/dist/canvas/deps/snap.js +1 -0
  47. package/dist/canvas/deps/snap.js.map +1 -1
  48. package/dist/canvas/pickReach.js +12 -8
  49. package/dist/canvas/pickReach.js.map +1 -1
  50. package/dist/canvas/pickWalk.js +7 -5
  51. package/dist/canvas/pickWalk.js.map +1 -1
  52. package/dist/canvas/scenePickSource.js +2 -2
  53. package/dist/canvas/scenePickSource.js.map +1 -1
  54. package/dist/canvas/useCanvasSurface.js +21 -3
  55. package/dist/canvas/useCanvasSurface.js.map +1 -1
  56. package/dist/canvas/useFrameLoop.js +6 -7
  57. package/dist/canvas/useFrameLoop.js.map +1 -1
  58. package/dist/canvas/useSurfaceSize.js +4 -3
  59. package/dist/canvas/useSurfaceSize.js.map +1 -1
  60. package/dist/core/geometry/regionHit.js +35 -16
  61. package/dist/core/geometry/regionHit.js.map +1 -1
  62. package/dist/core/scene/editMode.js +4 -1
  63. package/dist/core/scene/editMode.js.map +1 -1
  64. package/dist/core/scene/historyFacade.js +7 -50
  65. package/dist/core/scene/historyFacade.js.map +1 -1
  66. package/dist/core/scene/nodeGates.js +2 -2
  67. package/dist/core/scene/nodeGates.js.map +1 -1
  68. package/dist/core/scene/opTable.js +8 -8
  69. package/dist/core/scene/opTable.js.map +1 -1
  70. package/dist/core/scene/outerOps.js +2 -2
  71. package/dist/core/scene/outerOps.js.map +1 -1
  72. package/dist/core/scene/poseWrites.js +16 -15
  73. package/dist/core/scene/poseWrites.js.map +1 -1
  74. package/dist/core/scene/sceneJournals.js +78 -0
  75. package/dist/core/scene/sceneJournals.js.map +1 -0
  76. package/dist/core/scene/sceneRecording.js +3 -2
  77. package/dist/core/scene/sceneRecording.js.map +1 -1
  78. package/dist/core/viewport/useCanvasSize.js +47 -26
  79. package/dist/core/viewport/useCanvasSize.js.map +1 -1
  80. package/dist/{depSchema-DD9H89gX.d.ts → depSchema-ChQPzEkW.d.ts} +40 -13
  81. package/dist/domains/chrome-caps/resolve.js +1 -0
  82. package/dist/domains/chrome-caps/resolve.js.map +1 -1
  83. package/dist/domains/chrome-caps/useChromeCaps.js +4 -2
  84. package/dist/domains/chrome-caps/useChromeCaps.js.map +1 -1
  85. package/dist/domains/paths/pathEditState.js +0 -24
  86. package/dist/domains/paths/pathEditState.js.map +1 -1
  87. package/dist/domains/snap/probes2d.js +5 -0
  88. package/dist/domains/snap/probes2d.js.map +1 -1
  89. package/dist/domains/snap/sceneReads.js +11 -1
  90. package/dist/domains/snap/sceneReads.js.map +1 -1
  91. package/dist/domains/snap/sceneSession2d.js +4 -2
  92. package/dist/domains/snap/sceneSession2d.js.map +1 -1
  93. package/dist/domains/text/index.js +2 -2
  94. package/dist/icons.table.js +1 -1
  95. package/dist/icons.table.js.map +1 -1
  96. package/dist/icons.table.json +5 -0
  97. package/dist/index.d.ts +112 -60
  98. package/dist/index.js +12 -8
  99. package/dist/interactions/actions/booleans/action.js +4 -2
  100. package/dist/interactions/actions/booleans/action.js.map +1 -1
  101. package/dist/interactions/actions/defaults/anchorEditing.js +3 -6
  102. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  103. package/dist/interactions/actions/defaults/bendSegment.js +1 -2
  104. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  105. package/dist/interactions/actions/defaults/exitPathEdit.js +17 -1
  106. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  107. package/dist/interactions/actions/edit-anchors/action.js +3 -4
  108. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  109. package/dist/interactions/actions/insert/action.js +21 -2
  110. package/dist/interactions/actions/insert/action.js.map +1 -1
  111. package/dist/interactions/actions/insert/params.js +43 -0
  112. package/dist/interactions/actions/insert/params.js.map +1 -0
  113. package/dist/interactions/actions/notices.js +17 -0
  114. package/dist/interactions/actions/notices.js.map +1 -0
  115. package/dist/interactions/actions/outlines/createOutlines.js +8 -6
  116. package/dist/interactions/actions/outlines/createOutlines.js.map +1 -1
  117. package/dist/interactions/actions/outlines/createOutlinesAction.js +13 -8
  118. package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
  119. package/dist/interactions/actions/outlines/outlineStrokeAction.js +3 -2
  120. package/dist/interactions/actions/outlines/outlineStrokeAction.js.map +1 -1
  121. package/dist/interactions/actions/pathCommands/pathNodes.js +1 -1
  122. package/dist/interactions/actions/testUtils.js +0 -7
  123. package/dist/interactions/actions/testUtils.js.map +1 -1
  124. package/dist/interactions/actions/useNotices.js +13 -0
  125. package/dist/interactions/actions/useNotices.js.map +1 -0
  126. package/dist/test-seams.d.ts +5 -12
  127. package/dist/tools/builtin/anchors/anchorsTool.js +31 -0
  128. package/dist/tools/builtin/anchors/anchorsTool.js.map +1 -0
  129. package/dist/tools/builtin/polygon/usePolygonTool.js +1 -1
  130. package/dist/tools/builtin/star/useStarTool.js +1 -1
  131. package/dist/tools/index.js +4 -2
  132. package/dist/tools/toolOptions.js +47 -0
  133. package/dist/tools/toolOptions.js.map +1 -0
  134. package/dist/version.js +1 -1
  135. package/package.json +25 -25
  136. package/dist/interactions/actions/defaults/pathEditTools.js +0 -60
  137. package/dist/interactions/actions/defaults/pathEditTools.js.map +0 -1
@@ -5,12 +5,12 @@ import { ComponentType } from 'react';
5
5
  import { IconRef } from '@weasel-js/icons';
6
6
  import { PaintDrawKind, DrawCommand, Effect, CompositeOp, BlendFactors } from '@weasel-js/renderer';
7
7
  import * as _weasel_js_routing_react from '@weasel-js/routing/react';
8
+ import { TextOutlinesError, TextNodeSource, TextStyle, StyledRun, TextVerticalAlign } from '@weasel-js/text';
8
9
  import * as _weasel_js_geom from '@weasel-js/geom';
9
10
  import { FramedBox, View, Path, Affine, Rect, Scale2, PolygonPath, Point, PathFillRule, AnchorSet, PointOrigin } from '@weasel-js/geom';
10
11
  import { I as InheritedPaint, S as Scene, R as RectPose } from './types-DW14eu9B.js';
11
12
  import { Op, History } from '@weasel-js/history';
12
13
  import { NodeId, ModifierState, OverlayRole, WorldPoint, ResizeAnchor, ActionDeps, OngoingHandle, OngoingOverlay, IngestItem, SelectionApi, Point2, DragSample, DebugSink, LongPressState } from '@weasel-js/routing';
13
- import { TextNodeSource, TextOutlinesError, TextStyle, StyledRun, TextVerticalAlign } from '@weasel-js/text';
14
14
  import { CursorSpec } from '@weasel-js/cursor';
15
15
  import { ClaimableGesture } from '@weasel-js/gestures';
16
16
  import { SnapTier, ProbeRole } from '@weasel-js/geom/nd';
@@ -201,6 +201,27 @@ declare function paintKindOf(fill: Paint): PaintKindEntry | undefined;
201
201
  /** @internal Test helper — do not call from product code. */
202
202
  declare function _resetPaintKindsForTests(): void;
203
203
 
204
+ /**
205
+ * How an action tells the user something went wrong: the `notices` dep a host
206
+ * publishes, shown however it shows such things (draw raises a toast). Without
207
+ * one the message goes to the console.
208
+ */
209
+
210
+ /** What a notice says beyond its title. */
211
+ interface NoticeOptions {
212
+ /** One notice per id: a later one with the same id replaces it. */
213
+ id?: string;
214
+ description?: string;
215
+ }
216
+ /** The host's notice surface, read by actions whose result the user must hear about. */
217
+ interface NoticesDep {
218
+ warn(title: string, opts?: NoticeOptions): void;
219
+ }
220
+ /** Warn the user through `notices`, or under `tag` on the console without one. */
221
+ declare function warnUser(notices: NoticesDep | undefined, tag: string, title: string, opts?: NoticeOptions): void;
222
+ /** Warn that text in `error`'s face could not be turned into outlines. */
223
+ declare function warnTextOutlines(notices: NoticesDep | undefined, tag: string, error: TextOutlinesError): void;
224
+
204
225
  /** The paint a path node wears, resolved: what an action that turns ink into
205
226
  * geometry reads from a node and tells a consumer to put on a result. */
206
227
  interface PathPaint {
@@ -312,12 +333,14 @@ type CreateOutlinesResult =
312
333
  error: TextOutlinesError;
313
334
  };
314
335
  /**
315
- * Convert every text node in the selection to outlines, as one batch labeled
316
- * "Create Outlines". Other selected nodes are left alone and stay selected.
317
- * All or nothing: if any text node's face cannot supply outlines the scene is
318
- * untouched and the error comes back in the result.
336
+ * Convert every text node among `ids` — the selection, when not given — to
337
+ * outlines, as one batch labeled "Create Outlines". Other nodes are left
338
+ * alone, and the selection keeps them; a selected text node's replacement
339
+ * takes its place there. All or nothing: if any text node's face cannot
340
+ * supply outlines the scene is untouched and the error comes back in the
341
+ * result.
319
342
  */
320
- declare function applyCreateOutlines(adapter: CreateOutlinesAdapter): CreateOutlinesResult;
343
+ declare function applyCreateOutlines(adapter: CreateOutlinesAdapter, ids?: readonly NodeId[]): CreateOutlinesResult;
321
344
 
322
345
  /**
323
346
  * What each selected node gives a Boolean operation: its path, its glyph
@@ -1410,6 +1433,11 @@ interface SceneCanvasSnap {
1410
1433
  * turned container's lattice: a pose neither a rect nor a `Path` needs
1411
1434
  * its own. Default `AUTO_ORIGIN_PROJECTION`. */
1412
1435
  originProjection?: OriginProjection<unknown>;
1436
+ /** Points of node `id` a move snaps, in its own box frame (`sceneBoxFrames`:
1437
+ * from its box corner, through its pose transform, scale included), such as
1438
+ * a cabinet's hanger holes. Read once when a move begins; each becomes a
1439
+ * probe of role `'anchor'`, offered only to a source whose `roles` list it. */
1440
+ anchors?: SnapAnchors;
1413
1441
  /** Whether the canvas draws the snap marks. Default `true`. With `false`
1414
1442
  * the marks are still written to the `snapReadout` dep's store, for a
1415
1443
  * layer of the consumer's own. */
@@ -1417,6 +1445,8 @@ interface SceneCanvasSnap {
1417
1445
  /** The readout's default colors and line width, for a mark that sets no `color`. */
1418
1446
  readoutStyle?: SnapReadoutStyle;
1419
1447
  }
1448
+ /** Node `id`'s snap anchors in its own box frame; none for null or undefined. */
1449
+ type SnapAnchors = (id: string) => readonly Point[] | null | undefined;
1420
1450
  /** The snap readout's colors and line width, for a mark that sets no `color`. */
1421
1451
  interface SnapReadoutStyle {
1422
1452
  /** Points, lines and their labels. Default orange, as the path marker. */
@@ -2095,12 +2125,6 @@ interface EditAnchorsDep {
2095
2125
  width: number;
2096
2126
  height: number;
2097
2127
  } | null): void;
2098
- /** The path-edit tool in use, or null for plain anchor editing — one of a
2099
- * set of named tools, such as `'simplifyBrush'`, that each change what a
2100
- * plain drag in path edit does. Always null with no path open. */
2101
- tool: string | null;
2102
- /** Switch path-edit tool, or pass null for plain anchor editing. */
2103
- setTool(id: string | null): void;
2104
2128
  /** The handle locks held for every handle drag, on top of the modifiers —
2105
2129
  * what the `handleLock.angle` and `handleLock.length` toggles set. Absent,
2106
2130
  * the drag holds what its `handleLock` param names. Unlike the anchor
@@ -2782,6 +2806,9 @@ declare module '@weasel-js/routing' {
2782
2806
  * in both its `enabled` predicate and its invoker.
2783
2807
  */
2784
2808
  createOutlinesAdapter?: CreateOutlinesAdapter;
2809
+ /** Where an action tells the user something went wrong, such as text it
2810
+ * could not outline. Optional: without it the message goes to the console. */
2811
+ notices?: NoticesDep;
2785
2812
  /** Gesture dispatcher control surface, so an action can abort an
2786
2813
  * in-flight handle (Escape cancels a drag, etc.). */
2787
2814
  dispatcher?: {
@@ -2851,4 +2878,4 @@ declare module '@weasel-js/routing' {
2851
2878
  }
2852
2879
  }
2853
2880
 
2854
- export { type SourceCandidate as $, type AnchorMarkerShape as A, type CreateOutlinesAdapter as B, type ContentHandlerEntry as C, type Dims as D, type EditAnchorsDep as E, type FitViewToBoundsOptions as F, type GestureLifecycleOptions as G, type HandleLocks as H, type InertiaConfig as I, type PaintKindEntry as J, type PathEditTarget as K, type LayerHit as L, type FinishedEditFrame as M, type PathSymmetryDep as N, type BooleanOpOptions as O, type PathEditMark as P, type BooleansAdapter as Q, type RenderLayer as R, type SnapSource as S, type TextOverflow as T, type BooleanOp as U, type ViewAnimationOptions as V, type PathPaint as W, type BooleanOpResult as X, type BooleanStrokes as Y, type SnapMods as Z, _resetPaintKindsForTests as _, type PathEditExtension as a, type PreviewPathsDep as a$, type SnapProbeAt as a0, type SnapCollect as a1, type SnapMark as a2, type SnapDep as a3, type SnapOutcome as a4, type SnapReadoutStyle as a5, type AngleCandidate as a6, type AngleCollect as a7, type AnimateToBoundsOptions as a8, type AreaSelectDep as a9, type LayerRetainPolicy as aA, type LayoutDep as aB, type Locus as aC, type MinSize as aD, type NodeAtPointDep as aE, type OutlinePathSpec as aF, PATH_OVERLAY_ROLES as aG, type PaintKind as aH, type PaintKindEditorProps as aI, type PaintKindFamily as aJ, type PaintKindLoader as aK, type PanBounds as aL, type PathEditDrag as aM, type PathEditDragStart as aN, type PathEditFinishCtx as aO, type PathEditHandleMove as aP, type PathEditMarksCtx as aQ, type PathEditOther as aR, type PathEditPart as aS, type PathEditStore as aT, type PathForm as aU, type PathOverlay as aV, type PathOverlayOptions as aW, type PathOverlayStyle as aX, type PathRepresentation as aY, type PathTransformFinishCtx as aZ, type PoseChange as a_, type BooleanOpPreview as aa, type BooleanPreviewPiece as ab, type CanDropIntoDep as ac, type ClipboardDep as ad, type ClipboardIngestCtx as ae, type CreateOutlinesResult as af, type CustomPaintContext as ag, DEFAULT_LAYER_RETAIN as ah, type DecayLoopConfig as ai, type DropRefusal as aj, type DropVerdict as ak, type FinishedPathEdit as al, GEOMETRY_BOUND_DATA as am, type HandleLock as an, type HitTestView as ao, type IngestCtx as ap, type IngestionDep as aq, type InsertDep as ar, type InsertEnds as as, type InsertExtras as at, type InsertIntoDep as au, type InsertLive as av, type LassoSelectDep as aw, type LayerCommandCache as ax, type LayerDrawFailure as ay, type LayerDrawSpan as az, type SnapGesture as b, type ResizePolicy as b0, STICKY_DEFAULTS as b1, type SnapBegin as b2, type SnapFrame as b3, type SnapMarks as b4, type SnapReadoutDep as b5, type SnapSide as b6, type SvgUnpacker as b7, type TextEditDep as b8, type TextNodeData as b9, paletteInsertPaint as bA, pathPaintOf as bB, patternFamily as bC, previewBooleanOp as bD, refusalOf as bE, registerContentHandler as bF, registerPaintKind as bG, registerPaintKindLoader as bH, seedTile as bI, stickyData as bJ, stickyNodeFactory as bK, subscribeToSources as bL, switchGradientKind as bM, textBoxOf as bN, useDecayLoop as bO, useEnteredGroup as bP, useViewAnimation as bQ, useViewAnimationOn as bR, viewAxes as bS, withoutGeometryBoundData as bT, type TransformPreviewNode as ba, type TransientPath as bb, VIEW_ANIMATION_KEY as bc, type ViewAnimationApi as bd, type ViewApi as be, type ViewChannel as bf, applyBooleanOp as bg, applyCreateOutlines as bh, asPaint as bi, createEnteredGroup as bj, createPathOverlay as bk, createSnapOutcomeStore as bl, depsUnchanged as bm, drawLayers as bn, drawOneLayer as bo, fitViewToBounds as bp, getPaintKind as bq, isLayerPainted as br, isLayerVisible as bs, listGradientKinds as bt, listPaintKinds as bu, minSizeOf as bv, paintKindOf as bw, paintKindRegistry as bx, paintedLayers as by, paintsLayer as bz, type SnapConstrain as c, type SnapResizeBox as d, type SnapGeometry as e, type SnapOutcomeStore as f, type SnapSession as g, type LayerDims as h, type ViewportDims as i, type RedrawSource as j, type EditAnchorsParams as k, type SliceDep as l, type StickyStyle as m, type EnteredGroupApi as n, type ChromeState as o, type LayerGroup as p, type TransformFollower as q, type GeometryProjection as r, type InsertNodeFactory as s, type InsertPaint as t, type SvgIngestOptions as u, type SceneCanvasSnap as v, type Affordance as w, type AffordanceRegion as x, type AffordanceBinding as y, type CommonAffordanceScratch as z };
2881
+ export { type BooleanStrokes as $, type AnchorMarkerShape as A, type CommonAffordanceScratch as B, type ContentHandlerEntry as C, type Dims as D, type EditAnchorsDep as E, type FitViewToBoundsOptions as F, type GestureLifecycleOptions as G, type HandleLocks as H, type InertiaConfig as I, type CreateOutlinesAdapter as J, type PaintKindEntry as K, type LayerHit as L, type PathEditTarget as M, type NoticesDep as N, type FinishedEditFrame as O, type PathEditMark as P, type PathSymmetryDep as Q, type RenderLayer as R, type SnapSource as S, type TextOverflow as T, type BooleanOpOptions as U, type ViewAnimationOptions as V, type BooleansAdapter as W, type BooleanOp as X, type PathPaint as Y, type BooleanOpResult as Z, _resetPaintKindsForTests as _, type PathEditExtension as a, type PathRepresentation as a$, type SnapMods as a0, type SourceCandidate as a1, type SnapProbeAt as a2, type SnapCollect as a3, type SnapMark as a4, type SnapDep as a5, type SnapOutcome as a6, type SnapReadoutStyle as a7, type AngleCandidate as a8, type AngleCollect as a9, type LayerDrawFailure as aA, type LayerDrawSpan as aB, type LayerRetainPolicy as aC, type LayoutDep as aD, type Locus as aE, type MinSize as aF, type NodeAtPointDep as aG, type NoticeOptions as aH, type OutlinePathSpec as aI, PATH_OVERLAY_ROLES as aJ, type PaintKind as aK, type PaintKindEditorProps as aL, type PaintKindFamily as aM, type PaintKindLoader as aN, type PanBounds as aO, type PathEditDrag as aP, type PathEditDragStart as aQ, type PathEditFinishCtx as aR, type PathEditHandleMove as aS, type PathEditMarksCtx as aT, type PathEditOther as aU, type PathEditPart as aV, type PathEditStore as aW, type PathForm as aX, type PathOverlay as aY, type PathOverlayOptions as aZ, type PathOverlayStyle as a_, type AnimateToBoundsOptions as aa, type AreaSelectDep as ab, type BooleanOpPreview as ac, type BooleanPreviewPiece as ad, type CanDropIntoDep as ae, type ClipboardDep as af, type ClipboardIngestCtx as ag, type CreateOutlinesResult as ah, type CustomPaintContext as ai, DEFAULT_LAYER_RETAIN as aj, type DecayLoopConfig as ak, type DropRefusal as al, type DropVerdict as am, type FinishedPathEdit as an, GEOMETRY_BOUND_DATA as ao, type HandleLock as ap, type HitTestView as aq, type IngestCtx as ar, type IngestionDep as as, type InsertDep as at, type InsertEnds as au, type InsertExtras as av, type InsertIntoDep as aw, type InsertLive as ax, type LassoSelectDep as ay, type LayerCommandCache as az, type SnapGesture as b, type PathTransformFinishCtx as b0, type PoseChange as b1, type PreviewPathsDep as b2, type ResizePolicy as b3, STICKY_DEFAULTS as b4, type SnapBegin as b5, type SnapFrame as b6, type SnapMarks as b7, type SnapReadoutDep as b8, type SnapSide as b9, paintKindRegistry as bA, paintedLayers as bB, paintsLayer as bC, paletteInsertPaint as bD, pathPaintOf as bE, patternFamily as bF, previewBooleanOp as bG, refusalOf as bH, registerContentHandler as bI, registerPaintKind as bJ, registerPaintKindLoader as bK, seedTile as bL, stickyData as bM, stickyNodeFactory as bN, subscribeToSources as bO, switchGradientKind as bP, textBoxOf as bQ, useDecayLoop as bR, useEnteredGroup as bS, useViewAnimation as bT, useViewAnimationOn as bU, viewAxes as bV, warnTextOutlines as bW, warnUser as bX, withoutGeometryBoundData as bY, type SvgUnpacker as ba, type TextEditDep as bb, type TextNodeData as bc, type TransformPreviewNode as bd, type TransientPath as be, VIEW_ANIMATION_KEY as bf, type ViewAnimationApi as bg, type ViewApi as bh, type ViewChannel as bi, applyBooleanOp as bj, applyCreateOutlines as bk, asPaint as bl, createEnteredGroup as bm, createPathOverlay as bn, createSnapOutcomeStore as bo, depsUnchanged as bp, drawLayers as bq, drawOneLayer as br, fitViewToBounds as bs, getPaintKind as bt, isLayerPainted as bu, isLayerVisible as bv, listGradientKinds as bw, listPaintKinds as bx, minSizeOf as by, paintKindOf as bz, type SnapConstrain as c, type SnapResizeBox as d, type SnapGeometry as e, type SnapOutcomeStore as f, type SnapAnchors as g, type SnapSession as h, type LayerDims as i, type ViewportDims as j, type RedrawSource as k, type EditAnchorsParams as l, type SliceDep as m, type StickyStyle as n, type EnteredGroupApi as o, type ChromeState as p, type LayerGroup as q, type TransformFollower as r, type GeometryProjection as s, type InsertNodeFactory as t, type InsertPaint as u, type SvgIngestOptions as v, type SceneCanvasSnap as w, type Affordance as x, type AffordanceRegion as y, type AffordanceBinding as z };
@@ -23,6 +23,7 @@ function resolveVisibility(consumer, ctx) {
23
23
  allowedCapabilities: DEFAULT_ALLOWED_CAPABILITIES
24
24
  };
25
25
  return (id) => {
26
+ if (ruleCtx.hiddenChrome?.has(id)) return false;
26
27
  const entry = merged[id];
27
28
  if (entry === void 0) return true;
28
29
  if (typeof entry === "function") return entry(ruleCtx);
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","names":[],"sources":["../../../src/domains/chrome-caps/resolve.ts"],"sourcesContent":["import type { ChromeCtx, ChromeId, VisibilityRules } from './types';\nimport type { RuleCtx } from '@weasel-js/routing';\nimport { DEFAULT_ALLOWED_CAPABILITIES } from '@weasel-js/routing';\nimport { evaluate } from '@weasel-js/routing';\nimport { defaultVisibilityRules } from './defaults';\nimport { viewZoom } from 'core/viewport/view';\n\n/**\n * Build the per-frame visibility check: consumer rules merged on top of\n * the kit defaults, evaluated against `ctx`.\n *\n * `ChromeCtx` is the legacy shape; surfaces still on it get `mode='normal'`\n * and `DEFAULT_ALLOWED_CAPABILITIES` filled in here, so a caller that never\n * opted into modality behaves exactly like one sitting in normal mode.\n */\nexport function resolveVisibility(\n consumer: VisibilityRules | undefined,\n ctx: ChromeCtx | RuleCtx,\n): (id: ChromeId) => boolean {\n const merged: VisibilityRules = consumer\n ? { ...defaultVisibilityRules, ...consumer }\n : defaultVisibilityRules;\n const ruleCtx: RuleCtx = 'mode' in ctx\n ? ctx\n : {\n ...ctx,\n zoom: viewZoom(ctx.view),\n mode: 'normal',\n allowedCapabilities: DEFAULT_ALLOWED_CAPABILITIES,\n };\n return (id) => {\n const entry = merged[id];\n if (entry === undefined) return true;\n if (typeof entry === 'function') return entry(ruleCtx);\n return evaluate(entry, ruleCtx);\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAgB,kBACd,UACA,KAC2B;CAC3B,MAAM,SAA0B,WAC5B;EAAE,GAAG;EAAwB,GAAG;CAAS,IACzC;CACJ,MAAM,UAAmB,UAAU,MAC/B,MACA;EACA,GAAG;EACH,MAAM,SAAS,IAAI,IAAI;EACvB,MAAM;EACN,qBAAqB;CACvB;CACF,QAAQ,OAAO;EACb,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,QAAW,OAAO;EAChC,IAAI,OAAO,UAAU,YAAY,OAAO,MAAM,OAAO;EACrD,OAAO,SAAS,OAAO,OAAO;CAChC;AACF"}
1
+ {"version":3,"file":"resolve.js","names":[],"sources":["../../../src/domains/chrome-caps/resolve.ts"],"sourcesContent":["import type { ChromeCtx, ChromeId, VisibilityRules } from './types';\nimport type { RuleCtx } from '@weasel-js/routing';\nimport { DEFAULT_ALLOWED_CAPABILITIES } from '@weasel-js/routing';\nimport { evaluate } from '@weasel-js/routing';\nimport { defaultVisibilityRules } from './defaults';\nimport { viewZoom } from 'core/viewport/view';\n\n/**\n * Build the per-frame visibility check: consumer rules merged on top of\n * the kit defaults, evaluated against `ctx`.\n *\n * `ChromeCtx` is the legacy shape; surfaces still on it get `mode='normal'`\n * and `DEFAULT_ALLOWED_CAPABILITIES` filled in here, so a caller that never\n * opted into modality behaves exactly like one sitting in normal mode.\n */\nexport function resolveVisibility(\n consumer: VisibilityRules | undefined,\n ctx: ChromeCtx | RuleCtx,\n): (id: ChromeId) => boolean {\n const merged: VisibilityRules = consumer\n ? { ...defaultVisibilityRules, ...consumer }\n : defaultVisibilityRules;\n const ruleCtx: RuleCtx = 'mode' in ctx\n ? ctx\n : {\n ...ctx,\n zoom: viewZoom(ctx.view),\n mode: 'normal',\n allowedCapabilities: DEFAULT_ALLOWED_CAPABILITIES,\n };\n return (id) => {\n if (ruleCtx.hiddenChrome?.has(id)) return false;\n const entry = merged[id];\n if (entry === undefined) return true;\n if (typeof entry === 'function') return entry(ruleCtx);\n return evaluate(entry, ruleCtx);\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAgB,kBACd,UACA,KAC2B;CAC3B,MAAM,SAA0B,WAC5B;EAAE,GAAG;EAAwB,GAAG;CAAS,IACzC;CACJ,MAAM,UAAmB,UAAU,MAC/B,MACA;EACA,GAAG;EACH,MAAM,SAAS,IAAI,IAAI;EACvB,MAAM;EACN,qBAAqB;CACvB;CACF,QAAQ,OAAO;EACb,IAAI,QAAQ,cAAc,IAAI,EAAE,GAAG,OAAO;EAC1C,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,QAAW,OAAO;EAChC,IAAI,OAAO,UAAU,YAAY,OAAO,MAAM,OAAO;EACrD,OAAO,SAAS,OAAO,OAAO;CAChC;AACF"}
@@ -30,7 +30,8 @@ function surfaceRuleCtx(surface, view) {
30
30
  allowedCapabilities: surface.mode?.allowedCapabilities ?? DEFAULT_ALLOWED_CAPABILITIES,
31
31
  selectionResizable: resizable ? view.selection.every((id) => resizable(id)) : void 0,
32
32
  editingAnchors: surface.editingAnchors,
33
- device: surface.device
33
+ device: surface.device,
34
+ ...surface.hiddenChrome ? { hiddenChrome: surface.hiddenChrome } : {}
34
35
  });
35
36
  }
36
37
  /** The chrome-caps answers for a surface. The object keeps its identity for
@@ -47,7 +48,8 @@ function useChromeCaps(source) {
47
48
  mode: now.getMode?.(),
48
49
  resizable: now.resizable,
49
50
  editingAnchors: now.editingAnchors(),
50
- device: now.device
51
+ device: now.device,
52
+ hiddenChrome: now.getHiddenChrome?.()
51
53
  }, inputs);
52
54
  };
53
55
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"useChromeCaps.js","names":[],"sources":["../../../src/domains/chrome-caps/useChromeCaps.ts"],"sourcesContent":["/**\n * The rule context a surface's chrome-visibility rules and action eligibility\n * are evaluated against, and the hook that keeps one for `<SceneCanvas>`.\n */\nimport { useMemo } from 'react';\nimport type { ActiveMode } from '@weasel-js/modes';\nimport { useLatest } from '@weasel-js/react';\nimport {\n buildRuleCtx,\n DEFAULT_ALLOWED_CAPABILITIES,\n type DeviceProfile,\n type ModifierState,\n type RuleCtx,\n} from '@weasel-js/routing';\nimport type { NodeId } from '../../core/scene/types';\nimport { viewZoom, type View } from '../../core/viewport/view';\nimport { resolveVisibility } from './resolve';\nimport type { VisibilityRules } from './types';\n\n/**\n * The part of a rule context that belongs to one view. Everything else a\n * `RuleCtx` carries — mode, capabilities, device, focus — is the surface's and\n * the same for every view drawn on it.\n */\nexport interface ViewRuleInputs {\n selection: readonly NodeId[];\n view: View;\n /** What that view's dispatcher currently has in flight. */\n action: { kind: string | null; id: string | null };\n}\n\n/** The part of a rule context that belongs to the surface. */\nexport interface SurfaceRuleInputs {\n focused: boolean;\n /** The modifier keys held. Absent means none, so `modifierHeld` never matches. */\n modifiers?: ModifierState | undefined;\n hover: NodeId | null;\n /** Absent on a surface with no mode registry, which behaves as normal mode. */\n mode: ActiveMode | undefined;\n /** Whether one node may be resized. Absent means every node may. */\n resizable: ((id: string) => boolean) | undefined;\n editingAnchors: boolean;\n device: DeviceProfile;\n}\n\nconst NO_MODIFIERS = { alt: false, ctrl: false, meta: false, shift: false };\n\n/** One view's rule context on a surface. */\nexport function surfaceRuleCtx(surface: SurfaceRuleInputs, view: ViewRuleInputs): RuleCtx {\n const { resizable } = surface;\n return buildRuleCtx({\n focused: surface.focused,\n selection: view.selection,\n multiActive: view.selection.length > 1,\n modifiers: surface.modifiers ?? NO_MODIFIERS,\n action: view.action,\n hover: surface.hover,\n zoom: viewZoom(view.view),\n mode: surface.mode?.id ?? 'normal',\n // An empty set would make every `capability:` rule false and hide the\n // chrome those rules gate.\n allowedCapabilities: surface.mode?.allowedCapabilities ?? DEFAULT_ALLOWED_CAPABILITIES,\n // Left undefined without a predicate, which `resizable:` reads as resizable.\n selectionResizable: resizable ? view.selection.every((id) => resizable(id)) : undefined,\n editingAnchors: surface.editingAnchors,\n device: surface.device,\n });\n}\n\n/** Where a surface's rule context comes from. Read at the moment a rule is\n * evaluated, never at render. */\nexport interface ChromeCapsSource {\n /** The surface's rule table, over the kit defaults. */\n rules: VisibilityRules | undefined;\n /** The active mode. Absent when no mode registry is wired. */\n getMode: (() => ActiveMode) | undefined;\n /** Absent means the surface always counts as focused. */\n getFocused: (() => boolean) | undefined;\n getHover: () => NodeId | null;\n /** Absent means no modifier key ever counts as held. */\n getModifiers?: (() => ModifierState) | undefined;\n resizable: ((id: string) => boolean) | undefined;\n editingAnchors: () => boolean;\n device: DeviceProfile;\n}\n\n/**\n * A surface's chrome-caps answers for one view. The rules are surface-wide on\n * purpose — one consumer table, one set of kit defaults — but a rule keyed on\n * selection, camera or the in-flight action answers differently per view, so\n * the caller supplies that part and both the paint gate and the dispatcher's\n * eligibility filter read the same context.\n */\nexport interface ChromeCaps {\n /** `undefined` when the surface gates nothing: no mode registry is wired. */\n ruleCtx(inputs: ViewRuleInputs): RuleCtx | undefined;\n isVisible(inputs: ViewRuleInputs): (id: string) => boolean;\n}\n\n/** The chrome-caps answers for a surface. The object keeps its identity for\n * the life of the component and reads `source` as of the last commit. */\nexport function useChromeCaps(source: ChromeCapsSource): ChromeCaps {\n const live = useLatest(source);\n return useMemo(() => {\n const build = (inputs: ViewRuleInputs): RuleCtx => {\n const now = live.current;\n return surfaceRuleCtx({\n focused: now.getFocused ? now.getFocused() : true,\n modifiers: now.getModifiers?.(),\n hover: now.getHover(),\n mode: now.getMode?.(),\n resizable: now.resizable,\n editingAnchors: now.editingAnchors(),\n device: now.device,\n }, inputs);\n };\n return {\n ruleCtx: (inputs) => (live.current.getMode ? build(inputs) : undefined),\n isVisible: (inputs) => resolveVisibility(live.current.rules, build(inputs)),\n };\n }, [live]);\n}\n"],"mappings":";;;;;;;;;;;AA6CA,IAAM,eAAe;CAAE,KAAK;CAAO,MAAM;CAAO,MAAM;CAAO,OAAO;AAAM;;AAG1E,SAAgB,eAAe,SAA4B,MAA+B;CACxF,MAAM,EAAE,cAAc;CACtB,OAAO,aAAa;EAClB,SAAS,QAAQ;EACjB,WAAW,KAAK;EAChB,aAAa,KAAK,UAAU,SAAS;EACrC,WAAW,QAAQ,aAAa;EAChC,QAAQ,KAAK;EACb,OAAO,QAAQ;EACf,MAAM,SAAS,KAAK,IAAI;EACxB,MAAM,QAAQ,MAAM,MAAM;EAG1B,qBAAqB,QAAQ,MAAM,uBAAuB;EAE1D,oBAAoB,YAAY,KAAK,UAAU,OAAO,OAAO,UAAU,EAAE,CAAC,IAAI;EAC9E,gBAAgB,QAAQ;EACxB,QAAQ,QAAQ;CAClB,CAAC;AACH;;;AAkCA,SAAgB,cAAc,QAAsC;CAClE,MAAM,OAAO,UAAU,MAAM;CAC7B,OAAO,cAAc;EACnB,MAAM,SAAS,WAAoC;GACjD,MAAM,MAAM,KAAK;GACjB,OAAO,eAAe;IACpB,SAAS,IAAI,aAAa,IAAI,WAAW,IAAI;IAC7C,WAAW,IAAI,eAAe;IAC9B,OAAO,IAAI,SAAS;IACpB,MAAM,IAAI,UAAU;IACpB,WAAW,IAAI;IACf,gBAAgB,IAAI,eAAe;IACnC,QAAQ,IAAI;GACd,GAAG,MAAM;EACX;EACA,OAAO;GACL,UAAU,WAAY,KAAK,QAAQ,UAAU,MAAM,MAAM,IAAI;GAC7D,YAAY,WAAW,kBAAkB,KAAK,QAAQ,OAAO,MAAM,MAAM,CAAC;EAC5E;CACF,GAAG,CAAC,IAAI,CAAC;AACX"}
1
+ {"version":3,"file":"useChromeCaps.js","names":[],"sources":["../../../src/domains/chrome-caps/useChromeCaps.ts"],"sourcesContent":["/**\n * The rule context a surface's chrome-visibility rules and action eligibility\n * are evaluated against, and the hook that keeps one for `<SceneCanvas>`.\n */\nimport { useMemo } from 'react';\nimport type { ActiveMode } from '@weasel-js/modes';\nimport { useLatest } from '@weasel-js/react';\nimport {\n buildRuleCtx,\n DEFAULT_ALLOWED_CAPABILITIES,\n type DeviceProfile,\n type ModifierState,\n type RuleCtx,\n} from '@weasel-js/routing';\nimport type { NodeId } from '../../core/scene/types';\nimport { viewZoom, type View } from '../../core/viewport/view';\nimport { resolveVisibility } from './resolve';\nimport type { VisibilityRules } from './types';\n\n/**\n * The part of a rule context that belongs to one view. Everything else a\n * `RuleCtx` carries — mode, capabilities, device, focus — is the surface's and\n * the same for every view drawn on it.\n */\nexport interface ViewRuleInputs {\n selection: readonly NodeId[];\n view: View;\n /** What that view's dispatcher currently has in flight. */\n action: { kind: string | null; id: string | null };\n}\n\n/** The part of a rule context that belongs to the surface. */\nexport interface SurfaceRuleInputs {\n focused: boolean;\n /** The modifier keys held. Absent means none, so `modifierHeld` never matches. */\n modifiers?: ModifierState | undefined;\n hover: NodeId | null;\n /** Absent on a surface with no mode registry, which behaves as normal mode. */\n mode: ActiveMode | undefined;\n /** Whether one node may be resized. Absent means every node may. */\n resizable: ((id: string) => boolean) | undefined;\n editingAnchors: boolean;\n device: DeviceProfile;\n /** Chrome the active tool hides. Absent hides nothing. */\n hiddenChrome?: ReadonlySet<string> | undefined;\n}\n\nconst NO_MODIFIERS = { alt: false, ctrl: false, meta: false, shift: false };\n\n/** One view's rule context on a surface. */\nexport function surfaceRuleCtx(surface: SurfaceRuleInputs, view: ViewRuleInputs): RuleCtx {\n const { resizable } = surface;\n return buildRuleCtx({\n focused: surface.focused,\n selection: view.selection,\n multiActive: view.selection.length > 1,\n modifiers: surface.modifiers ?? NO_MODIFIERS,\n action: view.action,\n hover: surface.hover,\n zoom: viewZoom(view.view),\n mode: surface.mode?.id ?? 'normal',\n // An empty set would make every `capability:` rule false and hide the\n // chrome those rules gate.\n allowedCapabilities: surface.mode?.allowedCapabilities ?? DEFAULT_ALLOWED_CAPABILITIES,\n // Left undefined without a predicate, which `resizable:` reads as resizable.\n selectionResizable: resizable ? view.selection.every((id) => resizable(id)) : undefined,\n editingAnchors: surface.editingAnchors,\n device: surface.device,\n ...(surface.hiddenChrome ? { hiddenChrome: surface.hiddenChrome } : {}),\n });\n}\n\n/** Where a surface's rule context comes from. Read at the moment a rule is\n * evaluated, never at render. */\nexport interface ChromeCapsSource {\n /** The surface's rule table, over the kit defaults. */\n rules: VisibilityRules | undefined;\n /** The active mode. Absent when no mode registry is wired. */\n getMode: (() => ActiveMode) | undefined;\n /** Absent means the surface always counts as focused. */\n getFocused: (() => boolean) | undefined;\n getHover: () => NodeId | null;\n /** Absent means no modifier key ever counts as held. */\n getModifiers?: (() => ModifierState) | undefined;\n resizable: ((id: string) => boolean) | undefined;\n editingAnchors: () => boolean;\n device: DeviceProfile;\n /** The chrome the active tool hides (`ToolDef.hides`). Absent hides nothing. */\n getHiddenChrome?: (() => ReadonlySet<string> | undefined) | undefined;\n}\n\n/**\n * A surface's chrome-caps answers for one view. The rules are surface-wide on\n * purpose — one consumer table, one set of kit defaults — but a rule keyed on\n * selection, camera or the in-flight action answers differently per view, so\n * the caller supplies that part and both the paint gate and the dispatcher's\n * eligibility filter read the same context.\n */\nexport interface ChromeCaps {\n /** `undefined` when the surface gates nothing: no mode registry is wired. */\n ruleCtx(inputs: ViewRuleInputs): RuleCtx | undefined;\n isVisible(inputs: ViewRuleInputs): (id: string) => boolean;\n}\n\n/** The chrome-caps answers for a surface. The object keeps its identity for\n * the life of the component and reads `source` as of the last commit. */\nexport function useChromeCaps(source: ChromeCapsSource): ChromeCaps {\n const live = useLatest(source);\n return useMemo(() => {\n const build = (inputs: ViewRuleInputs): RuleCtx => {\n const now = live.current;\n return surfaceRuleCtx({\n focused: now.getFocused ? now.getFocused() : true,\n modifiers: now.getModifiers?.(),\n hover: now.getHover(),\n mode: now.getMode?.(),\n resizable: now.resizable,\n editingAnchors: now.editingAnchors(),\n device: now.device,\n hiddenChrome: now.getHiddenChrome?.(),\n }, inputs);\n };\n return {\n ruleCtx: (inputs) => (live.current.getMode ? build(inputs) : undefined),\n isVisible: (inputs) => resolveVisibility(live.current.rules, build(inputs)),\n };\n }, [live]);\n}\n"],"mappings":";;;;;;;;;;;AA+CA,IAAM,eAAe;CAAE,KAAK;CAAO,MAAM;CAAO,MAAM;CAAO,OAAO;AAAM;;AAG1E,SAAgB,eAAe,SAA4B,MAA+B;CACxF,MAAM,EAAE,cAAc;CACtB,OAAO,aAAa;EAClB,SAAS,QAAQ;EACjB,WAAW,KAAK;EAChB,aAAa,KAAK,UAAU,SAAS;EACrC,WAAW,QAAQ,aAAa;EAChC,QAAQ,KAAK;EACb,OAAO,QAAQ;EACf,MAAM,SAAS,KAAK,IAAI;EACxB,MAAM,QAAQ,MAAM,MAAM;EAG1B,qBAAqB,QAAQ,MAAM,uBAAuB;EAE1D,oBAAoB,YAAY,KAAK,UAAU,OAAO,OAAO,UAAU,EAAE,CAAC,IAAI;EAC9E,gBAAgB,QAAQ;EACxB,QAAQ,QAAQ;EAChB,GAAI,QAAQ,eAAe,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;CACvE,CAAC;AACH;;;AAoCA,SAAgB,cAAc,QAAsC;CAClE,MAAM,OAAO,UAAU,MAAM;CAC7B,OAAO,cAAc;EACnB,MAAM,SAAS,WAAoC;GACjD,MAAM,MAAM,KAAK;GACjB,OAAO,eAAe;IACpB,SAAS,IAAI,aAAa,IAAI,WAAW,IAAI;IAC7C,WAAW,IAAI,eAAe;IAC9B,OAAO,IAAI,SAAS;IACpB,MAAM,IAAI,UAAU;IACpB,WAAW,IAAI;IACf,gBAAgB,IAAI,eAAe;IACnC,QAAQ,IAAI;IACZ,cAAc,IAAI,kBAAkB;GACtC,GAAG,MAAM;EACX;EACA,OAAO;GACL,UAAU,WAAY,KAAK,QAAQ,UAAU,MAAM,MAAM,IAAI;GAC7D,YAAY,WAAW,kBAAkB,KAAK,QAAQ,OAAO,MAAM,MAAM,CAAC;EAC5E;CACF,GAAG,CAAC,IAAI,CAAC;AACX"}
@@ -22,7 +22,6 @@ function createPathEditState(source = {}) {
22
22
  let selectedAnchors = NO_ANCHORS;
23
23
  let marquee = null;
24
24
  let handleLocks = null;
25
- let ownTool = null;
26
25
  let idSet = NO_IDS;
27
26
  let selectedMark = null;
28
27
  let extensions = source.extensions ?? NO_EXTENSIONS;
@@ -47,7 +46,6 @@ function createPathEditState(source = {}) {
47
46
  },
48
47
  setEditingId(id) {
49
48
  const host = source.host?.();
50
- if (next(id) !== getEditingId()) ownTool = null;
51
49
  if (host) {
52
50
  host.setEditingId(id);
53
51
  return;
@@ -94,28 +92,6 @@ function createPathEditState(source = {}) {
94
92
  };
95
93
  notify();
96
94
  },
97
- getTool() {
98
- const on = getEditingId();
99
- const host = source.host?.();
100
- if (host?.tool) return on ? host.tool() : null;
101
- if (ownTool && ownTool.on !== on) ownTool = null;
102
- return ownTool?.id ?? null;
103
- },
104
- setTool(id) {
105
- const host = source.host?.();
106
- if (host?.tool && host.setTool) {
107
- host.setTool(id);
108
- return;
109
- }
110
- const on = getEditingId();
111
- const tool = id && on ? {
112
- id,
113
- on
114
- } : null;
115
- if (tool?.id === ownTool?.id && tool?.on === ownTool?.on) return;
116
- ownTool = tool;
117
- notify();
118
- },
119
95
  subscribe: changed.subscribe
120
96
  };
121
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pathEditState.js","names":[],"sources":["../../../src/domains/paths/pathEditState.ts"],"sourcesContent":["/**\n * Which path is being anchor-edited, which of its anchors are selected, and\n * the anchor marquee in flight: one object every reader shares — the\n * `editAnchors` dep, the anchor overlay, the selection overlay's suppressed\n * set, the chrome-caps rule context, and the select tool's shift-click lock.\n */\nimport { createEmitter } from '@weasel-js/registry';\nimport type { PathEditHost } from 'canvas/SceneCanvas/pathEditHost';\nimport type { HandleLocks } from 'interactions/actions/edit-anchors/params';\nimport type { PathEditExtension, PathEditMark } from './pathEditExtension';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { effectSourceFor } from 'domains/pathEffects/effectSources';\n\n/** A world-coord rect. */\nexport interface AnchorMarquee {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * What decides whether a stored edit target is still one: its node exists and\n * is selected, and the mode permits anchor editing. Each is read at the\n * moment of the question.\n */\nexport interface PathEditSource {\n /** Whether `id` names a node that exists and is selected. Absent means\n * every id does. */\n isSelectedNode?(id: string): boolean;\n /** A host that owns the id; the state then keeps none of its own. */\n host?(): PathEditHost | undefined;\n /**\n * Whether anchor editing is permitted right now — whether the active mode\n * allows the `edits-anchors` capability.\n *\n * **Omit this when no mode registry is wired.** Absent means \"no modality,\n * so nothing revokes edit mode\"; a predicate built from an empty capability\n * set would revoke edit mode instantly.\n */\n isAllowed?(): boolean;\n /** The extensions it starts with; `setExtensions` replaces them. */\n extensions?: readonly PathEditExtension[];\n}\n\nexport interface PathEditState {\n /**\n * The node whose anchors are editable now, or `''`: the stored id, or the\n * host's, while `PathEditSource` still lets it stand. Masked rather than\n * cleared: a host's mode machine can leave path-edit mode without running\n * `exitPathEdit`, and nothing re-renders when it does, so there is no\n * moment to write the id back.\n */\n getEditingId(): string;\n /** `getEditingId()` as the set the selection overlay skips. */\n getEditingIds(): ReadonlySet<string>;\n setEditingId(id: string | null): void;\n /** Selected anchors (flat indices) of the path being edited. */\n getSelectedAnchors(): ReadonlySet<number>;\n setSelectedAnchors(next: ReadonlySet<number>): void;\n /** The in-flight anchor-marquee rect, in the edited path's world. */\n getMarquee(): AnchorMarquee | null;\n setMarquee(rect: AnchorMarquee | null): void;\n /** The extension mark selected in place of anchors, or null. */\n getSelectedMark(): PathEditMark | null;\n setSelectedMark(mark: PathEditMark | null): void;\n /** The canvas's path-edit extensions, in order. */\n getExtensions(): readonly PathEditExtension[];\n /** Replace the extensions; the overlay repaints when they change. */\n setExtensions(next: readonly PathEditExtension[]): void;\n /** The handle locks the `handleLock.*` toggles set; null until one is\n * pressed. Not tied to the edited path, so they outlast a change of it. */\n getHandleLocks(): HandleLocks | null;\n setHandleLocks(next: HandleLocks): void;\n /**\n * The path-edit tool in use, or null for plain anchor editing. Path edit has\n * a set of named tools, one active at a time, each changing what a plain\n * drag does: `'simplifyBrush'` brushes the path simpler. A tool belongs to\n * the open path; with none open, or a different one, there is none.\n */\n getTool(): string | null;\n /** Switch tool, or pass null to go back to plain anchor editing. Ignored\n * with no path open. */\n setTool(id: string | null): void;\n /** Notified when a write changes what the anchor overlay paints. A host\n * that owns the id announces its own changes. */\n subscribe(listener: () => void): () => void;\n}\n\nconst NO_IDS: ReadonlySet<string> = new Set();\nconst NO_EXTENSIONS: readonly PathEditExtension[] = [];\nconst NO_ANCHORS: ReadonlySet<number> = new Set();\n\n/** Whether `id` names a node `scene` holds and `selection` includes, or the path a selected effect hides and is edited\n * through. */\nexport function isSelectedNode(\n scene: Pick<Scene<unknown, string, unknown>, 'get'>,\n selection: { readonly current: readonly string[] },\n id: string,\n): boolean {\n if (scene.get(id as NodeId) == null) return false;\n return selection.current.includes(id)\n || selection.current.some((sel) => effectSourceFor(scene, sel as NodeId, 'path') === id);\n}\n\nexport function createPathEditState(source: PathEditSource = {}): PathEditState {\n let ownId = '';\n let selectedAnchors = NO_ANCHORS;\n let marquee: AnchorMarquee | null = null;\n let handleLocks: HandleLocks | null = null;\n let ownTool: { id: string; on: string } | null = null;\n let idSet = NO_IDS;\n let selectedMark: PathEditMark | null = null;\n let extensions = source.extensions ?? NO_EXTENSIONS;\n const changed = createEmitter();\n const notify = changed.emit;\n\n const next = (id: string | null): string => id ?? '';\n const getEditingId = (): string => {\n const host = source.host?.();\n const id = host ? host.editingId() ?? '' : ownId;\n if (!id) return '';\n if (source.isAllowed && !source.isAllowed()) return '';\n if (source.isSelectedNode && !source.isSelectedNode(id)) return '';\n return id;\n };\n\n return {\n getEditingId,\n getEditingIds() {\n const id = getEditingId();\n if (!id) return NO_IDS;\n if (!idSet.has(id)) idSet = new Set([id]);\n return idSet;\n },\n setEditingId(id) {\n const host = source.host?.();\n if (next(id) !== getEditingId()) ownTool = null;\n if (host) { host.setEditingId(id); return; }\n if (next(id) === ownId) return;\n ownId = next(id);\n notify();\n },\n getSelectedAnchors: () => {\n const host = source.host?.();\n return host?.selectedAnchors && host.setSelectedAnchors ? host.selectedAnchors() : selectedAnchors;\n },\n setSelectedAnchors(next) {\n const host = source.host?.();\n if (host?.selectedAnchors && host.setSelectedAnchors) { host.setSelectedAnchors(next); return; }\n selectedAnchors = next;\n notify();\n },\n getMarquee: () => marquee,\n setMarquee(rect) {\n marquee = rect;\n notify();\n },\n getSelectedMark: () => selectedMark,\n setSelectedMark(mark) {\n if (mark?.owner === selectedMark?.owner && mark?.index === selectedMark?.index) return;\n selectedMark = mark;\n notify();\n },\n getExtensions: () => extensions,\n setExtensions(next) {\n if (next === extensions) return;\n extensions = next;\n notify();\n },\n getHandleLocks: () => handleLocks,\n setHandleLocks(next) {\n handleLocks = { angle: next.angle, length: next.length };\n notify();\n },\n getTool() {\n const on = getEditingId();\n const host = source.host?.();\n if (host?.tool) return on ? host.tool() : null;\n // Dropped on the read that finds its path gone: a host can close the\n // path without telling this state.\n if (ownTool && ownTool.on !== on) ownTool = null;\n return ownTool?.id ?? null;\n },\n setTool(id) {\n const host = source.host?.();\n if (host?.tool && host.setTool) { host.setTool(id); return; }\n const on = getEditingId();\n const tool = id && on ? { id, on } : null;\n if (tool?.id === ownTool?.id && tool?.on === ownTool?.on) return;\n ownTool = tool;\n notify();\n },\n subscribe: changed.subscribe,\n };\n}\n"],"mappings":";;;;;;;;;;AAyFA,IAAM,yBAA8B,IAAI,IAAI;AAC5C,IAAM,gBAA8C,CAAC;AACrD,IAAM,6BAAkC,IAAI,IAAI;;;AAIhD,SAAgB,eACd,OACA,WACA,IACS;CACT,IAAI,MAAM,IAAI,EAAY,KAAK,MAAM,OAAO;CAC5C,OAAO,UAAU,QAAQ,SAAS,EAAE,KAC/B,UAAU,QAAQ,MAAM,QAAQ,gBAAgB,OAAO,KAAe,MAAM,MAAM,EAAE;AAC3F;AAEA,SAAgB,oBAAoB,SAAyB,CAAC,GAAkB;CAC9E,IAAI,QAAQ;CACZ,IAAI,kBAAkB;CACtB,IAAI,UAAgC;CACpC,IAAI,cAAkC;CACtC,IAAI,UAA6C;CACjD,IAAI,QAAQ;CACZ,IAAI,eAAoC;CACxC,IAAI,aAAa,OAAO,cAAc;CACtC,MAAM,UAAU,cAAc;CAC9B,MAAM,SAAS,QAAQ;CAEvB,MAAM,QAAQ,OAA8B,MAAM;CAClD,MAAM,qBAA6B;EACjC,MAAM,OAAO,OAAO,OAAO;EAC3B,MAAM,KAAK,OAAO,KAAK,UAAU,KAAK,KAAK;EAC3C,IAAI,CAAC,IAAI,OAAO;EAChB,IAAI,OAAO,aAAa,CAAC,OAAO,UAAU,GAAG,OAAO;EACpD,IAAI,OAAO,kBAAkB,CAAC,OAAO,eAAe,EAAE,GAAG,OAAO;EAChE,OAAO;CACT;CAEA,OAAO;EACL;EACA,gBAAgB;GACd,MAAM,KAAK,aAAa;GACxB,IAAI,CAAC,IAAI,OAAO;GAChB,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,wBAAQ,IAAI,IAAI,CAAC,EAAE,CAAC;GACxC,OAAO;EACT;EACA,aAAa,IAAI;GACf,MAAM,OAAO,OAAO,OAAO;GAC3B,IAAI,KAAK,EAAE,MAAM,aAAa,GAAG,UAAU;GAC3C,IAAI,MAAM;IAAE,KAAK,aAAa,EAAE;IAAG;GAAQ;GAC3C,IAAI,KAAK,EAAE,MAAM,OAAO;GACxB,QAAQ,KAAK,EAAE;GACf,OAAO;EACT;EACA,0BAA0B;GACxB,MAAM,OAAO,OAAO,OAAO;GAC3B,OAAO,MAAM,mBAAmB,KAAK,qBAAqB,KAAK,gBAAgB,IAAI;EACrF;EACA,mBAAmB,MAAM;GACvB,MAAM,OAAO,OAAO,OAAO;GAC3B,IAAI,MAAM,mBAAmB,KAAK,oBAAoB;IAAE,KAAK,mBAAmB,IAAI;IAAG;GAAQ;GAC/F,kBAAkB;GAClB,OAAO;EACT;EACA,kBAAkB;EAClB,WAAW,MAAM;GACf,UAAU;GACV,OAAO;EACT;EACA,uBAAuB;EACvB,gBAAgB,MAAM;GACpB,IAAI,MAAM,UAAU,cAAc,SAAS,MAAM,UAAU,cAAc,OAAO;GAChF,eAAe;GACf,OAAO;EACT;EACA,qBAAqB;EACrB,cAAc,MAAM;GAClB,IAAI,SAAS,YAAY;GACzB,aAAa;GACb,OAAO;EACT;EACA,sBAAsB;EACtB,eAAe,MAAM;GACnB,cAAc;IAAE,OAAO,KAAK;IAAO,QAAQ,KAAK;GAAO;GACvD,OAAO;EACT;EACA,UAAU;GACR,MAAM,KAAK,aAAa;GACxB,MAAM,OAAO,OAAO,OAAO;GAC3B,IAAI,MAAM,MAAM,OAAO,KAAK,KAAK,KAAK,IAAI;GAG1C,IAAI,WAAW,QAAQ,OAAO,IAAI,UAAU;GAC5C,OAAO,SAAS,MAAM;EACxB;EACA,QAAQ,IAAI;GACV,MAAM,OAAO,OAAO,OAAO;GAC3B,IAAI,MAAM,QAAQ,KAAK,SAAS;IAAE,KAAK,QAAQ,EAAE;IAAG;GAAQ;GAC5D,MAAM,KAAK,aAAa;GACxB,MAAM,OAAO,MAAM,KAAK;IAAE;IAAI;GAAG,IAAI;GACrC,IAAI,MAAM,OAAO,SAAS,MAAM,MAAM,OAAO,SAAS,IAAI;GAC1D,UAAU;GACV,OAAO;EACT;EACA,WAAW,QAAQ;CACrB;AACF"}
1
+ {"version":3,"file":"pathEditState.js","names":[],"sources":["../../../src/domains/paths/pathEditState.ts"],"sourcesContent":["/**\n * Which path is being anchor-edited, which of its anchors are selected, and\n * the anchor marquee in flight: one object every reader shares — the\n * `editAnchors` dep, the anchor overlay, the selection overlay's suppressed\n * set, the chrome-caps rule context, and the select tool's shift-click lock.\n */\nimport { createEmitter } from '@weasel-js/registry';\nimport type { PathEditHost } from 'canvas/SceneCanvas/pathEditHost';\nimport type { HandleLocks } from 'interactions/actions/edit-anchors/params';\nimport type { PathEditExtension, PathEditMark } from './pathEditExtension';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { effectSourceFor } from 'domains/pathEffects/effectSources';\n\n/** A world-coord rect. */\nexport interface AnchorMarquee {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * What decides whether a stored edit target is still one: its node exists and\n * is selected, and the mode permits anchor editing. Each is read at the\n * moment of the question.\n */\nexport interface PathEditSource {\n /** Whether `id` names a node that exists and is selected. Absent means\n * every id does. */\n isSelectedNode?(id: string): boolean;\n /** A host that owns the id; the state then keeps none of its own. */\n host?(): PathEditHost | undefined;\n /**\n * Whether anchor editing is permitted right now — whether the active mode\n * allows the `edits-anchors` capability.\n *\n * **Omit this when no mode registry is wired.** Absent means \"no modality,\n * so nothing revokes edit mode\"; a predicate built from an empty capability\n * set would revoke edit mode instantly.\n */\n isAllowed?(): boolean;\n /** The extensions it starts with; `setExtensions` replaces them. */\n extensions?: readonly PathEditExtension[];\n}\n\nexport interface PathEditState {\n /**\n * The node whose anchors are editable now, or `''`: the stored id, or the\n * host's, while `PathEditSource` still lets it stand. Masked rather than\n * cleared: a host's mode machine can leave path-edit mode without running\n * `exitPathEdit`, and nothing re-renders when it does, so there is no\n * moment to write the id back.\n */\n getEditingId(): string;\n /** `getEditingId()` as the set the selection overlay skips. */\n getEditingIds(): ReadonlySet<string>;\n setEditingId(id: string | null): void;\n /** Selected anchors (flat indices) of the path being edited. */\n getSelectedAnchors(): ReadonlySet<number>;\n setSelectedAnchors(next: ReadonlySet<number>): void;\n /** The in-flight anchor-marquee rect, in the edited path's world. */\n getMarquee(): AnchorMarquee | null;\n setMarquee(rect: AnchorMarquee | null): void;\n /** The extension mark selected in place of anchors, or null. */\n getSelectedMark(): PathEditMark | null;\n setSelectedMark(mark: PathEditMark | null): void;\n /** The canvas's path-edit extensions, in order. */\n getExtensions(): readonly PathEditExtension[];\n /** Replace the extensions; the overlay repaints when they change. */\n setExtensions(next: readonly PathEditExtension[]): void;\n /** The handle locks the `handleLock.*` toggles set; null until one is\n * pressed. Not tied to the edited path, so they outlast a change of it. */\n getHandleLocks(): HandleLocks | null;\n setHandleLocks(next: HandleLocks): void;\n /** Notified when a write changes what the anchor overlay paints. A host\n * that owns the id announces its own changes. */\n subscribe(listener: () => void): () => void;\n}\n\nconst NO_IDS: ReadonlySet<string> = new Set();\nconst NO_EXTENSIONS: readonly PathEditExtension[] = [];\nconst NO_ANCHORS: ReadonlySet<number> = new Set();\n\n/** Whether `id` names a node `scene` holds and `selection` includes, or the path a selected effect hides and is edited\n * through. */\nexport function isSelectedNode(\n scene: Pick<Scene<unknown, string, unknown>, 'get'>,\n selection: { readonly current: readonly string[] },\n id: string,\n): boolean {\n if (scene.get(id as NodeId) == null) return false;\n return selection.current.includes(id)\n || selection.current.some((sel) => effectSourceFor(scene, sel as NodeId, 'path') === id);\n}\n\nexport function createPathEditState(source: PathEditSource = {}): PathEditState {\n let ownId = '';\n let selectedAnchors = NO_ANCHORS;\n let marquee: AnchorMarquee | null = null;\n let handleLocks: HandleLocks | null = null;\n let idSet = NO_IDS;\n let selectedMark: PathEditMark | null = null;\n let extensions = source.extensions ?? NO_EXTENSIONS;\n const changed = createEmitter();\n const notify = changed.emit;\n\n const next = (id: string | null): string => id ?? '';\n const getEditingId = (): string => {\n const host = source.host?.();\n const id = host ? host.editingId() ?? '' : ownId;\n if (!id) return '';\n if (source.isAllowed && !source.isAllowed()) return '';\n if (source.isSelectedNode && !source.isSelectedNode(id)) return '';\n return id;\n };\n\n return {\n getEditingId,\n getEditingIds() {\n const id = getEditingId();\n if (!id) return NO_IDS;\n if (!idSet.has(id)) idSet = new Set([id]);\n return idSet;\n },\n setEditingId(id) {\n const host = source.host?.();\n if (host) { host.setEditingId(id); return; }\n if (next(id) === ownId) return;\n ownId = next(id);\n notify();\n },\n getSelectedAnchors: () => {\n const host = source.host?.();\n return host?.selectedAnchors && host.setSelectedAnchors ? host.selectedAnchors() : selectedAnchors;\n },\n setSelectedAnchors(next) {\n const host = source.host?.();\n if (host?.selectedAnchors && host.setSelectedAnchors) { host.setSelectedAnchors(next); return; }\n selectedAnchors = next;\n notify();\n },\n getMarquee: () => marquee,\n setMarquee(rect) {\n marquee = rect;\n notify();\n },\n getSelectedMark: () => selectedMark,\n setSelectedMark(mark) {\n if (mark?.owner === selectedMark?.owner && mark?.index === selectedMark?.index) return;\n selectedMark = mark;\n notify();\n },\n getExtensions: () => extensions,\n setExtensions(next) {\n if (next === extensions) return;\n extensions = next;\n notify();\n },\n getHandleLocks: () => handleLocks,\n setHandleLocks(next) {\n handleLocks = { angle: next.angle, length: next.length };\n notify();\n },\n subscribe: changed.subscribe,\n };\n}\n"],"mappings":";;;;;;;;;;AA+EA,IAAM,yBAA8B,IAAI,IAAI;AAC5C,IAAM,gBAA8C,CAAC;AACrD,IAAM,6BAAkC,IAAI,IAAI;;;AAIhD,SAAgB,eACd,OACA,WACA,IACS;CACT,IAAI,MAAM,IAAI,EAAY,KAAK,MAAM,OAAO;CAC5C,OAAO,UAAU,QAAQ,SAAS,EAAE,KAC/B,UAAU,QAAQ,MAAM,QAAQ,gBAAgB,OAAO,KAAe,MAAM,MAAM,EAAE;AAC3F;AAEA,SAAgB,oBAAoB,SAAyB,CAAC,GAAkB;CAC9E,IAAI,QAAQ;CACZ,IAAI,kBAAkB;CACtB,IAAI,UAAgC;CACpC,IAAI,cAAkC;CACtC,IAAI,QAAQ;CACZ,IAAI,eAAoC;CACxC,IAAI,aAAa,OAAO,cAAc;CACtC,MAAM,UAAU,cAAc;CAC9B,MAAM,SAAS,QAAQ;CAEvB,MAAM,QAAQ,OAA8B,MAAM;CAClD,MAAM,qBAA6B;EACjC,MAAM,OAAO,OAAO,OAAO;EAC3B,MAAM,KAAK,OAAO,KAAK,UAAU,KAAK,KAAK;EAC3C,IAAI,CAAC,IAAI,OAAO;EAChB,IAAI,OAAO,aAAa,CAAC,OAAO,UAAU,GAAG,OAAO;EACpD,IAAI,OAAO,kBAAkB,CAAC,OAAO,eAAe,EAAE,GAAG,OAAO;EAChE,OAAO;CACT;CAEA,OAAO;EACL;EACA,gBAAgB;GACd,MAAM,KAAK,aAAa;GACxB,IAAI,CAAC,IAAI,OAAO;GAChB,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,wBAAQ,IAAI,IAAI,CAAC,EAAE,CAAC;GACxC,OAAO;EACT;EACA,aAAa,IAAI;GACf,MAAM,OAAO,OAAO,OAAO;GAC3B,IAAI,MAAM;IAAE,KAAK,aAAa,EAAE;IAAG;GAAQ;GAC3C,IAAI,KAAK,EAAE,MAAM,OAAO;GACxB,QAAQ,KAAK,EAAE;GACf,OAAO;EACT;EACA,0BAA0B;GACxB,MAAM,OAAO,OAAO,OAAO;GAC3B,OAAO,MAAM,mBAAmB,KAAK,qBAAqB,KAAK,gBAAgB,IAAI;EACrF;EACA,mBAAmB,MAAM;GACvB,MAAM,OAAO,OAAO,OAAO;GAC3B,IAAI,MAAM,mBAAmB,KAAK,oBAAoB;IAAE,KAAK,mBAAmB,IAAI;IAAG;GAAQ;GAC/F,kBAAkB;GAClB,OAAO;EACT;EACA,kBAAkB;EAClB,WAAW,MAAM;GACf,UAAU;GACV,OAAO;EACT;EACA,uBAAuB;EACvB,gBAAgB,MAAM;GACpB,IAAI,MAAM,UAAU,cAAc,SAAS,MAAM,UAAU,cAAc,OAAO;GAChF,eAAe;GACf,OAAO;EACT;EACA,qBAAqB;EACrB,cAAc,MAAM;GAClB,IAAI,SAAS,YAAY;GACzB,aAAa;GACb,OAAO;EACT;EACA,sBAAsB;EACtB,eAAe,MAAM;GACnB,cAAc;IAAE,OAAO,KAAK;IAAO,QAAQ,KAAK;GAAO;GACvD,OAAO;EACT;EACA,WAAW,QAAQ;CACrB;AACF"}
@@ -60,6 +60,11 @@ function moveProbes(press, ids, reads, box) {
60
60
  role: "origin"
61
61
  });
62
62
  }
63
+ if (reads.anchors) for (const id of ids) reads.anchors(id).forEach((at, i) => out.push({
64
+ id: `anchor:${id}:${i}`,
65
+ at,
66
+ role: "anchor"
67
+ }));
63
68
  if (box) out.push({
64
69
  id: "box.min",
65
70
  at: {
@@ -1 +1 @@
1
- {"version":3,"file":"probes2d.js","names":[],"sources":["../../../src/domains/snap/probes2d.ts"],"sourcesContent":["import { unionBounds } from 'core/geometry/unionBounds';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { SnapBegin } from 'interactions/actions/snapDep';\nimport type { SnapProbeAt } from './types';\nimport { resizeProbes, resizeSubject, type ResizeSubject } from './resizeBox';\nimport type { Point } from '@weasel-js/geom';\n\n/** How a 2D session reads its subject at press. */\nexport interface ProbeReads {\n /** `id`'s world pose now, or undefined when it is gone. */\n pose(id: string): unknown;\n /** The axis-aligned box `id` is drawn with at `pose`, stroke included. */\n drawnBox(id: string, pose: unknown): Bounds;\n /** The point of `pose` the `origin` probe sits at: its `OriginProjection` origin. */\n origin(pose: unknown): Point;\n /** The point of the subject's own geometry nearest `press`, or null. */\n geometryAt?(press: Point, ids: readonly string[]): Point | null;\n}\n\n/** The union drawn box of the subjects, or null when none is left. */\nexport function subjectBox(ids: readonly string[], reads: ProbeReads): Bounds | null {\n const boxes: Bounds[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) boxes.push(reads.drawnBox(id, pose));\n }\n return unionBounds(boxes);\n}\n\n/** What a gesture drags, read once at press. */\nexport interface Subject2d {\n /** A move's subjects' union drawn box. */\n box: Bounds | null;\n /** A resize's box and frame: the one it names, else the subjects' drawn\n * box, axis-aligned. */\n resize: ResizeSubject | null;\n}\n\n/** The subject of `req`, null where the gesture has none or nothing is left. */\nexport function subjectOf(req: SnapBegin, reads: ProbeReads): Subject2d {\n if (req.gesture === 'move') return { box: subjectBox(req.ids ?? [], reads), resize: null };\n if (req.gesture !== 'resize' || !req.handle) return { box: null, resize: null };\n if (req.box) return { box: null, resize: resizeSubject(req.box, req.handle) };\n const bounds = subjectBox(req.ids ?? [], reads);\n return { box: null, resize: bounds ? resizeSubject({ bounds }, req.handle) : null };\n}\n\n/** The probes a gesture snaps, where they are at press. */\nexport function buildProbes(req: SnapBegin, reads: ProbeReads, subject = subjectOf(req, reads)): SnapProbeAt[] {\n const ids = req.ids ?? [];\n switch (req.gesture) {\n case 'point':\n return [{ id: 'pointer', at: req.press, role: 'pointer' }];\n case 'resize':\n return subject.resize ? resizeProbes(subject.resize) : [{ id: 'pointer', at: req.press, role: 'pointer' }];\n case 'move':\n return moveProbes(req.press, ids, reads, subject.box);\n case 'rotate':\n case 'handle':\n return [];\n }\n}\n\nfunction moveProbes(press: Point, ids: readonly string[], reads: ProbeReads, box: Bounds | null): SnapProbeAt[] {\n const out: SnapProbeAt[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) out.push({ id: `origin:${id}`, at: reads.origin(pose), role: 'origin' });\n }\n if (box) {\n out.push(\n { id: 'box.min', at: { x: box.x, y: box.y }, role: 'box' },\n { id: 'box.center', at: { x: box.x + box.width / 2, y: box.y + box.height / 2 }, role: 'box' },\n { id: 'box.max', at: { x: box.x + box.width, y: box.y + box.height }, role: 'box' },\n );\n }\n const geometry = reads.geometryAt?.(press, ids) ?? press;\n out.push({ id: 'geometry', at: { x: geometry.x, y: geometry.y }, role: 'geometry' });\n return out;\n}\n"],"mappings":";;;;;AAoBA,SAAgB,WAAW,KAAwB,OAAkC;CACnF,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,KAAK,EAAE;EAC1B,IAAI,SAAS,QAAW,MAAM,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC;CAC7D;CACA,OAAO,YAAY,KAAK;AAC1B;;AAYA,SAAgB,UAAU,KAAgB,OAA8B;CACtE,IAAI,IAAI,YAAY,QAAQ,OAAO;EAAE,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,KAAK;EAAG,QAAQ;CAAK;CACzF,IAAI,IAAI,YAAY,YAAY,CAAC,IAAI,QAAQ,OAAO;EAAE,KAAK;EAAM,QAAQ;CAAK;CAC9E,IAAI,IAAI,KAAK,OAAO;EAAE,KAAK;EAAM,QAAQ,cAAc,IAAI,KAAK,IAAI,MAAM;CAAE;CAC5E,MAAM,SAAS,WAAW,IAAI,OAAO,CAAC,GAAG,KAAK;CAC9C,OAAO;EAAE,KAAK;EAAM,QAAQ,SAAS,cAAc,EAAE,OAAO,GAAG,IAAI,MAAM,IAAI;CAAK;AACpF;;AAGA,SAAgB,YAAY,KAAgB,OAAmB,UAAU,UAAU,KAAK,KAAK,GAAkB;CAC7G,MAAM,MAAM,IAAI,OAAO,CAAC;CACxB,QAAQ,IAAI,SAAZ;EACE,KAAK,SACH,OAAO,CAAC;GAAE,IAAI;GAAW,IAAI,IAAI;GAAO,MAAM;EAAU,CAAC;EAC3D,KAAK,UACH,OAAO,QAAQ,SAAS,aAAa,QAAQ,MAAM,IAAI,CAAC;GAAE,IAAI;GAAW,IAAI,IAAI;GAAO,MAAM;EAAU,CAAC;EAC3G,KAAK,QACH,OAAO,WAAW,IAAI,OAAO,KAAK,OAAO,QAAQ,GAAG;EACtD,KAAK;EACL,KAAK,UACH,OAAO,CAAC;CACZ;AACF;AAEA,SAAS,WAAW,OAAc,KAAwB,OAAmB,KAAmC;CAC9G,MAAM,MAAqB,CAAC;CAC5B,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,KAAK,EAAE;EAC1B,IAAI,SAAS,QAAW,IAAI,KAAK;GAAE,IAAI,UAAU;GAAM,IAAI,MAAM,OAAO,IAAI;GAAG,MAAM;EAAS,CAAC;CACjG;CACA,IAAI,KACF,IAAI,KACF;EAAE,IAAI;EAAW,IAAI;GAAE,GAAG,IAAI;GAAG,GAAG,IAAI;EAAE;EAAG,MAAM;CAAM,GACzD;EAAE,IAAI;EAAc,IAAI;GAAE,GAAG,IAAI,IAAI,IAAI,QAAQ;GAAG,GAAG,IAAI,IAAI,IAAI,SAAS;EAAE;EAAG,MAAM;CAAM,GAC7F;EAAE,IAAI;EAAW,IAAI;GAAE,GAAG,IAAI,IAAI,IAAI;GAAO,GAAG,IAAI,IAAI,IAAI;EAAO;EAAG,MAAM;CAAM,CACpF;CAEF,MAAM,WAAW,MAAM,aAAa,OAAO,GAAG,KAAK;CACnD,IAAI,KAAK;EAAE,IAAI;EAAY,IAAI;GAAE,GAAG,SAAS;GAAG,GAAG,SAAS;EAAE;EAAG,MAAM;CAAW,CAAC;CACnF,OAAO;AACT"}
1
+ {"version":3,"file":"probes2d.js","names":[],"sources":["../../../src/domains/snap/probes2d.ts"],"sourcesContent":["import { unionBounds } from 'core/geometry/unionBounds';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { SnapBegin } from 'interactions/actions/snapDep';\nimport type { SnapProbeAt } from './types';\nimport { resizeProbes, resizeSubject, type ResizeSubject } from './resizeBox';\nimport type { Point } from '@weasel-js/geom';\n\n/** How a 2D session reads its subject at press. */\nexport interface ProbeReads {\n /** `id`'s world pose now, or undefined when it is gone. */\n pose(id: string): unknown;\n /** The axis-aligned box `id` is drawn with at `pose`, stroke included. */\n drawnBox(id: string, pose: unknown): Bounds;\n /** The point of `pose` the `origin` probe sits at: its `OriginProjection` origin. */\n origin(pose: unknown): Point;\n /** The point of the subject's own geometry nearest `press`, or null. */\n geometryAt?(press: Point, ids: readonly string[]): Point | null;\n /** `id`'s declared snap anchors, in world. */\n anchors?(id: string): readonly Point[];\n}\n\n/** The union drawn box of the subjects, or null when none is left. */\nexport function subjectBox(ids: readonly string[], reads: ProbeReads): Bounds | null {\n const boxes: Bounds[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) boxes.push(reads.drawnBox(id, pose));\n }\n return unionBounds(boxes);\n}\n\n/** What a gesture drags, read once at press. */\nexport interface Subject2d {\n /** A move's subjects' union drawn box. */\n box: Bounds | null;\n /** A resize's box and frame: the one it names, else the subjects' drawn\n * box, axis-aligned. */\n resize: ResizeSubject | null;\n}\n\n/** The subject of `req`, null where the gesture has none or nothing is left. */\nexport function subjectOf(req: SnapBegin, reads: ProbeReads): Subject2d {\n if (req.gesture === 'move') return { box: subjectBox(req.ids ?? [], reads), resize: null };\n if (req.gesture !== 'resize' || !req.handle) return { box: null, resize: null };\n if (req.box) return { box: null, resize: resizeSubject(req.box, req.handle) };\n const bounds = subjectBox(req.ids ?? [], reads);\n return { box: null, resize: bounds ? resizeSubject({ bounds }, req.handle) : null };\n}\n\n/** The probes a gesture snaps, where they are at press. */\nexport function buildProbes(req: SnapBegin, reads: ProbeReads, subject = subjectOf(req, reads)): SnapProbeAt[] {\n const ids = req.ids ?? [];\n switch (req.gesture) {\n case 'point':\n return [{ id: 'pointer', at: req.press, role: 'pointer' }];\n case 'resize':\n return subject.resize ? resizeProbes(subject.resize) : [{ id: 'pointer', at: req.press, role: 'pointer' }];\n case 'move':\n return moveProbes(req.press, ids, reads, subject.box);\n case 'rotate':\n case 'handle':\n return [];\n }\n}\n\nfunction moveProbes(press: Point, ids: readonly string[], reads: ProbeReads, box: Bounds | null): SnapProbeAt[] {\n const out: SnapProbeAt[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) out.push({ id: `origin:${id}`, at: reads.origin(pose), role: 'origin' });\n }\n if (reads.anchors) {\n for (const id of ids) {\n reads.anchors(id).forEach((at, i) => out.push({ id: `anchor:${id}:${i}`, at, role: 'anchor' }));\n }\n }\n if (box) {\n out.push(\n { id: 'box.min', at: { x: box.x, y: box.y }, role: 'box' },\n { id: 'box.center', at: { x: box.x + box.width / 2, y: box.y + box.height / 2 }, role: 'box' },\n { id: 'box.max', at: { x: box.x + box.width, y: box.y + box.height }, role: 'box' },\n );\n }\n const geometry = reads.geometryAt?.(press, ids) ?? press;\n out.push({ id: 'geometry', at: { x: geometry.x, y: geometry.y }, role: 'geometry' });\n return out;\n}\n"],"mappings":";;;;;AAsBA,SAAgB,WAAW,KAAwB,OAAkC;CACnF,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,KAAK,EAAE;EAC1B,IAAI,SAAS,QAAW,MAAM,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC;CAC7D;CACA,OAAO,YAAY,KAAK;AAC1B;;AAYA,SAAgB,UAAU,KAAgB,OAA8B;CACtE,IAAI,IAAI,YAAY,QAAQ,OAAO;EAAE,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,KAAK;EAAG,QAAQ;CAAK;CACzF,IAAI,IAAI,YAAY,YAAY,CAAC,IAAI,QAAQ,OAAO;EAAE,KAAK;EAAM,QAAQ;CAAK;CAC9E,IAAI,IAAI,KAAK,OAAO;EAAE,KAAK;EAAM,QAAQ,cAAc,IAAI,KAAK,IAAI,MAAM;CAAE;CAC5E,MAAM,SAAS,WAAW,IAAI,OAAO,CAAC,GAAG,KAAK;CAC9C,OAAO;EAAE,KAAK;EAAM,QAAQ,SAAS,cAAc,EAAE,OAAO,GAAG,IAAI,MAAM,IAAI;CAAK;AACpF;;AAGA,SAAgB,YAAY,KAAgB,OAAmB,UAAU,UAAU,KAAK,KAAK,GAAkB;CAC7G,MAAM,MAAM,IAAI,OAAO,CAAC;CACxB,QAAQ,IAAI,SAAZ;EACE,KAAK,SACH,OAAO,CAAC;GAAE,IAAI;GAAW,IAAI,IAAI;GAAO,MAAM;EAAU,CAAC;EAC3D,KAAK,UACH,OAAO,QAAQ,SAAS,aAAa,QAAQ,MAAM,IAAI,CAAC;GAAE,IAAI;GAAW,IAAI,IAAI;GAAO,MAAM;EAAU,CAAC;EAC3G,KAAK,QACH,OAAO,WAAW,IAAI,OAAO,KAAK,OAAO,QAAQ,GAAG;EACtD,KAAK;EACL,KAAK,UACH,OAAO,CAAC;CACZ;AACF;AAEA,SAAS,WAAW,OAAc,KAAwB,OAAmB,KAAmC;CAC9G,MAAM,MAAqB,CAAC;CAC5B,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,KAAK,EAAE;EAC1B,IAAI,SAAS,QAAW,IAAI,KAAK;GAAE,IAAI,UAAU;GAAM,IAAI,MAAM,OAAO,IAAI;GAAG,MAAM;EAAS,CAAC;CACjG;CACA,IAAI,MAAM,SACR,KAAK,MAAM,MAAM,KACf,MAAM,QAAQ,EAAE,CAAC,CAAC,SAAS,IAAI,MAAM,IAAI,KAAK;EAAE,IAAI,UAAU,GAAG,GAAG;EAAK;EAAI,MAAM;CAAS,CAAC,CAAC;CAGlG,IAAI,KACF,IAAI,KACF;EAAE,IAAI;EAAW,IAAI;GAAE,GAAG,IAAI;GAAG,GAAG,IAAI;EAAE;EAAG,MAAM;CAAM,GACzD;EAAE,IAAI;EAAc,IAAI;GAAE,GAAG,IAAI,IAAI,IAAI,QAAQ;GAAG,GAAG,IAAI,IAAI,IAAI,SAAS;EAAE;EAAG,MAAM;CAAM,GAC7F;EAAE,IAAI;EAAW,IAAI;GAAE,GAAG,IAAI,IAAI,IAAI;GAAO,GAAG,IAAI,IAAI,IAAI;EAAO;EAAG,MAAM;CAAM,CACpF;CAEF,MAAM,WAAW,MAAM,aAAa,OAAO,GAAG,KAAK;CACnD,IAAI,KAAK;EAAE,IAAI;EAAY,IAAI;GAAE,GAAG,SAAS;GAAG,GAAG,SAAS;EAAE;EAAG,MAAM;CAAW,CAAC;CACnF,OAAO;AACT"}
@@ -9,11 +9,21 @@ import { applyToPoint, invert } from "@weasel-js/geom";
9
9
  function sceneProbeReads(args) {
10
10
  const { scene } = args;
11
11
  const frame = scenePoseFrame(scene);
12
+ const anchors = args.anchors;
12
13
  return {
13
14
  pose: (id) => scene.get(id) === void 0 ? void 0 : frame.world(id),
14
15
  drawnBox: (id, pose) => args.boxes.aabb(id, pose),
15
16
  origin: (pose) => (args.originProjection ?? AUTO_ORIGIN_PROJECTION).getOrigin(pose),
16
- ...args.geometryAt ? { geometryAt: args.geometryAt } : {}
17
+ ...args.geometryAt ? { geometryAt: args.geometryAt } : {},
18
+ ...anchors ? { anchors: worldAnchors(scene, anchors) } : {}
19
+ };
20
+ }
21
+ function worldAnchors(scene, anchors) {
22
+ const frames = sceneSnapFrames(scene);
23
+ return (id) => {
24
+ const local = anchors(id);
25
+ const frame = local?.length ? frames(id) : void 0;
26
+ return local && frame ? local.map((p) => frame.toWorld(p)) : [];
17
27
  };
18
28
  }
19
29
  /** Each node's world rotation, read through its own descriptor the way a
@@ -1 +1 @@
1
- {"version":3,"file":"sceneReads.js","names":[],"sources":["../../../src/domains/snap/sceneReads.ts"],"sourcesContent":["import { applyToPoint, invert, type Point } from '@weasel-js/geom';\nimport type { WorldPoint } from '@weasel-js/routing';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport type { DrawnBoxes } from 'interactions/actions/drawnBoxes';\nimport { nodeDescriptors, poseDescriptorOf } from 'interactions/actions/poseDescriptorDep';\nimport { sceneBoxFrames, scenePoseFrame } from 'interactions/actions/poseFrame';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { AUTO_ORIGIN_PROJECTION, type OriginProjection } from 'interactions/gestures/shared/originProjection';\nimport type { ProbeReads } from './probes2d';\nimport type { SnapFrame } from './types';\n\n/** {@link ProbeReads} over a canvas's scene, read when a session begins.\n * `boxes` is the canvas's `drawnBoxes`, which alignment measures with too. */\nexport function sceneProbeReads(args: {\n scene: Scene<unknown, string, unknown>;\n boxes: DrawnBoxes;\n geometryAt?: (press: WorldPoint, ids: readonly string[]) => WorldPoint | null;\n /** How a pose's origin is read. Default `AUTO_ORIGIN_PROJECTION`. */\n originProjection?: OriginProjection<unknown>;\n}): ProbeReads {\n const { scene } = args;\n const frame = scenePoseFrame(scene);\n return {\n pose: (id) => (scene.get(id as NodeId) === undefined ? undefined : frame.world(id as NodeId)),\n drawnBox: (id, pose) => args.boxes.aabb(id, pose),\n origin: (pose) => (args.originProjection ?? AUTO_ORIGIN_PROJECTION).getOrigin(pose),\n ...(args.geometryAt ? { geometryAt: args.geometryAt } : {}),\n };\n}\n\n/** Each node's world rotation, read through its own descriptor the way a\n * rotate reads its subject; undefined for a node that cannot turn. */\nexport function sceneRotations(\n scene: Scene<unknown, string, unknown>,\n descriptor?: PoseDescriptor<unknown>,\n): (id: string) => number | undefined {\n const frame = scenePoseFrame(scene);\n const descriptorOf = nodeDescriptors(scene, poseDescriptorOf(descriptor));\n return (id) => {\n const node = scene.get(id as NodeId);\n if (!node) return undefined;\n const d = descriptorOf(id);\n if (!d.withRotation || !d.getRotation || d.supportsRotation?.(node.pose) === false) return undefined;\n return d.getRotation(frame.world(id as NodeId));\n };\n}\n\n/** Each node's box frame, as `SnapCollect.frameOf` hands it to a source. */\nexport function sceneSnapFrames(scene: Scene<unknown, string, unknown>): (id: string) => SnapFrame | undefined {\n const boxes = sceneBoxFrames(scene);\n return (id) => {\n const m = boxes(id);\n if (!m) return undefined;\n const back = invert(m);\n const through = (t: typeof m, p: Point): Point => {\n const [x, y] = applyToPoint(t, p.x, p.y);\n return { x, y };\n };\n return { toWorld: (p) => through(m, p), toLocal: (p) => (back ? through(back, p) : null) };\n };\n}\n\n/** `ids` and every node under them: what a gesture moving `ids` carries. */\nexport function withSubtrees(scene: Scene<unknown, string, unknown>, ids: readonly string[]): string[] {\n const out = new Set<string>();\n const walk = (id: string): void => {\n if (out.has(id)) return;\n out.add(id);\n if (scene.get(id as NodeId) !== undefined) for (const c of scene.childrenOf(id as NodeId)) walk(c);\n };\n for (const id of ids) walk(id);\n return [...out];\n}\n"],"mappings":";;;;;;;;AAaA,SAAgB,gBAAgB,MAMjB;CACb,MAAM,EAAE,UAAU;CAClB,MAAM,QAAQ,eAAe,KAAK;CAClC,OAAO;EACL,OAAO,OAAQ,MAAM,IAAI,EAAY,MAAM,SAAY,SAAY,MAAM,MAAM,EAAY;EAC3F,WAAW,IAAI,SAAS,KAAK,MAAM,KAAK,IAAI,IAAI;EAChD,SAAS,UAAU,KAAK,oBAAoB,uBAAA,CAAwB,UAAU,IAAI;EAClF,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;CAC3D;AACF;;;AAIA,SAAgB,eACd,OACA,YACoC;CACpC,MAAM,QAAQ,eAAe,KAAK;CAClC,MAAM,eAAe,gBAAgB,OAAO,iBAAiB,UAAU,CAAC;CACxE,QAAQ,OAAO;EACb,MAAM,OAAO,MAAM,IAAI,EAAY;EACnC,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,IAAI,aAAa,EAAE;EACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC,EAAE,eAAe,EAAE,mBAAmB,KAAK,IAAI,MAAM,OAAO,OAAO;EAC3F,OAAO,EAAE,YAAY,MAAM,MAAM,EAAY,CAAC;CAChD;AACF;;AAGA,SAAgB,gBAAgB,OAA+E;CAC7G,MAAM,QAAQ,eAAe,KAAK;CAClC,QAAQ,OAAO;EACb,MAAM,IAAI,MAAM,EAAE;EAClB,IAAI,CAAC,GAAG,OAAO;EACf,MAAM,OAAO,OAAO,CAAC;EACrB,MAAM,WAAW,GAAa,MAAoB;GAChD,MAAM,CAAC,GAAG,KAAK,aAAa,GAAG,EAAE,GAAG,EAAE,CAAC;GACvC,OAAO;IAAE;IAAG;GAAE;EAChB;EACA,OAAO;GAAE,UAAU,MAAM,QAAQ,GAAG,CAAC;GAAG,UAAU,MAAO,OAAO,QAAQ,MAAM,CAAC,IAAI;EAAM;CAC3F;AACF;;AAGA,SAAgB,aAAa,OAAwC,KAAkC;CACrG,MAAM,sBAAM,IAAI,IAAY;CAC5B,MAAM,QAAQ,OAAqB;EACjC,IAAI,IAAI,IAAI,EAAE,GAAG;EACjB,IAAI,IAAI,EAAE;EACV,IAAI,MAAM,IAAI,EAAY,MAAM,QAAW,KAAK,MAAM,KAAK,MAAM,WAAW,EAAY,GAAG,KAAK,CAAC;CACnG;CACA,KAAK,MAAM,MAAM,KAAK,KAAK,EAAE;CAC7B,OAAO,CAAC,GAAG,GAAG;AAChB"}
1
+ {"version":3,"file":"sceneReads.js","names":[],"sources":["../../../src/domains/snap/sceneReads.ts"],"sourcesContent":["import { applyToPoint, invert, type Point } from '@weasel-js/geom';\nimport type { WorldPoint } from '@weasel-js/routing';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport type { DrawnBoxes } from 'interactions/actions/drawnBoxes';\nimport { nodeDescriptors, poseDescriptorOf } from 'interactions/actions/poseDescriptorDep';\nimport { sceneBoxFrames, scenePoseFrame } from 'interactions/actions/poseFrame';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { AUTO_ORIGIN_PROJECTION, type OriginProjection } from 'interactions/gestures/shared/originProjection';\nimport type { ProbeReads } from './probes2d';\nimport type { SnapAnchors, SnapFrame } from './types';\n\n/** {@link ProbeReads} over a canvas's scene, read when a session begins.\n * `boxes` is the canvas's `drawnBoxes`, which alignment measures with too. */\nexport function sceneProbeReads(args: {\n scene: Scene<unknown, string, unknown>;\n boxes: DrawnBoxes;\n geometryAt?: (press: WorldPoint, ids: readonly string[]) => WorldPoint | null;\n /** How a pose's origin is read. Default `AUTO_ORIGIN_PROJECTION`. */\n originProjection?: OriginProjection<unknown>;\n /** Each node's snap anchors, in its box frame. */\n anchors?: SnapAnchors;\n}): ProbeReads {\n const { scene } = args;\n const frame = scenePoseFrame(scene);\n const anchors = args.anchors;\n return {\n pose: (id) => (scene.get(id as NodeId) === undefined ? undefined : frame.world(id as NodeId)),\n drawnBox: (id, pose) => args.boxes.aabb(id, pose),\n origin: (pose) => (args.originProjection ?? AUTO_ORIGIN_PROJECTION).getOrigin(pose),\n ...(args.geometryAt ? { geometryAt: args.geometryAt } : {}),\n ...(anchors ? { anchors: worldAnchors(scene, anchors) } : {}),\n };\n}\n\nfunction worldAnchors(scene: Scene<unknown, string, unknown>, anchors: SnapAnchors): (id: string) => readonly Point[] {\n const frames = sceneSnapFrames(scene);\n return (id) => {\n const local = anchors(id);\n const frame = local?.length ? frames(id) : undefined;\n return local && frame ? local.map((p) => frame.toWorld(p)) : [];\n };\n}\n\n/** Each node's world rotation, read through its own descriptor the way a\n * rotate reads its subject; undefined for a node that cannot turn. */\nexport function sceneRotations(\n scene: Scene<unknown, string, unknown>,\n descriptor?: PoseDescriptor<unknown>,\n): (id: string) => number | undefined {\n const frame = scenePoseFrame(scene);\n const descriptorOf = nodeDescriptors(scene, poseDescriptorOf(descriptor));\n return (id) => {\n const node = scene.get(id as NodeId);\n if (!node) return undefined;\n const d = descriptorOf(id);\n if (!d.withRotation || !d.getRotation || d.supportsRotation?.(node.pose) === false) return undefined;\n return d.getRotation(frame.world(id as NodeId));\n };\n}\n\n/** Each node's box frame, as `SnapCollect.frameOf` hands it to a source. */\nexport function sceneSnapFrames(scene: Scene<unknown, string, unknown>): (id: string) => SnapFrame | undefined {\n const boxes = sceneBoxFrames(scene);\n return (id) => {\n const m = boxes(id);\n if (!m) return undefined;\n const back = invert(m);\n const through = (t: typeof m, p: Point): Point => {\n const [x, y] = applyToPoint(t, p.x, p.y);\n return { x, y };\n };\n return { toWorld: (p) => through(m, p), toLocal: (p) => (back ? through(back, p) : null) };\n };\n}\n\n/** `ids` and every node under them: what a gesture moving `ids` carries. */\nexport function withSubtrees(scene: Scene<unknown, string, unknown>, ids: readonly string[]): string[] {\n const out = new Set<string>();\n const walk = (id: string): void => {\n if (out.has(id)) return;\n out.add(id);\n if (scene.get(id as NodeId) !== undefined) for (const c of scene.childrenOf(id as NodeId)) walk(c);\n };\n for (const id of ids) walk(id);\n return [...out];\n}\n"],"mappings":";;;;;;;;AAaA,SAAgB,gBAAgB,MAQjB;CACb,MAAM,EAAE,UAAU;CAClB,MAAM,QAAQ,eAAe,KAAK;CAClC,MAAM,UAAU,KAAK;CACrB,OAAO;EACL,OAAO,OAAQ,MAAM,IAAI,EAAY,MAAM,SAAY,SAAY,MAAM,MAAM,EAAY;EAC3F,WAAW,IAAI,SAAS,KAAK,MAAM,KAAK,IAAI,IAAI;EAChD,SAAS,UAAU,KAAK,oBAAoB,uBAAA,CAAwB,UAAU,IAAI;EAClF,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;EACzD,GAAI,UAAU,EAAE,SAAS,aAAa,OAAO,OAAO,EAAE,IAAI,CAAC;CAC7D;AACF;AAEA,SAAS,aAAa,OAAwC,SAAwD;CACpH,MAAM,SAAS,gBAAgB,KAAK;CACpC,QAAQ,OAAO;EACb,MAAM,QAAQ,QAAQ,EAAE;EACxB,MAAM,QAAQ,OAAO,SAAS,OAAO,EAAE,IAAI;EAC3C,OAAO,SAAS,QAAQ,MAAM,KAAK,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC;CAChE;AACF;;;AAIA,SAAgB,eACd,OACA,YACoC;CACpC,MAAM,QAAQ,eAAe,KAAK;CAClC,MAAM,eAAe,gBAAgB,OAAO,iBAAiB,UAAU,CAAC;CACxE,QAAQ,OAAO;EACb,MAAM,OAAO,MAAM,IAAI,EAAY;EACnC,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,IAAI,aAAa,EAAE;EACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC,EAAE,eAAe,EAAE,mBAAmB,KAAK,IAAI,MAAM,OAAO,OAAO;EAC3F,OAAO,EAAE,YAAY,MAAM,MAAM,EAAY,CAAC;CAChD;AACF;;AAGA,SAAgB,gBAAgB,OAA+E;CAC7G,MAAM,QAAQ,eAAe,KAAK;CAClC,QAAQ,OAAO;EACb,MAAM,IAAI,MAAM,EAAE;EAClB,IAAI,CAAC,GAAG,OAAO;EACf,MAAM,OAAO,OAAO,CAAC;EACrB,MAAM,WAAW,GAAa,MAAoB;GAChD,MAAM,CAAC,GAAG,KAAK,aAAa,GAAG,EAAE,GAAG,EAAE,CAAC;GACvC,OAAO;IAAE;IAAG;GAAE;EAChB;EACA,OAAO;GAAE,UAAU,MAAM,QAAQ,GAAG,CAAC;GAAG,UAAU,MAAO,OAAO,QAAQ,MAAM,CAAC,IAAI;EAAM;CAC3F;AACF;;AAGA,SAAgB,aAAa,OAAwC,KAAkC;CACrG,MAAM,sBAAM,IAAI,IAAY;CAC5B,MAAM,QAAQ,OAAqB;EACjC,IAAI,IAAI,IAAI,EAAE,GAAG;EACjB,IAAI,IAAI,EAAE;EACV,IAAI,MAAM,IAAI,EAAY,MAAM,QAAW,KAAK,MAAM,KAAK,MAAM,WAAW,EAAY,GAAG,KAAK,CAAC;CACnG;CACA,KAAK,MAAM,MAAM,KAAK,KAAK,EAAE;CAC7B,OAAO,CAAC,GAAG,GAAG;AAChB"}
@@ -11,7 +11,8 @@ function sceneSession2d(i) {
11
11
  scene: i.scene,
12
12
  boxes: i.boxes,
13
13
  ...i.geometryAt ? { geometryAt: i.geometryAt } : {},
14
- ...i.originProjection ? { originProjection: i.originProjection } : {}
14
+ ...i.originProjection ? { originProjection: i.originProjection } : {},
15
+ ...i.anchors ? { anchors: i.anchors } : {}
15
16
  });
16
17
  const subject = subjectOf(i.req, reads);
17
18
  return createSession2d({
@@ -73,7 +74,8 @@ function createSnapSession2d(opts) {
73
74
  reachPx: opts.reachPx ?? DEFAULT_SNAP_PX,
74
75
  store: opts.store ?? createSnapOutcomeStore(),
75
76
  ...opts.originProjection ? { originProjection: opts.originProjection } : {},
76
- ...opts.poseDescriptor ? { poseDescriptor: opts.poseDescriptor } : {}
77
+ ...opts.poseDescriptor ? { poseDescriptor: opts.poseDescriptor } : {},
78
+ ...opts.anchors ? { anchors: opts.anchors } : {}
77
79
  });
78
80
  }
79
81
 
@@ -1 +1 @@
1
- {"version":3,"file":"sceneSession2d.js","names":[],"sources":["../../../src/domains/snap/sceneSession2d.ts"],"sourcesContent":["import type { ResizeAnchor, WorldPoint } from '@weasel-js/routing';\nimport type { View } from '@weasel-js/geom';\nimport type { DebugSink } from '@weasel-js/routing';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport type { Scene } from 'core/scene/types';\nimport type { PlaneMap } from 'core/viewport/parallax';\nimport type { OriginProjection } from 'interactions/gestures/shared/originProjection';\nimport { drawnBoxes, type DrawnBoxes } from 'interactions/actions/drawnBoxes';\nimport type { SnapBegin, SnapConstrain, SnapGeometry, SnapResizeBox, SnapSession } from 'interactions/actions/snapDep';\nimport { createSnapOutcomeStore, type SnapOutcomeStore } from './outcomeStore';\nimport { buildProbes, subjectOf } from './probes2d';\nimport { sceneProbeReads, sceneRotations, sceneSnapFrames, withSubtrees } from './sceneReads';\nimport { createSession2d, DEFAULT_SNAP_PX } from './session2d';\nimport type { SnapGesture, SnapSource } from './types';\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\n/** What a session over a scene runs on: its subject's probes and boxes come\n * from `scene`, measured through `boxes`. */\nexport interface SceneSessionInputs {\n req: SnapBegin;\n scene: AnyScene;\n boxes: DrawnBoxes;\n sources: readonly SnapSource[];\n view(): View | null;\n reachPx: number;\n store: SnapOutcomeStore;\n geometryAt?: (press: WorldPoint, ids: readonly string[]) => WorldPoint | null;\n /** How a pose's origin is read. Default `AUTO_ORIGIN_PROJECTION`. */\n originProjection?: OriginProjection<unknown>;\n /** How a node's rotation is read. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<unknown>;\n /** Where each frame's candidates are recorded. */\n debug?: DebugSink;\n}\n\n/** A 2D session whose probes are the subject's, read from the scene at press. */\nexport function sceneSession2d(i: SceneSessionInputs): SnapSession {\n const reads = sceneProbeReads({\n scene: i.scene, boxes: i.boxes,\n ...(i.geometryAt ? { geometryAt: i.geometryAt } : {}),\n ...(i.originProjection ? { originProjection: i.originProjection } : {}),\n });\n const subject = subjectOf(i.req, reads);\n return createSession2d({\n req: i.req,\n sources: i.sources,\n probes: buildProbes(i.req, reads, subject),\n box: subject.box,\n resize: subject.resize,\n drawnBox: (id) => {\n const pose = reads.pose(id);\n return pose === undefined ? undefined : reads.drawnBox(id, pose);\n },\n ...(i.req.ids ? { exclude: withSubtrees(i.scene, i.req.ids) } : {}),\n view: i.view,\n reachPx: i.reachPx,\n scene: i.scene,\n rotationOf: sceneRotations(i.scene, i.poseDescriptor),\n frameOf: sceneSnapFrames(i.scene),\n store: i.store,\n ...(i.debug ? { debug: i.debug } : {}),\n });\n}\n\n/** Options for {@link createSnapSession2d}. */\nexport interface SnapSession2dOptions {\n scene: AnyScene;\n /** The camera the reach is measured through. */\n view: View;\n sources: readonly SnapSource[];\n gesture: SnapGesture;\n /** World coords. */\n press: WorldPoint;\n /** The subject, whose geometry offers no targets. */\n ids?: readonly string[];\n /** The container a subject not yet in the scene lands in. */\n parent?: string;\n /** Point gestures: the previous anchor, a line's start. */\n origin?: WorldPoint;\n constrain?: SnapConstrain;\n /** Resize: the sides the drag pins. */\n handle?: ResizeAnchor;\n /** Rotate: the point the angle turns about. */\n pivot?: WorldPoint;\n /** Rotate: the subject's angle at press. */\n theta0?: number;\n /** Angle gestures: the reach as an angle, radians. */\n reachRad?: number;\n /** Resize: the box `handle` belongs to. Default: the subjects' drawn box. */\n box?: SnapResizeBox;\n also?: SnapGeometry;\n /** The reach of a source that sets none, in screen px. Default `DEFAULT_SNAP_PX`. */\n reachPx?: number;\n /** Where the session writes its marks. Default: a store of its own. */\n store?: SnapOutcomeStore;\n /** How poses are read. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<unknown>;\n /** Whether drawn boxes include the stroke. Default `true`. */\n boundsIncludeStroke?: boolean;\n /** How a pose's origin, the point a grid or a guide snaps, is read.\n * Default `AUTO_ORIGIN_PROJECTION`: a rect's `{x, y}`, a path's bounds. */\n originProjection?: OriginProjection<unknown>;\n /** How `view`'s world maps into the parallax plane the gesture's points\n * are in. Default: they are in `view`'s own world. */\n plane?: PlaneMap | null;\n}\n\n/**\n * One gesture's snapping over a scene, for a host without `<SceneCanvas>`:\n * the session a canvas's `snap` dep begins, built from the same probes and\n * drawn boxes. Call `at` with each world point the gesture reaches, and\n * `end` when it lets go.\n */\nexport function createSnapSession2d(opts: SnapSession2dOptions): SnapSession {\n const view = opts.view;\n const req: SnapBegin = {\n gesture: opts.gesture,\n press: opts.press,\n ...(opts.ids ? { ids: opts.ids } : {}),\n ...(opts.parent !== undefined ? { parent: opts.parent } : {}),\n ...(opts.origin ? { origin: opts.origin } : {}),\n ...(opts.constrain ? { constrain: opts.constrain } : {}),\n ...(opts.handle ? { handle: opts.handle } : {}),\n ...(opts.pivot ? { pivot: opts.pivot } : {}),\n ...(opts.theta0 !== undefined ? { theta0: opts.theta0 } : {}),\n ...(opts.reachRad !== undefined ? { reachRad: opts.reachRad } : {}),\n ...(opts.box ? { box: opts.box } : {}),\n ...(opts.also ? { also: opts.also } : {}),\n ...(opts.plane ? { plane: () => opts.plane ?? null } : {}),\n };\n return sceneSession2d({\n req,\n scene: opts.scene,\n boxes: drawnBoxes({\n scene: opts.scene,\n view: { get: () => view, set: () => {} },\n ...(opts.poseDescriptor ? { poseDescriptor: opts.poseDescriptor } : {}),\n ...(opts.boundsIncludeStroke !== undefined ? { boundsIncludeStroke: opts.boundsIncludeStroke } : {}),\n }),\n sources: opts.sources,\n view: () => view,\n reachPx: opts.reachPx ?? DEFAULT_SNAP_PX,\n store: opts.store ?? createSnapOutcomeStore(),\n ...(opts.originProjection ? { originProjection: opts.originProjection } : {}),\n ...(opts.poseDescriptor ? { poseDescriptor: opts.poseDescriptor } : {}),\n });\n}\n"],"mappings":";;;;;;;;AAqCA,SAAgB,eAAe,GAAoC;CACjE,MAAM,QAAQ,gBAAgB;EAC5B,OAAO,EAAE;EAAO,OAAO,EAAE;EACzB,GAAI,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,IAAI,CAAC;EACnD,GAAI,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,IAAI,CAAC;CACvE,CAAC;CACD,MAAM,UAAU,UAAU,EAAE,KAAK,KAAK;CACtC,OAAO,gBAAgB;EACrB,KAAK,EAAE;EACP,SAAS,EAAE;EACX,QAAQ,YAAY,EAAE,KAAK,OAAO,OAAO;EACzC,KAAK,QAAQ;EACb,QAAQ,QAAQ;EAChB,WAAW,OAAO;GAChB,MAAM,OAAO,MAAM,KAAK,EAAE;GAC1B,OAAO,SAAS,SAAY,SAAY,MAAM,SAAS,IAAI,IAAI;EACjE;EACA,GAAI,EAAE,IAAI,MAAM,EAAE,SAAS,aAAa,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,IAAI,CAAC;EACjE,MAAM,EAAE;EACR,SAAS,EAAE;EACX,OAAO,EAAE;EACT,YAAY,eAAe,EAAE,OAAO,EAAE,cAAc;EACpD,SAAS,gBAAgB,EAAE,KAAK;EAChC,OAAO,EAAE;EACT,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;CACtC,CAAC;AACH;;;;;;;AAmDA,SAAgB,oBAAoB,MAAyC;CAC3E,MAAM,OAAO,KAAK;CAgBlB,OAAO,eAAe;EACpB;GAfA,SAAS,KAAK;GACd,OAAO,KAAK;GACZ,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;GACpC,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC3D,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC7C,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;GACtD,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC7C,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;GAC1C,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC3D,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;GACjE,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;GACpC,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;GACvC,GAAI,KAAK,QAAQ,EAAE,aAAa,KAAK,SAAS,KAAK,IAAI,CAAC;EAGxD;EACA,OAAO,KAAK;EACZ,OAAO,WAAW;GAChB,OAAO,KAAK;GACZ,MAAM;IAAE,WAAW;IAAM,WAAW,CAAC;GAAE;GACvC,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;GACrE,GAAI,KAAK,wBAAwB,SAAY,EAAE,qBAAqB,KAAK,oBAAoB,IAAI,CAAC;EACpG,CAAC;EACD,SAAS,KAAK;EACd,YAAY;EACZ,SAAS,KAAK,WAAW;EACzB,OAAO,KAAK,SAAS,uBAAuB;EAC5C,GAAI,KAAK,mBAAmB,EAAE,kBAAkB,KAAK,iBAAiB,IAAI,CAAC;EAC3E,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;CACvE,CAAC;AACH"}
1
+ {"version":3,"file":"sceneSession2d.js","names":[],"sources":["../../../src/domains/snap/sceneSession2d.ts"],"sourcesContent":["import type { ResizeAnchor, WorldPoint } from '@weasel-js/routing';\nimport type { View } from '@weasel-js/geom';\nimport type { DebugSink } from '@weasel-js/routing';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport type { Scene } from 'core/scene/types';\nimport type { PlaneMap } from 'core/viewport/parallax';\nimport type { OriginProjection } from 'interactions/gestures/shared/originProjection';\nimport { drawnBoxes, type DrawnBoxes } from 'interactions/actions/drawnBoxes';\nimport type { SnapBegin, SnapConstrain, SnapGeometry, SnapResizeBox, SnapSession } from 'interactions/actions/snapDep';\nimport { createSnapOutcomeStore, type SnapOutcomeStore } from './outcomeStore';\nimport { buildProbes, subjectOf } from './probes2d';\nimport { sceneProbeReads, sceneRotations, sceneSnapFrames, withSubtrees } from './sceneReads';\nimport { createSession2d, DEFAULT_SNAP_PX } from './session2d';\nimport type { SnapAnchors, SnapGesture, SnapSource } from './types';\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\n/** What a session over a scene runs on: its subject's probes and boxes come\n * from `scene`, measured through `boxes`. */\nexport interface SceneSessionInputs {\n req: SnapBegin;\n scene: AnyScene;\n boxes: DrawnBoxes;\n sources: readonly SnapSource[];\n view(): View | null;\n reachPx: number;\n store: SnapOutcomeStore;\n geometryAt?: (press: WorldPoint, ids: readonly string[]) => WorldPoint | null;\n /** How a pose's origin is read. Default `AUTO_ORIGIN_PROJECTION`. */\n originProjection?: OriginProjection<unknown>;\n /** How a node's rotation is read. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<unknown>;\n /** Each node's snap anchors, in its box frame: `SceneCanvasSnap.anchors`. */\n anchors?: SnapAnchors;\n /** Where each frame's candidates are recorded. */\n debug?: DebugSink;\n}\n\n/** A 2D session whose probes are the subject's, read from the scene at press. */\nexport function sceneSession2d(i: SceneSessionInputs): SnapSession {\n const reads = sceneProbeReads({\n scene: i.scene, boxes: i.boxes,\n ...(i.geometryAt ? { geometryAt: i.geometryAt } : {}),\n ...(i.originProjection ? { originProjection: i.originProjection } : {}),\n ...(i.anchors ? { anchors: i.anchors } : {}),\n });\n const subject = subjectOf(i.req, reads);\n return createSession2d({\n req: i.req,\n sources: i.sources,\n probes: buildProbes(i.req, reads, subject),\n box: subject.box,\n resize: subject.resize,\n drawnBox: (id) => {\n const pose = reads.pose(id);\n return pose === undefined ? undefined : reads.drawnBox(id, pose);\n },\n ...(i.req.ids ? { exclude: withSubtrees(i.scene, i.req.ids) } : {}),\n view: i.view,\n reachPx: i.reachPx,\n scene: i.scene,\n rotationOf: sceneRotations(i.scene, i.poseDescriptor),\n frameOf: sceneSnapFrames(i.scene),\n store: i.store,\n ...(i.debug ? { debug: i.debug } : {}),\n });\n}\n\n/** Options for {@link createSnapSession2d}. */\nexport interface SnapSession2dOptions {\n scene: AnyScene;\n /** The camera the reach is measured through. */\n view: View;\n sources: readonly SnapSource[];\n gesture: SnapGesture;\n /** World coords. */\n press: WorldPoint;\n /** The subject, whose geometry offers no targets. */\n ids?: readonly string[];\n /** The container a subject not yet in the scene lands in. */\n parent?: string;\n /** Point gestures: the previous anchor, a line's start. */\n origin?: WorldPoint;\n constrain?: SnapConstrain;\n /** Resize: the sides the drag pins. */\n handle?: ResizeAnchor;\n /** Rotate: the point the angle turns about. */\n pivot?: WorldPoint;\n /** Rotate: the subject's angle at press. */\n theta0?: number;\n /** Angle gestures: the reach as an angle, radians. */\n reachRad?: number;\n /** Resize: the box `handle` belongs to. Default: the subjects' drawn box. */\n box?: SnapResizeBox;\n also?: SnapGeometry;\n /** The reach of a source that sets none, in screen px. Default `DEFAULT_SNAP_PX`. */\n reachPx?: number;\n /** Where the session writes its marks. Default: a store of its own. */\n store?: SnapOutcomeStore;\n /** How poses are read. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<unknown>;\n /** Whether drawn boxes include the stroke. Default `true`. */\n boundsIncludeStroke?: boolean;\n /** How a pose's origin, the point a grid or a guide snaps, is read.\n * Default `AUTO_ORIGIN_PROJECTION`: a rect's `{x, y}`, a path's bounds. */\n originProjection?: OriginProjection<unknown>;\n /** Points of each node a move snaps, in its own box frame, as\n * `SceneCanvasSnap.anchors`. */\n anchors?: SnapAnchors;\n /** How `view`'s world maps into the parallax plane the gesture's points\n * are in. Default: they are in `view`'s own world. */\n plane?: PlaneMap | null;\n}\n\n/**\n * One gesture's snapping over a scene, for a host without `<SceneCanvas>`:\n * the session a canvas's `snap` dep begins, built from the same probes and\n * drawn boxes. Call `at` with each world point the gesture reaches, and\n * `end` when it lets go.\n */\nexport function createSnapSession2d(opts: SnapSession2dOptions): SnapSession {\n const view = opts.view;\n const req: SnapBegin = {\n gesture: opts.gesture,\n press: opts.press,\n ...(opts.ids ? { ids: opts.ids } : {}),\n ...(opts.parent !== undefined ? { parent: opts.parent } : {}),\n ...(opts.origin ? { origin: opts.origin } : {}),\n ...(opts.constrain ? { constrain: opts.constrain } : {}),\n ...(opts.handle ? { handle: opts.handle } : {}),\n ...(opts.pivot ? { pivot: opts.pivot } : {}),\n ...(opts.theta0 !== undefined ? { theta0: opts.theta0 } : {}),\n ...(opts.reachRad !== undefined ? { reachRad: opts.reachRad } : {}),\n ...(opts.box ? { box: opts.box } : {}),\n ...(opts.also ? { also: opts.also } : {}),\n ...(opts.plane ? { plane: () => opts.plane ?? null } : {}),\n };\n return sceneSession2d({\n req,\n scene: opts.scene,\n boxes: drawnBoxes({\n scene: opts.scene,\n view: { get: () => view, set: () => {} },\n ...(opts.poseDescriptor ? { poseDescriptor: opts.poseDescriptor } : {}),\n ...(opts.boundsIncludeStroke !== undefined ? { boundsIncludeStroke: opts.boundsIncludeStroke } : {}),\n }),\n sources: opts.sources,\n view: () => view,\n reachPx: opts.reachPx ?? DEFAULT_SNAP_PX,\n store: opts.store ?? createSnapOutcomeStore(),\n ...(opts.originProjection ? { originProjection: opts.originProjection } : {}),\n ...(opts.poseDescriptor ? { poseDescriptor: opts.poseDescriptor } : {}),\n ...(opts.anchors ? { anchors: opts.anchors } : {}),\n });\n}\n"],"mappings":";;;;;;;;AAuCA,SAAgB,eAAe,GAAoC;CACjE,MAAM,QAAQ,gBAAgB;EAC5B,OAAO,EAAE;EAAO,OAAO,EAAE;EACzB,GAAI,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,IAAI,CAAC;EACnD,GAAI,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,IAAI,CAAC;EACrE,GAAI,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;CAC5C,CAAC;CACD,MAAM,UAAU,UAAU,EAAE,KAAK,KAAK;CACtC,OAAO,gBAAgB;EACrB,KAAK,EAAE;EACP,SAAS,EAAE;EACX,QAAQ,YAAY,EAAE,KAAK,OAAO,OAAO;EACzC,KAAK,QAAQ;EACb,QAAQ,QAAQ;EAChB,WAAW,OAAO;GAChB,MAAM,OAAO,MAAM,KAAK,EAAE;GAC1B,OAAO,SAAS,SAAY,SAAY,MAAM,SAAS,IAAI,IAAI;EACjE;EACA,GAAI,EAAE,IAAI,MAAM,EAAE,SAAS,aAAa,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,IAAI,CAAC;EACjE,MAAM,EAAE;EACR,SAAS,EAAE;EACX,OAAO,EAAE;EACT,YAAY,eAAe,EAAE,OAAO,EAAE,cAAc;EACpD,SAAS,gBAAgB,EAAE,KAAK;EAChC,OAAO,EAAE;EACT,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;CACtC,CAAC;AACH;;;;;;;AAsDA,SAAgB,oBAAoB,MAAyC;CAC3E,MAAM,OAAO,KAAK;CAgBlB,OAAO,eAAe;EACpB;GAfA,SAAS,KAAK;GACd,OAAO,KAAK;GACZ,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;GACpC,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC3D,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC7C,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;GACtD,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC7C,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;GAC1C,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC3D,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;GACjE,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;GACpC,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;GACvC,GAAI,KAAK,QAAQ,EAAE,aAAa,KAAK,SAAS,KAAK,IAAI,CAAC;EAGxD;EACA,OAAO,KAAK;EACZ,OAAO,WAAW;GAChB,OAAO,KAAK;GACZ,MAAM;IAAE,WAAW;IAAM,WAAW,CAAC;GAAE;GACvC,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;GACrE,GAAI,KAAK,wBAAwB,SAAY,EAAE,qBAAqB,KAAK,oBAAoB,IAAI,CAAC;EACpG,CAAC;EACD,SAAS,KAAK;EACd,YAAY;EACZ,SAAS,KAAK,WAAW;EACzB,OAAO,KAAK,SAAS,uBAAuB;EAC5C,GAAI,KAAK,mBAAmB,EAAE,kBAAkB,KAAK,iBAAiB,IAAI,CAAC;EAC3E,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;EACrE,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;CAClD,CAAC;AACH"}
@@ -1,7 +1,7 @@
1
1
  import { useSceneTextEdit } from "./useSceneTextEdit.js";
2
2
  import { createTextLayer } from "./textLayer.js";
3
- import { textCommand as textCommand$1, textCommandFromPose as textCommandFromPose$1, textCommandFromRuns as textCommandFromRuns$1 } from "@weasel-js/renderer";
3
+ import { textCommand as textCommand$1, textCommandFromPose as textCommandFromPose$1, textCommandFromRuns as textCommandFromRuns$1, textCommandFromRunsInto } from "@weasel-js/renderer";
4
4
  import { TextOutlinesError as TextOutlinesError$1, loadTextOutlines as loadTextOutlines$1, textToPath as textToPath$1, textToPathsByPaint as textToPathsByPaint$1 } from "@weasel-js/text";
5
5
  import { TEXT_EDIT_FONT_HOLD, charOffsetToDomPosition, domPositionToCharOffset, domToRuns, runsToDom, useTextEdit as useTextEdit$1 } from "@weasel-js/text-dom";
6
6
 
7
- export { TEXT_EDIT_FONT_HOLD, TextOutlinesError$1 as TextOutlinesError, charOffsetToDomPosition, createTextLayer, domPositionToCharOffset, domToRuns, loadTextOutlines$1 as loadTextOutlines, runsToDom, textCommand$1 as textCommand, textCommandFromPose$1 as textCommandFromPose, textCommandFromRuns$1 as textCommandFromRuns, textToPath$1 as textToPath, textToPathsByPaint$1 as textToPathsByPaint, useSceneTextEdit, useTextEdit$1 as useTextEdit };
7
+ export { TEXT_EDIT_FONT_HOLD, TextOutlinesError$1 as TextOutlinesError, charOffsetToDomPosition, createTextLayer, domPositionToCharOffset, domToRuns, loadTextOutlines$1 as loadTextOutlines, runsToDom, textCommand$1 as textCommand, textCommandFromPose$1 as textCommandFromPose, textCommandFromRuns$1 as textCommandFromRuns, textCommandFromRunsInto, textToPath$1 as textToPath, textToPathsByPaint$1 as textToPathsByPaint, useSceneTextEdit, useTextEdit$1 as useTextEdit };