@weasel-js/core 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/dist/{DrawCommand-Dl0bXNfS.d.ts → DrawCommand-CJtqqt8H.d.ts} +26 -2
  3. package/dist/{chunk-7V6JEOXE.js → chunk-7F3SDUJ4.js} +7884 -9167
  4. package/dist/chunk-7F3SDUJ4.js.map +1 -0
  5. package/dist/chunk-FSZEXVCR.js +171 -0
  6. package/dist/chunk-FSZEXVCR.js.map +1 -0
  7. package/dist/clone.d.ts +1 -1
  8. package/dist/geometry-D9BDMiQi.d.ts +114 -0
  9. package/dist/{grid-Cf87knjU.d.ts → grid-CaSK9bHV.d.ts} +1 -1
  10. package/dist/index-D31EADQG.d.ts +2847 -0
  11. package/dist/index.css +0 -35
  12. package/dist/index.css.map +1 -1
  13. package/dist/index.d.ts +913 -2546
  14. package/dist/index.js +2 -2
  15. package/dist/insert.d.ts +2 -2
  16. package/dist/move.d.ts +3 -3
  17. package/dist/{options-BPPBWMa7.d.ts → options-DMWeTELe.d.ts} +1 -1
  18. package/dist/{pointSnapToGrid-D7s7QmOF.d.ts → pointSnapToGrid-C3EruUwt.d.ts} +3 -54
  19. package/dist/renderer.css +0 -35
  20. package/dist/renderer.css.map +1 -1
  21. package/dist/renderer.d.ts +5 -4
  22. package/dist/renderer.js +2 -2
  23. package/dist/resize.d.ts +3 -3
  24. package/dist/routing.d.ts +10 -8
  25. package/dist/routing.js +1 -1
  26. package/dist/{types-BjUi2vA-.d.ts → types-Dcaa0tPq.d.ts} +1 -25
  27. package/dist/{registerFont-CP-wCsrz.d.ts → viewToMat3-D4lrBigW.d.ts} +2 -44
  28. package/package.json +6 -5
  29. package/dist/chunk-7V6JEOXE.js.map +0 -1
  30. package/dist/chunk-AM6ARSPN.js +0 -517
  31. package/dist/chunk-AM6ARSPN.js.map +0 -1
  32. package/dist/fitViewToBounds-evGsnR8Q.d.ts +0 -62
  33. package/dist/index-DZBYMsHI.d.ts +0 -1555
  34. package/dist/routing.css +0 -35
  35. package/dist/routing.css.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,41 +1,42 @@
1
1
  import * as React$1 from 'react';
2
- import React__default, { RefObject, MutableRefObject, ReactNode, ReactElement, Ref, MouseEvent as MouseEvent$1 } from 'react';
2
+ import React__default, { RefObject, ReactNode, Ref, MouseEvent as MouseEvent$1, MutableRefObject } from 'react';
3
3
  import { a as ViewTransform, V as View, Z as ZoomBound, b as ZoomFactor } from './view-DSQgxBJB.js';
4
4
  export { s as screenToWorld, v as viewToTransform, w as worldToScreen } from './view-DSQgxBJB.js';
5
- import { a as UnitValue, b as UnitSystem, U as UseMoveOptions, O as OriginProjection } from './grid-Cf87knjU.js';
6
- export { I as IMPERIAL_INCHES, M as METRIC_MM, P as PIXELS, R as RECT_ORIGIN_PROJECTION, c as Unit, f as formatUnit, g as gridSnapStrategy, p as pointToGridCell, r as resolveUnit } from './grid-Cf87knjU.js';
7
- import { R as RenderLayer, D as Dims, S as SelectionApi, A as ActionDeps, P as Point2, G as GestureBinding, T as Tool, O as OngoingHandle, B as BindingOpts, I as Invoker, a as AffordanceHit, b as AnyTool, c as ToolsDispatcher, d as ToolCtx, e as ToolsDispatcherOptions, f as ToolKeybinding, H as HotkeyTrigger, C as ChromeState, U as UseSelectionOptions, g as Affordance, h as AffordanceBinding } from './index-DZBYMsHI.js';
8
- export { i as AffordanceRegion, j as CustomPaintContext, k as DblTapChannel, l as Decision, m as DragChannel, n as ImmediateInvoker, o as InvocationCtx, K as KeyboardChannel, p as OngoingInvoker, q as OngoingOverlay, r as PointerChannel, s as ResolveQuery, t as ResolveResult, u as RouteMatch, v as SelectionExtendKey, w as SelectionMode, x as ToolHit, y as ToolModifiers, z as ToolPresentation, E as ToolSlot, W as WheelChannel, F as createToolsDispatcher, J as drawLayers, L as routing, M as useSelection } from './index-DZBYMsHI.js';
9
- import { a as Stroke, F as FillStyle, D as DrawCommand, S as ShaderProgramHandle, P as PathDrawCommand, f as StyledRun, R as ResolvedTextStyle, g as TextStyle, h as TextVerticalAlign } from './DrawCommand-Dl0bXNfS.js';
10
- export { i as DEFAULT_TEXT_STYLE, G as GradStop, b as GroupDrawCommand, I as ImageDrawCommand, M as Mat3, j as Region, k as ResolvedRun, c as ShaderDrawCommand, d as ShaderUniform, l as StrokeAlign, T as TextDrawCommand, n as alignedStrokeRect, o as fontString, p as markdownToRuns, q as resolveRuns, s as resolveTextStyle, t as runsToMarkdown, u as runsToPlainText, v as toRuns, w as verticalAlignOffset } from './DrawCommand-Dl0bXNfS.js';
11
- import { B as Bounds, F as FitViewToBoundsOptions, V as ViewportDims } from './fitViewToBounds-evGsnR8Q.js';
12
- export { f as fitViewToBounds } from './fitViewToBounds-evGsnR8Q.js';
13
- import { N as NodeId, S as Scene, P as PoseComposition, a as Node$1, b as SerializedScene, R as RectPose, U as UseSceneOptions, c as SceneRegistry, d as RegisteredOp } from './types-Cpb4hii1.js';
14
- export { A as AddLayerSpec, e as AddNodeSpec, C as ContainerNode, I as IDENTITY_POSE_COMPOSITION, L as LayerRecord, f as LeafNode, g as PoseAdapter, h as SerializedNode, i as SystemLayerRecord, j as SystemLayerSpec, k as UserLayerRecord, l as asNodeId, m as composeRectPose, n as composeWorldPose, o as decomposeRectPose, r as rebaseLocalPose, t as translateRectPose, w as worldPoseLookup } from './types-Cpb4hii1.js';
15
- import { M as ModifierState, R as ResizePose, B as BoundsConstraint, P as PointSnapBehavior, c as ResizeAnchor, L as LassoSelectBehavior, b as RotatedPose, e as RotateBehavior, f as AreaSelectBehavior, S as SnapStrategy, a as MoveBehavior, I as InsertBehavior } from './types-BjUi2vA-.js';
16
- export { A as ActionBehavior, g as AreaSelectMoveResult, h as AreaSelectOverlay, i as AreaSelectPose, j as AreaSelectProposed, k as BehaviorMoveResult, l as BehaviorResult, C as CloneBehavior, m as CloneLayer, n as ClonePose, G as GestureContext, o as GroupTransform, p as InsertMoveResult, q as InsertOverlay, r as InsertPoint, s as InsertProposed, t as LassoSelectMoveResult, u as LassoSelectOverlay, v as LassoSelectPose, w as LassoSelectProposed, x as PointSnapContext, d as PointSnapFrame, y as PointSnapResult, z as PointerState, D as ResizeMoveResult, E as ResizeOverlay, F as ResizeProposed, H as RotateMoveResult, J as RotateOverlay, K as RotateProposed } from './types-BjUi2vA-.js';
17
- import { CapabilityTag } from '@weasel-js/modes';
18
- import { IngestItem, InputEvent, GestureSpec } from '@weasel-js/gestures';
5
+ import { a as UnitValue, b as UnitSystem, U as UseMoveOptions, O as OriginProjection } from './grid-CaSK9bHV.js';
6
+ export { I as IMPERIAL_INCHES, M as METRIC_MM, P as PIXELS, R as RECT_ORIGIN_PROJECTION, c as Unit, f as formatUnit, g as gridSnapStrategy, p as pointToGridCell, r as resolveUnit } from './grid-CaSK9bHV.js';
7
+ import { R as RenderLayer, D as Dims, C as Condition, a as Rule, b as RuleCtx, V as VisibilityRules, c as ChromeCtx, d as ChromeId, A as Action, e as DepSchema, S as SliceDep, f as ActionsRegistry, T as Tool, g as AffordanceHit, h as Dispatcher, i as AnyTool, j as ToolCtx, k as ToolKeybinding, H as HotkeyTrigger, l as AffordanceBinding, m as ChromeState, n as SelectionApi, I as InsertExtras, G as GeometryProjection, o as ContentHandlerEntry, p as SvgIngestOptions, U as UseSelectionOptions, q as ActionsProp, r as Affordance, B as BooleansAdapter } from './index-D31EADQG.js';
8
+ export { s as ALWAYS, t as ActionDeps, u as ActionDisabledReason, v as ActionEnabledResult, w as ActionEntry, x as ActionsProvider, y as ActiveToolContextProvider, z as ActiveToolContextProviderIfRoot, E as ActiveToolContextProviderProps, F as ActiveToolContextValue, J as AffordanceRegion, K as AreaSelectDep, L as BindingOpts, M as BindingScope, N as BooleanOp, O as BooleanOpResult, P as BoundGesture, Q as BuildRuleCtxArgs, W as ClipboardIngestCtx, X as CustomPaintContext, Y as DepName, Z as DepRegistry, _ as DepRegistryProvider, $ as DispatcherContext, a0 as DragSample, a1 as EditAnchorsDep, a2 as GestureBinding, a3 as ImmediateInvoker, a4 as IngestCtx, a5 as IngestionDep, a6 as InsertDep, a7 as InvocationCtx, a8 as Invoker, a9 as LassoSelectDep, aa as LayoutDep, ab as MatchResult, ac as NEVER, ad as NodeAtPointDep, ae as OngoingHandle, af as OngoingInvoker, ag as OngoingOverlay, ah as Point2, ai as PointerContextProvider, aj as PointerContextValue, ak as PointerWorldPos, al as ResizePolicy, am as ResolveAllOptions, an as ResolveOnlyResult, ao as ResolvedCandidate, ap as ScopedBinding, aq as SelectionExtendKey, ar as SelectionMode, as as Selector, at as SnapDep, au as SvgUnpacker, av as TextEditDep, aw as ToolModifiers, ax as ToolPresentation, ay as ToolSlot, az as UiOngoingControl, aA as ViewApi, aB as applyBooleanOp, aC as buildRuleCtx, aD as createDispatcher, aE as describeRule, aF as drawLayers, aG as enterTextEditAction, aH as evaluate, aI as evaluateEnabled, aJ as registerContentHandler, aK as routing, aL as sliceAction, aM as specificity, aN as useAction, aO as useActionsRegistry, aP as useActiveToolContext, aQ as useDepRegistry, aR as useDepSource, aS as useOptionalActiveToolContext, aT as useOptionalDepRegistry, aU as usePointerContext, aV as useSelection } from './index-D31EADQG.js';
9
+ import { a as Stroke, F as FillStyle, D as DrawCommand, S as ShaderProgramHandle, P as PathDrawCommand, f as StyledRun, R as ResolvedTextStyle, g as TextStyle, h as TextVerticalAlign } from './DrawCommand-CJtqqt8H.js';
10
+ export { i as DEFAULT_TEXT_STYLE, G as GradStop, b as GroupDrawCommand, I as ImageDrawCommand, M as Mat3, j as Region, k as ResolvedRun, c as ShaderDrawCommand, d as ShaderUniform, l as StrokeAlign, T as TextDrawCommand, n as alignedStrokeRect, o as fontString, p as markdownToRuns, q as resolveRuns, s as resolveTextStyle, t as runsToMarkdown, u as runsToPlainText, v as toRuns, w as verticalAlignOffset } from './DrawCommand-CJtqqt8H.js';
11
+ import { B as Bounds, P as PoseProjection, F as FitViewToBoundsOptions, V as ViewportDims } from './geometry-D9BDMiQi.js';
12
+ export { R as RECT_POSE_DESCRIPTOR, a as ROTATED_POSE_DESCRIPTOR, f as fitViewToBounds } from './geometry-D9BDMiQi.js';
13
+ import { N as NodeId, S as Scene, a as Node$1, b as SerializedScene, R as RectPose, U as UseSceneOptions, c as SceneRegistry, d as RegisteredOp } from './types-Cpb4hii1.js';
14
+ export { A as AddLayerSpec, e as AddNodeSpec, C as ContainerNode, I as IDENTITY_POSE_COMPOSITION, L as LayerRecord, f as LeafNode, g as PoseAdapter, P as PoseComposition, h as SerializedNode, i as SystemLayerRecord, j as SystemLayerSpec, k as UserLayerRecord, l as asNodeId, m as composeRectPose, n as composeWorldPose, o as decomposeRectPose, r as rebaseLocalPose, t as translateRectPose, w as worldPoseLookup } from './types-Cpb4hii1.js';
15
+ import { M as ModifierState, R as ResizePose, B as BoundsConstraint, P as PointSnapBehavior, L as LassoSelectBehavior, a as RotatedPose, e as RotateBehavior, S as SnapStrategy, c as ResizeAnchor, b as MoveBehavior, I as InsertBehavior } from './types-Dcaa0tPq.js';
16
+ export { A as ActionBehavior, f as AreaSelectOverlay, g as BehaviorMoveResult, h as BehaviorResult, C as CloneBehavior, i as CloneLayer, j as ClonePose, G as GestureContext, k as GroupTransform, l as InsertMoveResult, m as InsertOverlay, n as InsertPoint, o as InsertProposed, p as LassoSelectMoveResult, q as LassoSelectOverlay, r as LassoSelectPose, s as LassoSelectProposed, t as PointSnapContext, d as PointSnapFrame, u as PointSnapResult, v as PointerState, w as ResizeMoveResult, x as ResizeOverlay, y as ResizeProposed, z as RotateMoveResult, D as RotateOverlay, E as RotateProposed } from './types-Dcaa0tPq.js';
17
+ import { U as UseResizeOptions } from './pointSnapToGrid-C3EruUwt.js';
18
+ export { C as CORNER_ANCHORS, a as CornerAnchor, e as CornerEdge, b as CornerHandle, c as cornerPoint, d as cornerResizeHandles, h as hitCornerHandle, p as pointSnapToGrid } from './pointSnapToGrid-C3EruUwt.js';
19
+ import { BodyClassification, IngestItem } from '@weasel-js/gestures';
19
20
  export { ClickSpec, DragSpec, DropSpec, GestureSpec, IngestItem, InputEvent, KeyHeldSpec, KeySpec, ModSpec, MultiTouchSpec, PasteSpec, TargetSpec, WheelSpec } from '@weasel-js/gestures';
20
- import * as react_jsx_runtime from 'react/jsx-runtime';
21
- import { P as Path, a as PolygonPath, b as PathFillRule, R as RectPath } from './types-B6MMiodD.js';
22
- export { c as PATH_C, d as PATH_CMD_LENGTHS, e as PATH_L, f as PATH_M, g as PATH_Q, h as PATH_Z } from './types-B6MMiodD.js';
23
- import { Op, History, CreateHistoryOptions } from '@weasel-js/history';
24
- export * from '@weasel-js/history';
25
- export { Op } from '@weasel-js/history';
26
- import { L as LayoutStrategy, I as InsertAdapter, M as MoveAdapter, A as AreaSelectAdapter, R as RotateAdapter, a as LassoHitMode, b as LassoSelectAdapter, c as ResizeAdapter, d as SceneAdapter, e as LayerEnumerableAdapter, C as ClipboardSnapshot, O as OrderedAdapter, f as LayoutSnap } from './types-B_-khFM0.js';
21
+ import { M as MoveAdapter, A as AreaSelectAdapter, R as RotateAdapter, a as LassoHitMode, b as LassoSelectAdapter, c as ResizeAdapter, d as SceneAdapter, L as LayoutStrategy, e as LayerEnumerableAdapter, I as InsertAdapter, C as ClipboardSnapshot, O as OrderedAdapter, f as LayoutSnap } from './types-B_-khFM0.js';
27
22
  export { g as ContainerBounds, D as DropTarget, h as LayoutChild, i as LayoutContainer, j as LayoutDragged, S as SnapTarget } from './types-B_-khFM0.js';
28
- import { P as PoseProjection, U as UseResizeOptions } from './pointSnapToGrid-D7s7QmOF.js';
29
- export { C as CORNER_ANCHORS, a as CornerAnchor, g as CornerEdge, b as CornerHandle, R as RECT_POSE_DESCRIPTOR, c as ROTATED_POSE_DESCRIPTOR, d as cornerPoint, e as cornerResizeHandles, h as hitCornerHandle, p as pointSnapToGrid } from './pointSnapToGrid-D7s7QmOF.js';
30
- import { Mat3 } from '@weasel-js/geom';
31
23
  import { D as DebugSink, a as DebugConfig, b as DebugSnapshot, c as DebugTheme } from './types-BJ8_cyT7.js';
32
24
  export { d as DebugFeature, H as HandleKind, e as HitShape, R as RecordedBounds, f as RecordedHandle, g as RecordedHitbox, h as RecordedLayer, i as RecordedOrigin, j as RecordedSnap } from './types-BJ8_cyT7.js';
33
- import { U as UseInsertOptions } from './options-BPPBWMa7.js';
25
+ import { a as PolygonPath, P as Path, b as PathFillRule, R as RectPath } from './types-B6MMiodD.js';
26
+ export { c as PATH_C, d as PATH_CMD_LENGTHS, e as PATH_L, f as PATH_M, g as PATH_Q, h as PATH_Z } from './types-B6MMiodD.js';
27
+ import * as react_jsx_runtime from 'react/jsx-runtime';
28
+ import { Op, CreateHistoryOptions, History } from '@weasel-js/history';
29
+ export * from '@weasel-js/history';
30
+ export { Op } from '@weasel-js/history';
34
31
  import { T as TextureHandle } from './registerTexture-BzHTLhD9.js';
32
+ import { Mat3 } from '@weasel-js/geom';
35
33
  import { G as Guide } from './types-D2tTKEU0.js';
34
+ export { U as UseInsertOptions } from './options-DMWeTELe.js';
36
35
  export { UseClipboardOpsOptions, UseClipboardOpsReturn, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, buildWeaselClipboardText, embedWeaselMetadataInSvg, extractWeaselClipboardFromSvg, parseWeaselClipboardText, sniffWeaselClipboardText, useClipboardOps } from './clipboard.js';
37
36
  export { cloneByAltDrag } from './clone.js';
38
- export { F as FontVariant, I as ImageMinification, i as isCanvasFont, r as registerCanvasFont, a as registerFont, s as subscribeGlyphReady, u as unregisterCanvasFont, v as viewToMat3 } from './registerFont-CP-wCsrz.js';
37
+ export { I as ImageMinification, v as viewToMat3 } from './viewToMat3-D4lrBigW.js';
38
+ export { FontVariant, isCanvasFont, registerCanvasFont, registerFont, subscribeGlyphReady, unregisterCanvasFont } from '@weasel-js/font';
39
+ import '@weasel-js/modes';
39
40
 
40
41
  /** Round `value` to the nearest multiple of `cellSize`. Returns 0 when the result would be -0. */
41
42
  declare function roundToCell(value: number, cellSize: number): number;
@@ -836,182 +837,6 @@ declare function isEditableTarget(target: EventTarget | null): boolean;
836
837
  */
837
838
  declare function matchesKeyBinding(e: KeyboardEvent, b: KeyBinding): boolean;
838
839
 
839
- /**
840
- * Live state read by rule evaluation. Built once per frame on the consuming
841
- * surface — chrome-caps, the affordance pipeline, the dispatcher's
842
- * eligibility filter — and discarded.
843
- *
844
- * Adding a new field is additive: existing rules don't change, new
845
- * selector atoms can read it.
846
- */
847
- interface RuleCtx {
848
- readonly focused: boolean;
849
- readonly selection: readonly NodeId[];
850
- readonly multiActive: boolean;
851
- readonly modifiers: ModifierState;
852
- readonly action: {
853
- readonly kind: string | null;
854
- readonly id: string | null;
855
- };
856
- readonly hover: NodeId | null;
857
- readonly view: View;
858
- /** Active mode id. `'normal'` when no non-default mode is engaged. */
859
- readonly mode: string;
860
- /** Capability tags allowed by the active mode (the union of
861
- * `ModeDefinition.allows` plus implicit tags). The `capability:`
862
- * selector reads this to determine whether a tag is permitted. */
863
- readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
864
- /** Whether the current selection may be resized. `<SceneCanvas>` folds
865
- * `selectTool.resize.resizable` over the selection (true only when every
866
- * selected node is resizable). Read by the `resizable:` selector to gate
867
- * `selection.resize-handles`. Absent (legacy ctx builders) is treated as
868
- * resizable — back-compat: handles show unless a consumer opts a node out. */
869
- readonly selectionResizable?: boolean;
870
- }
871
- interface BuildRuleCtxArgs {
872
- focused: boolean;
873
- selection: readonly NodeId[];
874
- multiActive: boolean;
875
- modifiers: ModifierState;
876
- action: {
877
- kind: string | null;
878
- id: string | null;
879
- };
880
- hover: NodeId | null;
881
- view: View;
882
- mode: string;
883
- allowedCapabilities: ReadonlySet<CapabilityTag>;
884
- /** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
885
- selectionResizable?: boolean;
886
- }
887
- declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
888
-
889
- /**
890
- * A selector is a conjunction of key/value tests. Multiple keys at the same
891
- * level AND together. Each key maps to a selector primitive in the evaluator.
892
- */
893
- interface Selector {
894
- selection?: {
895
- is?: number;
896
- atLeast?: number;
897
- empty?: boolean;
898
- };
899
- mode?: string | {
900
- not: string;
901
- } | {
902
- in: readonly string[];
903
- };
904
- capability?: CapabilityTag | readonly CapabilityTag[] | {
905
- in: readonly CapabilityTag[];
906
- } | {
907
- not: CapabilityTag;
908
- };
909
- gesturing?: boolean;
910
- actionIs?: string;
911
- modifierHeld?: keyof ModifierState;
912
- focused?: boolean;
913
- hovering?: boolean;
914
- hoveringSelected?: boolean;
915
- zoomAtLeast?: number;
916
- /** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
917
- * (resizable), so `{ resizable: true }` passes for legacy ctx builders
918
- * that don't compute it. */
919
- resizable?: boolean;
920
- }
921
- /**
922
- * Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
923
- * over `Selector` leaves. `when` is the escape hatch — its closure is
924
- * opaque to introspection and should be avoided when a declarative form
925
- * exists. Empty `all` is true; empty `any` is false.
926
- */
927
- type Rule = Selector | {
928
- all: readonly Rule[];
929
- } | {
930
- any: readonly Rule[];
931
- } | {
932
- not: Rule;
933
- } | {
934
- when: (ctx: RuleCtx) => boolean;
935
- };
936
- /** Constant rules. Kept here so they have a single source. */
937
- declare const ALWAYS: Rule;
938
- declare const NEVER: Rule;
939
- declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
940
-
941
- /**
942
- * Live state read by chrome-visibility {@link Condition}s. Backward-compat
943
- * alias for the legacy ChromeCtx shape — kept for consumers that still
944
- * import `ChromeCtx`. Subset of `RuleCtx`: legacy ChromeCtx didn't carry
945
- * mode/capability info. The resolver builds a `RuleCtx` for evaluation;
946
- * surfaces that still operate in `ChromeCtx` shape supply defaults
947
- * (mode='normal', empty allowedCapabilities) at the construction site.
948
- */
949
- interface ChromeCtx {
950
- readonly focused: boolean;
951
- readonly selection: readonly NodeId[];
952
- readonly multiActive: boolean;
953
- readonly modifiers: ModifierState;
954
- readonly action: {
955
- readonly kind: string | null;
956
- readonly id: string | null;
957
- };
958
- readonly hover: NodeId | null;
959
- readonly view: View;
960
- }
961
- /**
962
- * Composable visibility predicate with fluent surface. Carries its underlying
963
- * `Rule` tree at `.rule` so the resolver can introspect / share trees with
964
- * the affordance pipeline and the dispatcher's eligibility filter.
965
- *
966
- * Callable form `cond(ctx)` evaluates the tree against ctx. The fluent
967
- * methods return new Conditions wrapping new trees.
968
- *
969
- * **Chain semantics: strict left-to-right, no precedence.**
970
- * `a.and(b).or(c)` is `(a && b) || c`; `a.or(b).and(c)` is
971
- * `(a || b) && c`. Mix `.and` and `.or` only when you mean
972
- * left-to-right evaluation. For grouped disjunction, name the
973
- * subexpression or use the top-level `or(...)`.
974
- */
975
- interface Condition {
976
- (ctx: RuleCtx): boolean;
977
- readonly rule: Rule;
978
- /** `this && other` */
979
- and(other: Condition | Rule): Condition;
980
- /** `this || other` */
981
- or(other: Condition | Rule): Condition;
982
- /** `this && !other` */
983
- andNot(other: Condition | Rule): Condition;
984
- /** `this || !other` */
985
- orNot(other: Condition | Rule): Condition;
986
- }
987
- /**
988
- * Stable identifier for one user-visible chrome element. The same id
989
- * gates both paint and hit-test — there is no separate
990
- * `affordance.X` / `selection.X` split — so toggling a rule cannot
991
- * leave a visually-present but un-hittable handle (or vice versa).
992
- *
993
- * Naming convention by lifecycle:
994
- *
995
- * - `selection.*` — chrome reflecting committed selection state
996
- * (persists between actions).
997
- * - `action.*` — chrome that only exists during an in-flight
998
- * action (vanishes on commit / cancel).
999
- * - `snap.*` — snapping system chrome (guides, target highlights).
1000
- * - `grid`, `debug.*` — environment chrome.
1001
- *
1002
- * The intersection `(string & {})` keeps the union open so consumers
1003
- * can register their own ids; the kit's built-ins are listed
1004
- * explicitly for autocomplete.
1005
- */
1006
- type ChromeId = 'selection.outline' | 'selection.resize-handles' | 'selection.rotation-handle' | 'action.marquee' | 'action.lasso' | 'action.move-ghosts' | 'action.insert-preview' | 'action.commands' | 'snap.guides' | 'snap.targets' | 'grid' | (string & {});
1007
- /**
1008
- * Consumer override map. Merged on top of the kit's
1009
- * `defaultVisibilityRules`; absent keys fall through to defaults,
1010
- * absent ids fall through to `always`. Entries may be either fluent
1011
- * `Condition` instances OR raw `Rule` trees — the resolver normalizes.
1012
- */
1013
- type VisibilityRules = Partial<Record<ChromeId, Condition | Rule>>;
1014
-
1015
840
  /**
1016
841
  * Promote a Rule tree to a fluent Condition. The function form evaluates
1017
842
  * the tree against a RuleCtx; combinator methods produce new Conditions
@@ -1097,11 +922,16 @@ declare const capabilityNot: (cap: string) => Condition;
1097
922
  * each rule depends on are immediately visible. The fluent atoms compile
1098
923
  * to the same trees; either form is valid in a VisibilityRules entry.
1099
924
  *
1100
- * Mode gating: selection chrome is suppressed in `path-edit` because the
1101
- * path-editing overlay takes over visually. Path-edit-specific chrome
1102
- * ids (`path-edit.*`) are positively gated to that mode. This replaces
1103
- * the old `suppressedIds` set, which only the selection-outline path
1104
- * honored.
925
+ * Gating is written in terms of **capabilities, not mode ids**. A
926
+ * capability rule keeps holding when a new mode is added that permits the
927
+ * same thing; a mode rule has to be found and edited every time. The one
928
+ * exception is the `path-edit.*` chrome below, which is genuinely
929
+ * mode-specific: it's the visual signature of that mode, not a statement
930
+ * about what the user is allowed to do.
931
+ *
932
+ * Chrome that gates on the *absence* of a capability (the selection
933
+ * outline, suppressed while an anchor-editing overlay owns the visuals)
934
+ * uses `capability: { not: … }` for the same reason.
1105
935
  */
1106
936
  declare const defaultVisibilityRules: VisibilityRules;
1107
937
 
@@ -1116,10 +946,9 @@ declare const defaultVisibilityRules: VisibilityRules;
1116
946
  * Predicates are O(1) and run once per chrome id per frame; no
1117
947
  * memoization in v1 — profile before adding any.
1118
948
  *
1119
- * `ChromeCtx` is the legacy shape; surfaces still on it supply
1120
- * `mode='normal'` and an empty `allowedCapabilities` to satisfy
1121
- * `RuleCtx` when calling. We do the same here so callers in
1122
- * legacy shape keep working transparently.
949
+ * `ChromeCtx` is the legacy shape; surfaces still on it get `mode='normal'`
950
+ * and `DEFAULT_ALLOWED_CAPABILITIES` filled in here, so a caller that never
951
+ * opted into modality behaves exactly like one sitting in normal mode.
1123
952
  */
1124
953
  declare function resolveVisibility(consumer: VisibilityRules | undefined, ctx: ChromeCtx | RuleCtx): (id: ChromeId) => boolean;
1125
954
 
@@ -1181,1426 +1010,158 @@ interface UseHoverTrackingArgs {
1181
1010
  }
1182
1011
  declare function useHoverTracking(args: UseHoverTrackingArgs): () => NodeId | null;
1183
1012
 
1184
- /** Boolean op identifiers — five Pathfinder primaries plus Crop. */
1185
- type BooleanOp = 'union' | 'intersect' | 'subtract' | 'exclude' | 'divide' | 'crop';
1186
1013
  /**
1187
- * z-position descriptor for a path node. `parentId` is the direct parent
1188
- * (or `null` for a top-level node); `index` is the position within that
1189
- * parent's child order. Used by the optional `getZOrder` hook below to
1190
- * reposition the result of a boolean op at the topmost source's slot.
1014
+ * `moveAction` first `ongoing`-timing Action descriptor.
1015
+ *
1016
+ * Mirrors the per-frame translate semantics of the `useMove` hook:
1017
+ * - `start`: capture origin poses for all selected nodes; record the
1018
+ * current drag delta in scratch each frame.
1019
+ * - `onMove`: update the in-scratch `currentDelta` only — no scene writes.
1020
+ * This avoids polluting the undo stack with O(N-frames) entries.
1021
+ * - `onEnd('commit')`: emit the final delta as transform ops and route
1022
+ * them through the consumer `applyOps` hook when present, else
1023
+ * `scene.applyBatch(ops, 'Move', adapter)` — either way a single batch
1024
+ * → exactly one undo entry for the whole drag.
1025
+ * - `onEnd('cancel')`: no scene writes — the scene was never mutated during
1026
+ * the drag, so no restoration is needed.
1027
+ *
1028
+ * ## Why no per-frame scene writes
1029
+ *
1030
+ * `Scene.setPose` calls `executeAndLog`, which immediately records an undo
1031
+ * entry on the scene's history engine. Per-frame writes during drag would create
1032
+ * O(frames) history entries — matching `useMove`'s approach of tracking
1033
+ * poses only in React state (overlay) during the drag and committing a
1034
+ * single `createTransformOp` batch at the end.
1035
+ *
1036
+ * The behavior pipeline (snap-to-grid, snap-back-or-delete, snap-to-container,
1037
+ * etc.) via `opts.behaviors` from `BindingOpts` IS wired: `start` builds a
1038
+ * `GestureContext` + scene-backed adapter (`moveGestureAdapter`), `onMove`
1039
+ * folds each behavior's proposed-transform result, and `onEnd` runs a
1040
+ * first-non-undefined-wins reducer (`Op[]` commits via `scene.applyBatch`,
1041
+ * `null` aborts, all-`undefined` falls through to the default translate path).
1042
+ *
1043
+ * ## Pose generics
1044
+ *
1045
+ * `scene` dep is typed `Scene<unknown, string, unknown>` (the erased DepSchema
1046
+ * entry). Poses are read and written as `unknown`; `translatePoseGeneric`
1047
+ * delegates to `RECT_POSE_DESCRIPTOR.translate` which treats any pose as
1048
+ * `{x, y, ...}`. Consumers with non-rect poses should register a custom
1049
+ * action with a typed translatePose.
1191
1050
  */
1192
- /** @internal */
1193
- interface BooleanZOrder {
1194
- parentId: string | null;
1195
- index: number;
1196
- }
1197
- /** Adapter the hook and the pure core both consume. */
1198
- interface BooleansAdapter {
1199
- getSelection(): NodeId[];
1200
- getWorldPath(id: NodeId): Path | undefined;
1201
- compareZ(a: NodeId, b: NodeId): number;
1202
- /**
1203
- * Mint a new node from a boolean-op result `Path`. `producedBy` names the
1204
- * op that synthesized it — adapters that store provenance (e.g. for a
1205
- * layer-panel icon) record it; others ignore the arg.
1206
- */
1207
- createPathNode(path: Path, producedBy: BooleanOp): {
1208
- id: string;
1209
- };
1210
- /**
1211
- * Optional: return the full object for an id, used by the delete ops so
1212
- * their `invert` (an insert) can restore the complete object on undo.
1213
- * If omitted, a `{ id }` stub is captured — undo will reinstate the id
1214
- * but consumers reading other fields (path, fill, etc.) will see them as
1215
- * undefined. Mirrors `DeleteAdapter.getNode`; should be provided whenever
1216
- * undo over boolean ops is expected to be lossless.
1217
- */
1218
- getNode?(id: NodeId): {
1219
- id: string;
1220
- } | undefined | null;
1221
- /**
1222
- * Optional: return the parent + child-index of `id` so the result of a
1223
- * boolean op can be placed in the topmost source's z-slot. Adapters that
1224
- * also expose `getChildren`/`setChildOrder` (the `ReorderAdapter`
1225
- * contract) will have the kit emit a `createMoveToIndexOp` after the
1226
- * inserts. Adapters that omit this method get v1 behavior — the result
1227
- * lands wherever the adapter's plain `insertNode` defaults to.
1228
- */
1229
- getZOrder?(id: NodeId): BooleanZOrder | undefined;
1230
- applyOps?(ops: Op[], label?: string): void;
1231
- setSelection?(ids: NodeId[]): void;
1232
- insertNode?(node: {
1233
- id: string;
1234
- }): void;
1235
- removeNode?(id: string): void;
1236
- }
1237
- /** Outcome reported back to callers (lets the hook surface no-op signals). */
1238
- type BooleanOpResult = {
1239
- kind: 'applied';
1240
- resultIds: string[];
1241
- } | {
1242
- kind: 'noop';
1243
- reason: 'no-paths' | 'too-few-for-subtract' | 'empty-result';
1244
- };
1245
- declare function applyBooleanOp(adapter: BooleansAdapter, op: BooleanOp): BooleanOpResult;
1246
-
1247
- /** Context handed to every content handler for one ingest event. */
1248
- interface IngestCtx {
1249
- /** World-space arrival point (drop / pointed imperative ingest); `null`
1250
- * for paste and point-less calls — handlers pick their own policy
1251
- * (the kit image handler centers on the viewport). */
1252
- point: {
1253
- x: number;
1254
- y: number;
1255
- } | null;
1256
- /** Visible canvas area in world coordinates. */
1257
- viewportWorldRect(): {
1258
- x: number;
1259
- y: number;
1260
- width: number;
1261
- height: number;
1262
- };
1263
- /** The kit insert dep — id/layer/undoable-op supplied; the canonical way
1264
- * for a handler to mint a node (`insert.commit(bounds, { kind, ... })`). */
1265
- insert: InsertDep;
1266
- /** Raw op commit for handlers that build their own ops. */
1267
- applyOps(ops: Op[], label?: string): void;
1268
- scene: Scene<unknown, string, unknown>;
1269
- selection: SelectionApi;
1270
- /** Consumer file→src resolver (SceneCanvas `ingestion.resolveSrc`).
1271
- * When absent, the kit image handler embeds as a `data:` URI. */
1272
- resolveSrc?: (file: File) => Promise<string>;
1273
- /** Kit SVG-handler options (SceneCanvas `ingestion.svg`) — e.g.
1274
- * `{ unpack: unpackSvgFiles }` (from `@weasel-js/svg`) to parse SVG files
1275
- * into scene nodes. */
1276
- svg?: SvgIngestOptions;
1277
- /** Clipboard-paste seam — present when the hosting `SceneCanvas` supplied
1278
- * an adapter with `commitPaste`. `reviver` comes from
1279
- * `SceneCanvasProps.ingestion.clipboard`. Absent ⇒ the kit weasel-JSON
1280
- * handler declines inert (dwarn, nothing ingested) — its matched items
1281
- * were already consumed at match time, so they do NOT fall through;
1282
- * only match-level misses flow on to other handlers. */
1283
- clipboard?: ClipboardIngestCtx;
1284
- /** Set to `true` by the kit weasel-JSON handler when it successfully
1285
- * pastes a payload in this event. The `ctx` object is shared across all
1286
- * handlers in one `runIngest` call, and higher-priority handlers' `handle`
1287
- * bodies run (synchronously) before lower ones — so `kit:svg`'s
1288
- * `text/plain` SVG fallback reads this to decline the SVG flavor of a copy
1289
- * whose canonical weasel-JSON flavor already ingested (avoids a
1290
- * double-paste when both flavors ride one clipboard event). */
1291
- consumedWeaselPayload?: boolean;
1292
- /** Full action-deps bag, for consumer handlers that need more. */
1293
- deps: ActionDeps;
1294
- }
1295
- interface ContentHandlerEntry {
1296
- /** Stable identifier — used for unregistration and debugging
1297
- * (`'kit:image'`, `'app:csv'`). */
1298
- id: string;
1299
- /** MIME glob(s) (`'image/*'`, `'text/csv'`) or an item predicate. */
1300
- match: string | string[] | ((item: IngestItem) => boolean);
1301
- /** Higher runs earlier. Kit defaults register at -100 so any consumer
1302
- * handler (default 0) beats them. */
1303
- priority?: number;
1304
- handle(items: IngestItem[], ctx: IngestCtx): void | Promise<void>;
1305
- }
1306
- /** Register a content handler. Returns a disposer that removes it. */
1307
- declare function registerContentHandler(entry: ContentHandlerEntry): () => void;
1308
1051
 
1309
1052
  /**
1310
1053
  * @experimental
1311
- * PointerContext a tiny ambient context that publishes the world-space
1312
- * position of the canvas pointer, refreshed on every `pointermove` over
1313
- * the canvas. Cleared (set to `null`) on `pointerleave`.
1314
- *
1315
- * Why ref-based and not state-based: cursor moves fire dozens of times per
1316
- * second; routing those through React state would re-render every consumer
1317
- * in the tree. The context exposes a stable `pointerRef` whose `.current`
1318
- * is mutated directly by the publisher, plus a thunk `getDropPoint()` that
1319
- * reads it on demand. Consumers (e.g. `useClipboard`) pull via the thunk
1320
- * inside their callbacks — no subscription, no re-render.
1321
- *
1322
- * `<SceneCanvas>` publishes automatically. `useClipboardOps` consumes when
1323
- * the caller didn't pass an explicit `getDropPoint` option. Other future
1324
- * hit-on-cursor consumers (drop-zone hover, context-menu anchor) can reuse
1325
- * the same context.
1326
- */
1327
-
1328
- /** @experimental World-space pointer position, or `null` when the pointer
1329
- * isn't over the publishing canvas. */
1330
- type PointerWorldPos = {
1331
- worldX: number;
1332
- worldY: number;
1333
- } | null;
1334
- /** @experimental */
1335
- interface PointerContextValue {
1336
- /** Live ref — mutate to publish, read for the latest snapshot. The
1337
- * identity is stable for the lifetime of the provider. */
1338
- readonly pointerRef: MutableRefObject<PointerWorldPos>;
1339
- /** Convenience thunk equivalent to `() => pointerRef.current`. Stable
1340
- * identity for the lifetime of the provider; safe to pass to hooks. */
1341
- readonly getDropPoint: () => PointerWorldPos;
1342
- }
1343
- /**
1344
- * @experimental
1345
- * Wrap the part of the React tree that should share a pointer-position
1346
- * context. Usually placed at the demo / app root, alongside
1347
- * `<ActionsProvider>` and `<SelectionContextProvider>`.
1054
+ * Static descriptor for the `move` Action.
1348
1055
  *
1349
- * Most consumers don't need to mount this directly — `<SceneCanvas>` mounts
1350
- * an internal provider when no parent provider is in scope, so child hooks
1351
- * (`useClipboard` without an explicit `getDropPoint`) read the canvas's
1352
- * tracked pointer for free.
1353
- */
1354
- declare function PointerContextProvider({ children }: {
1355
- children: ReactNode;
1356
- }): ReactNode;
1357
- /** @experimental Read the surrounding pointer-context value, or `null` when
1358
- * no provider is in scope. */
1359
- declare function usePointerContext(): PointerContextValue | null;
1360
-
1361
- interface ActiveToolContextValue {
1362
- active: string;
1363
- hotkeyStack: string[];
1364
- setActive(id: string): void;
1365
- pushHotkey(id: string): void;
1366
- popHotkey(): void;
1367
- }
1368
- interface ActiveToolContextProviderProps {
1369
- children: ReactNode;
1370
- initialActive?: string;
1371
- }
1372
- declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react_jsx_runtime.JSX.Element;
1373
- declare function useActiveToolContext(): ActiveToolContextValue;
1374
- /**
1375
- * Like `useActiveToolContext`, but returns `null` when no
1376
- * `<ActiveToolContextProvider>` is in scope instead of throwing. Used by
1377
- * `useStandardActions` to preserve its silent-no-op contract when no provider
1378
- * is present.
1379
- */
1380
- declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
1381
- /**
1382
- * Conditional `<ActiveToolContextProvider>` wrapper. Mounts a provider only
1383
- * when no parent provider is in scope — otherwise renders children unwrapped
1384
- * so callers (e.g. `<WeaselProvider>`, `<SceneCanvas>`) defer to the host's
1385
- * existing scope. Mirrors `ActionsProviderIfRoot` / `DepRegistryProviderIfRoot`.
1056
+ * Requires dep-schema entries: `selection`, `scene`.
1057
+ *
1058
+ * The invoker is `ongoing` it returns an `OngoingHandle` from `start` that
1059
+ * the dispatcher pumps via `onMove`/`onEnd` for the duration of the drag.
1060
+ *
1061
+ * @see useMove the React hook this descriptor mirrors for the simple case.
1386
1062
  */
1387
- declare function ActiveToolContextProviderIfRoot({ children, }: {
1388
- children: ReactNode;
1389
- }): react_jsx_runtime.JSX.Element;
1063
+ declare const moveAction: Action & {
1064
+ requires: string[];
1065
+ };
1390
1066
 
1391
1067
  /**
1392
- * `enterTextEditAction` — immediate Action descriptor for entering in-place
1393
- * text editing on a selected text node.
1068
+ * `resizeAction` — ongoing Action descriptor for anchor-relative drag resize.
1394
1069
  *
1395
1070
  * ## Status: REAL
1396
1071
  *
1397
- * Fires via `useTextTool.bindings` when the user clicks on a
1398
- * selected text node. Calls `deps.textEdit.startEdit(id)` to activate the
1399
- * contenteditable overlay managed by `useTextEdit` / `useSceneTextEdit`.
1400
- *
1401
- * ## No defaultBinding / defaultBinding
1402
- *
1403
- * This action has no ambient key or gesture binding — it fires ONLY via
1404
- * `useTextTool`'s `Tool.bindings` entry:
1405
- *
1406
- * ```ts
1407
- * bindings: [
1408
- * { spec: { kind: 'click', target: 'selected-body' }, actionId: 'enterTextEdit' },
1409
- * ]
1410
- * ```
1411
- *
1412
- * Keeping it binding-free avoids ambient double-fire and scopes the action to
1413
- * the text tool context where `classifyTarget` is already wired.
1414
- *
1415
- * ## Self-guard: only act on text nodes
1416
- *
1417
- * The `classifyTarget` thunk yields `'selected-body'` for any selected node
1418
- * kind — there's no per-kind filter yet (that's a Phase 14e follow-up). To
1419
- * avoid entering text-edit mode when the text tool happens to have a non-text
1420
- * node selected, the action self-guards via an optional `isTextNode` predicate
1421
- * on `TextEditDep`:
1422
- *
1423
- * - When `isTextNode` is absent: action fires unconditionally (the binding
1424
- * spec is the real gate — consumers should only bind this action from the
1425
- * text tool).
1426
- * - When `isTextNode(id)` returns `false`: action is a no-op for that node.
1072
+ * Performs real anchor-relative resize math for rect-shaped poses. Reads
1073
+ * `ctx.drag.affordance.anchor` to determine which corner is fixed, captures
1074
+ * start poses, applies per-frame bounds remapping, and commits a single
1075
+ * `scene.batch` entry on end.
1427
1076
  *
1428
- * ### Future: per-kind target classification (Phase 14e)
1077
+ * ## Behaviors / point-snap / expandIds / geometry — via `resizePolicy` dep
1429
1078
  *
1430
- * Once `classifyTarget` surfaces node-kind info on body hits, the binding spec
1431
- * can use a `{ kindOf: hit => hit?.kind === 'text' }` predicate to pre-filter
1432
- * at dispatch time, making the `isTextNode` guard redundant. The action can
1433
- * then drop `isTextNode` from `TextEditDep`.
1079
+ * The resize-behaviors-api wired the four behavior options the
1080
+ * legacy `useResize` hook exposed (`behaviors`, `pointSnapBehaviors`,
1081
+ * `expandIds`, `geometry`) through the `resizePolicy` dep entry. When
1082
+ * the dep is registered, this invoker:
1434
1083
  *
1435
- * ## Migration plan for useTextTool
1084
+ * - Calls `expandIds([id])` at start. When the result expands beyond the
1085
+ * starting id, takes the group path (union-AABB origin, per-leaf remap).
1086
+ * - Projects poses through the supplied `geometry: PoseProjection<TPose>`
1087
+ * instead of the rect-shaped `RECT_POSE_DESCRIPTOR` default.
1088
+ * - Runs `behaviors[]` after the raw anchor-math bounds are computed.
1089
+ * Behaviors return `{ pose? }` and rewrite the proposed bounds before
1090
+ * they're projected back into pose space.
1091
+ * - Runs `pointSnap[]` after the bounds→pose projection. The first non-null
1092
+ * `PointSnapResult` wins; this invoker back-solves a pose so the chosen
1093
+ * frame's world point lands on the snap target.
1436
1094
  *
1437
- * When wiring `useTextTool` to `Tool.bindings`:
1095
+ * When the dep is absent the invoker falls back to identity defaults
1096
+ * (no behaviors, no snap, `ids => ids` expansion, `RECT_POSE_DESCRIPTOR`),
1097
+ * which matches the unrotated rect-pose path the invoker already
1098
+ * implemented.
1438
1099
  *
1439
- * 1. Add to `useTextTool`'s `bindings`:
1440
- * ```ts
1441
- * { spec: { kind: 'click', target: 'selected-body' }, actionId: 'enterTextEdit' }
1442
- * ```
1443
- * 2. Register a `textEdit` dep sourced from the `useTextEdit` / `useSceneTextEdit`
1444
- * return value, plus an `isTextNode` predicate that checks `data.kind === 'text'`
1445
- * (or however the consumer identifies text nodes).
1446
- * 3. The existing `hitExisting` gate in `useTextTool`'s click route becomes
1447
- * redundant — remove it in Phase 14e dead-code cleanup.
1100
+ * @see useResize the React hook this descriptor mirrors.
1101
+ * @see src/interactions/actions/resize/geometry.ts — `PoseProjection`.
1448
1102
  */
1449
1103
 
1450
- /**
1451
- * Dep for `enterTextEditAction`.
1452
- *
1453
- * Wrap the return value of `useTextEdit` / `useSceneTextEdit` to source this
1454
- * dep. The `isTextNode` predicate is optional — when absent the action fires
1455
- * unconditionally (the binding spec acts as the gate).
1456
- *
1457
- * @example
1458
- * ```ts
1459
- * const textEdit = useSceneTextEdit({ scene, container });
1460
- * useDepSource('textEdit', () => ({
1461
- * startEdit: textEdit.startEdit,
1462
- * isTextNode: (id) => scene.get(id as NodeId)?.data?.kind === 'text',
1463
- * }));
1464
- * ```
1465
- */
1466
- interface TextEditDep {
1467
- /**
1468
- * Begin editing the node with `id`. Activates the contenteditable overlay
1469
- * managed by `useTextEdit` / `useSceneTextEdit`.
1470
- */
1471
- startEdit(id: string, opts?: {
1472
- caret?: number | 'all';
1473
- }): void;
1474
- /**
1475
- * Optional predicate: returns `true` when the node with `id` is a text node.
1476
- * When absent the action fires on any selected node (binding spec is the gate).
1477
- * When present and returning `false`, the invocation is a no-op.
1478
- */
1479
- isTextNode?(id: string): boolean;
1480
- }
1481
1104
  /**
1482
1105
  * @experimental
1483
- * Static descriptor for the `enterTextEdit` Action.
1484
- *
1485
- * Requires dep-schema entries: `textEdit`, `selection`.
1106
+ * Static descriptor for the `resize` Action.
1486
1107
  *
1487
- * No `defaultBinding` / `defaultBinding` — fires only via `Tool.bindings`.
1488
- * Self-guards via `TextEditDep.isTextNode` when provided.
1108
+ * Requires dep-schema entries: `selection`, `scene`.
1109
+ * Reads optional `resizePolicy` dep when present.
1110
+ * Requires `InvocationCtx.drag.affordance` with a `handle:*` kind.
1489
1111
  */
1490
- declare const enterTextEditAction: Action & {
1112
+ declare const resizeAction: Action & {
1491
1113
  requires: string[];
1492
1114
  };
1493
1115
 
1494
1116
  /**
1495
- * Consumer-supplied commit for the Slice action. `commit` receives the finite
1496
- * slice segment (world coords); the consumer scans the scene, splits crossed
1497
- * paths via `splitPathByLine`, and applies the result as one undoable batch.
1117
+ * `rotateAction` ongoing Action descriptor for pointer-driven rotation.
1118
+ *
1119
+ * ## Status: REAL (unrotated-pivot path)
1120
+ *
1121
+ * Implements the core rotation math from `useRotate` for rect-shaped poses:
1122
+ * - `start`: captures origin poses + AABB centers; computes start pointer
1123
+ * angle around the union center of selected nodes.
1124
+ * - `onMove`: derives pointer angle delta from start, applies to each node's
1125
+ * origin rotation. In union-pivot mode (multi-selection) orbits each item's
1126
+ * center around the union center.
1127
+ * - `onEnd('commit')`: builds one `createTransformOp` per node (from =
1128
+ * pre-mutation origin pose, to = rotated pose) and commits them as a
1129
+ * single batch → one undo entry for the whole drag. Routes through the
1130
+ * optional `applyOps` dep (consumer history) when present, else
1131
+ * `scene.applyBatch` + `defaultCommitAdapter`.
1132
+ * - `onEnd('cancel')`: no scene writes (scene never mutated during drag).
1133
+ *
1134
+ * ## Constraints vs `useRotate`
1135
+ *
1136
+ * - Assumes rect-shaped poses `{ x, y, width, height, rotation? }`. Non-rect
1137
+ * TPose consumers need a custom action with a typed `RotateGeometry`.
1138
+ * - No behavior pipeline (snap, etc.). Behaviors wait for a later phase.
1139
+ * - No overlay rendering — deferred to Phase 7 overlay surface.
1140
+ * - Shift-snap (15° quantum) is NOT wired in this phase — omitted deliberately
1141
+ * to keep the invoker self-contained (would need to read shift from onMove ctx).
1142
+ * TODO: thread shift from InvocationCtx.modifiers.shift into snap logic.
1498
1143
  */
1499
- interface SliceDep {
1500
- commit(a: Point2, b: Point2): void;
1501
- }
1144
+
1502
1145
  /**
1503
1146
  * @experimental
1504
- * Static descriptor for the `slice` Action.
1147
+ * Static descriptor for the `rotate` Action.
1148
+ *
1149
+ * Requires dep-schema entries: `selection`, `scene`.
1150
+ *
1151
+ * Implements the unrotated-pivot rotation path from `useRotate` for rect-
1152
+ * shaped poses. Non-rect or behavior-rich consumers should register a custom
1153
+ * `rotateAction` with a typed `RotateGeometry` dep.
1505
1154
  *
1506
- * Ongoing drag invoker: tracks a slice line from drag start to current
1507
- * pointer, renders a live line overlay while the gesture is in flight,
1508
- * and on commit calls `SliceDep.commit(a, b)`. No-ops gracefully when
1509
- * the `slice` dep is absent.
1155
+ * @see useRotate the React hook this descriptor mirrors for the rect case.
1510
1156
  */
1511
- declare const sliceAction: Action & {
1157
+ declare const rotateAction: Action & {
1512
1158
  requires: string[];
1513
1159
  };
1514
1160
 
1515
- /** Optional consumer seam: given a node and the affine `m` that a pose-transform
1516
- * action applied to the node's POSE, return updated `data` with the node's
1517
- * data-held geometry transformed by `m`, or `null` if this node has no
1518
- * data-held geometry (the kit leaves `data` alone). */
1519
- interface GeometryProjection {
1520
- transform(node: {
1521
- id?: string;
1522
- data: unknown;
1523
- pose: unknown;
1524
- }, m: Mat3): unknown | null;
1525
- }
1526
-
1527
- /** Minimal view API the action layer consumes. May be refined later. */
1528
- interface ViewApi {
1529
- get(): View;
1530
- set(v: View): void;
1531
- /** Optional recenter callback. When wired, `viewportZoomAction`'s
1532
- * `reset` branch (Cmd-0) calls this instead of resetting to identity —
1533
- * letting consumers re-fit the page (or other reference bounds) into
1534
- * the workspace. Receives no args; the consumer reads its own bounds
1535
- * + host dims and dispatches `setView(...)`. */
1536
- recenter?(): void;
1537
- /** Optional canvas-local host dimensions (CSS px). When wired,
1538
- * `viewportZoomAction`'s keyboard branches (Cmd+= / Cmd+-) anchor at the
1539
- * host center instead of the top-left origin. Null when the host isn't
1540
- * measurable (unmounted). */
1541
- hostSize?(): {
1542
- width: number;
1543
- height: number;
1544
- } | null;
1545
- }
1546
1161
  /**
1547
- * Adapter dep for `areaSelectAction`.
1162
+ * `areaSelectAction` ongoing Action descriptor for marquee area selection.
1548
1163
  *
1549
- * Provided by `<SceneCanvas>` / `<StandardActionsRegistrar>` via AABB
1550
- * overlap over scene nodes. Consumers with custom hit-testing override this
1551
- * dep entry in their own registrar.
1552
- */
1553
- /**
1554
- * Topmost-node-at-world-point dep, consumed by `moveAction` for
1555
- * reparent-on-drop and available to any action that needs a single-best
1556
- * pick. Mirrors the same hit-test plumbing `<SceneCanvas>` feeds to the
1557
- * tool dispatcher; consumers with custom hit-testing override here.
1558
- *
1559
- * `exclude` is iterated once per call and treated as a set membership
1560
- * test — the dep walks hits front-to-back and returns the first id not
1561
- * in the exclude set. Pass moving-node roots + their descendants when
1562
- * the caller wants to ignore the nodes it's manipulating.
1563
- */
1564
- type NodeAtPointDep = (point: {
1565
- x: number;
1566
- y: number;
1567
- }, exclude?: Iterable<NodeId>) => NodeId | null;
1568
- interface AreaSelectDep {
1569
- /** Return ids of all scene nodes whose AABB overlaps `bounds`. */
1570
- hitTestArea(bounds: {
1571
- x: number;
1572
- y: number;
1573
- width: number;
1574
- height: number;
1575
- }): NodeId[];
1576
- /** Return the current selection id list. */
1577
- getSelection(): NodeId[];
1578
- /** Replace the current selection. */
1579
- setSelection(ids: NodeId[]): void;
1580
- }
1581
- /**
1582
- * Adapter dep for `editAnchorsAction`.
1583
- *
1584
- * Provides narrow read/write access to the editable polygon for a single
1585
- * node. Consumers register this dep so anchor-edit actions can read/write
1586
- * the polygon WITHOUT knowing whether it lives directly on the node's
1587
- * pose (`pose.kind === 'polygon'`) or on `node.data.path` (the kit's
1588
- * built-in pen-tool default, also WeaselDraw's shape).
1589
- *
1590
- * Note on live previews: in-flight edit state is surfaced through the
1591
- * dispatcher's standard `OngoingHandle.previewIds/previewPose/previewData`
1592
- * triple (not this dep), so chrome and preview-ghost stay in lock-step
1593
- * via one source of truth.
1594
- */
1595
- interface EditAnchorsDep {
1596
- /** Id of the node currently being edited. Empty string means no node is
1597
- * currently in edit mode — the chrome and gesture both opt out. */
1598
- editingId: string;
1599
- /** Enter/exit edit mode for a specific node. Pass `null` (or an empty
1600
- * string) to exit. `enterPathEditAction` and `exitPathEditAction` call
1601
- * this; consumers can call it directly to drive edit mode programmatically. */
1602
- setEditingId(id: string | null): void;
1603
- /** Returns the COMMITTED editable polygon in world coordinates, or
1604
- * null if this node has no editable polygon. Does NOT consult in-
1605
- * flight previews — callers that need live state read the dispatcher's
1606
- * in-flight handles. */
1607
- getEditablePath(id: string): unknown;
1608
- /** Returns where the polygon is stored — `'pose'` when `node.pose`
1609
- * IS the polygon, `'data'` when it lives on `node.data.path` with a
1610
- * rect pose, or `null` when the node has no editable polygon. The
1611
- * action uses this to know which preview-ghost axis to populate
1612
- * (`previewPose` only / `previewData` + `previewPose` for data.path). */
1613
- getStorageKind(id: string): 'pose' | 'data' | null;
1614
- /** Returns the node's raw `pose` and `data` so storage-aware actions
1615
- * can capture origin state at gesture-start and synthesize a matching
1616
- * `previewPose` / `previewData` during `onMove`. Used by
1617
- * `editAnchorsAction` for the data.path branch (rect pose + data
1618
- * carrying extra fields like fill / stroke that must be preserved
1619
- * through the preview). Returns null when the node is gone. */
1620
- getNodeShape(id: string): {
1621
- pose: unknown;
1622
- data: unknown;
1623
- } | null;
1624
- /** Commit `worldPath` as the new value for `id`. Implementation routes
1625
- * to setPose (when pose IS the polygon) or batched setPose+update
1626
- * (when the polygon lives on data.path). Records one history entry
1627
- * labelled `label`. */
1628
- applyEdit(id: string, worldPath: unknown, label: string): void;
1629
- }
1630
- /**
1631
- * Adapter dep for `lassoSelectAction`.
1632
- *
1633
- * Provides polygon-lasso hit-testing + selection read/write.
1634
- * Consumers that don't implement `hitTestLasso` can omit it; the action
1635
- * falls back to a bounding-box AABB test via `hitTestArea`.
1636
- */
1637
- interface LassoSelectDep {
1638
- /**
1639
- * Hit-test against a closed polygon (vertex order CW or CCW; last→first
1640
- * closing edge is implicit). Returns matching node ids.
1641
- * Optional — when absent, `lassoSelectAction` falls back to AABB via
1642
- * `hitTestArea`.
1643
- */
1644
- hitTestLasso?(polygon: ReadonlyArray<{
1645
- x: number;
1646
- y: number;
1647
- }>, mode: 'centers' | 'intersect' | 'enclosed'): string[];
1648
- /** Return ids of nodes whose AABB overlaps the given rect (fallback). */
1649
- hitTestArea(bounds: {
1650
- x: number;
1651
- y: number;
1652
- width: number;
1653
- height: number;
1654
- }): string[];
1655
- /** Return the current selection id list. */
1656
- getSelection(): string[];
1657
- /** Replace the current selection. */
1658
- setSelection(ids: string[]): void;
1659
- }
1660
- /**
1661
- * Options for the kit `image/svg+xml` content handler, threaded from
1662
- * SceneCanvas's `ingestion={{ svg }}` prop.
1663
- */
1664
- interface SvgIngestOptions {
1665
- /** Parse dropped/pasted/picked SVG files into native scene nodes (path /
1666
- * text leaves under containers mirroring the source `<g>` structure)
1667
- * instead of the default single embedded-image node.
1668
- *
1669
- * Pass `unpackSvgFiles` from `@weasel-js/svg`:
1670
- *
1671
- * ```ts
1672
- * import { unpackSvgFiles } from '@weasel-js/svg';
1673
- * <SceneCanvas ingestion={{ svg: { unpack: unpackSvgFiles } }} />
1674
- * ```
1675
- *
1676
- * It is injected rather than flagged on with `true` because the SVG parser
1677
- * lives in `@weasel-js/svg`, which depends on this package — core importing
1678
- * it back would make the two mutually dependent and unpublishable
1679
- * separately. Passing the function keeps the parser out of core's bundle
1680
- * for consumers who never unpack. */
1681
- unpack?: SvgUnpacker;
1682
- }
1683
- /** Parses SVG files and inserts the resulting nodes into `ctx.scene`, as one
1684
- * `applyOps` batch per file. Implemented by `unpackSvgFiles` in
1685
- * `@weasel-js/svg`; see {@link SvgIngestOptions.unpack}. */
1686
- type SvgUnpacker = (files: File[], ctx: IngestCtx) => Promise<void>;
1687
- /**
1688
- * Clipboard-paste seam consumed by the kit weasel-JSON content handler
1689
- * (`IngestCtx.clipboard`). Built by `<SceneCanvas>` from its own synthesized
1690
- * adapter + the `ingestion.clipboard` prop; absent when the consumer set
1691
- * `ingestion.clipboard.enabled === false` or the adapter lacks `commitPaste`.
1692
- * Absence makes the handler decline inert (dwarn, nothing ingested) — its
1693
- * matched items were already consumed at match time and do not fall through
1694
- * to other handlers.
1695
- */
1696
- interface ClipboardIngestCtx {
1697
- /** The hosting canvas's adapter — `commitPaste` materializes the pasted
1698
- * nodes (fresh ids, offset applied); insertion still goes through ops. */
1699
- adapter: InsertAdapter<{
1700
- id: string;
1701
- }>;
1702
- /** JSON reviver for the weasel wire payload (typed arrays etc.) — from
1703
- * `SceneCanvasProps.ingestion.clipboard.reviver`. */
1704
- reviver?: (key: string, value: unknown) => unknown;
1705
- }
1706
- /**
1707
- * Dep for the `ingest` action (external-content ingestion).
1708
- * Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>` via
1709
- * `useIngestionDepSource` — canvas rect + current view.
1710
- */
1711
- interface IngestionDep {
1712
- /** Visible canvas area in world coordinates. */
1713
- viewportWorldRect(): {
1714
- x: number;
1715
- y: number;
1716
- width: number;
1717
- height: number;
1718
- };
1719
- /** Consumer file→src resolver (from SceneCanvas's `ingestion` prop).
1720
- * Live accessor — read it at use time. Destructuring (or copying the
1721
- * property early) snapshots the current value and won't track later
1722
- * prop changes across an `await`. */
1723
- resolveSrc?: (file: File) => Promise<string>;
1724
- /** Kit SVG-handler options (from SceneCanvas's `ingestion` prop).
1725
- * Live accessor, same caveat as `resolveSrc`. */
1726
- svg?: SvgIngestOptions;
1727
- /** Clipboard-paste seam for the kit weasel-JSON handler.
1728
- * Live accessor, same caveat as `resolveSrc`. */
1729
- clipboard?: ClipboardIngestCtx;
1730
- }
1731
- /**
1732
- * Per-kind extra geometry passed to `InsertDep.commit`.
1733
- *
1734
- * Built-in tools populate a typed variant so the kit's default factory can
1735
- * render the true tool params (line endpoints, polygon side count, star
1736
- * geometry, pencil sample list). Consumer-defined tools may pass any
1737
- * `{ kind: string; ... }` payload; the kit's factory falls back to AABB
1738
- * inscription for unknown kinds.
1739
- *
1740
- * `bounds` is still passed alongside as a useful AABB pose hint — factories
1741
- * may use it as the node's pose even when richer geometry is available.
1742
- */
1743
- type InsertExtras = {
1744
- kind: 'rect';
1745
- } | {
1746
- kind: 'ellipse';
1747
- } | {
1748
- kind: 'line';
1749
- a: {
1750
- x: number;
1751
- y: number;
1752
- };
1753
- b: {
1754
- x: number;
1755
- y: number;
1756
- };
1757
- } | {
1758
- kind: 'polygon';
1759
- sides: number;
1760
- rotation: number;
1761
- center?: {
1762
- x: number;
1763
- y: number;
1764
- };
1765
- radius?: number;
1766
- } | {
1767
- kind: 'star';
1768
- points: number;
1769
- innerRadiusRatio: number;
1770
- rotation: number;
1771
- center?: {
1772
- x: number;
1773
- y: number;
1774
- };
1775
- outerRadius?: number;
1776
- } | {
1777
- kind: 'pencil';
1778
- samples: ReadonlyArray<{
1779
- x: number;
1780
- y: number;
1781
- }>;
1782
- } | {
1783
- kind: 'text';
1784
- text?: string;
1785
- } | {
1786
- kind: string;
1787
- [extra: string]: unknown;
1788
- };
1789
- /**
1790
- * Adapter dep for `insertAction`.
1791
- *
1792
- * Provided by `<SceneCanvas>` / `<StandardActionsRegistrar>`. The `extras`
1793
- * carry the active tool's kind + per-kind geometry. Callers
1794
- * that need typed data must supply a richer `insert` dep.
1795
- */
1796
- interface InsertDep {
1797
- /**
1798
- * Materialise a new node from the given drag-rect bounds and typed
1799
- * per-kind extras. Returns the new node's id, or `null` if the consumer
1800
- * rejected the insert (e.g. sub-threshold bounds, unknown kind).
1801
- */
1802
- commit(bounds: {
1803
- x: number;
1804
- y: number;
1805
- width: number;
1806
- height: number;
1807
- }, extras: InsertExtras): NodeId | null;
1808
- }
1809
- /**
1810
- * Adapter dep for `resizeAction`.
1811
- *
1812
- * Carries the four behavior-shaping options the legacy `useResize` hook
1813
- * exposed through `UseResizeOptions`: bounds-frame behaviors (e.g.
1814
- * `lockAspectWithModifier`), world-space anchor-point snap behaviors (e.g.
1815
- * `pointSnapToGrid`), group-expansion (`expandIds`), and pose↔bounds
1816
- * projection (`geometry`).
1817
- *
1818
- * Optional in `DepSchema`: when absent, `resizeAction` falls back to
1819
- * identity defaults (no behaviors, identity expandIds, `RECT_POSE_DESCRIPTOR`
1820
- * geometry). Consumers wire the dep via `useDepSource('resizePolicy', ...)`
1821
- * from any descendant of `<DepRegistryProvider>` / `<SceneCanvas>`.
1822
- *
1823
- * The generic is erased to `unknown` at the schema entry; consumers cast at
1824
- * the call site (mirrors the `scene` entry's convention).
1825
- */
1826
- interface ResizePolicy<TPose> {
1827
- /** Bounds-frame constraints. Constrained to `TPose extends ResizePose` since
1828
- * constraints read/write `{x,y,width,height}`. For non-rect TPose pass `[]`. */
1829
- constraints: TPose extends ResizePose ? BoundsConstraint<TPose>[] : never[];
1830
- /** World-space anchor-point snap behaviors. Same TPose constraint as
1831
- * `constraints`. */
1832
- pointSnap: TPose extends ResizePose ? PointSnapBehavior<TPose>[] : never[];
1833
- /** Group-expansion at gesture start. Identity (`ids => ids`) when group
1834
- * resize isn't wanted. */
1835
- expandIds: (ids: string[]) => string[];
1836
- /** Projection from `TPose` to bounds and back. Use `RECT_POSE_DESCRIPTOR`
1837
- * for plain rect poses. */
1838
- projection: PoseProjection<TPose>;
1839
- }
1840
- /**
1841
- * Layout-strategy lookup by container id, consumed by `moveAction` to run
1842
- * the drag-time reflow pass. Sourced by `<SceneCanvas>` from its `layouts`
1843
- * prop. Optional: `getLayout` returns null for any container when no layout
1844
- * is configured, so the reflow pass is a no-op then.
1845
- */
1846
- interface LayoutDep {
1847
- getLayout(containerId: string): LayoutStrategy<unknown> | null;
1848
- }
1849
- interface DepSchema {
1850
- /** Kit selection state — ids of currently selected nodes. */
1851
- selection: SelectionApi;
1852
- /** Current viewport — camera position + scale. */
1853
- view: ViewApi;
1854
- /**
1855
- * Scene tree — structural reads + undoable mutations.
1856
- *
1857
- * The entry uses the fully-erased form `Scene<unknown, string, unknown>`
1858
- * because `DepSchema` must be concrete. Actions that need a typed scene
1859
- * should cast: `deps.scene as Scene<MyData, MyLayer, MyPose>`.
1860
- */
1861
- scene: Scene<unknown, string, unknown>;
1862
- /** Undo/redo history bound to the current scene. */
1863
- history: History;
1864
- /**
1865
- * Canvas pointer position in world space.
1866
- *
1867
- * Exposes `pointerRef` (mutable live ref) and `getDropPoint()` thunk.
1868
- * Marked `@experimental` in the source.
1869
- */
1870
- pointer: PointerContextValue;
1871
- /** Currently active tool id + hotkey-hold stack. */
1872
- activeTool: ActiveToolContextValue;
1873
- /**
1874
- * Area-select dep — AABB hit-test + selection read/write.
1875
- *
1876
- * Sourced from `<SceneCanvas>` via AABB overlap over all scene
1877
- * nodes. Override per-consumer for custom hit-testing (e.g. contain-mode,
1878
- * lock-aware filtering).
1879
- */
1880
- areaSelect: AreaSelectDep;
1881
- /**
1882
- * Topmost node at a world-space point. Sourced by `<SceneCanvas>` from
1883
- * the same picker that feeds the tool dispatcher's `getNodeAtPoint`.
1884
- * Optional: actions that read this (e.g. `moveAction` reparent-on-drop)
1885
- * fall back to a no-op when the dep isn't registered.
1886
- */
1887
- nodeAtPoint?: NodeAtPointDep;
1888
- /**
1889
- * Insert dep — node factory for drag-to-insert.
1890
- *
1891
- * Sourced from `<SceneCanvas>`. The `kind` param comes from
1892
- * the active binding's `opts.params.kind`. Override per-consumer to
1893
- * provide a typed node factory (e.g. with custom data payloads).
1894
- */
1895
- insert: InsertDep;
1896
- /**
1897
- * Lasso-select dep — polygon hit-test + selection read/write.
1898
- *
1899
- * Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>`.
1900
- * Falls back to AABB hit-test when `hitTestLasso` is absent.
1901
- */
1902
- lassoSelect: LassoSelectDep;
1903
- /**
1904
- * Edit-anchors dep — narrow read/write of one polygon's path pose.
1905
- *
1906
- * Sourced from consumer. Wraps `getPose`/`setPose`/`applyOps`
1907
- * for the currently-being-edited polygon node.
1908
- *
1909
- * The `editAnchorsAction` requires this dep to be registered when anchor
1910
- * editing is active. If absent, `start` returns an empty handle (no-op).
1911
- */
1912
- editAnchors: EditAnchorsDep;
1913
- /**
1914
- * Text-edit dep — activates the in-place text editing overlay.
1915
- *
1916
- * Sourced from consumer via `useTextEdit` / `useSceneTextEdit`.
1917
- * The `enterTextEditAction` requires this dep to be registered by the text
1918
- * tool when text editing is available.
1919
- *
1920
- * The optional `isTextNode` predicate guards against entering edit mode on
1921
- * non-text nodes when no per-kind binding filter is available (Phase 14e
1922
- * follow-up will add per-kind classification to `classifyTarget`).
1923
- */
1924
- textEdit: TextEditDep;
1925
- /**
1926
- * Resize-policy dep — bounds constraints, point-snap behaviors,
1927
- * group expansion, and pose↔bounds projection for `resizeAction`.
1928
- *
1929
- * Optional: when omitted, `resizeAction` falls back to identity defaults
1930
- * (no constraints, no snap, identity expandIds, `RECT_POSE_DESCRIPTOR`).
1931
- * Consumers wire via `useDepSource('resizePolicy', ...)` or the
1932
- * `useResizePolicy` helper.
1933
- */
1934
- resizePolicy?: ResizePolicy<unknown>;
1935
- /**
1936
- * Booleans adapter — read selection ids, fetch world-space `Path`s,
1937
- * compare z-order, and mint result nodes for Pathfinder ops.
1938
- *
1939
- * Consumers wire via `useBooleansAdapter(adapter)` (a thin wrapper
1940
- * around `useDepSource('booleansAdapter', ...)`). The descriptor's
1941
- * `enabled` predicate reads `deps.selection` for the count check; the
1942
- * invoker reads `deps.booleansAdapter` to execute the op.
1943
- */
1944
- booleansAdapter?: BooleansAdapter;
1945
- /**
1946
- * Gesture dispatcher control surface — exposes `cancelAll(reason)` so
1947
- * actions that need to abort an in-flight handle (Escape cancels a
1948
- * drag, etc.) can do so. Sourced by `<SceneCanvas>` from the
1949
- * dispatcher instance it already owns.
1950
- */
1951
- dispatcher?: {
1952
- cancelAll(reason: 'commit' | 'cancel'): void;
1953
- };
1954
- /**
1955
- * Layout-strategy lookup. Sourced by `<SceneCanvas>` from `layouts`.
1956
- * Optional: absent (or all-null) → `moveAction` skips reflow.
1957
- */
1958
- layout?: LayoutDep;
1959
- /**
1960
- * Slice dep — consumer-supplied commit for the Slice action.
1961
- *
1962
- * Receives the finite slice segment in world coordinates; the consumer
1963
- * scans the scene, splits crossed paths via `splitPathByLine`, and
1964
- * applies the result as one undoable batch.
1965
- *
1966
- * Optional: when absent, `sliceAction` is a no-op.
1967
- */
1968
- slice?: SliceDep;
1969
- /**
1970
- * Optional consumer commit hook. When present, `moveAction` (and other
1971
- * default actions) submit their committed ops through it instead of
1972
- * `scene.applyBatch`, so apps with their own history integration
1973
- * (checkpoint + push entry) capture the gesture as one undo entry.
1974
- * When absent, commits fall back to `scene.applyBatch`.
1975
- */
1976
- applyOps?: (ops: Op[], label: string) => void;
1977
- /** Optional pose-composition strategy for hierarchical (local-pose) scenes.
1978
- * When absent, defaults to IDENTITY (absolute-pose: nodes store world
1979
- * coords). Local-pose consumers supply { compose: composeRectPose,
1980
- * decompose: decomposeRectPose } (or their pose shape's equivalent). */
1981
- poseComposition?: PoseComposition<unknown>;
1982
- /**
1983
- * Ingestion dep — canvas viewport rect + consumer file→src resolver.
1984
- *
1985
- * Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>` via
1986
- * `useIngestionDepSource`. Feeds `ingestAction` with the world-space
1987
- * viewport rect for paste-placement and image fit-clamping, and forwards
1988
- * the consumer's optional `resolveSrc` seam.
1989
- *
1990
- * Optional: when absent, the `ingest` action no-ops (there is no
1991
- * placement geometry to work with).
1992
- */
1993
- ingestion?: IngestionDep;
1994
- /**
1995
- * Optional consumer seam for the eager-sync layer: lets pose-transform
1996
- * actions (resize/move/nudge/flip — NOT rotate) ALSO rewrite a node's
1997
- * data-held geometry. Given a node and the affine `m` applied to its pose,
1998
- * `transform(node, m)` returns updated `data` (geometry mapped by `m`) or
1999
- * `null` for nodes with no data-held geometry.
2000
- *
2001
- * Strictly opt-in: when absent (or when `transform` returns null), the kit
2002
- * emits only the pose op and leaves `data` untouched. apps/draw wires this
2003
- * to mirror `data.path` through `transformPath`. Rotate intentionally never
2004
- * consults this seam (rotation lives on the pose, baked at render).
2005
- */
2006
- geometryProjection?: GeometryProjection;
2007
- }
2008
- /**
2009
- * Every dep name the registry knows about — derived from {@link DepSchema} so
2010
- * the two can't drift.
2011
- *
2012
- * Declared here rather than beside the registry so that this `keyof` reference
2013
- * resolves to the exported `DepSchema` declaration; from another module it
2014
- * resolves to that module's import alias, which the API docs can't link.
2015
- */
2016
- type DepName = keyof DepSchema;
2017
-
2018
- interface DepRegistry {
2019
- register<K extends DepName>(name: K, source: () => DepSchema[K]): () => void;
2020
- get<K extends DepName>(name: K): DepSchema[K] | undefined;
2021
- }
2022
- declare function DepRegistryProvider({ children }: {
2023
- children: ReactNode;
2024
- }): react_jsx_runtime.JSX.Element;
2025
- declare function useDepRegistry(): DepRegistry;
2026
- /**
2027
- * Like `useDepRegistry`, but returns `null` when no `<DepRegistryProvider>` is
2028
- * in scope instead of throwing. Used by `useStandardActions` to preserve its
2029
- * silent-no-op contract when neither provider is present.
2030
- */
2031
- declare function useOptionalDepRegistry(): DepRegistry | null;
2032
- /** Register a live source for `name` for the lifetime of the calling
2033
- * component. The `source` thunk is called at dispatch time and should
2034
- * return the latest value. */
2035
- declare function useDepSource<K extends DepName>(name: K, source: () => DepSchema[K]): void;
2036
-
2037
- /**
2038
- * Pure matcher primitives live in `@weasel-js/gestures`. This file
2039
- * re-exports them for kit-internal consumers and layers the actions-layer
2040
- * binding-scope / matchBest logic on top.
2041
- */
2042
-
2043
- type BindingScope = 'ambient' | 'active' | 'hotkey';
2044
- interface ScopedBinding {
2045
- binding: GestureBinding;
2046
- scope: BindingScope;
2047
- /** Tool id that owns this binding — `'&'`-channel phase atoms resolve
2048
- * to this. `null` for ambient bindings that came from a registered
2049
- * Action with no owning tool. */
2050
- ownerToolId: string | null;
2051
- }
2052
- interface MatchResult {
2053
- binding: GestureBinding;
2054
- scope: BindingScope;
2055
- /** Tool id that owns the binding — propagated from `ScopedBinding`
2056
- * so the dispatcher can record it as the handle owner. */
2057
- ownerToolId: string | null;
2058
- }
2059
-
2060
- /**
2061
- * Dispatcher orchestrator — pure module, no React, no DOM.
2062
- *
2063
- * Assembles `ScopedBinding[]` from the actions registry, active tool, and
2064
- * hotkey stack; matches input events via `matchSorted`; gates each candidate
2065
- * on `enabled()`; then invokes `immediate` or `ongoing` invokers and tracks
2066
- * in-flight handles.
2067
- *
2068
- * ## Specificity-ordered fall-through
2069
- * `matchSorted` returns every matching binding in precedence order
2070
- * (hotkey > active > ambient, first-declared within scope). The dispatcher
2071
- * walks that list and fires the first action whose `enabled()` returns
2072
- * `true`. If every candidate's `enabled()` returns a disabled reason, the
2073
- * event is unhandled. This mirrors CSS-style specificity matching with a
2074
- * `:not(:disabled)` filter, and lets a tool declare a high-specificity
2075
- * binding (e.g. drag-on-empty → areaSelect) that gracefully falls through
2076
- * to a lower-specificity ambient binding (e.g. drag → viewport.dragPan)
2077
- * when its required deps aren't wired.
2078
- *
2079
- * ## gestureId scheme
2080
- * - `key-held` ongoing actions: `key-held-<key>` (e.g. `key-held- ` for Space).
2081
- * Chosen because key-held gestures are identified by the held key alone.
2082
- * - `pointerdown` / drag ongoing actions: `pointer-<pointerId>`, where
2083
- * `pointerId` defaults to `'mouse'` (Phase 3 has no real pointer IDs; the
2084
- * React seam in Task 4 will supply the actual DOM pointerId).
2085
- * - `multitouch` ongoing actions: `multitouch-<fingers>`.
2086
- * - Fallback for any other kind that triggers an ongoing invoker: `ongoing-<kind>`.
2087
- *
2088
- * ## Action-lookup miss behavior
2089
- * When `matchBest` resolves a binding whose `actionId` has no entry in
2090
- * `ctx.actions.list()`, the dispatcher emits `console.warn` and returns
2091
- * `'unhandled'`. The user's input gesture falls through as if unmatched.
2092
- * This preserves input flow (nothing is swallowed silently) while flagging
2093
- * the misconfiguration at dev time.
2094
- */
2095
-
2096
- interface DispatcherContext {
2097
- /** All registered actions; the dispatcher walks `.defaultBinding` for ambient bindings. */
2098
- actions: ActionsRegistry;
2099
- /** Dep sources keyed by name. */
2100
- depRegistry: DepRegistry;
2101
- /** Active tool's id (from ActiveToolContext). */
2102
- activeToolId: string;
2103
- /** Held-hotkey stack, top of stack last. */
2104
- hotkeyStack: readonly string[];
2105
- /** Lookup for tool definitions. */
2106
- toolsById: ReadonlyMap<string, Tool>;
2107
- /** Platform flag for `mod` shorthand resolution. */
2108
- isMac: boolean;
2109
- /**
2110
- * Thunk returning a fresh `RuleCtx` for the current frame. When
2111
- * supplied, the dispatcher filters matched candidates by their
2112
- * declared `Action.eligible` rule (omitted => always eligible).
2113
- * When omitted, no eligibility filtering is applied — preserves
2114
- * backward compatibility for callers (tests, legacy harnesses) that
2115
- * don't wire up chrome-caps state.
2116
- */
2117
- getRuleCtx?: () => RuleCtx;
2118
- }
2119
- /**
2120
- * Handle returned by `Dispatcher.beginUiOngoing()` for driving an
2121
- * ongoing invoker from a UI control (color picker, slider).
2122
- *
2123
- * - `update(params)` rebuilds an `InvocationCtx` with the new params and
2124
- * calls the handle's `onMove`. Safe to call many times.
2125
- * - `end(reason)` calls `onEnd(ctx, reason)` once and removes the handle
2126
- * from the in-flight map. Idempotent — further calls are no-ops.
2127
- */
2128
- interface UiOngoingControl {
2129
- readonly gestureId: string;
2130
- update(params?: Record<string, unknown>): void;
2131
- end(reason: 'commit' | 'cancel'): void;
2132
- }
2133
- /**
2134
- * Successful `Dispatcher.resolveOnly` prediction: the binding + action that
2135
- * would fire if `event` were dispatched for real. `action` is the resolved
2136
- * descriptor so callers (the hover-cursor pump) can read metadata like
2137
- * `Action.cursor` without a second registry lookup.
2138
- */
2139
- interface ResolveOnlyResult {
2140
- actionId: string;
2141
- action: Action;
2142
- scope: BindingScope;
2143
- /** Tool id owning the winning binding; `null` for ambient action bindings. */
2144
- ownerToolId: string | null;
2145
- }
2146
- interface Dispatcher {
2147
- /**
2148
- * Route an input event through the binding pipeline. Returns `'handled'`
2149
- * when a binding matched and the action invoked successfully (whether it
2150
- * returned ops or not). Returns `'unhandled'` when no binding matched or
2151
- * the matched action's `enabled()` returned a disabled reason.
2152
- */
2153
- handleInput(event: InputEvent, ctx: DispatcherContext): 'handled' | 'unhandled';
2154
- /**
2155
- * Predict which action `event` would route to WITHOUT invoking it. Replays
2156
- * the same walk as `handleInput` — scope assembly, specificity-sorted
2157
- * match, eligibility filter, per-candidate `enabled()` gate — and returns
2158
- * the first candidate that would fire, or `null` when the event would go
2159
- * unhandled. Pure query: no invoker runs, no in-flight state changes, no
2160
- * trace-log entry.
2161
- *
2162
- * Known divergence from a real dispatch: an ongoing invoker that matches
2163
- * but returns an empty handle at `start()` (runtime bail) makes the real
2164
- * dispatch fall through to the next candidate; prediction cannot see that
2165
- * and reports the bailing action. Keep `enabled()` accurate on actions
2166
- * that rely on prediction (hover cursors).
2167
- */
2168
- resolveOnly(event: InputEvent, ctx: DispatcherContext): ResolveOnlyResult | null;
2169
- /**
2170
- * Synthesize an end-of-gesture for every in-flight ongoing handle.
2171
- * Used by tool-switch cancellation (Q2 decision).
2172
- */
2173
- cancelAll(reason: 'commit' | 'cancel'): void;
2174
- /**
2175
- * Read-only view of currently in-flight ongoing handles, keyed by gestureId.
2176
- * For debug/testing.
2177
- */
2178
- inFlight(): ReadonlyMap<string, OngoingHandle>;
2179
- /**
2180
- * Read-only iterator over currently in-flight `OngoingHandle` instances.
2181
- *
2182
- * Surface for the canvas's preview-ghost layer (`usePreviewGhostLayer`)
2183
- * to walk each handle's `previewIds()` / `previewPose(id)` and render
2184
- * dispatcher-driven gesture previews. Read-only by design:
2185
- * external consumers must not mutate the in-flight map.
2186
- */
2187
- getInFlightHandles(): Iterable<OngoingHandle>;
2188
- /**
2189
- * Subscribe to in-flight state changes. The callback fires after every
2190
- * mutation that affects what the preview-ghost / dispatcher-overlay
2191
- * layers read — handle start, every `onMove` pump, end, cancel,
2192
- * cancel-all. Consumers re-read `getInFlightHandles()` and re-render.
2193
- *
2194
- * Returns an unsubscribe function.
2195
- */
2196
- subscribe(fn: () => void): () => void;
2197
- /**
2198
- * Snapshot of the currently active action, for surfaces (chrome-caps
2199
- * visibility rules, debug HUDs) that need to react to "what action
2200
- * is in flight right now."
2201
- *
2202
- * - `kind` — the `OngoingHandle.kind` reported by the in-flight
2203
- * handle (e.g. `'marquee'`, `'move'`). `null` when no action is
2204
- * in flight OR the handle didn't declare a kind.
2205
- * - `id` — the dispatcher's internal `gestureId` (`pointer-mouse`,
2206
- * `key-held-Space`, …) — the pointer/key channel the action rode
2207
- * in on. `null` when no action is in flight.
2208
- *
2209
- * When multiple handles are in flight simultaneously (e.g. a key-held
2210
- * action overlapping a pointer action), the most-recently-started
2211
- * handle wins. This matches user intent: the latest interaction is
2212
- * the one consumers care about.
2213
- */
2214
- getActiveAction(): {
2215
- kind: string | null;
2216
- id: string | null;
2217
- };
2218
- /**
2219
- * Start an ongoing action driven by UI (not a gesture). Builds an
2220
- * `InvocationCtx` with the given `deps` and `params`, calls
2221
- * `action.invoker.start(ctx, { params })`, and registers the returned
2222
- * handle in the in-flight map so `getInFlightHandles()` reports it —
2223
- * enabling preview rendering via `SceneCanvas`.
2224
- *
2225
- * Returns `null` if `actionId` is unknown, the action's invoker is not
2226
- * ongoing, or `start` returned an empty handle.
2227
- *
2228
- * If a UI-driven handle for the same `actionId` is already in flight,
2229
- * it is committed (`end('commit')`) before the new one starts.
2230
- */
2231
- beginUiOngoing(actionId: string, deps: ActionDeps, params?: Record<string, unknown>): UiOngoingControl | null;
2232
- }
2233
- declare function createDispatcher(opts?: {
2234
- getAction?: (id: string) => Action | undefined;
2235
- }): Dispatcher;
2236
-
2237
- /**
2238
- * @experimental
2239
- * A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
2240
- * (no per-binding opts) or an object form that pairs a spec with
2241
- * `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
2242
- * Use the object form when two bindings for the same action differ only in
2243
- * a runtime parameter — the dispatcher extracts `opts.params` and passes
2244
- * them to `ImmediateInvoker.run` as its second argument.
2245
- */
2246
- type BoundGesture = GestureSpec | {
2247
- spec: GestureSpec;
2248
- opts: BindingOpts;
2249
- };
2250
- /**
2251
- * @experimental
2252
- * Single registered action. v1: one binding per action.
2253
- */
2254
- interface Action {
2255
- id: string;
2256
- label: string;
2257
- /** The gesture-spec form of the binding, read by the gesture dispatcher.
2258
- * May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
2259
- * or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
2260
- * `{ spec, opts }` — use the object form for parametric actions where two
2261
- * bindings for the same action differ only by `opts.params` (e.g. `flip`
2262
- * with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
2263
- * passes them to `ImmediateInvoker.run` as its second argument. */
2264
- defaultBinding?: GestureSpec | BoundGesture[];
2265
- /** Names of the deps this action's invoker reads (keys of `DepSchema`).
2266
- * The dispatcher (and `trigger`, when `requires` is present) resolves
2267
- * each name against the `DepRegistry` at invocation time and passes the
2268
- * resulting bag to the invoker. Dev builds warn when the invoker reads a
2269
- * dep it didn't declare here — see `buildDepsFromRequires`. */
2270
- requires?: readonly DepName[];
2271
- /** Inline-SVG icon for palette / toolbar surfaces. Mirrors
2272
- * `ToolPresentation.icon` so a generic `<ActionBar>` can render from
2273
- * action metadata the same way `<ToolPalette>` renders from tool
2274
- * metadata. May be a static `ReactNode` or a function (rare; useful
2275
- * for state-aware icons like a "lock" toggle). */
2276
- icon?: ReactNode | (() => ReactNode);
2277
- /** Grouping key for palette/menu surfaces. Free-form string; the kit
2278
- * ships defaults for `'align'` (six edges/centers), `'distribute'`
2279
- * (two axes), and recommends `'pathfinder'` for boolean ops. */
2280
- group?: string;
2281
- /** Display override for the keyboard shortcut. When omitted, palette
2282
- * surfaces derive a label from `defaultBinding` via their own
2283
- * formatter. */
2284
- shortcut?: string;
2285
- /** Pluggable invocation strategy. The gesture dispatcher routes matched
2286
- * bindings through `invoker.start` / `invoker.run` depending on timing.
2287
- * All kit-standard descriptors ship one; consumer-supplied actions
2288
- * without an invoker can still register but won't be triggered. */
2289
- invoker?: Invoker;
2290
- /** When set to `'hotkey'`, this action's `defaultBinding` rides the hotkey
2291
- * `BindingScope` instead of the ambient scope — meaning it beats any
2292
- * active-tool binding on the same input shape. Use for tool-switch
2293
- * shortcuts and global held-key triggers. Default: ambient. */
2294
- scope?: 'hotkey';
2295
- /**
2296
- * @experimental
2297
- * Optional predicate the command palette consults when rendering. Return
2298
- * `true` when the action is currently triggerable. Return a reason string
2299
- * (e.g. `'Selection required'`) when disabled — the palette greys out
2300
- * the row, skips it in keyboard nav, ignores clicks, and shows the
2301
- * reason next to the label. Keystroke dispatch (the registered binding)
2302
- * is unaffected; the action's own `run` should self-guard.
2303
- *
2304
- * **Contract:** must be pure (no side effects), fast (< 4ms in dev), and
2305
- * must not throw. If a call throws or exceeds the budget in dev mode,
2306
- * `evaluateEnabled` logs a one-time warning per action id; throws are
2307
- * caught and treated as disabled with reason `'(predicate threw)'`.
2308
- *
2309
- * Snapshot-on-open semantics: the palette evaluates `enabled` once when
2310
- * opened and does NOT re-evaluate on selection changes while open. Live
2311
- * reactive updates are deferred — palette is short-lived.
2312
- *
2313
- * The reason set is a closed enum — to add a new reason, edit
2314
- * `ActionDisabledReason` and the consumer's display map.
2315
- *
2316
- * The optional `deps` argument is the same bag passed to
2317
- * `ImmediateInvoker.run`; callers (`evaluateEnabled` / the ActionBar) may
2318
- * synthesize it from the surrounding `DepRegistry` so predicates can
2319
- * inspect selection / scene / etc. Predicates that don't need deps just
2320
- * ignore the arg.
2321
- */
2322
- enabled?: (deps?: ActionDeps) => true | ActionDisabledReason;
2323
- /**
2324
- * Declarative eligibility rule, evaluated against the current
2325
- * `RuleCtx` by the dispatcher before invoking `start()`. Omitted =
2326
- * always eligible.
2327
- *
2328
- * Accepts either a fluent `Condition` (callable with `.rule`) or a
2329
- * raw `Rule` tree; the dispatcher normalizes via `.rule` unwrap.
2330
- *
2331
- * Prefer `capability:`-based rules (e.g. `{ capability: 'transforms-selection' }`)
2332
- * over `mode:` rules — capability rules survive new modes being added
2333
- * that allow the same capability.
2334
- */
2335
- eligible?: Rule | Condition;
2336
- /**
2337
- * CSS cursor shown while the pointer hovers a spot where this action
2338
- * would win the drag. The hover-cursor pump (in `useGestureDispatcher`)
2339
- * runs `Dispatcher.resolveOnly` on each idle pointermove — the same
2340
- * match walk a real pointerdown takes — and applies the winning
2341
- * action's `cursor`, so the hint and the actual click target stay in
2342
- * sync by construction. Omitted = no override (the active tool's
2343
- * `Tool.cursor` shows). Affordance hits are resolved earlier in the
2344
- * pump via `AffordanceRegion.cursor` and never reach this field.
2345
- *
2346
- * Static string only. Prediction runs `enabled()` but cannot run the
2347
- * invoker, so an action that matches yet bails at `start()` (empty
2348
- * handle) may still show its cursor — keep `enabled` accurate for
2349
- * actions that declare one.
2350
- */
2351
- cursor?: string;
2352
- }
2353
- /**
2354
- * @experimental
2355
- * Closed enum of reasons an action might report itself as disabled. The
2356
- * consumer (palette, menu, etc.) maps these symbolic values to display
2357
- * strings via its own label map — see `demo/CommandPalette.tsx` for the
2358
- * canonical mapping.
2359
- */
2360
- declare const ActionDisabledReason: {
2361
- readonly SelectionRequired: "selection-required";
2362
- readonly SceneEmpty: "scene-empty";
2363
- readonly NotApplicable: "not-applicable";
2364
- /** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
2365
- readonly PredicateThrew: "predicate-threw";
2366
- };
2367
- type ActionDisabledReason = (typeof ActionDisabledReason)[keyof typeof ActionDisabledReason];
2368
- /**
2369
- * @experimental
2370
- * Result of evaluating an Action's `enabled` predicate.
2371
- */
2372
- interface ActionEnabledResult {
2373
- enabled: boolean;
2374
- reason?: ActionDisabledReason;
2375
- }
2376
- declare function evaluateEnabled(action: Action, deps?: ActionDeps): ActionEnabledResult;
2377
- /**
2378
- * @experimental
2379
- * Partial override or full descriptor passed via `<SceneCanvas actions={...}>`.
2380
- * `null` disables a default at this id.
2381
- */
2382
- type ActionEntry = null | Partial<Action> | Action;
2383
- /**
2384
- * @experimental
2385
- * Shape of the `actions` prop on `<SceneCanvas>`. `null` disables all defaults.
2386
- */
2387
- type ActionsProp = null | Record<string, ActionEntry>;
2388
- /**
2389
- * @experimental
2390
- * Imperative API exposed by `useActionsRegistry()`.
2391
- */
2392
- interface ActionsRegistry {
2393
- register(action: Action): () => void;
2394
- unregister(id: string): void;
2395
- list(): readonly Action[];
2396
- /** Fire an immediate-invoker action by id. The optional `params` arg is
2397
- * forwarded to `ImmediateInvoker.run` as its second argument — use it for
2398
- * parametric actions (e.g. `trigger('tool.activate', { toolId: 'rect' })`).
2399
- * Ongoing-invoker actions are not reachable from `trigger`. */
2400
- trigger(id: string, params?: Record<string, unknown>): boolean;
2401
- /**
2402
- * Subscribe to registry mutations. The callback fires after any
2403
- * `register`/`unregister` that changes the version. Returns an
2404
- * unsubscribe function. Designed for `useSyncExternalStore`-driven
2405
- * surfaces (e.g. `<ActionBar>` in `@weasel-js/ui`) that need to
2406
- * re-render when the action set changes.
2407
- */
2408
- subscribe(listener: () => void): () => void;
2409
- /**
2410
- * Start an ongoing action driven by UI (color picker, opacity slider).
2411
- * Returns a control object with `update(params)` and `end(reason)`.
2412
- *
2413
- * Returns `null` if no dispatcher is wired into this registry, the
2414
- * action is unknown, or its invoker is not ongoing.
2415
- *
2416
- * See `Dispatcher.beginUiOngoing` for full semantics including
2417
- * auto-commit when a prior UI handle for the same action is in flight.
2418
- */
2419
- begin(id: string, params?: Record<string, unknown>): UiOngoingControl | null;
2420
- /** Wire a dispatcher into the registry so `begin()` can delegate to it.
2421
- * Call with `null` to detach. Idempotent. */
2422
- setDispatcher(d: Dispatcher | null): void;
2423
- /** Wire a `DepRegistry` into the registry so `trigger()` / `begin()` can
2424
- * resolve action deps even when this provider is mounted ABOVE the dep
2425
- * registry (e.g. a consumer's root `<ActionsProvider>` reused by
2426
- * SceneCanvas's `ActionsProviderIfRoot`). Takes precedence over the dep
2427
- * registry read from context at the provider's own level. Call with
2428
- * `null` to detach. */
2429
- setDepRegistry(r: DepRegistry | null): void;
2430
- }
2431
- /**
2432
- * @experimental
2433
- * Mounts an `ActionsRegistry` and one `document` keydown listener for its
2434
- * lifetime. Children call `useActionsRegistry()` or `useAction()` to participate.
2435
- */
2436
- declare function ActionsProvider({ children }: {
2437
- children: ReactNode;
2438
- }): ReactElement;
2439
- /**
2440
- * @experimental
2441
- * Returns the parent `ActionsRegistry`, or `null` when no provider is in scope.
2442
- */
2443
- declare function useActionsRegistry(): ActionsRegistry | null;
2444
- /**
2445
- * @experimental
2446
- * Register an `Action` for the lifetime of the calling component. No-op when
2447
- * no `ActionsProvider` is in scope. Re-registers on `action` reference change
2448
- * (consumers should memoize stable identities to avoid churn).
2449
- */
2450
- declare function useAction(action: Action): void;
2451
-
2452
- /**
2453
- * `moveAction` — first `ongoing`-timing Action descriptor.
2454
- *
2455
- * Mirrors the per-frame translate semantics of the `useMove` hook:
2456
- * - `start`: capture origin poses for all selected nodes; record the
2457
- * current drag delta in scratch each frame.
2458
- * - `onMove`: update the in-scratch `currentDelta` only — no scene writes.
2459
- * This avoids polluting the undo stack with O(N-frames) entries.
2460
- * - `onEnd('commit')`: emit the final delta as transform ops and route
2461
- * them through the consumer `applyOps` hook when present, else
2462
- * `scene.applyBatch(ops, 'Move', adapter)` — either way a single batch
2463
- * → exactly one undo entry for the whole drag.
2464
- * - `onEnd('cancel')`: no scene writes — the scene was never mutated during
2465
- * the drag, so no restoration is needed.
2466
- *
2467
- * ## Why no per-frame scene writes
2468
- *
2469
- * `Scene.setPose` calls `executeAndLog`, which immediately records an undo
2470
- * entry on the scene's history engine. Per-frame writes during drag would create
2471
- * O(frames) history entries — matching `useMove`'s approach of tracking
2472
- * poses only in React state (overlay) during the drag and committing a
2473
- * single `createTransformOp` batch at the end.
2474
- *
2475
- * The behavior pipeline (snap-to-grid, snap-back-or-delete, snap-to-container,
2476
- * etc.) via `opts.behaviors` from `BindingOpts` IS wired: `start` builds a
2477
- * `GestureContext` + scene-backed adapter (`moveGestureAdapter`), `onMove`
2478
- * folds each behavior's proposed-transform result, and `onEnd` runs a
2479
- * first-non-undefined-wins reducer (`Op[]` commits via `scene.applyBatch`,
2480
- * `null` aborts, all-`undefined` falls through to the default translate path).
2481
- *
2482
- * ## Pose generics
2483
- *
2484
- * `scene` dep is typed `Scene<unknown, string, unknown>` (the erased DepSchema
2485
- * entry). Poses are read and written as `unknown`; `translatePoseGeneric`
2486
- * delegates to `RECT_POSE_DESCRIPTOR.translate` which treats any pose as
2487
- * `{x, y, ...}`. Consumers with non-rect poses should register a custom
2488
- * action with a typed translatePose.
2489
- */
2490
-
2491
- /**
2492
- * @experimental
2493
- * Static descriptor for the `move` Action.
2494
- *
2495
- * Requires dep-schema entries: `selection`, `scene`.
2496
- *
2497
- * The invoker is `ongoing` — it returns an `OngoingHandle` from `start` that
2498
- * the dispatcher pumps via `onMove`/`onEnd` for the duration of the drag.
2499
- *
2500
- * @see useMove — the React hook this descriptor mirrors for the simple case.
2501
- */
2502
- declare const moveAction: Action & {
2503
- requires: string[];
2504
- };
2505
-
2506
- /**
2507
- * `resizeAction` — ongoing Action descriptor for anchor-relative drag resize.
2508
- *
2509
- * ## Status: REAL
2510
- *
2511
- * Performs real anchor-relative resize math for rect-shaped poses. Reads
2512
- * `ctx.drag.affordance.anchor` to determine which corner is fixed, captures
2513
- * start poses, applies per-frame bounds remapping, and commits a single
2514
- * `scene.batch` entry on end.
2515
- *
2516
- * ## Behaviors / point-snap / expandIds / geometry — via `resizePolicy` dep
2517
- *
2518
- * The resize-behaviors-api wired the four behavior options the
2519
- * legacy `useResize` hook exposed (`behaviors`, `pointSnapBehaviors`,
2520
- * `expandIds`, `geometry`) through the `resizePolicy` dep entry. When
2521
- * the dep is registered, this invoker:
2522
- *
2523
- * - Calls `expandIds([id])` at start. When the result expands beyond the
2524
- * starting id, takes the group path (union-AABB origin, per-leaf remap).
2525
- * - Projects poses through the supplied `geometry: PoseProjection<TPose>`
2526
- * instead of the rect-shaped `RECT_POSE_DESCRIPTOR` default.
2527
- * - Runs `behaviors[]` after the raw anchor-math bounds are computed.
2528
- * Behaviors return `{ pose? }` and rewrite the proposed bounds before
2529
- * they're projected back into pose space.
2530
- * - Runs `pointSnap[]` after the bounds→pose projection. The first non-null
2531
- * `PointSnapResult` wins; this invoker back-solves a pose so the chosen
2532
- * frame's world point lands on the snap target.
2533
- *
2534
- * When the dep is absent the invoker falls back to identity defaults
2535
- * (no behaviors, no snap, `ids => ids` expansion, `RECT_POSE_DESCRIPTOR`),
2536
- * which matches the unrotated rect-pose path the invoker already
2537
- * implemented.
2538
- *
2539
- * @see useResize — the React hook this descriptor mirrors.
2540
- * @see src/interactions/actions/resize/geometry.ts — `PoseProjection`.
2541
- */
2542
-
2543
- /**
2544
- * @experimental
2545
- * Static descriptor for the `resize` Action.
2546
- *
2547
- * Requires dep-schema entries: `selection`, `scene`.
2548
- * Reads optional `resizePolicy` dep when present.
2549
- * Requires `InvocationCtx.drag.affordance` with a `handle:*` kind.
2550
- */
2551
- declare const resizeAction: Action & {
2552
- requires: string[];
2553
- };
2554
-
2555
- /**
2556
- * `rotateAction` — ongoing Action descriptor for pointer-driven rotation.
2557
- *
2558
- * ## Status: REAL (unrotated-pivot path)
2559
- *
2560
- * Implements the core rotation math from `useRotate` for rect-shaped poses:
2561
- * - `start`: captures origin poses + AABB centers; computes start pointer
2562
- * angle around the union center of selected nodes.
2563
- * - `onMove`: derives pointer angle delta from start, applies to each node's
2564
- * origin rotation. In union-pivot mode (multi-selection) orbits each item's
2565
- * center around the union center.
2566
- * - `onEnd('commit')`: builds one `createTransformOp` per node (from =
2567
- * pre-mutation origin pose, to = rotated pose) and commits them as a
2568
- * single batch → one undo entry for the whole drag. Routes through the
2569
- * optional `applyOps` dep (consumer history) when present, else
2570
- * `scene.applyBatch` + `defaultCommitAdapter`.
2571
- * - `onEnd('cancel')`: no scene writes (scene never mutated during drag).
2572
- *
2573
- * ## Constraints vs `useRotate`
2574
- *
2575
- * - Assumes rect-shaped poses `{ x, y, width, height, rotation? }`. Non-rect
2576
- * TPose consumers need a custom action with a typed `RotateGeometry`.
2577
- * - No behavior pipeline (snap, etc.). Behaviors wait for a later phase.
2578
- * - No overlay rendering — deferred to Phase 7 overlay surface.
2579
- * - Shift-snap (15° quantum) is NOT wired in this phase — omitted deliberately
2580
- * to keep the invoker self-contained (would need to read shift from onMove ctx).
2581
- * TODO: thread shift from InvocationCtx.modifiers.shift into snap logic.
2582
- */
2583
-
2584
- /**
2585
- * @experimental
2586
- * Static descriptor for the `rotate` Action.
2587
- *
2588
- * Requires dep-schema entries: `selection`, `scene`.
2589
- *
2590
- * Implements the unrotated-pivot rotation path from `useRotate` for rect-
2591
- * shaped poses. Non-rect or behavior-rich consumers should register a custom
2592
- * `rotateAction` with a typed `RotateGeometry` dep.
2593
- *
2594
- * @see useRotate — the React hook this descriptor mirrors for the rect case.
2595
- */
2596
- declare const rotateAction: Action & {
2597
- requires: string[];
2598
- };
2599
-
2600
- /**
2601
- * `areaSelectAction` — ongoing Action descriptor for marquee area selection.
2602
- *
2603
- * ## Status: REAL
1164
+ * ## Status: REAL
2604
1165
  *
2605
1166
  * Implements the marquee selection logic from `useAreaSelect`:
2606
1167
  * - `start`: records the drag start world point and shift state.
@@ -2669,12 +1230,19 @@ declare const areaSelectAction: Action & {
2669
1230
  * delegating to the scene's `add()` with a sensible default data payload for
2670
1231
  * the given `kind`. Override per-consumer for custom node factories.
2671
1232
  *
1233
+ * ## Modifiers and snapping
1234
+ *
1235
+ * Grid snapping comes from the optional `snap` dep, applied to the drag's
1236
+ * start and current point (never to freehand pencil samples). The `line`
1237
+ * kind additionally honors Shift (constrain to 15°) and reads Alt/center as
1238
+ * "mirror the start around the pointer" rather than "grow a symmetric AABB".
1239
+ * All of it resolves in `resolveEndpoints`, which both `overlay()` and
1240
+ * `onEnd()` call — so the live preview and the committed node can't disagree.
1241
+ *
2672
1242
  * ## What this does NOT wire (vs `useInsert`)
2673
1243
  *
2674
- * - `InsertBehavior` pipeline (snap, etc.) — deferred to a later phase.
2675
1244
  * - `pointInsert` fallback for click / sub-threshold drags — not wired; a
2676
1245
  * sub-threshold drag produces no insert.
2677
- * - Live insert overlay — deferred to Phase 7 overlay surface.
2678
1246
  * - `clickOnly` mode — not applicable to the descriptor model.
2679
1247
  *
2680
1248
  * ## Live preview
@@ -2730,17 +1298,32 @@ declare const insertAction: Action & {
2730
1298
  * This avoids ambient double-fire and keeps the action scoped to select-tool
2731
1299
  * context where `classifyTarget` is wired.
2732
1300
  *
2733
- * ## enabled
1301
+ * ## enabled — the anchor-editing fall-through
1302
+ *
1303
+ * There is no "is anything selected?" guard: clearing an already-empty
1304
+ * selection is a safe no-op, and the binding spec is the real gate for
1305
+ * where the click landed.
2734
1306
  *
2735
- * Returns `SelectionRequired` disabled reason when nothing is selected, so the
2736
- * dispatcher skips the action on an already-empty canvas (no-op guard).
1307
+ * What `enabled` *does* do is decline while a path is in anchor-edit mode,
1308
+ * so the dispatcher falls through to `selectAnchorAction`. This is the
1309
+ * click twin of the opt-out `areaSelectAction.start` already performs for
1310
+ * the drag gesture, and it exists for the same reason: `useSelectTool`
1311
+ * binds this action at **active** scope while `selectAnchor` is **ambient**,
1312
+ * so without the gate an anchor that happens to sit over empty canvas
1313
+ * clears the node selection instead of selecting the anchor.
1314
+ *
1315
+ * Capability eligibility (`creates-selection`, which `path-edit` does not
1316
+ * allow) already covers consumers that wired a mode registry. Consumers
1317
+ * without one get no eligibility filtering at all, so this gate is the only
1318
+ * thing separating the two. Both mechanisms are needed — see the header of
1319
+ * `anchorEditing.ts`.
2737
1320
  */
2738
1321
 
2739
1322
  /**
2740
1323
  * @experimental
2741
1324
  * Static descriptor for the `clearSelection` Action.
2742
1325
  *
2743
- * Requires dep-schema entry: `selection`.
1326
+ * Requires dep-schema entries: `selection`, `editAnchors`.
2744
1327
  *
2745
1328
  * Registered via `useStandardActions`; bound via `useSelectTool.bindings`.
2746
1329
  */
@@ -2920,40 +1503,43 @@ declare const viewportZoomAction: Action & {
2920
1503
  /**
2921
1504
  * `editAnchorsAction` — ongoing Action descriptor for editing polygon anchors.
2922
1505
  *
2923
- * ## Status: REAL
1506
+ * Owns the whole press-on-an-anchor gesture, because pressing and dragging
1507
+ * an anchor are two outcomes of one interaction rather than two
1508
+ * interactions:
1509
+ *
1510
+ * - **Press** selects. Bare press replaces the anchor selection; Shift
1511
+ * toggles the pressed anchor in or out of it. Pressing an anchor that
1512
+ * is already part of a multi-anchor selection leaves the selection
1513
+ * alone, so you can grab a set and move it.
1514
+ * - **Drag** then moves whatever the press left selected — one anchor,
1515
+ * or every selected anchor together.
1516
+ * - **Dragging a control handle** moves just that handle, mirroring the
1517
+ * opposite one while the anchor is smooth. Alt breaks the mirror.
1518
+ *
1519
+ * Selection lives on the `editAnchors` dep (flat anchor indices) rather
1520
+ * than in this handle's scratch, because the keyboard actions
1521
+ * (`nudgeAnchors`, `deleteAnchors`) and the overlay all need to read it
1522
+ * between gestures.
1523
+ *
1524
+ * ## Wiring
2924
1525
  *
2925
- * The descriptor is fully operational when the consumer wires:
1526
+ * The descriptor is operational when the consumer wires:
2926
1527
  * 1. `buildAffordanceAt` with a `getAnchorState` thunk so the dispatcher
2927
1528
  * classifies anchor/control-handle hits as `anchor:N`, `controlIn:N`,
2928
1529
  * or `controlOut:N` affordances on pointerdown.
2929
1530
  * 2. The `editAnchors` dep in the DepSchema.
2930
1531
  *
2931
- * The invoker reads `ctx.drag.affordance.kind` to identify the hit anchor,
2932
- * derives the coord index from `enumerateAnchors`, and on every `onMove`
2933
- * writes the new absolute world position via `withCoord`. On `onEnd('commit')`
2934
- * it dispatches a `createTransformOp` through `dispatchApplyBatch` so the
2935
- * edit is undoable.
2936
- *
2937
1532
  * When `ctx.drag.affordance` is absent or is not an anchor/control kind,
2938
1533
  * `start` returns `{}` (no-op) so other bindings can handle the drag.
2939
1534
  *
2940
- * @see useEditAnchors — the React hook this descriptor mirrors.
2941
1535
  * @see buildAffordanceAt — the classifier that produces anchor affordances.
1536
+ * @see anchorEdits — the pure geometry these handlers drive.
2942
1537
  */
2943
1538
 
2944
1539
  /**
2945
1540
  * Static descriptor for the `editAnchors` Action.
2946
1541
  *
2947
1542
  * Requires dep-schema entries: `selection`, `editAnchors`.
2948
- *
2949
- * The invoker reads `ctx.drag.affordance.kind` to identify the anchor hit.
2950
- * If the affordance is absent or not an anchor kind, `start` returns `{}`
2951
- * (no-op), allowing other bindings to handle the drag.
2952
- *
2953
- * Consumers must provide `buildAffordanceAt` with a `getAnchorState` thunk
2954
- * so that anchor handles are classified at pointerdown.
2955
- *
2956
- * @see useEditAnchors — the React hook this descriptor mirrors.
2957
1543
  */
2958
1544
  declare const editAnchorsAction: Action & {
2959
1545
  requires: string[];
@@ -3130,8 +1716,28 @@ interface UseGestureDispatcherOptions {
3130
1716
  actions: ActionsRegistry;
3131
1717
  /** Tool definitions keyed by id. Typically passes an empty Map. */
3132
1718
  toolsById: ReadonlyMap<string, Tool>;
1719
+ /** Ids (within `toolsById`) of always-on tools, whose bindings assemble at
1720
+ * ambient scope. See `DispatcherContext.ambientToolIds`. */
1721
+ ambientToolIds?: readonly string[];
3133
1722
  /** Default true. Set false to opt out of dispatcher wiring (e.g. demos that disable it). */
3134
1723
  enabled?: boolean;
1724
+ /**
1725
+ * Observer fired whenever the dispatcher synthesizes a double click, in
1726
+ * world coordinates. Runs BEFORE the event is dispatched and independently
1727
+ * of which binding (if any) handles it.
1728
+ *
1729
+ * This is deliberately not an Action. `<SceneCanvas onDoubleClick>` is a
1730
+ * notification — "the user double-clicked, here's what they hit" — and a
1731
+ * notification must not compete with behavior for the gesture. As a binding
1732
+ * it would lose to `enterPathEdit` on any body hit and silently never fire.
1733
+ * Routing it here keeps a single definition of "double click" (the point of
1734
+ * consolidating the kit's three detectors) without giving it
1735
+ * first-match-wins semantics it shouldn't have.
1736
+ */
1737
+ onDoubleClick?: (world: {
1738
+ x: number;
1739
+ y: number;
1740
+ }) => void;
3135
1741
  /**
3136
1742
  * Default true. Set false to leave the window `keydown`/`keyup` listeners
3137
1743
  * unattached so keyboard-bound actions never dispatch — pointer, wheel, and
@@ -3160,23 +1766,25 @@ interface UseGestureDispatcherOptions {
3160
1766
  y: number;
3161
1767
  }) => AffordanceHit | null;
3162
1768
  /**
3163
- * Optional body-target classifier. Called on every pointerdown with the
3164
- * world-space coordinates of the pointer. Returns a classification string
3165
- * that `matchTarget` uses to resolve string-form `TargetSpec` values in
3166
- * `Tool.bindings` drag specs.
1769
+ * Optional body classifier. Called on every pointerdown with the world-space
1770
+ * coordinates of the pointer. Its result is packed onto the event as
1771
+ * `bodyTarget` + `bodyKind`, which `matchTarget` reads to resolve the
1772
+ * string-form `TargetSpec` values in `Tool.bindings`.
3167
1773
  *
3168
- * Returns `'empty'` when nothing is under the pointer, `'selected-body'`
1774
+ * `body` is `'empty'` when nothing is under the pointer, `'selected-body'`
3169
1775
  * when the topmost hit belongs to the current selection, or
3170
- * `'unselected-body'` when it belongs to a node that isn't selected.
1776
+ * `'unselected-body'` when it belongs to a node that isn't selected. `kind`
1777
+ * is the hit node's semantic kind, when the scene can name it.
3171
1778
  *
3172
- * When omitted, string-form target specs (`'empty'`, `'selected-body'`,
3173
- * `'unselected-body'`) never match — bindings using those specs are
3174
- * silently skipped. `<SceneCanvas>` wires this.
1779
+ * When omitted, every body-derived target form (`'empty'`,
1780
+ * `'selected-body'`, `'unselected-body'`, `kind:<k>`, `kind:<k>:selected`)
1781
+ * never matches bindings using those specs are silently skipped.
1782
+ * `<SceneCanvas>` wires this.
3175
1783
  */
3176
1784
  classifyTarget?: (worldPoint: {
3177
1785
  x: number;
3178
1786
  y: number;
3179
- }) => 'empty' | 'selected-body' | 'unselected-body';
1787
+ }) => BodyClassification;
3180
1788
  /**
3181
1789
  * Optional pre-created `Dispatcher`. When provided, this hook pumps events
3182
1790
  * into the supplied instance instead of creating its own. Lets a parent
@@ -3292,290 +1900,78 @@ interface DecayLoopConfig {
3292
1900
  vx: number;
3293
1901
  vy: number;
3294
1902
  };
3295
- friction?: number;
3296
- minSpeed?: number;
3297
- /** Bounds for boundary clamping. Requires `boundary` to take effect. */
3298
- viewBounds?: PanBounds;
3299
- /**
3300
- * What to do when the accumulated position hits `viewBounds`. Default: no clamping.
3301
- * - `'stop'`: clamp at boundary, kill velocity component.
3302
- * - `'bounce'`: linear reflection — flip velocity sign, magnitude preserved.
3303
- * - `'spring'`: damped reflection — flip velocity sign and shrink magnitude
3304
- * by `SPRING_DAMPING` per bounce so the motion settles naturally.
3305
- */
3306
- boundary?: 'stop' | 'bounce' | 'spring';
3307
- /** Starting position for internal boundary tracking. Required when `viewBounds` is set. */
3308
- initialPosition?: {
3309
- x: number;
3310
- y: number;
3311
- };
3312
- onTick: (dx: number, dy: number) => void;
3313
- onEnd?: () => void;
3314
- }
3315
- declare function useDecayLoop(): {
3316
- start: (config: DecayLoopConfig) => void;
3317
- cancel: () => void;
3318
- };
3319
-
3320
- declare function useViewTween(setView: (v: View) => void): {
3321
- animateTo: (from: View, to: View, opts?: {
3322
- duration?: number;
3323
- easing?: (t: number) => number;
3324
- }) => void;
3325
- cancel: () => void;
3326
- isAnimating: React$1.RefObject<boolean>;
3327
- };
3328
-
3329
- /**
3330
- * Detects two-finger pinch on a canvas element.
3331
- *
3332
- * @param onPinch Called with the gesture midpoint in **client coordinates** and
3333
- * a per-frame scale factor (ratio of current distance to previous frame's
3334
- * distance, not cumulative from gesture start). With 3+ fingers, the first
3335
- * two by insertion order are tracked.
3336
- */
3337
- declare function usePinchGesture(canvasRef: React.RefObject<HTMLCanvasElement | null>, onPinch: (anchor: {
3338
- x: number;
3339
- y: number;
3340
- }, scaleFactor: number) => void, enabled?: boolean): void;
3341
-
3342
- /** Options accepted by {@link useViewAnimation}'s `animateToBounds`. */
3343
- interface AnimateToBoundsOptions extends FitViewToBoundsOptions {
3344
- /** Tween duration in ms (forwarded to `animateTo`). */
3345
- duration?: number;
3346
- /** Tween easing (forwarded to `animateTo`). */
3347
- easing?: (t: number) => number;
3348
- }
3349
- /**
3350
- * Tween the viewport `View` between values. Wraps `useViewTween`'s `animateTo`
3351
- * + `cancel`, and adds an `animateToBounds` convenience that composes
3352
- * `fitViewToBounds` with the existing tween so consumers can say "zoom to this
3353
- * bounds with animation" in one call.
3354
- *
3355
- * `animateToBounds` needs the current `View` and the current viewport
3356
- * dimensions to compute the target — both are passed as arguments so this
3357
- * hook stays a leaf (no canvas-size subscription, no `View` storage).
3358
- */
3359
- declare function useViewAnimation(setView: (v: View) => void): {
3360
- animateTo: (from: View, to: View, opts?: {
3361
- duration?: number;
3362
- easing?: (t: number) => number;
3363
- }) => void;
3364
- animateToBounds: (bounds: Bounds, currentView: View, viewportDims: ViewportDims, opts?: AnimateToBoundsOptions) => void;
3365
- cancel: () => void;
3366
- };
3367
-
3368
- interface ResizeController<TNode extends {
3369
- id: string;
3370
- } = {
3371
- id: string;
3372
- }, TPose = unknown> {
3373
- start(id: string, anchor: ResizeAnchor, worldX: number, worldY: number): void;
3374
- move(worldX: number, worldY: number, modifiers: ModifierState): boolean | void;
3375
- end(): void;
3376
- cancel(): void;
3377
- isResizing?: boolean;
3378
- overlay?: unknown;
3379
- adapter?: unknown;
3380
- readonly __posePhantom?: TPose;
3381
- readonly __nodePhantom?: TNode;
3382
- }
3383
- interface MoveController<TNode extends {
3384
- id: string;
3385
- } = {
3386
- id: string;
3387
- }, TPose = unknown> {
3388
- start(args: {
3389
- ids: string[];
3390
- worldX: number;
3391
- worldY: number;
3392
- clientX?: number;
3393
- clientY?: number;
3394
- }): void;
3395
- move(args: {
3396
- worldX: number;
3397
- worldY: number;
3398
- clientX?: number;
3399
- clientY?: number;
3400
- modifiers: ModifierState;
3401
- }): void;
3402
- end(): void;
3403
- cancel(): void;
3404
- adapter?: unknown;
3405
- readonly __posePhantom?: TPose;
3406
- readonly __nodePhantom?: TNode;
3407
- }
3408
- interface RotateController<TNode extends {
3409
- id: string;
3410
- } = {
3411
- id: string;
3412
- }, TPose = unknown> {
3413
- start(args: {
3414
- id: string;
3415
- worldX: number;
3416
- worldY: number;
3417
- }): void;
3418
- move(args: {
3419
- worldX: number;
3420
- worldY: number;
3421
- modifiers: ModifierState;
3422
- }): void;
3423
- end(): void;
3424
- cancel(): void;
3425
- readonly __posePhantom?: TPose;
3426
- readonly __nodePhantom?: TNode;
3427
- }
3428
- interface InsertController<TNode extends {
3429
- id: string;
3430
- } = {
3431
- id: string;
3432
- }, TPose = unknown> {
3433
- start(worldX: number, worldY: number, modifiers: ModifierState): void;
3434
- move(worldX: number, worldY: number, modifiers: ModifierState): void;
3435
- end(): void;
3436
- cancel(): void;
3437
- readonly __posePhantom?: TPose;
3438
- readonly __nodePhantom?: TNode;
3439
- }
3440
- interface AreaSelectController {
3441
- start(worldX: number, worldY: number, modifiers: ModifierState): void;
3442
- move(worldX: number, worldY: number, modifiers: ModifierState): void;
3443
- end(): void;
3444
- cancel(): void;
3445
- }
3446
-
3447
- /** Pointer event handlers ready to spread onto a `<canvas>`. */
3448
- interface PointerGestureBindings {
3449
- onPointerDown: (e: React__default.PointerEvent<HTMLCanvasElement>) => void;
3450
- onPointerMove: (e: React__default.PointerEvent<HTMLCanvasElement>) => void;
3451
- onPointerUp: (e: React__default.PointerEvent<HTMLCanvasElement>) => void;
3452
- onPointerCancel: (e: React__default.PointerEvent<HTMLCanvasElement>) => void;
3453
- onLostPointerCapture: (e: React__default.PointerEvent<HTMLCanvasElement>) => void;
3454
- }
3455
- /** Context object passed to body-hit / tap-empty callbacks. */
3456
- interface PointerGestureCallbackCtx {
3457
- event: React__default.PointerEvent<HTMLCanvasElement>;
3458
- worldX: number;
3459
- worldY: number;
3460
- modifiers: ModifierState;
3461
- }
3462
- /** Options for `usePointerGestures` — wires move/resize/rotate/insert/area-select controllers into a single canvas. */
3463
- interface UsePointerGesturesOptions<TMovePose, TResizePose> {
3464
- /** clientX/Y → world coords. Default: `clientToCanvas` (no pan/zoom). Apps
3465
- * with a viewport transform compose pan/zoom into this callback. */
3466
- clientToWorld?: (canvas: HTMLCanvasElement, clientX: number, clientY: number) => [number, number];
3467
- /** Live move interaction. Omit to disable body-drag dispatch. */
3468
- move?: MoveController<{
3469
- id: string;
3470
- }, TMovePose>;
3471
- /** Live resize interaction. Omit to disable handle-drag dispatch. */
3472
- resize?: ResizeController<{
3473
- id: string;
3474
- }, TResizePose>;
3475
- /** Live rotation interaction. Omit to disable rotation-handle dispatch. */
3476
- rotate?: RotateController<{
3477
- id: string;
3478
- }, TResizePose>;
3479
- /** Live insert interaction. When `tool === 'insert'`, an empty-space
3480
- * pointer-down dispatches to `insert.start` instead of `onTapEmpty`. */
3481
- insert?: InsertController<{
3482
- id: string;
3483
- }, unknown>;
3484
- /** Live area-select interaction. When `tool === 'select'` (or undefined),
3485
- * an empty-space pointer-down dispatches to `areaSelect.start` instead of
3486
- * `onTapEmpty`. */
3487
- areaSelect?: AreaSelectController;
3488
- /** Empty-space tool. Default `'select'`. Picks insert vs area-select on
3489
- * empty-space pointer-down. Ignored if neither controller is wired. */
3490
- tool?: 'select' | 'insert' | 'none';
3491
- /** Currently rotatable target. The hook positions the rotation handle
3492
- * above the (rotated) top-center of `bounds` and dispatches
3493
- * `rotate.start({ id, ... })` on hit.
3494
- *
3495
- * When omitted but `rotate`, `selection`, and `boundsOf` are all
3496
- * supplied, defaults to single-selection bounds (multi → null). */
3497
- rotateTarget?: () => {
3498
- id: string;
3499
- bounds: Bounds;
3500
- rotation?: number;
3501
- } | null;
3502
- /** World-pixel distance from the top edge to the rotation handle.
3503
- * Default `DEFAULT_ROTATION_HANDLE_DISTANCE`. */
3504
- rotationHandleDistance?: number;
3505
- /** Currently resizable target. Hook computes corner handles, hit-tests, and
3506
- * dispatches `resize.start(id, anchor, ...)`. Return `null` for none.
3507
- *
3508
- * When omitted but `selection` and `boundsOf` are both supplied, defaults
3509
- * to single-selection bounds (multi-selection returns `null`). */
3510
- resizeTarget?: () => {
3511
- id: string;
3512
- bounds: Bounds;
3513
- rotation?: number;
3514
- } | null;
3515
- /** Hit-test radius for resize/rotation handles, in **screen** pixels.
3516
- * Divided by `view.scale` (via `getView`) at compare time so the hit
3517
- * area matches the visually rendered handle size under zoom.
3518
- * Default 8. */
3519
- handleHitRadius?: number;
3520
- /** Returns the current view. Used to convert `handleHitRadius` (screen px)
3521
- * into world units at hit-test time. Defaults to identity (scale=1) so
3522
- * legacy callers retain world-px semantics until they wire a view. */
3523
- getView?: () => View;
3524
- /** Body hit-test for starting a move. Return id(s) to drag, or `null` to
3525
- * fall through to `onTapEmpty`. */
3526
- pickEvery?: (worldX: number, worldY: number) => string | string[] | null;
3527
- /** Selection api (see {@link SelectionApi}). When supplied, the hook wires
3528
- * selection-driven defaults: `onBodyHit` defaults to `selection.applyClick`
3529
- * for the first hit id, `onTapEmpty` defaults to `selection.clear`, and
3530
- * body-drag promotes-then-drags (clicking an unselected obj selects it
3531
- * first, then drags the resulting selection; clicking a selected obj
3532
- * drags the entire selection). Explicit callbacks override these defaults. */
3533
- selection?: SelectionApi;
3534
- /** Per-id bounds lookup. Combined with `selection`, defaults `resizeTarget`
3535
- * to single-selection bounds when `resizeTarget` is not explicitly passed. */
3536
- boundsOf?: (id: string) => Bounds | null;
3537
- /** Called whenever a body hit occurs. Fires regardless of whether `move` is
3538
- * wired — selection-only callers still receive notifications. */
3539
- onBodyHit?: (ids: string[], ctx: PointerGestureCallbackCtx) => void;
3540
- /** Called when the pointer hits neither a handle nor a body. Defaults to
3541
- * `selection.clear()` when `selection` is supplied. */
3542
- onTapEmpty?: (ctx: PointerGestureCallbackCtx) => void;
3543
- /** Optional debug sink for the overlay subsystem. When supplied, body
3544
- * hit-test results record one `recordHitbox(id, 'body', rect)` per id
3545
- * that `pickEvery` returns — visualises which body shapes the kit
3546
- * evaluated as hit. Tree-shakes when omitted (optional-chain). */
3547
- debug?: DebugSink;
1903
+ friction?: number;
1904
+ minSpeed?: number;
1905
+ /** Bounds for boundary clamping. Requires `boundary` to take effect. */
1906
+ viewBounds?: PanBounds;
1907
+ /**
1908
+ * What to do when the accumulated position hits `viewBounds`. Default: no clamping.
1909
+ * - `'stop'`: clamp at boundary, kill velocity component.
1910
+ * - `'bounce'`: linear reflection — flip velocity sign, magnitude preserved.
1911
+ * - `'spring'`: damped reflection — flip velocity sign and shrink magnitude
1912
+ * by `SPRING_DAMPING` per bounce so the motion settles naturally.
1913
+ */
1914
+ boundary?: 'stop' | 'bounce' | 'spring';
1915
+ /** Starting position for internal boundary tracking. Required when `viewBounds` is set. */
1916
+ initialPosition?: {
1917
+ x: number;
1918
+ y: number;
1919
+ };
1920
+ onTick: (dx: number, dy: number) => void;
1921
+ onEnd?: () => void;
3548
1922
  }
1923
+ declare function useDecayLoop(): {
1924
+ start: (config: DecayLoopConfig) => void;
1925
+ cancel: () => void;
1926
+ };
1927
+
1928
+ declare function useViewTween(setView: (v: View) => void): {
1929
+ animateTo: (from: View, to: View, opts?: {
1930
+ duration?: number;
1931
+ easing?: (t: number) => number;
1932
+ }) => void;
1933
+ cancel: () => void;
1934
+ isAnimating: React$1.RefObject<boolean>;
1935
+ };
1936
+
3549
1937
  /**
3550
- * Pointer-event dispatcher that wires `useMove` + `useResize` to a canvas.
3551
- * Owns the four `onPointer*` handlers, modifier extraction, world-coord
3552
- * conversion, pointer capture, and the handle-vs-body dispatch decision.
3553
- *
3554
- * Caller still owns selection state, what counts as a body, and which
3555
- * object (if any) is currently resizable. Pass a `selection` from
3556
- * {@link useSelection} to opt into the standard click-promote-drag flow
3557
- * with no extra wiring.
1938
+ * Detects two-finger pinch on a canvas element.
3558
1939
  *
3559
- * Spread the returned bindings onto a `<canvas>`:
3560
- * ```tsx
3561
- * const bindings = usePointerGestures({ move, resize, pickEvery, selection });
3562
- * return <canvas ref={canvasRef} {...bindings} />;
3563
- * ```
1940
+ * @param onPinch Called with the gesture midpoint in **client coordinates** and
1941
+ * a per-frame scale factor (ratio of current distance to previous frame's
1942
+ * distance, not cumulative from gesture start). With 3+ fingers, the first
1943
+ * two by insertion order are tracked.
3564
1944
  */
3565
- declare function usePointerGestures<TMovePose, TResizePose>(options: UsePointerGesturesOptions<TMovePose, TResizePose>): PointerGestureBindings;
1945
+ declare function usePinchGesture(canvasRef: React.RefObject<HTMLCanvasElement | null>, onPinch: (anchor: {
1946
+ x: number;
1947
+ y: number;
1948
+ }, scaleFactor: number) => void, enabled?: boolean): void;
3566
1949
 
1950
+ /** Options accepted by {@link useViewAnimation}'s `animateToBounds`. */
1951
+ interface AnimateToBoundsOptions extends FitViewToBoundsOptions {
1952
+ /** Tween duration in ms (forwarded to `animateTo`). */
1953
+ duration?: number;
1954
+ /** Tween easing (forwarded to `animateTo`). */
1955
+ easing?: (t: number) => number;
1956
+ }
3567
1957
  /**
3568
- * Identity helper for declaring a `Tool`. Exists for TypeScript inference:
3569
- * passing the spec directly to a generic site loses `TScratch` inference, so
3570
- * authors would have to spell out the type argument.
1958
+ * Tween the viewport `View` between values. Wraps `useViewTween`'s `animateTo`
1959
+ * + `cancel`, and adds an `animateToBounds` convenience that composes
1960
+ * `fitViewToBounds` with the existing tween so consumers can say "zoom to this
1961
+ * bounds with animation" in one call.
3571
1962
  *
3572
- * const pen = defineTool({
3573
- * id: 'pen',
3574
- * initScratch: () => ({ anchors: [] as Point[] }),
3575
- * drag: { onMove: (e, ctx) => { ctx.scratch.anchors.push(...); return 'claim'; } },
3576
- * });
1963
+ * `animateToBounds` needs the current `View` and the current viewport
1964
+ * dimensions to compute the target — both are passed as arguments so this
1965
+ * hook stays a leaf (no canvas-size subscription, no `View` storage).
3577
1966
  */
3578
- declare function defineTool<TScratch = undefined>(spec: Tool<TScratch>): Tool<TScratch>;
1967
+ declare function useViewAnimation(setView: (v: View) => void): {
1968
+ animateTo: (from: View, to: View, opts?: {
1969
+ duration?: number;
1970
+ easing?: (t: number) => number;
1971
+ }) => void;
1972
+ animateToBounds: (bounds: Bounds, currentView: View, viewportDims: ViewportDims, opts?: AnimateToBoundsOptions) => void;
1973
+ cancel: () => void;
1974
+ };
3579
1975
 
3580
1976
  interface UseToolsOptions {
3581
1977
  /** Initial active-slot tool id. Must exist in `registry`. */
@@ -3586,40 +1982,17 @@ interface UseToolsOptions {
3586
1982
  registry: Record<string, AnyTool>;
3587
1983
  /** Always-on tools — listen continuously regardless of active slot. */
3588
1984
  ambient?: AnyTool[];
3589
- /** Optional fallback tool consulted ONLY for `pointer.onClick` when the
3590
- * active tool's click handler returns `'pass'` (or has no handler).
3591
- * Lets a non-select tool stay active while unhandled clicks fall through
3592
- * to the select tool for click-to-select. Not consulted for pointerdown,
3593
- * drag, keyboard, wheel, or dblTap. */
3594
- fallback?: AnyTool;
3595
- /** Per-event base ctx supplier. `<Canvas>` wires this to inject world
3596
- * coords, modifiers, selection, adapter, applyOps. Tests can supply
3597
- * a stub. Optional — the dispatcher works with a default empty ctx
3598
- * for tests that don't need the wiring. */
3599
- getCtx?: (overrides?: {
3600
- clientX?: number;
3601
- clientY?: number;
3602
- modifiers?: {
3603
- alt: boolean;
3604
- shift: boolean;
3605
- meta: boolean;
3606
- ctrl: boolean;
3607
- };
3608
- }) => Omit<ToolCtx, 'scratch'>;
3609
- /** Optional scene hit-test for populating `ctx.target` on pointer events.
3610
- * `<Canvas>` wires this from the effective `pickEvery` + adapter. Tests
3611
- * may supply a stub; omit for the always-empty fallback. */
3612
- getNodeAtPoint?: ToolsDispatcherOptions['getNodeAtPoint'];
3613
1985
  }
3614
1986
  interface ToolsApi {
3615
1987
  /** Current active-slot tool id. */
3616
1988
  active: string;
3617
- /** Set the active-slot tool. Cancels any in-flight gesture. */
1989
+ /** Set the active-slot tool. The gesture dispatcher watches the active
1990
+ * tool and cancels any in-flight handle itself. */
3618
1991
  setActive: (id: string) => void;
3619
1992
  /** Currently hotkey-engaged tool id (or `null`). Derived as the top of
3620
1993
  * the hotkey stack for backwards compat with the pre-stack API. */
3621
1994
  hotkeyEngaged: string | null;
3622
- /** Engage a hotkey-slot tool by id. No-op if a gesture is in flight. */
1995
+ /** Engage a hotkey-slot tool by id. */
3623
1996
  engageHotkey: (id: string) => void;
3624
1997
  /** Disengage the hotkey-slot tool, if any. */
3625
1998
  disengageHotkey: () => void;
@@ -3627,13 +2000,6 @@ interface ToolsApi {
3627
2000
  ambient: readonly AnyTool[];
3628
2001
  /** Full registry — for userland UI (palette buttons, etc.). */
3629
2002
  registry: Readonly<Record<string, AnyTool>>;
3630
- /** The dispatcher `<Canvas>` wires to its DOM events. */
3631
- dispatcher: ToolsDispatcher;
3632
- /** Increments whenever an in-flight gesture starts, transitions phase, or
3633
- * ends. Consumers (e.g. `<Canvas>` cursor resolution) include this in
3634
- * their render deps to re-evaluate derived state on real DOM events
3635
- * rather than waiting for an unrelated re-render. */
3636
- gestureTick: number;
3637
2003
  /** Returns true if a tool with the given id is in the registry or ambient list. */
3638
2004
  has(id: string): boolean;
3639
2005
  /** All overlay layers from currently-engaged tools (active slot, hotkey
@@ -3643,7 +2009,12 @@ interface ToolsApi {
3643
2009
  getActiveOverlays(): RenderLayer<unknown>[];
3644
2010
  }
3645
2011
  /**
3646
- * Manages the active tool, hotkey slot, and gesture dispatcher.
2012
+ * Manages the active tool and hotkey slot.
2013
+ *
2014
+ * It used to also own a dispatcher: `useTools` constructed the tool-routing
2015
+ * dispatcher and `<Canvas>` pumped DOM events into it. Input now belongs
2016
+ * entirely to `useGestureDispatcher`, so what's left here is slot state plus
2017
+ * the overlay roll-up.
3647
2018
  *
3648
2019
  * Requires `<ActiveToolContextProvider>` (or `<WeaselProvider>` /
3649
2020
  * `<SceneCanvas>`, which mount one internally) in scope: active/hotkey state
@@ -3664,10 +2035,20 @@ interface UseKeybindingsOptions {
3664
2035
  * `tools.active` was when the hook first ran (i.e. the initial active
3665
2036
  * tool). Pass `null` to disable Escape-returns-to-default behavior. */
3666
2037
  defaultTool?: string | null;
2038
+ /**
2039
+ * Gate for keyboard tool activation: return false to refuse a tool the
2040
+ * active mode doesn't allow. `<SceneCanvas>` wires this from
2041
+ * `getActiveMode` + each tool's `capabilities`, using the same predicate
2042
+ * `ToolPalette` uses to grey a button out — so the grey-out becomes a
2043
+ * guarantee rather than a hint.
2044
+ *
2045
+ * Omit for consumers with no mode registry: every tool stays activatable.
2046
+ */
2047
+ isToolEligible?: (toolId: string) => boolean;
3667
2048
  }
3668
2049
  declare function useKeybindings(tools: ToolsApi, options?: UseKeybindingsOptions): void;
3669
2050
 
3670
- type ToolPrefKind = 'number' | 'boolean' | 'string' | 'enum' | 'color';
2051
+ type ToolPrefKind = 'number' | 'boolean' | 'string' | 'enum' | 'color' | 'paint';
3671
2052
  interface ToolPrefBase<K extends string, Value> {
3672
2053
  kind: K;
3673
2054
  /** Human-readable label. */
@@ -3734,7 +2115,22 @@ interface ToolPrefColor extends ToolPrefBase<'color', string> {
3734
2115
  */
3735
2116
  interface ToolPrefCustom extends ToolPrefBase<string, unknown> {
3736
2117
  }
3737
- type ToolPref = ToolPrefNumber | ToolPrefBoolean | ToolPrefString | ToolPrefEnum | ToolPrefColor;
2118
+ /**
2119
+ * A whole `FillStyle`, not a color inside one. Use it wherever the value is
2120
+ * the tagged paint union — a solid color, a pattern, a gradient — rather
2121
+ * than a hex string.
2122
+ *
2123
+ * Addressing `…fill.color` instead reads `undefined` off a gradient (so the
2124
+ * control shows its default and claims the text is black) and writes a
2125
+ * hybrid `{ fill: 'gradient', stops, color }` that the renderer's structural
2126
+ * `'color' in paint` checks then paint flat solid. The union has to be
2127
+ * edited as a union.
2128
+ */
2129
+ interface ToolPrefPaint extends ToolPrefBase<'paint', unknown> {
2130
+ /** Offer an opacity control alongside the color. */
2131
+ alpha?: boolean;
2132
+ }
2133
+ type ToolPref = ToolPrefNumber | ToolPrefBoolean | ToolPrefString | ToolPrefEnum | ToolPrefColor | ToolPrefPaint;
3738
2134
  /** Built-in or app-defined leaf. */
3739
2135
  type ToolPrefLeaf = ToolPref | ToolPrefCustom;
3740
2136
  /** Nestable group: branch nodes a tool can use to organize its prefs. */
@@ -3744,61 +2140,7 @@ interface ToolPrefGroup {
3744
2140
  children: Record<string, ToolPrefLeaf | ToolPrefGroup>;
3745
2141
  }
3746
2142
 
3747
- interface InsertOverlayStyle {
3748
- fill?: string;
3749
- stroke?: string;
3750
- dash?: number[];
3751
- lineWidth?: number;
3752
- }
3753
-
3754
- interface UseInsertToolOptions<TPose, TNode extends {
3755
- id: string;
3756
- } = {
3757
- id: string;
3758
- }> extends UseInsertOptions<TPose, TNode> {
3759
- overlayStyle?: InsertOverlayStyle;
3760
- /** Hit-test gate consulted before insertion. On hit, selects via
3761
- * ctx.selection.set and skips both the click and drag paths. */
3762
- hitExisting?: (point: {
3763
- x: number;
3764
- y: number;
3765
- }) => string | string[] | null;
3766
- }
3767
- /** Drag-to-insert Tool. The legacy `useInsert` hook is
3768
- * no longer consumed here — the gesture is owned end-to-end by the
3769
- * dispatcher's `insertAction` (see `src/interactions/actions/defaults/insert.ts`),
3770
- * which requires the `insert` dep. `SceneCanvas` registers that dep via
3771
- * `useInsertDepSource`; consumers using bare `<Canvas>` must wire it themselves.
3772
- *
3773
- * Note: this tool is now a thin declarative wrapper — no local route-table
3774
- * drag handler, no local insert overlay layer, no click-to-insert path.
3775
- * Live-preview marquee and click-to-insert are dispatcher-side features
3776
- * deferred to a later phase (see insertAction's "What this does NOT wire"
3777
- * comment). The `overlayStyle` / `hitExisting` / `pointInsert` options
3778
- * remain on the option surface for forward-compat but are currently
3779
- * ignored. */
3780
- declare function useInsertTool<TNode extends {
3781
- id: string;
3782
- }, TPose>(_adapter: InsertAdapter<TNode>, _options?: UseInsertToolOptions<TPose, TNode>): Tool<undefined>;
3783
-
3784
- /** Legacy style hooks for the area-select marquee. The dispatcher
3785
- * overlay layer (`useDispatcherOverlayLayer`) now owns marquee paint; this
3786
- * type is retained on the option surface for source-compat with callers
3787
- * threading it through. */
3788
- interface AreaSelectOverlayStyle {
3789
- fill?: string;
3790
- stroke?: string;
3791
- dash?: number[];
3792
- lineWidth?: number;
3793
- }
3794
- /** Legacy style hook for the move ghost. The preview-ghost layer
3795
- * renders moved silhouettes via `drawOne` now. Retained for source-compat. */
3796
- interface MoveOverlayStyle {
3797
- ghostAlpha?: number;
3798
- }
3799
- interface UseSelectToolOptions<TNode extends {
3800
- id: string;
3801
- }, TPose> {
2143
+ interface UseSelectToolOptions<TPose> {
3802
2144
  /** Return ids of all objects whose painted body covers (worldX, worldY).
3803
2145
  * Order doesn't matter — the tool collapses parent/child overlap via
3804
2146
  * `pickTopMostHit`. When omitted, defaults to a rect AABB-vs-point scan
@@ -3809,8 +2151,6 @@ interface UseSelectToolOptions<TNode extends {
3809
2151
  /** Optional alt-aware selection-update hit returning the single id the
3810
2152
  * click should act on. */
3811
2153
  pickBest?: (worldX: number, worldY: number, alt: boolean, selection: readonly string[]) => string | null;
3812
- /** Return the world-space bounds of `id`, or null if not found. */
3813
- boundsOf?: (id: string) => Bounds | null;
3814
2154
  /** Project a pose to its AABB. Default: identity. */
3815
2155
  poseBounds?: (pose: TPose) => Bounds;
3816
2156
  /** Move-action options. The move gesture is dispatcher-routed,
@@ -3818,36 +2158,23 @@ interface UseSelectToolOptions<TNode extends {
3818
2158
  * `opts.behaviors`. Other `UseMoveOptions` fields are accepted for API shape
3819
2159
  * but not read by this tool. */
3820
2160
  move?: UseMoveOptions<TPose>;
3821
- /** Legacy `useAreaSelect` options. Ignored now —
3822
- * `areaSelectAction` configuration moved to the action registration. */
3823
- areaSelect?: unknown;
2161
+ /**
2162
+ * When this returns true, a Shift/Meta extend-click must NOT change the
2163
+ * node selection.
2164
+ *
2165
+ * `<SceneCanvas>` wires it to "a path is in anchor-edit mode", where
2166
+ * Shift-click means "add this anchor to the anchor selection". Without
2167
+ * the lock the same click also toggles the edited node out of the node
2168
+ * selection, and the `editAnchors` dep treats that as the edit target
2169
+ * disappearing — so multi-selecting anchors silently exited edit mode.
2170
+ *
2171
+ * Only extend-clicks are locked. A plain click still re-selects, so
2172
+ * clicking a different node exits edit mode as usual.
2173
+ */
2174
+ extendClickLocked?: () => boolean;
3824
2175
  /** Optional debug sink. Reserved for future overlay/affordance hitbox
3825
2176
  * recording. */
3826
2177
  debug?: DebugSink;
3827
- /** Style for the area-select marquee. Pre-14e wired the legacy overlay;
3828
- * after T3 it's consumed by the dispatcher overlay layer via
3829
- * `useDispatcherOverlayLayer`'s `style` arg (not by this tool). Retained
3830
- * for option-surface compat. */
3831
- areaSelectOverlayStyle?: AreaSelectOverlayStyle;
3832
- /** Style for the move ghost. Pre-14e wired the legacy overlay's alpha;
3833
- * preview-ghost layer alpha now lives in `usePreviewGhostLayer`. */
3834
- moveOverlayStyle?: MoveOverlayStyle;
3835
- /** Consumer's draw function for ghost objects. Pre-14e the select tool
3836
- * invoked this from its own overlay; the preview-ghost layer now calls
3837
- * the scene slot's `drawOne` directly, so this option is no longer in
3838
- * the runtime path. */
3839
- drawGhost?: (obj: TNode | null, pose: TPose, view: {
3840
- x: number;
3841
- y: number;
3842
- scale: {
3843
- x: number;
3844
- y: number;
3845
- };
3846
- }) => DrawCommand[];
3847
- /** Object lookup paired with `drawGhost`. Pre-14e runtime hook; vestigial. */
3848
- getNode?: (id: string) => TNode | null;
3849
- /** Returns the live selection ids. */
3850
- getSelection?: () => readonly string[];
3851
2178
  /** Reparent-on-drop behavior for drag-to-move. `'off'` (default)
3852
2179
  * preserves translate-only commits. `'top'` lands the moved nodes at
3853
2180
  * the top of the container under the drop point. `'above'` lands them
@@ -3855,33 +2182,16 @@ interface UseSelectToolOptions<TNode extends {
3855
2182
  * semantics when the hit is itself a container). Requires the
3856
2183
  * `nodeAtPoint` dep to be registered (sourced by `<SceneCanvas>`). */
3857
2184
  reparentOnDrop?: 'off' | 'top' | 'above';
3858
- /** Optional double-tap hook. */
3859
- onDoubleTap?: (args: {
3860
- worldX: number;
3861
- worldY: number;
3862
- ids: string[];
3863
- event: PointerEvent;
3864
- }) => void;
3865
2185
  }
3866
2186
  /** Intersection of the move + area-select adapter interfaces.
3867
2187
  * Resize / rotate adapters moved to `useResizeTool` / `useRotateTool`. */
3868
2188
  type SelectAdapter<TNode extends {
3869
2189
  id: string;
3870
2190
  }, TPose> = MoveAdapter<TNode, TPose> & AreaSelectAdapter;
3871
- /** @internal */
3872
- type SelectScratch = {
3873
- kind: 'idle';
3874
- } | {
3875
- kind: 'move';
3876
- ids: string[];
3877
- deferredClickId: string | null;
3878
- } | {
3879
- kind: 'area';
3880
- };
3881
2191
  /** Active-slot Tool:
3882
- * - pointerDown classifier still runs the tool's own pickBest/pickEvery
3883
- * so click semantics (selection replace, extend, deferred collapse) are
3884
- * preserved across the same code path.
2192
+ * - `select.pick` classifies the press (pointerDown) it runs the tool's own
2193
+ * pickBest/pickEvery so click semantics (selection replace, extend,
2194
+ * deferred collapse) live in one place.
3885
2195
  * - drag is owned exclusively by the dispatcher via `Tool.bindings`
3886
2196
  * (selected-body → moveAction, rotate-handle → rotateAction, handle:* →
3887
2197
  * resizeAction, empty → areaSelectAction, click on empty → clearSelection).
@@ -3891,81 +2201,7 @@ type SelectScratch = {
3891
2201
  */
3892
2202
  declare function useSelectTool<TNode extends {
3893
2203
  id: string;
3894
- }, TPose>(adapter: SelectAdapter<TNode, TPose>, options: UseSelectToolOptions<TNode, TPose>): Tool<SelectScratch>;
3895
-
3896
- /** @internal */
3897
- interface HitAdapter<TNode extends {
3898
- id: string;
3899
- }, TPose> {
3900
- getNode: (id: string) => TNode | undefined;
3901
- getNodes: () => TNode[];
3902
- getPose: (id: string) => TPose;
3903
- getParent: (id: string) => string | null;
3904
- }
3905
- interface NestedHitOpts<TNode extends {
3906
- id: string;
3907
- }, TPose> {
3908
- /** Compose a child's local pose into world coords given its parent's world
3909
- * pose. Same shape as `composeRectPose` (the default expectation). */
3910
- composePose: (parent: TPose, child: TPose) => TPose;
3911
- /** Derive an axis-aligned bounding rect from a (world-space) pose. Default
3912
- * reads `x` / `y` / `width` / `height` straight off the pose (matches
3913
- * `RectPose`). Override for non-rect poses (e.g. paths). */
3914
- poseBounds?: (pose: TPose) => Bounds;
3915
- /** Predicate for "this object is a nesting parent body". The leaf scan
3916
- * skips objects for which this returns true so a click on a parent's
3917
- * painted body resolves to a child leaf, not the parent itself. Default:
3918
- * never (treat every object as hittable). */
3919
- isGroup?: (id: string, obj: TNode | undefined) => boolean;
3920
- }
3921
- interface NestedHitTester {
3922
- /** Outermost-ancestor pick. Suitable as the chrome-level `pickEvery`: a
3923
- * casual click selects the whole top-level ancestor. Returns `null` on
3924
- * empty space. */
3925
- pickOutermost: (worldX: number, worldY: number) => string | null;
3926
- /** Alt-aware selection-update pick. Without `alt`, returns the outermost
3927
- * ancestor (same as `pickOutermost`). With `alt`, returns one level
3928
- * deeper than the deepest currently-selected ancestor in the leaf's
3929
- * chain — repeated alt-clicks step ancestor → descendant → leaf. With
3930
- * `alt` and nothing in the chain selected, jumps straight to the leaf.
3931
- * Plug into `useSelectTool({ pickBest })`. */
3932
- pickBest: (worldX: number, worldY: number, alt: boolean, selection: readonly string[]) => string | null;
3933
- }
3934
- declare function nestedHitTester<TNode extends {
3935
- id: string;
3936
- }, TPose>(adapter: HitAdapter<TNode, TPose>, opts: NestedHitOpts<TNode, TPose>): NestedHitTester;
3937
-
3938
- /** Adapter contract: SelectAdapter (Move + AreaSelect) plus the lookups
3939
- * `nestedHitTester` needs (`getParent` + the existing `getNode`/`getNodes`/`getPose`). */
3940
- type NestedSelectAdapter<TNode extends {
3941
- id: string;
3942
- }, TPose> = SelectAdapter<TNode, TPose> & {
3943
- getNode: (id: string) => TNode | undefined;
3944
- getNodes: () => TNode[];
3945
- getParent: (id: string) => string | null;
3946
- };
3947
- interface UseNestedSelectToolOptions<TNode extends {
3948
- id: string;
3949
- }, TPose> extends NestedHitOpts<TNode, TPose>, Omit<UseSelectToolOptions<TNode, TPose>, 'pickBest'> {
3950
- }
3951
- /**
3952
- * Convenience over `useSelectTool` for scenes with nesting. Builds a
3953
- * `nestedHitTester` from `composePose` + `isGroup` and wires its `pickBest`
3954
- * into the select tool — alt-clicks drill outermost ancestor → child → leaf
3955
- * automatically. Casual clicks select the outermost ancestor.
3956
- *
3957
- * Equivalent to writing:
3958
- *
3959
- * ```tsx
3960
- * const hitter = useMemo(() => nestedHitTester(adapter, { composePose, isGroup }), [adapter]);
3961
- * const select = useSelectTool(adapter, { pickBest: (...args) => hitter.pickBest(...args) });
3962
- * ```
3963
- *
3964
- * but with one fewer concept on the consumer surface.
3965
- */
3966
- declare function useNestedSelectTool<TNode extends {
3967
- id: string;
3968
- }, TPose>(adapter: NestedSelectAdapter<TNode, TPose>, options: UseNestedSelectToolOptions<TNode, TPose>): ReturnType<typeof useSelectTool<TNode, TPose>>;
2204
+ }, TPose>(adapter: SelectAdapter<TNode, TPose>, options: UseSelectToolOptions<TPose>): Tool<null>;
3969
2205
 
3970
2206
  interface UseRotateToolOptions<TNode extends {
3971
2207
  id: string;
@@ -4098,40 +2334,16 @@ interface HandScratch {
4098
2334
  */
4099
2335
  declare function useHandTool(opts?: UseHandToolOptions): Tool<HandScratch | null>;
4100
2336
 
4101
- interface UseTextToolOptions<TNode extends {
4102
- id: string;
4103
- }> {
4104
- pointInsert: (point: {
4105
- x: number;
4106
- y: number;
4107
- }) => TNode | null;
4108
- commitInsert?: InsertAdapter<TNode>['commitInsert'];
4109
- hitExisting?: (point: {
4110
- x: number;
4111
- y: number;
4112
- }) => string | string[] | null;
4113
- minBounds?: {
4114
- width: number;
4115
- height: number;
4116
- };
4117
- marqueeStyle?: InsertOverlayStyle;
4118
- }
4119
- /** Text Tool. The legacy `useInsert` hook is dropped — drag-rect
4120
- * insertion is owned end-to-end by the dispatcher's `insertAction` (kind:
4121
- * 'text'); click-on-selected-text-node enters edit mode via the
4122
- * `enterTextEdit` action.
4123
- *
4124
- * Consumers must register both the `insert` dep (e.g. through `SceneCanvas`'s
4125
- * `useInsertDepSource`) and the `textEdit` dep (`enterTextEditAction`'s
4126
- * contract — see `src/interactions/actions/defaults/enterTextEdit.ts`).
4127
- *
4128
- * Note: `pointInsert`, `hitExisting`, `marqueeStyle`, `minBounds` remain on
4129
- * the option surface for forward-compat but are currently ignored — they map
4130
- * onto dispatcher-side features that are wired through dep sources, not
4131
- * through the tool record. */
4132
- declare function useTextTool<TNode extends {
4133
- id: string;
4134
- }>(_options: UseTextToolOptions<TNode>): Tool<undefined>;
2337
+ /** Text tool. Drag-rect insertion is owned end-to-end by the dispatcher's
2338
+ * `insertAction` (kind: `'text'`); click-on-selected-text-node enters edit
2339
+ * mode via the `enterTextEdit` action.
2340
+ *
2341
+ * Consumers must register both the `insert` dep (e.g. through
2342
+ * `SceneCanvas`'s `useInsertDepSource`) and the `textEdit` dep
2343
+ * (`enterTextEditAction`'s contract see
2344
+ * `src/interactions/actions/defaults/enterTextEdit.ts`). Custom node
2345
+ * factories and hit gating belong on those deps, not on the tool. */
2346
+ declare function useTextTool(): Tool<undefined>;
4135
2347
 
4136
2348
  interface PinchZoomToolOpts {
4137
2349
  min?: number;
@@ -4147,51 +2359,13 @@ interface PinchZoomToolOpts {
4147
2359
  */
4148
2360
  declare function usePinchZoomTool(canvasRef: React.RefObject<HTMLCanvasElement | null>, view: View, setView: (v: View) => void, opts?: PinchZoomToolOpts): void;
4149
2361
 
4150
- /**
4151
- * Counts the path anchors used by the per-anchor coloring surface. An
4152
- * "anchor" is the destination point of a path command: M, L, C, Q each
4153
- * contribute one (the (x, y) where the pen ends up); Z contributes none
4154
- * (it closes back to the subpath's first M). RectPath has four implicit
4155
- * anchors — the corners — matching its M/L/L/L/Z stroke tessellation.
4156
- *
4157
- * Consumers use this to size their per-anchor color array; the renderer
4158
- * uses it to validate the array length in dev builds.
4159
- */
4160
-
4161
- interface PenAnchor$1 {
4162
- x: number;
4163
- y: number;
4164
- inHandle?: {
4165
- x: number;
4166
- y: number;
4167
- };
4168
- outHandle?: {
4169
- x: number;
4170
- y: number;
4171
- };
4172
- }
4173
- /**
4174
- * Derive a per-subpath anchor model from a PolygonPath. Subpaths split on
4175
- * every `M` command; a subpath is closed iff it ends with `Z`.
4176
- *
4177
- * Cubic-segment control points become the outHandle of the previous anchor
4178
- * and the inHandle of the next anchor. Quadratic segments are upgraded to
4179
- * cubics (each control reused for both adjacent handles) — this loses no
4180
- * geometry. Linear segments produce anchors with no handles.
4181
- */
4182
- declare function pathToAnchors(path: PolygonPath): {
4183
- anchors: PenAnchor$1[][];
4184
- closed: boolean[];
4185
- };
4186
- declare function countPathAnchors(path: Path): number;
4187
-
4188
2362
  /**
4189
2363
  * In-progress pen anchor. `outHandle` is set when the anchor was placed via
4190
2364
  * click-drag (smooth anchor); undefined for click-placed corners.
4191
2365
  * `inHandle` is mirrored from the previous anchor's outHandle on segment
4192
2366
  * emission unless `altBroken` is set on that previous anchor.
4193
2367
  */
4194
- interface PenAnchor {
2368
+ interface PenAnchor$1 {
4195
2369
  x: number;
4196
2370
  y: number;
4197
2371
  outHandle?: {
@@ -4207,57 +2381,32 @@ interface PenAnchor {
4207
2381
  altBroken?: boolean;
4208
2382
  }
4209
2383
  interface PenSubpath {
4210
- anchors: PenAnchor[];
2384
+ anchors: PenAnchor$1[];
4211
2385
  closed: boolean;
4212
2386
  }
4213
- /** Edit-mode state. `anchors` uses the kit's PenAnchor (committed/derived
4214
- * geometry); `altBroken` has no post-commit meaning and is correctly absent. */
4215
- /** @internal */
4216
- interface PenEditState {
4217
- objId: string;
4218
- anchors: PenAnchor$1[][];
4219
- closed: boolean[];
4220
- selectedAnchors: Set<string>;
4221
- activeHandle: {
4222
- sub: number;
4223
- anchor: number;
4224
- side: 'in' | 'out';
4225
- } | null;
4226
- dirty: boolean;
4227
- preConvert: {
4228
- path: unknown;
4229
- closed: boolean;
4230
- params: unknown;
4231
- } | null;
4232
- /** Snapshot of the path-as-it-was at the start of the current gesture
4233
- * (drag, click, or nudge keystroke). Used as the `from` of the
4234
- * SetPathOp emitted on gesture completion so each pushed entry rewinds
4235
- * only its own gesture, not the whole edit session. Null between
4236
- * gestures. */
4237
- gestureBaseline: {
4238
- path: unknown;
4239
- closed: boolean;
4240
- params: unknown;
4241
- } | null;
4242
- /** In-flight marquee rect (world-space). Null when not dragging. */
4243
- marquee: {
4244
- x0: number;
4245
- y0: number;
4246
- x1: number;
4247
- y1: number;
4248
- additive: boolean;
4249
- } | null;
4250
- }
4251
- /** Mutable scratch shared across pen-tool gestures. The hook keeps a stable
4252
- * reference to a single instance and `initScratch` returns it on every call,
4253
- * so click-by-click state survives gesture boundaries and the preview layer
4254
- * can read the same object. */
2387
+ /**
2388
+ * Mutable scratch shared across pen-tool gestures. The hook keeps a stable
2389
+ * reference to a single instance and `initScratch` returns it on every call,
2390
+ * so click-by-click state survives gesture boundaries and the preview layer
2391
+ * can read the same object.
2392
+ *
2393
+ * ## The pen creates paths and nothing else
2394
+ *
2395
+ * Reshaping an existing path is anchor editing: double-click a path to
2396
+ * enter edit mode, then use the `editAnchors` / `insertPathAnchor` /
2397
+ * `nudgeAnchors` / `deleteAnchors` / `marqueeAnchors` / `cutPathAtAnchor`
2398
+ * Actions.
2399
+ *
2400
+ * The pen used to carry a second, private implementation of all of that,
2401
+ * with its own scratch mode, hit-test override, overlay, and undo
2402
+ * plumbing. It was reachable only through a `getPathObj` option whose
2403
+ * contract required `pose.kind` to be `'polygon'` or `'rect'` — which no
2404
+ * kit-created node has, including the ones the pen itself creates. So it
2405
+ * was dead in every consumer while shadowing the live implementation, and
2406
+ * the two had drifted. Its geometry now lives in
2407
+ * `features/paths/anchorEdits.ts`, driven by those Actions.
2408
+ */
4255
2409
  interface PenScratch {
4256
- /** Whether the pen is in create (draw new path) or edit (reshape existing
4257
- * path) mode. Defaults to 'create'. */
4258
- mode: 'create' | 'edit';
4259
- /** Edit-mode state, populated when `mode === 'edit'`. Null in create mode. */
4260
- edit: PenEditState | null;
4261
2410
  finishedSubpaths: PenSubpath[];
4262
2411
  current: PenSubpath | null;
4263
2412
  cursor: {
@@ -4266,23 +2415,6 @@ interface PenScratch {
4266
2415
  } | null;
4267
2416
  draggingHandleAt: number | null;
4268
2417
  closeHintActive: boolean;
4269
- /** Pointer-down world coords + modifiers, captured on every pointer.onDown.
4270
- * Used by drag.onStart (anchor lands at the down coords, not the
4271
- * threshold-crossing coords) and by pointer.onClick. Internal. */
4272
- _pendingDown: {
4273
- worldX: number;
4274
- worldY: number;
4275
- alt: boolean;
4276
- shift: boolean;
4277
- } | null;
4278
- /** Timestamp + world coords of the most recent click, used to detect a
4279
- * double-click on the last placed anchor (Illustrator convention for
4280
- * open-finish). Internal. */
4281
- _lastClick: {
4282
- t: number;
4283
- x: number;
4284
- y: number;
4285
- } | null;
4286
2418
  }
4287
2419
  interface UsePenToolOptions<TPose> {
4288
2420
  /** Wrap a finished PolygonPath in the consumer's pose type. */
@@ -4293,9 +2425,6 @@ interface UsePenToolOptions<TPose> {
4293
2425
  adapter: {
4294
2426
  addNode: (pose: TPose) => string;
4295
2427
  setSelection: (ids: string[]) => void;
4296
- /** Apply an op batch (with optional history label). Required for pen-edit;
4297
- * safe to omit if the consumer only uses create mode. */
4298
- applyOps?: (ops: Op[], label: string) => void;
4299
2428
  };
4300
2429
  /** Auto-select the new object after commit. Default `true`. */
4301
2430
  autoSelect?: boolean;
@@ -4326,36 +2455,9 @@ interface UsePenToolOptions<TPose> {
4326
2455
  x: number;
4327
2456
  y: number;
4328
2457
  }) => {
4329
- x: number;
4330
- y: number;
4331
- };
4332
- /** Resolve a path obj from id. Returns the obj's path, closed flag, params,
4333
- * and the tool that created it (used to decide whether the trapdoor applies).
4334
- * Required when pen-edit is wanted; safe to omit if the consumer only uses
4335
- * create mode (dblclick-to-edit will be a no-op). */
4336
- getPathObj?: (id: string) => {
4337
- path: PolygonPath | {
4338
- kind: 'rect';
4339
- x: number;
4340
- y: number;
4341
- width: number;
4342
- height: number;
4343
- };
4344
- closed: boolean;
4345
- params: unknown;
4346
- tool: string;
4347
- } | null;
4348
- }
4349
- interface UsePenToolReturn {
4350
- tool: Tool<PenScratch>;
4351
- /**
4352
- * True when the pen is in edit mode (reshaping an existing path). Consumers
4353
- * should apply a CSS class like `pen-edit-active` to their canvas container
4354
- * when `isEditing` is true, to give users a visual cue that they're in edit
4355
- * mode (e.g. a subtle background tint). The kit does not own the DOM and
4356
- * ships no default stylesheet for this.
4357
- */
4358
- isEditing: boolean;
2458
+ x: number;
2459
+ y: number;
2460
+ };
4359
2461
  }
4360
2462
  /**
4361
2463
  * Active-slot Tool: click + drag to build a `PolygonPath` Illustrator-style.
@@ -4367,12 +2469,9 @@ interface UsePenToolReturn {
4367
2469
  * discards. Shift constrains the placement-drag handle to 0/45/90/135°;
4368
2470
  * Alt during drag breaks the handle mirror for the next segment.
4369
2471
  *
4370
- * @returns `{ tool, isEditing }` the pen tool definition plus a reactive
4371
- * `isEditing` boolean. Apply a CSS class like `pen-edit-active` to your canvas
4372
- * container when `isEditing` is true for a visual cue. The kit owns no DOM and
4373
- * ships no default stylesheet for this.
2472
+ * Reshaping an existing path is not this tool's job see {@link PenScratch}.
4374
2473
  */
4375
- declare function usePenTool<TPose>(options: UsePenToolOptions<TPose>): UsePenToolReturn;
2474
+ declare function usePenTool<TPose>(options: UsePenToolOptions<TPose>): Tool<PenScratch>;
4376
2475
  declare namespace usePenTool {
4377
2476
  var prefs: {
4378
2477
  name: string;
@@ -4388,80 +2487,28 @@ declare namespace usePenTool {
4388
2487
  };
4389
2488
  }
4390
2489
 
4391
- interface RectBounds {
4392
- x: number;
4393
- y: number;
4394
- width: number;
4395
- height: number;
4396
- }
4397
- interface UseRectToolOptions<TNode extends {
4398
- id: string;
4399
- }> {
4400
- create: (bounds: RectBounds) => TNode | null;
4401
- label?: string;
4402
- minBounds?: {
4403
- width: number;
4404
- height: number;
4405
- };
4406
- overlayStyle?: InsertOverlayStyle;
4407
- /** Optional: snap world-space points to the active grid (or any other
4408
- * snap target). Applied to every coord the gesture ingests, so both the
4409
- * live overlay and the committed geometry use the snapped values. */
4410
- snapPoint?: (p: {
4411
- x: number;
4412
- y: number;
4413
- }) => {
4414
- x: number;
4415
- y: number;
4416
- };
4417
- }
4418
2490
  /**
4419
- * Drag-to-draw rectangle tool. The user drags a rect; on release the `create`
4420
- * factory is called with the final bounds and the returned object is inserted
4421
- * into the scene via an undoable op.
2491
+ * Drag-to-draw rectangle tool.
4422
2492
  *
4423
- * Role model for tools that create scene objects: uses `ctx.applyOps` +
4424
- * `createInsertOp` directly rather than routing through adapter.commitInsert.
2493
+ * The gesture is owned end-to-end by the dispatcher: the `drag` binding
2494
+ * routes to `insertAction`, which tracks the live bounds, paints the
2495
+ * preview through its `overlay()` surface, and commits via the `insert`
2496
+ * dep on release. `<SceneCanvas>` sources that dep from
2497
+ * `useInsertDepSource`; consumers wanting a custom node factory override
2498
+ * the dep (`useDepSource('insert', …)`) rather than the tool.
2499
+ *
2500
+ * Grid snapping comes from the `snap` dep, also read by `insertAction` —
2501
+ * see `SnapDep` in `interactions/actions/depSchema.ts`.
4425
2502
  */
4426
- declare function useRectTool<TNode extends {
4427
- id: string;
4428
- }>(options: UseRectToolOptions<TNode>): Tool<null>;
2503
+ declare function useRectTool(): Tool<null>;
4429
2504
 
4430
- interface EllipseBounds {
4431
- x: number;
4432
- y: number;
4433
- width: number;
4434
- height: number;
4435
- }
4436
- interface UseEllipseToolOptions<TNode extends {
4437
- id: string;
4438
- }> {
4439
- create: (bounds: EllipseBounds) => TNode | null;
4440
- label?: string;
4441
- minBounds?: {
4442
- width: number;
4443
- height: number;
4444
- };
4445
- overlayStyle?: InsertOverlayStyle;
4446
- /** Optional: snap world-space points to the active grid (or any other
4447
- * snap target). Applied to every coord the gesture ingests, so both the
4448
- * live overlay and the committed geometry use the snapped values. */
4449
- snapPoint?: (p: {
4450
- x: number;
4451
- y: number;
4452
- }) => {
4453
- x: number;
4454
- y: number;
4455
- };
4456
- }
4457
2505
  /**
4458
- * Drag-to-draw ellipse tool. The `create` factory receives the bounding
4459
- * rect and returns the node to insert (consumer-defined shape — the kit
4460
- * doesn't impose an ellipse geometry encoding here). Mirrors `useRectTool`.
2506
+ * Drag-to-draw ellipse tool. Mirrors `useRectTool`: the `drag` binding
2507
+ * routes to `insertAction`, which owns the live preview and commits
2508
+ * through the `insert` dep. Alt toggles from-corner from-center
2509
+ * mid-drag via the action's live modifier read.
4461
2510
  */
4462
- declare function useEllipseTool<TNode extends {
4463
- id: string;
4464
- }>(options: UseEllipseToolOptions<TNode>): Tool<null>;
2511
+ declare function useEllipseTool(): Tool<null>;
4465
2512
 
4466
2513
  interface UseImageToolOptions {
4467
2514
  /** Image source for inserted nodes — a URL, a `blob:` URL, or a
@@ -4488,7 +2535,8 @@ interface UseEyedropperToolOptions {
4488
2535
  * on a real hit; empty-click is a no-op in v1. */
4489
2536
  onPick: (color: string | null) => void;
4490
2537
  /** Map a node id to a color string, or `null` if the node has no color
4491
- * to sample. Called on click with `ctx.target.id`. */
2538
+ * to sample. Called on click with the id of the topmost node under the
2539
+ * click point. */
4492
2540
  colorOf: (id: string) => string | null;
4493
2541
  /** Override the default `{ key: 'I' }` keybinding. Pass `null` to omit
4494
2542
  * the keybinding entirely (palette-only or hotkey-only wiring). */
@@ -4504,6 +2552,10 @@ interface UseEyedropperToolOptions {
4504
2552
  *
4505
2553
  * Pure-read tool — does NOT mutate the scene. v1 is click-only; drag is
4506
2554
  * unbound (a future drag-to-sample option is additive).
2555
+ *
2556
+ * The node under the click comes from the `nodeAtPoint` dep, so an
2557
+ * eyedropper in a consumer that never registered that dep samples nothing.
2558
+ * `<SceneCanvas>` sources it from the same picker the rest of the kit hits.
4507
2559
  */
4508
2560
  declare function useEyedropperTool(opts: UseEyedropperToolOptions): Tool<null>;
4509
2561
 
@@ -4511,39 +2563,19 @@ interface LinePoint {
4511
2563
  x: number;
4512
2564
  y: number;
4513
2565
  }
4514
- interface UseLineToolOptions<TNode extends {
4515
- id: string;
4516
- }> {
4517
- create: (a: LinePoint, b: LinePoint) => TNode | null;
4518
- label?: string;
4519
- minLength?: number;
4520
- /** Optional: snap world-space points to the active grid (or any other
4521
- * snap target). Applied to every coord the gesture ingests, so both the
4522
- * live overlay and the committed endpoints use the snapped values.
4523
- *
4524
- * Order with modifiers: the Shift-constrain-to-15° branch runs on raw
4525
- * coords first, then the result is snapped — this preserves the user
4526
- * intent (constrain the angle), then aligns the endpoint to grid. */
4527
- snapPoint?: (p: LinePoint) => LinePoint;
4528
- }
4529
- /** @internal */
4530
- interface LineScratch {
4531
- start: LinePoint;
4532
- current: LinePoint;
4533
- /** Modifier snapshot captured on the latest move — used by the live
4534
- * overlay so the ghost reflects shift-snap / alt-mirror in real time. */
4535
- shift: boolean;
4536
- alt: boolean;
4537
- }
4538
2566
  /**
4539
- * Click-down → drag → release-place line tool. Commits via the consumer's
4540
- * `create` factory called with the two endpoints. Modifiers:
2567
+ * Click-down → drag → release line tool. The `drag` binding routes to
2568
+ * `insertAction`, which owns the live preview and commits through the
2569
+ * `insert` dep with `{ kind: 'line', a, b }` endpoints (not the AABB
2570
+ * diagonal), so the drag direction is preserved.
2571
+ *
2572
+ * Modifiers are applied by `insertAction`:
4541
2573
  * - shift: constrain to 15° increments
4542
- * - alt: mirror end around start (drag is treated as half-line)
2574
+ * - alt: mirror the end around the start (the drag is treated as a
2575
+ * half-line) — the line-specific reading of the action's
2576
+ * corner ⇄ center origin toggle
4543
2577
  */
4544
- declare function useLineTool<TNode extends {
4545
- id: string;
4546
- }>(options: UseLineToolOptions<TNode>): Tool<LineScratch | null>;
2578
+ declare function useLineTool(): Tool<null>;
4547
2579
 
4548
2580
  /** Option surface for the `lasso-select` action.
4549
2581
  *
@@ -4652,54 +2684,31 @@ declare function useStarTool<TNode extends {
4652
2684
  * `pressure: 0.5` while a button is held, `0` otherwise (per the Pointer
4653
2685
  * Events spec), so a consumer that wants stylus-only modulation should
4654
2686
  * gate on `pointerType` from the originating event — the kit exposes
4655
- * `usePointerStylus()` for that. */
2687
+ * `usePointerStylus()` for that.
2688
+ *
2689
+ * The dispatcher accumulates these on the drag trail and hands the whole
2690
+ * array to `insertAction`, which forwards it as the `pencil` insert
2691
+ * extras' `samples`. A consumer that wants pressure-driven
2692
+ * `Stroke.vertexWidths` reads it off the samples in its own `insert` dep
2693
+ * (`useDepSource('insert', …)`) — see `apps/site/demos/VertexWidthsDemo.tsx`.
2694
+ */
4656
2695
  interface PencilPoint {
4657
2696
  x: number;
4658
2697
  y: number;
4659
- /** 0..1. Optional for backward compat older `create` factories that
4660
- * treat samples as `{x,y}` keep working. */
2698
+ /** 0..1. Absent when the originating event carried no pressure. */
4661
2699
  pressure?: number;
4662
2700
  /** Degrees, ±90. Zero for mouse/touch. */
4663
2701
  tiltX?: number;
4664
2702
  /** Degrees, ±90. Zero for mouse/touch. */
4665
2703
  tiltY?: number;
4666
- /** Per-sample stroke width, populated when the tool's
4667
- * `pressureToWidth` option is set. Consumers can read these widths to
4668
- * build a parallel `vertexWidths` array for a tapered stroke. */
4669
- width?: number;
4670
- }
4671
- interface UsePencilToolOptions<TNode extends {
4672
- id: string;
4673
- }> {
4674
- create: (path: PolygonPath, opts: {
4675
- closed: boolean;
4676
- widths?: number[];
4677
- }) => TNode | null;
4678
- label?: string;
4679
- tolerance?: number;
4680
- closeThreshold?: number;
4681
- /** Optional callback that maps a captured sample to a stroke width.
4682
- * When provided, every PencilPoint gets a `width` field and the
4683
- * consumer's `create` factory also receives `widths`: one width per
4684
- * output bezier anchor, ready to drop into `Stroke.vertexWidths`. The
4685
- * default `pressureToWidth(p, { minWidth, maxWidth, gamma })` helper
4686
- * is a common drop-in. */
4687
- pressureToWidth?: (sample: PencilPoint) => number;
4688
- }
4689
- /** @internal */
4690
- interface PencilScratch {
4691
- samples: PencilPoint[];
4692
2704
  }
4693
2705
  /**
4694
- * Freehand pencil tool. Captures pointer samples through the drag, then
4695
- * runs `schneiderFit` on release to produce a cubic-Bezier path. If the
4696
- * first and last samples are within `closeThreshold` world units, the
4697
- * `create` factory receives `{ closed: true }` so the consumer can close
4698
- * its path.
2706
+ * Freehand pencil tool. The `drag` binding routes to `insertAction`,
2707
+ * which accumulates the pointer trail and commits it as
2708
+ * `{ kind: 'pencil', samples }`. The kit's default `insert` dep runs
2709
+ * `schneiderFit` over the samples to produce a cubic-Bezier path.
4699
2710
  */
4700
- declare function usePencilTool<TNode extends {
4701
- id: string;
4702
- }>(options: UsePencilToolOptions<TNode>): Tool<PencilScratch | null>;
2711
+ declare function usePencilTool(): Tool<null>;
4703
2712
 
4704
2713
  /** Standard slot names — render in this canonical order.
4705
2714
  * `cellHighlight` is internal: emitted from the `grid` slot's nested
@@ -4743,9 +2752,24 @@ interface CanvasExtensionApi {
4743
2752
  readonly element: HTMLCanvasElement | null;
4744
2753
  requestRedraw(): void;
4745
2754
  /** Register an externally-owned RenderLayer. The layer participates in the
4746
- * draw stack and, if it implements `hitTest`, in the dispatcher's hit-test
4747
- * pipeline (see `src/tools/dispatcher.ts`'s `getHitTestContext`). */
2755
+ * draw stack and, if it implements `hitTest`, in {@link hitTestExtras}. */
4748
2756
  registerLayer(layer: RenderLayer<unknown>): () => void;
2757
+ /**
2758
+ * Hit-test the registered layers (topmost-first, last-registered wins) at a
2759
+ * world-space point. Returns the id of the layer that claimed the point and
2760
+ * whatever its `hitTest` resolved, or `null` when none did.
2761
+ *
2762
+ * `<SceneCanvas>` folds this into the `affordanceAt` thunk it hands the
2763
+ * gesture dispatcher — ahead of the kit's own selection chrome, since
2764
+ * registered layers draw on top — so a hit surfaces to actions as an
2765
+ * `AffordanceHit` with kind `layer:<id>` and the binding's `initialScratch`
2766
+ * as its `payload`. A layer's owner binds a `kindOf` predicate on that kind
2767
+ * to claim the gesture; see `@weasel-js/hud` for the worked example.
2768
+ */
2769
+ hitTestExtras(worldX: number, worldY: number): {
2770
+ layerId: string;
2771
+ binding: AffordanceBinding;
2772
+ } | null;
4749
2773
  /** Feed external content into the ingestion pipeline imperatively — the
4750
2774
  * same content-handler registry that OS drop and clipboard paste hit.
4751
2775
  * `input` may be raw `File[]` (e.g. from `openFilePicker`) or
@@ -4775,6 +2799,52 @@ interface SceneCanvasApi extends CanvasExtensionApi {
4775
2799
  }): void;
4776
2800
  }
4777
2801
 
2802
+ /**
2803
+ * Union math behind `CanvasHelpers.getGestureBounds()`.
2804
+ *
2805
+ * `getGestureBounds` answers "where is the thing the user is dragging right
2806
+ * now, in total?" — a question every other lookup on `CanvasHelpers` can't
2807
+ * answer because they're keyed by node id, and a drag-to-insert has no node
2808
+ * yet. Canvas collects the parts (preview bounds per in-flight id, plus any
2809
+ * id-less nascent-insert AABBs) and folds them here.
2810
+ *
2811
+ * Kept free of tool / dispatcher types so it stays a pure geometry fold that
2812
+ * both `<Canvas>` and its tests can call without a render.
2813
+ */
2814
+
2815
+ /**
2816
+ * Everything `<Canvas>` needs to know about in-flight gestures that it can't
2817
+ * see for itself.
2818
+ *
2819
+ * `<Canvas>` is deliberately dispatcher-agnostic — it owns the union math and
2820
+ * the `CanvasHelpers` contract, but the live gesture state lives in the
2821
+ * gesture dispatcher that `<SceneCanvas>` creates. This is the one seam
2822
+ * between them (`createGestureSource` in `SceneCanvas/dispatcherGestureBounds.ts`
2823
+ * builds it); bare `<Canvas>` consumers leave it unwired and get the
2824
+ * "no gesture in flight" answers.
2825
+ *
2826
+ * @public
2827
+ */
2828
+ interface GestureSource {
2829
+ /**
2830
+ * Ids the in-flight gesture is previewing, so the canvas can resolve each
2831
+ * one's preview bounds.
2832
+ *
2833
+ * Wider than `previewIdsExtra`, which honors `previewHidesSource: false`
2834
+ * because its job is deciding whose committed paint to suppress: a clone
2835
+ * ghost hides nothing yet still proposes content at the drag target.
2836
+ */
2837
+ ids(): Iterable<string> | null;
2838
+ /** World-space AABBs of in-flight gestures that have no scene id at all —
2839
+ * a drag-to-insert, pre-commit. */
2840
+ bounds(): Iterable<Bounds> | null;
2841
+ /** Fires whenever the above can have changed. See
2842
+ * `CanvasHelpers.subscribeGestures` for the published guarantee. */
2843
+ subscribe(fn: () => void): () => void;
2844
+ /** Monotonic counter bumped on the same events `subscribe` fires on. */
2845
+ getVersion(): number;
2846
+ }
2847
+
4778
2848
  /**
4779
2849
  * Selection overlay primitives — render layers for drawing selection
4780
2850
  * outlines and corner resize handles in world space.
@@ -5146,16 +3216,6 @@ interface CanvasProps<TNode extends {
5146
3216
  * force-graph renderers or read-only viewers).
5147
3217
  */
5148
3218
  selection?: SelectionApi;
5149
- /**
5150
- * Fires when the canvas receives a pointer event that the tool dispatcher
5151
- * did not handle (no active gesture was started on pointerdown, or the
5152
- * pointer was released without a gesture). Useful for "click on background
5153
- * clears selection" — `<SceneCanvas>` wires this to `selection.clear()`.
5154
- *
5155
- * The listener is only installed when this callback is supplied. Omitting
5156
- * the prop means no background-click listener is registered.
5157
- */
5158
- onBackgroundClick?: () => void;
5159
3219
  /**
5160
3220
  * Pose↔bounds projection for non-rect `TPose` types. When supplied, drives
5161
3221
  * the default `boundsOf` fallback and the selection-overlay bounds source so
@@ -5291,6 +3351,14 @@ interface CanvasProps<TNode extends {
5291
3351
  * through geometry.getBounds(pose).
5292
3352
  */
5293
3353
  previewBoundsExtra?: (id: string) => Bounds | null;
3354
+ /**
3355
+ * In-flight gesture state `<Canvas>` can't see for itself. Backs the
3356
+ * `getGestureBounds` / `subscribeGestures` / `getGestureVersion` trio on
3357
+ * `helpersRef`; wired by `<SceneCanvas>` from the gesture dispatcher
3358
+ * (`createGestureSource`). Leaving it unwired is fine — those three then
3359
+ * report "no gesture in flight" and never fire.
3360
+ */
3361
+ gestureSource?: GestureSource;
5294
3362
  /**
5295
3363
  * Pinch-zoom DOM listener attachment for the canvas surface. When supplied,
5296
3364
  * `<Canvas>` calls `usePinchZoomTool` with `canvasRef` so two-finger pinch
@@ -5346,19 +3414,6 @@ interface CanvasProps<TNode extends {
5346
3414
  * HUD shows the same best-candidate SceneCanvas would pick.
5347
3415
  */
5348
3416
  pickBest?: (worldX: number, worldY: number) => string | null;
5349
- /** Resolves a single hit (id + kind + pose + data) at world coords for the
5350
- * tool dispatcher. `<SceneCanvas>` synthesizes this from its node-kind
5351
- * registry and adapter via `makeGetNodeAtPoint`. When omitted, the
5352
- * dispatcher receives no hit info and tool routing based on `target.kind`
5353
- * will not fire. Bare-`<Canvas>` consumers that need kind-based routing
5354
- * should build this with `makeGetNodeAtPoint`. */
5355
- getNodeAtPoint?: (worldX: number, worldY: number) => {
5356
- id: string;
5357
- kind: string;
5358
- pose: unknown;
5359
- data: unknown;
5360
- meta?: Record<string, unknown>;
5361
- } | null;
5362
3417
  /**
5363
3418
  * Optional mode-owned decoration layer. When supplied, Canvas inserts it
5364
3419
  * between the scene-render slot and the tool-overlay slot so decoration
@@ -5394,6 +3449,63 @@ interface CanvasHelpers<TPose> {
5394
3449
  getEffectivePose(id: string): TPose | null;
5395
3450
  /** Overlay-aware bounds for `id`. */
5396
3451
  getEffectiveBounds(id: string): Bounds | null;
3452
+ /**
3453
+ * World-space AABB of everything the in-flight gesture proposes — the
3454
+ * displaced poses of nodes being moved / resized / rotated / cloned, plus
3455
+ * any nascent insert that has no scene node yet. `null` when no gesture is
3456
+ * in flight.
3457
+ *
3458
+ * This reports the *gesture*, not the document: committed content the
3459
+ * gesture isn't touching is excluded, so a consumer that wants the union
3460
+ * with the rest of the scene still walks its own ids through
3461
+ * `getEffectiveBounds`. It exists because every other lookup here is keyed
3462
+ * by node id, which can't answer "where is the shape the user is drawing
3463
+ * right now" — a drag-to-insert has no id until pointer-up.
3464
+ *
3465
+ * Select-only gestures are deliberately excluded: a marquee or lasso has
3466
+ * geometry but proposes no content, and a consumer sizing itself to the
3467
+ * gesture must not grow because the user swept a selection rectangle.
3468
+ *
3469
+ * The result is a plain AABB — never rotated. Rotated parts are folded in
3470
+ * by their rotated extent (a union of several oriented boxes has no single
3471
+ * orientation to report).
3472
+ */
3473
+ getGestureBounds(): Bounds | null;
3474
+ /**
3475
+ * Subscribe to the gesture layer's change signal — the other half of the
3476
+ * `useSyncExternalStore` contract for everything on this object that moves
3477
+ * during a drag (`getEffectivePose`, `getEffectiveBounds`,
3478
+ * `getGestureBounds`). Returns an unsubscribe.
3479
+ *
3480
+ * Fires once per dispatcher pump: gesture start, every pointermove that
3481
+ * reaches an in-flight handle, end, and cancel — plus UI-driven ongoing
3482
+ * actions (a slider bound to an ongoing action pumps the same way). It
3483
+ * fires on the pump, not on a diff: a pump that changed nothing observable
3484
+ * still notifies, so don't hang expensive work directly off the callback.
3485
+ *
3486
+ * It does **not** cover committed scene edits (subscribe to the scene for
3487
+ * those) or previews a consumer's own tool publishes from React state
3488
+ * (that tool re-renders on its own).
3489
+ *
3490
+ * Without a gesture source wired — a bare `<Canvas>` — this is a no-op
3491
+ * subscription that never fires.
3492
+ */
3493
+ subscribeGestures(fn: () => void): () => void;
3494
+ /**
3495
+ * Monotonic counter bumped on exactly the events `subscribeGestures` fires
3496
+ * on. Pair the two for `useSyncExternalStore`:
3497
+ *
3498
+ * ```ts
3499
+ * const gestureVersion = useSyncExternalStore(
3500
+ * useCallback((cb) => helpersRef.current?.subscribeGestures(cb) ?? (() => {}), []),
3501
+ * () => helpersRef.current?.getGestureVersion() ?? 0,
3502
+ * );
3503
+ * ```
3504
+ *
3505
+ * Starts at 0 and only increases. `0` is also what a bare `<Canvas>` with
3506
+ * no gesture source reports, forever.
3507
+ */
3508
+ getGestureVersion(): number;
5397
3509
  /** Returns the live ChromeState built once per render. Affordances and
5398
3510
  * custom layers that need overlay-aware selection state (selection ids,
5399
3511
  * bounds, multi-union AABB, modifier flags) read from this. */
@@ -5863,27 +3975,6 @@ interface UseRotateOptions<TPose> {
5863
3975
  pivot?: 'each' | 'union';
5864
3976
  }
5865
3977
 
5866
- /** Option surface for the `area-select` action.
5867
- *
5868
- * Lives in a sibling file (not `areaSelect.ts`) so the type contract stays
5869
- * stable even after the legacy `useAreaSelect` hook is gone.
5870
- * Consumers should import from here directly; `areaSelect.ts`
5871
- * re-exports the same symbol for back-compat. */
5872
-
5873
- interface UseAreaSelectOptions {
5874
- behaviors?: AreaSelectBehavior[];
5875
- /** When set, overrides any behavior's `defaultTransient`. Default: behaviors decide. */
5876
- transient?: boolean;
5877
- /** Label used when transient is false and the hook falls back to applyOps. Default 'Area select'. */
5878
- label?: string;
5879
- onGestureStart?: () => void;
5880
- onGestureEnd?: (committed: boolean) => void;
5881
- /** Optional debug sink. When supplied, records the in-progress marquee
5882
- * rectangle as a `bounds` entry under the synthetic id `'area-select'`
5883
- * on every move. Tree-shakes via optional-chain when omitted. */
5884
- debug?: DebugSink;
5885
- }
5886
-
5887
3978
  /**
5888
3979
  * Built-in shape tool ids handled by `useBuiltinShapeTools`. Each maps to a
5889
3980
  * kit tool hook + a default `create` that produces a leaf node compatible
@@ -5907,10 +3998,13 @@ interface BuiltinToolOptions {
5907
3998
  mode?: LassoHitMode;
5908
3999
  };
5909
4000
  /** Snap world-space points to the active grid (or any other snap target).
5910
- * Applied by the rect / ellipse / line tools to every coord they ingest,
5911
- * so both the live overlay and the committed geometry use snapped
5912
- * values. polygon / star / pencil / text go through the dispatcher's
5913
- * `insertAction` descriptor, which doesn't yet honor this hook. */
4001
+ *
4002
+ * Registered as the `snap` dep, which `insertAction` applies to the
4003
+ * drag start and current point so the live preview and the committed
4004
+ * geometry agree — and which the pen tool reads for its own anchor
4005
+ * placement. Covers every drag-to-insert tool (rect / ellipse / line /
4006
+ * polygon / star / text); freehand pencil samples are deliberately left
4007
+ * unsnapped. */
5914
4008
  snapPoint?: (p: {
5915
4009
  x: number;
5916
4010
  y: number;
@@ -6252,10 +4346,6 @@ type SceneCanvasProps<TData, TLayer extends string, TPose> = Omit<CanvasProps<No
6252
4346
  rotate?: UseRotateOptions<TPose> | false;
6253
4347
  snap?: SnapStrategy<TPose>;
6254
4348
  handleHitRadius?: number;
6255
- /** Marquee area-select. Default: no behaviors (a drag from empty space
6256
- * doesn't mutate the selection). Pass
6257
- * `{ behaviors: [selectFromMarquee()] }` to enable rubber-band select. */
6258
- areaSelect?: UseAreaSelectOptions;
6259
4349
  /** Override the body-pick used on click/pointerdown. Alt-aware: receives
6260
4350
  * the live alt state + current selection so consumers can implement
6261
4351
  * alt-cycling through an overlapping stack. Default: top-most hit
@@ -6397,15 +4487,6 @@ type SceneCanvasProps<TData, TLayer extends string, TPose> = Omit<CanvasProps<No
6397
4487
  * the default select. If you supply your own `tools` prop, this is
6398
4488
  * ignored — wire `ambient` through your own `useTools` call instead. */
6399
4489
  ambient?: AnyTool[];
6400
- /** Click-only fallback tool. Its `pointer.onClick` fires only when the
6401
- * in-flight tool didn't claim the click (e.g. the active select tool
6402
- * returned `pass` on an empty-space click). Use it for a
6403
- * click-to-deselect or click-to-spawn behavior that shouldn't interfere
6404
- * with the active tool's own clicks. Forwarded to the internal
6405
- * `useTools` as its `fallback` slot. If you supply your own `tools`
6406
- * prop (takeover form), this is ignored — pass `fallback` to your own
6407
- * `useTools` call instead. */
6408
- clickFallback?: AnyTool;
6409
4490
  /** Viewport feature wiring.
6410
4491
  *
6411
4492
  * - `inertia`, `pinchZoom`, `animatedZoom` are opt-in: pass `true`
@@ -6737,10 +4818,11 @@ declare const inferredNodeRouting: readonly NodeRoutingEntry[];
6737
4818
  * registry: a declarative schema of the kind's editable properties.
6738
4819
  *
6739
4820
  * Leaf keys inside `schema` are dotted node paths (`pose.x`,
6740
- * `data.fill`) — two segments, rooted at `pose` or `data` — so schema
6741
- * consumers (weasel-ui `SelectionPanel`) can read/aggregate/write
6742
- * generically with no per-kind code. Group keys are organizational
6743
- * only; they do not contribute to the node path.
4821
+ * `data.fill`, `data.style.fontSize`) — a path of any depth rooted at
4822
+ * `pose` or `data` — so schema consumers (weasel-ui `SelectionPanel`)
4823
+ * can read/aggregate/write generically with no per-kind code. Group
4824
+ * keys are organizational only; they do not contribute to the node
4825
+ * path.
6744
4826
  *
6745
4827
  * Kind names share the routing trait's vocabulary — an entry registered
6746
4828
  * as `'rect'` describes nodes `NodeRouting.classify` maps to `'rect'`.
@@ -7505,6 +5587,17 @@ declare function createMarkdownRenderer(ctx: CanvasRenderingContext2D, text: str
7505
5587
  * already matches `style` (use `fontString(style)`).
7506
5588
  */
7507
5589
 
5590
+ /**
5591
+ * Advance width of `text` in world units, tracking included.
5592
+ *
5593
+ * `letter-spacing` is not part of the CSS `font` shorthand, so a context
5594
+ * whose `font` was set from `fontString(style)` measures glyphs only. The
5595
+ * GL path (`layoutRuns`) adds `letterSpacing` after **every** code point
5596
+ * including the last, matching CSS, so this does too — and every 2D-side
5597
+ * width has to go through here or the two paths disagree about where a line
5598
+ * breaks.
5599
+ */
5600
+ declare function measuredWidth(ctx: CanvasRenderingContext2D, text: string, style: ResolvedTextStyle): number;
7508
5601
  /** Result of `measureText`: wrapped lines, per-line source offsets, and total block height. */
7509
5602
  interface MeasuredText {
7510
5603
  lines: string[];
@@ -7651,6 +5744,82 @@ interface FitTextPoseOptions {
7651
5744
  /** Recompute a `TextPose`'s `width`/`height` to fit its content; pure helper, doesn't mutate scene state. */
7652
5745
  declare function fitTextPose(ctx: CanvasRenderingContext2D, pose: TextPose, opts?: FitTextPoseOptions): TextPose;
7653
5746
 
5747
+ /**
5748
+ * The kit-wide "these values disagree" sentinel. Used wherever a value is
5749
+ * aggregated across more than one source and the sources don't agree — e.g.
5750
+ * a UI panel aggregating a property across a multi-node selection, or text
5751
+ * styling aggregated across the runs in a character range. One symbol, one
5752
+ * `isMixed` check, regardless of what's being aggregated.
5753
+ */
5754
+ declare const MIXED: unique symbol;
5755
+ type Mixed = typeof MIXED;
5756
+
5757
+ /**
5758
+ * Range-addressed styling over `StyledRun[]`. The panel addresses node paths;
5759
+ * a caret addresses a character range — these are the functions for the
5760
+ * second case. Pure and React-free so the semantics are unit-testable, the
5761
+ * same split `SelectionPanel` makes between `model.ts` and its component.
5762
+ *
5763
+ * Character offsets index the concatenated run text (`runsToPlainText`), and
5764
+ * a range is half-open: `[start, end)`. Ranges are clamped, never wrapped —
5765
+ * a negative `start` clamps to 0, an `end` past the text clamps to its
5766
+ * length, and a collapsed or inverted range (`start >= end`) is empty.
5767
+ * Offsets are UTF-16 code units, as everywhere else in this feature, so a
5768
+ * boundary can bisect a surrogate pair (`[1, 2)` over `'a👍b'` splits the
5769
+ * thumbs-up into lone surrogates). A caret-derived range never does this.
5770
+ *
5771
+ * **Run-level flags are additive over the node's `TextStyle`: a run can
5772
+ * turn `bold` / `italic` / `underline` / `strikethrough` on, never off.**
5773
+ * So a flag is stored only when true, and absent reads as `false`. For
5774
+ * `bold` this falls out of the model — node weight is numeric, and
5775
+ * `run.bold ? 700 : baseWeight` has nowhere to put "not bold." For the
5776
+ * boolean decorations it does not: both levels are booleans, so a tri-state
5777
+ * (`true` / `false` / inherit) is expressible and we are collapsing it by
5778
+ * choice, to keep one canonical form per styling. Resolution must therefore
5779
+ * read decorations as `run.underline || style.underline` — a `??` would
5780
+ * make "select a word in an underlined node, hit U to turn it off" look
5781
+ * supported by the type while this file discards the `false` that expresses it.
5782
+ *
5783
+ * Copies here are shallow: a `fill` object is shared by reference between
5784
+ * the input runs, the patch, every run the patch touched, and the value
5785
+ * `styleAtRange` reports. Treat `fill` values as immutable.
5786
+ */
5787
+
5788
+ /** Every styleable key of a run, each either a concrete value or MIXED (the
5789
+ * runs in the range disagree at that key). */
5790
+ type RangeStyle = {
5791
+ [K in Exclude<keyof StyledRun, 'text'>]?: StyledRun[K] | Mixed;
5792
+ };
5793
+ /**
5794
+ * What `applyStyleToRange` writes. `text` is deliberately not part of it —
5795
+ * this is a styling operation, not an edit.
5796
+ */
5797
+ type RunStylePatch = Partial<Omit<StyledRun, 'text'>>;
5798
+ /** Every key of a run that carries styling — i.e. everything but `text`. */
5799
+ type StyleKey = Exclude<keyof StyledRun, 'text'>;
5800
+ /**
5801
+ * The styling shared by every run overlapping `[start, end)`: a concrete
5802
+ * value where the range agrees, `MIXED` where it doesn't. Keys no run
5803
+ * in range sets are absent (they inherit the node's `TextStyle`) — except
5804
+ * the additive flags, which read as `false` rather than `undefined` since a
5805
+ * run cannot un-set them. An empty range reads as `{}`. A reported `fill`
5806
+ * is the run's own object, not a copy — treat it as immutable.
5807
+ */
5808
+ declare function styleAtRange(runs: readonly StyledRun[], start: number, end: number): RangeStyle;
5809
+ /**
5810
+ * Write `patch` over `[start, end)`, splitting runs at the boundaries and
5811
+ * coalescing neighbors that end up identical. A patch value of `undefined`
5812
+ * or `false` deletes the key instead of storing it — see the additive-flags
5813
+ * contract at the top of the file.
5814
+ *
5815
+ * Returns a new array of new runs; neither the input array nor its runs are
5816
+ * mutated (though a nested `fill` is shared by reference). Normalization —
5817
+ * merging identical neighbors, dropping empty runs — applies to the whole
5818
+ * array, not just the patched span, and to every call, including one whose
5819
+ * range is empty and so patches nothing.
5820
+ */
5821
+ declare function applyStyleToRange(runs: readonly StyledRun[], start: number, end: number, patch: RunStylePatch): StyledRun[];
5822
+
7654
5823
  /**
7655
5824
  * In-place text editing via a contenteditable overlay positioned over the
7656
5825
  * text node's screen-space pose. Enter / blur commit; Shift+Enter inserts a
@@ -7662,17 +5831,51 @@ declare function fitTextPose(ctx: CanvasRenderingContext2D, pose: TextPose, opts
7662
5831
  * (`isHidden: (n) => n.id === editingId`) so the overlay isn't drawn twice.
7663
5832
  */
7664
5833
 
5834
+ /**
5835
+ * The caret's character range within the text being edited. Half-open
5836
+ * `[start, end)` over the concatenated run text, normalized so `start <= end`
5837
+ * regardless of which way the user dragged. `start === end` is a collapsed
5838
+ * caret — a real position, not the absence of one, which is why the hook
5839
+ * reports `null` rather than a zero-width range when there is no caret.
5840
+ */
5841
+ interface TextEditSelection {
5842
+ start: number;
5843
+ end: number;
5844
+ }
7665
5845
  /** Screen-space pose passed to `useTextEdit` so the overlay can be placed and sized in CSS pixels. */
7666
5846
  interface TextEditScreenPose {
7667
- /** Top-left in CSS pixels relative to `container`. */
5847
+ /** Top-left in CSS pixels relative to `container`. Always screen pixels,
5848
+ * including when `zoom` is set — the scale is anchored at this point, not
5849
+ * translated by it. */
7668
5850
  x: number;
7669
5851
  y: number;
5852
+ /** Pre-scale — CSS pixels, or world units when `zoom` is set. */
7670
5853
  width: number;
7671
5854
  height: number;
7672
- /** Effective on-screen font size (style.fontSize * zoom). */
5855
+ /** Pre-scale font size: `style.fontSize * zoom` when `zoom` is omitted, the
5856
+ * world-unit `style.fontSize` when it is set. */
7673
5857
  fontSize: number;
7674
5858
  /** Effective on-screen line height multiplier (defaults to style.lineHeight). */
7675
5859
  lineHeight?: number;
5860
+ /**
5861
+ * CSS scale applied to the overlay (`transform: scale(zoom)`, anchored at
5862
+ * its top-left). Every other size on this pose, and every typographic
5863
+ * metric the hook writes, is then **pre-scale** — pass world units and the
5864
+ * transform does the world→screen conversion.
5865
+ *
5866
+ * This is the only way run-level typography can be correct at a zoom other
5867
+ * than 1. `runsToDom` emits run `fontSize` / `letterSpacing` in world units
5868
+ * and `domToRuns` reads them straight back; threading a scale through the
5869
+ * writer and its inverse through the reader would go lossy on fractional
5870
+ * zooms. Scaling the whole overlay instead leaves that serializer pure and
5871
+ * scales node-level and run-level values by the same factor for free.
5872
+ *
5873
+ * Omit it (the default, `1`) and the pose is plain screen pixels — the hook
5874
+ * then infers the world→screen factor from `fontSize / style.fontSize` to
5875
+ * scale node-level `letterSpacing`, and run-level overrides are left
5876
+ * unscaled. Correct only at zoom 1.
5877
+ */
5878
+ zoom?: number;
7676
5879
  }
7677
5880
  /** Options for `useTextEdit`. */
7678
5881
  interface UseTextEditOptions {
@@ -7697,9 +5900,27 @@ interface UseTextEditOptions {
7697
5900
  * Optional: commit rich-text runs back to the node. When omitted, only
7698
5901
  * `setText` is called with the plain-text form on commit. When provided,
7699
5902
  * commit calls both `setText` (with `runsToPlainText(runs)`) and
7700
- * `setRuns(id, runs)`.
5903
+ * `setRuns(id, runs)` — but only when runs are actually in play: the node
5904
+ * already had some, or the edit produced styling. A plain-text edit of a
5905
+ * plain-text node still calls `setText` alone, so a node that has never
5906
+ * been styled doesn't grow a single-run `runs` array just for being edited.
7701
5907
  */
7702
5908
  setRuns?: (id: string, runs: StyledRun[]) => void;
5909
+ /**
5910
+ * Is `el` part of the editor's own chrome — a character-options bar, a
5911
+ * color popover, anything whose whole purpose is to style the text being
5912
+ * edited? Focus moving into one does not end the edit.
5913
+ *
5914
+ * Without this, the controls the feature exists for are exactly what
5915
+ * destroys it: clicking a size field blurs the overlay, blur commits, and
5916
+ * the caret the control was about to act on is gone. Toggle buttons can
5917
+ * dodge it by `preventDefault()`-ing their own mousedown, but a field the
5918
+ * user has to type into cannot.
5919
+ *
5920
+ * Chrome focus does not disturb the reported `selection` either — see
5921
+ * `UseTextEditReturn.selection`.
5922
+ */
5923
+ isEditorChrome?: (el: Element) => boolean;
7703
5924
  }
7704
5925
  /** Options for `useTextEdit().startEdit`. */
7705
5926
  interface StartEditOptions {
@@ -7716,6 +5937,37 @@ interface UseTextEditReturn {
7716
5937
  cancelEdit: () => void;
7717
5938
  commit: () => void;
7718
5939
  isEditing: (id: string) => boolean;
5940
+ /**
5941
+ * The caret's character range, or `null` when nothing is being edited. A
5942
+ * collapsed caret reports `{ start: n, end: n }`, so `null` and "caret at
5943
+ * n" stay distinguishable — a character-styling control routes the
5944
+ * collapsed case to the node's `TextStyle` instead of to a range.
5945
+ *
5946
+ * Follows the DOM selection, which browsers (and jsdom) report from a task
5947
+ * rather than synchronously; anything this hook writes itself updates it
5948
+ * synchronously. A DOM selection that leaves the overlay does **not** clear
5949
+ * it: that is what happens when the user clicks a styling control, and
5950
+ * reporting `null` there would read as "collapsed caret" and send the
5951
+ * control's patch to the node instead of to the range. It is cleared on
5952
+ * `startEdit` and when the edit ends.
5953
+ */
5954
+ selection: TextEditSelection | null;
5955
+ /**
5956
+ * The styling shared by every run in `selection` — a concrete value where
5957
+ * the range agrees, `MIXED` where it doesn't. `null` exactly when
5958
+ * `selection` is `null`. A collapsed caret reports `{}`: no run is in
5959
+ * range, so the range reader has nothing to say and the node's style is
5960
+ * what applies.
5961
+ */
5962
+ rangeStyle: RangeStyle | null;
5963
+ /**
5964
+ * Write `patch` over `selection`. A no-op with no active edit, with a
5965
+ * collapsed caret (there is no range to style — patch the node's
5966
+ * `TextStyle` instead), or with an empty patch. The caret survives, so a
5967
+ * second style can be applied without re-selecting, and `rangeStyle`
5968
+ * reflects the write before this returns.
5969
+ */
5970
+ applyStyleToSelection: (patch: RunStylePatch) => void;
7719
5971
  }
7720
5972
  /** In-place text editing via a contenteditable overlay positioned over the text node's screen-space pose. */
7721
5973
  declare function useTextEdit(opts: UseTextEditOptions): UseTextEditReturn;
@@ -7748,8 +6000,10 @@ declare function unionBounds<TPose extends RectPose>(poses: Iterable<TPose>): Re
7748
6000
  * (`getText` / `getStyle` / `getRuns` / `setText` / `setRuns`).
7749
6001
  *
7750
6002
  * Pose component: the helper reads `(x, y, width, height)` straight off
7751
- * the node's pose (typed `RectPose`). Consumers with non-rect poses or
7752
- * zoom-aware screen mapping should drop down to raw `useTextEdit` and
6003
+ * the node's pose (typed `RectPose`). Pass `view` and it projects that box
6004
+ * through the viewport; omit it and world units are handed through as
6005
+ * screen pixels, which is correct only for an unpanned, unzoomed canvas.
6006
+ * Consumers with non-rect poses should drop down to raw `useTextEdit` and
7753
6007
  * supply their own `getScreenPose`.
7754
6008
  */
7755
6009
 
@@ -7775,6 +6029,25 @@ interface UseSceneTextEditOptions<TData> {
7775
6029
  setRuns?: (data: TData, runs: StyledRun[]) => TData;
7776
6030
  /** Fallback fontSize when `style.fontSize` is unset. Default `16`. */
7777
6031
  defaultFontSize?: number;
6032
+ /**
6033
+ * Current viewport. Supply it on a canvas that pans or zooms: the overlay
6034
+ * is then positioned at the node's projected screen origin and CSS-scaled
6035
+ * by the view, so every typographic metric on it — including the
6036
+ * `fontSize` / `letterSpacing` a *run* carries — stays in world units and
6037
+ * scales together. Omit it and the node's world box is passed through as
6038
+ * screen pixels (correct at `{x: 0, y: 0, scale: 1}`).
6039
+ *
6040
+ * The overlay takes a single scale factor, so a non-uniform view scale is
6041
+ * represented by its `scale.x`; text under `scale.x !== scale.y` will not
6042
+ * match the canvas.
6043
+ */
6044
+ view?: View;
6045
+ /**
6046
+ * Forwarded to `useTextEdit`: is `el` part of the editor's own chrome?
6047
+ * Focus moving into it does not end the edit. Wire it to whatever renders
6048
+ * the character controls.
6049
+ */
6050
+ isEditorChrome?: (el: Element) => boolean;
7778
6051
  }
7779
6052
  /** Return shape extends `UseTextEditReturn` with an `onDoubleClick`
7780
6053
  * binding for the canvas container. Double-clicking inside a text
@@ -7790,10 +6063,12 @@ interface UseSceneTextEditReturn extends UseTextEditReturn {
7790
6063
  declare function useSceneTextEdit<TData extends DefaultTextData, TLayer extends string, TPose extends RectPose = RectPose>(scene: Scene<TData, TLayer, TPose>, container: HTMLElement | null, options?: UseSceneTextEditOptions<TData>): UseSceneTextEditReturn;
7791
6064
 
7792
6065
  /**
7793
- * Build a `TextDrawCommand` for a single plain-text string. The string is
7794
- * wrapped in a single resolved run, taking style defaults via
7795
- * `resolveTextStyle` + `resolveRuns`. For inline-styled (multi-run) text,
7796
- * build the command directly using `resolveRuns(runs, resolveTextStyle(style))`.
6066
+ * Build a `TextDrawCommand`. `textCommand` takes a plain string and wraps it
6067
+ * in one run; `textCommandFromRuns` takes the styled runs directly. Both
6068
+ * resolve through `resolveTextStyle` + `resolveRuns`, so every emitter of a
6069
+ * text command derives `align` and per-run resolution the same way a
6070
+ * caller that assembles the command by hand is one field away from a
6071
+ * silently different result.
7797
6072
  */
7798
6073
 
7799
6074
  declare function textCommand(x: number, y: number, text: string, style?: TextStyle, maxWidth?: number, height?: number, verticalAlign?: TextVerticalAlign): DrawCommand;
@@ -7823,8 +6098,15 @@ declare function charOffsetToDomPosition(parent: HTMLElement, offset: number): {
7823
6098
  /**
7824
6099
  * Inverse of `charOffsetToDomPosition`. Walks text nodes in document order;
7825
6100
  * sums the lengths of every text node preceding `node` and adds `offset`.
7826
- * If `node` is an element (not a text node), counts to the end of the
7827
- * preceding text content.
6101
+ *
6102
+ * When `node` is an element the DOM offset indexes *child nodes*, not
6103
+ * characters, so it can't simply be added — `(overlay, 0)` is the start of
6104
+ * the text and `(overlay, childNodes.length)` its end. That shape is not
6105
+ * exotic: `Range.selectNodeContents`, which `useTextEdit` uses for its
6106
+ * select-all caret, produces exactly it. So resolve an element position by
6107
+ * counting the text that precedes the boundary point. A text node can never
6108
+ * *contain* that point (the container isn't one), so each is wholly before
6109
+ * or wholly after it.
7828
6110
  */
7829
6111
  declare function domPositionToCharOffset(parent: HTMLElement, node: Node, offset: number): number;
7830
6112
 
@@ -8072,6 +6354,48 @@ declare function registerOpFactory<A>(name: string, build: (args: A) => Op): voi
8072
6354
  * a no-op placeholder so stack ordering survives across kit version skew). */
8073
6355
  declare function rebuildOp(name: string, args: unknown): Op | null;
8074
6356
 
6357
+ /** @internal */
6358
+ interface HitAdapter<TNode extends {
6359
+ id: string;
6360
+ }, TPose> {
6361
+ getNode: (id: string) => TNode | undefined;
6362
+ getNodes: () => TNode[];
6363
+ getPose: (id: string) => TPose;
6364
+ getParent: (id: string) => string | null;
6365
+ }
6366
+ interface NestedHitOpts<TNode extends {
6367
+ id: string;
6368
+ }, TPose> {
6369
+ /** Compose a child's local pose into world coords given its parent's world
6370
+ * pose. Same shape as `composeRectPose` (the default expectation). */
6371
+ composePose: (parent: TPose, child: TPose) => TPose;
6372
+ /** Derive an axis-aligned bounding rect from a (world-space) pose. Default
6373
+ * reads `x` / `y` / `width` / `height` straight off the pose (matches
6374
+ * `RectPose`). Override for non-rect poses (e.g. paths). */
6375
+ poseBounds?: (pose: TPose) => Bounds;
6376
+ /** Predicate for "this object is a nesting parent body". The leaf scan
6377
+ * skips objects for which this returns true so a click on a parent's
6378
+ * painted body resolves to a child leaf, not the parent itself. Default:
6379
+ * never (treat every object as hittable). */
6380
+ isGroup?: (id: string, obj: TNode | undefined) => boolean;
6381
+ }
6382
+ interface NestedHitTester {
6383
+ /** Outermost-ancestor pick. Suitable as the chrome-level `pickEvery`: a
6384
+ * casual click selects the whole top-level ancestor. Returns `null` on
6385
+ * empty space. */
6386
+ pickOutermost: (worldX: number, worldY: number) => string | null;
6387
+ /** Alt-aware selection-update pick. Without `alt`, returns the outermost
6388
+ * ancestor (same as `pickOutermost`). With `alt`, returns one level
6389
+ * deeper than the deepest currently-selected ancestor in the leaf's
6390
+ * chain — repeated alt-clicks step ancestor → descendant → leaf. With
6391
+ * `alt` and nothing in the chain selected, jumps straight to the leaf.
6392
+ * Plug into `useSelectTool({ pickBest })`. */
6393
+ pickBest: (worldX: number, worldY: number, alt: boolean, selection: readonly string[]) => string | null;
6394
+ }
6395
+ declare function nestedHitTester<TNode extends {
6396
+ id: string;
6397
+ }, TPose>(adapter: HitAdapter<TNode, TPose>, opts: NestedHitOpts<TNode, TPose>): NestedHitTester;
6398
+
8075
6399
  /**
8076
6400
  * Fluent builder for `PolygonPath`. Hides the `Uint8Array` / `Float32Array`
8077
6401
  * encoding behind move/line/curve/close calls and a final `build()`. Use
@@ -8173,6 +6497,44 @@ declare function linePath(a: {
8173
6497
  /** AABB of a `Path`, returned as a `RectPath` for direct reuse with rect-fast-path machinery. */
8174
6498
  declare function boundsOfPath(path: Path): RectPath;
8175
6499
 
6500
+ /**
6501
+ * Counts the path anchors used by the per-anchor coloring surface. An
6502
+ * "anchor" is the destination point of a path command: M, L, C, Q each
6503
+ * contribute one (the (x, y) where the pen ends up); Z contributes none
6504
+ * (it closes back to the subpath's first M). RectPath has four implicit
6505
+ * anchors — the corners — matching its M/L/L/L/Z stroke tessellation.
6506
+ *
6507
+ * Consumers use this to size their per-anchor color array; the renderer
6508
+ * uses it to validate the array length in dev builds.
6509
+ */
6510
+
6511
+ interface PenAnchor {
6512
+ x: number;
6513
+ y: number;
6514
+ inHandle?: {
6515
+ x: number;
6516
+ y: number;
6517
+ };
6518
+ outHandle?: {
6519
+ x: number;
6520
+ y: number;
6521
+ };
6522
+ }
6523
+ /**
6524
+ * Derive a per-subpath anchor model from a PolygonPath. Subpaths split on
6525
+ * every `M` command; a subpath is closed iff it ends with `Z`.
6526
+ *
6527
+ * Cubic-segment control points become the outHandle of the previous anchor
6528
+ * and the inHandle of the next anchor. Quadratic segments are upgraded to
6529
+ * cubics (each control reused for both adjacent handles) — this loses no
6530
+ * geometry. Linear segments produce anchors with no handles.
6531
+ */
6532
+ declare function pathToAnchors(path: PolygonPath): {
6533
+ anchors: PenAnchor[][];
6534
+ closed: boolean[];
6535
+ };
6536
+ declare function countPathAnchors(path: Path): number;
6537
+
8176
6538
  interface Point {
8177
6539
  x: number;
8178
6540
  y: number;
@@ -8654,10 +7016,12 @@ declare function createPenPreviewLayer(opts: CreatePenPreviewLayerOptions): Rend
8654
7016
  * `getEditingId()` + `getPose(id)` thunks. Returns `[]` when no editing
8655
7017
  * target is set or the target isn't a polygon — the layer never throws.
8656
7018
  *
8657
- * Visual treatment matches `penEditOverlay.ts` so the two overlays look
8658
- * consistent: anchors render as small white-filled stroked squares;
8659
- * control points as small filled circles connected to their anchor by a
8660
- * thin stem line.
7019
+ * Anchors render as small white-filled stroked squares filled solid
7020
+ * when selected; control points as small filled circles connected to
7021
+ * their anchor by a thin stem line. This is the kit's only anchor
7022
+ * chrome; the pen tool's parallel overlay and `apps/draw`'s
7023
+ * mode-decoration painter both drew the same thing from different state
7024
+ * and have been removed.
8661
7025
  *
8662
7026
  * All coordinates are projected via `worldToScreen` so the marker sizes
8663
7027
  * stay constant regardless of zoom.
@@ -8671,7 +7035,26 @@ interface CreatePathEditingOverlayLayerOptions {
8671
7035
  /** Returns the pose for an id, or null if the node has been deleted.
8672
7036
  * Non-polygon poses are tolerated (the layer no-ops on them). */
8673
7037
  getPose(id: string): Path | null;
8674
- /** Optional styling overrides. Defaults match `penEditOverlay`. */
7038
+ /** Flat indices of the selected anchors. Selected anchors render
7039
+ * filled; unselected ones hollow — the standard vector-editor cue for
7040
+ * "these are what the arrow keys and Delete will act on". Omit when
7041
+ * the consumer has no anchor selection to show. */
7042
+ getSelectedAnchors?(): ReadonlySet<number>;
7043
+ /** In-flight anchor-marquee rect in world coords, or null. Drawn as a
7044
+ * rubber band while `marqueeAnchorsAction` is dragging. */
7045
+ getMarquee?(): {
7046
+ x: number;
7047
+ y: number;
7048
+ width: number;
7049
+ height: number;
7050
+ } | null;
7051
+ /** Chrome-caps predicate. The layer asks `'path-edit.anchors'` before
7052
+ * drawing anything, so paint and the anchor hit-test in
7053
+ * `affordanceAt` consult the same rule — otherwise a consumer that
7054
+ * hides the chrome still gets grabbable invisible anchors, or vice
7055
+ * versa. Omit to always draw when an edit target is set. */
7056
+ isVisible?(chromeId: string): boolean;
7057
+ /** Optional styling overrides. */
8675
7058
  style?: PathEditingOverlayStyle;
8676
7059
  }
8677
7060
  interface PathEditingOverlayStyle {
@@ -8685,6 +7068,13 @@ interface PathEditingOverlayStyle {
8685
7068
  * curve / background show through, which matches Figma's "translucent
8686
7069
  * handle dot" idiom. */
8687
7070
  handleDotFill?: string;
7071
+ /** Fill of a selected anchor square. Defaults to the anchor stroke
7072
+ * color, so selection reads as "the marker filled in". */
7073
+ anchorFillSelected?: string;
7074
+ /** Stroke of the marquee rubber band. */
7075
+ marqueeStroke?: string;
7076
+ /** Fill of the marquee rubber band. */
7077
+ marqueeFill?: string;
8688
7078
  }
8689
7079
  declare function createPathEditingOverlayLayer(opts: CreatePathEditingOverlayLayerOptions): RenderLayer<unknown>;
8690
7080
 
@@ -9421,36 +7811,6 @@ interface AnchorHit {
9421
7811
  * are within `threshold` (controls are smaller and rendered on top). */
9422
7812
  declare function hitAnchor(path: PolygonPath, worldX: number, worldY: number, threshold: number): AnchorHit | null;
9423
7813
 
9424
- /** Visual options for the anchor-edit overlay. */
9425
- interface AnchorEditOverlayOpts {
9426
- /** Returns the live editing state (or `null` when not editing). The overlay
9427
- * draws nothing when `null`. */
9428
- getOverlay: () => {
9429
- pose: PolygonPath;
9430
- selectedAnchors: number[];
9431
- } | null;
9432
- /** Tangent-line stroke color. Default `#888`. */
9433
- tangentStroke?: string;
9434
- /** Anchor circle radius (world units). Default 4. */
9435
- anchorRadius?: number;
9436
- /** Control circle radius (world units). Default 3. */
9437
- controlRadius?: number;
9438
- /** Anchor fill. Default `#fff`. */
9439
- anchorFill?: string;
9440
- /** Anchor stroke. Default `#1a130d`. */
9441
- anchorStroke?: string;
9442
- /** Selected-anchor fill (highlight). Default `#7fb069`. */
9443
- selectedAnchorFill?: string;
9444
- /** Control fill. Default `#1a130d`. */
9445
- controlFill?: string;
9446
- /** Control stroke. Default `#fff`. */
9447
- controlStroke?: string;
9448
- }
9449
- declare function createAnchorEditOverlayLayer(opts: AnchorEditOverlayOpts): RenderLayer<unknown>;
9450
-
9451
- /** Default area-select behavior: replace selection with hits inside the marquee, or extend with shift held. */
9452
- declare function selectFromMarquee(): AreaSelectBehavior;
9453
-
9454
7814
  /**
9455
7815
  * Typed keys for the gesture/behavior scratch store.
9456
7816
  *
@@ -10197,4 +8557,11 @@ declare function ExcludeIcon(): react_jsx_runtime.JSX.Element;
10197
8557
  declare function DivideIcon(): react_jsx_runtime.JSX.Element;
10198
8558
  declare function CropIcon(): react_jsx_runtime.JSX.Element;
10199
8559
 
10200
- export { ALWAYS, type Action, ActionDeps, ActionDisabledReason, type ActionEnabledResult, type ActionEntry, type ActionsProp, ActionsProvider, ActionsProviderIfRoot, type ActionsRegistry, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, type ActiveToolContextProviderProps, type ActiveToolContextValue, Affordance, AffordanceBinding, AffordanceHit, type AlignAdapter, type AlignAnchor, type AlignBounds, type AlignBoundsProjection, type AlignEdge, type AlignMatchResult, type AlignMoveArgs, type AlignmentBehaviorBase, type AnchorEditOverlayOpts, type AnchorHit, type AnimateOnSetPoseOptions, type AnimateToBoundsOptions, type AnimationHandle, type Animator, AnyTool, AreaSelectAdapter, AreaSelectBehavior, type AreaSelectDep, type AreaSelectOverlayStyle, type ArrayAdapter, type ArrayAdapterConfig, BUNDLE_TOOLS, BindingOpts, type BindingScope, type BooleanOp, type BooleanOpResult, type BooleansAdapter, type BoundGesture, Bounds, BoundsConstraint, type BuildChromeCtxArgs, type BuildRuleCtxArgs, type BuiltinShapeToolId, type BuiltinToolId, type BuiltinToolOptions, CURVE_REPS, type CanvasExtensionApi, type CanvasFocusReturn, type CanvasHelpers, type CanvasProps, type CanvasSelectionMode, type CanvasSize, type CanvasSizeSnapshot, type CellHighlightLayerOpts, type ChromeCtx, type ChromeId, ChromeState, type ClampBounds, type ClipboardIngestCtx, ClipboardSnapshot, type CoalescedCtx, type ColorInterpolate, type ColorOverride, type ColorOverrideFn, ColorOverrideRegistry, type ColorSpace, type ComposeSelectionPoseOpts, type ComputeFitViewOptions, type Condition, type ContentHandlerEntry, type CornerResizeAffordanceOptions, type CornerResizeScratch, type CreateChildrenLayerOpts, type CreateParallaxLayerOpts, type CreatePathEditingOverlayLayerOptions, type CreatePathLayerOpts, type CreatePenPreviewLayerOptions, type CreateTextLayerOpts, type CreateViewportLayerOpts, CropIcon, CursorCoordsHud, type CursorCoordsHudProps, type CurveRepKind, type CurveRepresentation, type CustomLayerEntry, type CycleHandle, type CycleVertexColorsOptions, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_THEME, DEFAULT_FILL_COLOR, DEFAULT_FLATTEN_TOLERANCE, DEFAULT_GUIDE_TOLERANCE_PX, DEFAULT_HANDLE_SIZE, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_STROKE_COLOR, DEFAULT_VELOCITY_DECAY, DebugConfig, DebugSink, DebugSnapshot, DebugTheme, type DecayLoopConfig, type DecayOptions, type DefaultTextData, type DepName, type DepRegistry, DepRegistryProvider, DepRegistryProviderIfRoot, type DepSchema, type DeriveAlignmentGuidesOptions, Dims, type Discriminator, type Dispatcher, type DispatcherContext, type DistributeAdapter, type DistributeAxis, type DistributeMode, DivideIcon, type DragGestureController, type DragGestureCtx, type DragGestureEndCtx, type DragGesturePhase, type DragGesturePoint, type DragHandleOptions, type DragPayload, type DragRadialController, type DragRadialCtx, type DragRadialEndCtx, type DragRadialPoint, type DragRadialState, type DragRectBounds, type DragRectController, type DragRectCtx, type DragRectEndCtx, type DragRectPoint, DrawCommand, type DropZoneOptions, EASINGS, type EasingFn, type EasingName, type EditAnchorsDep, type EllipseBounds, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, FillStyle, type FitTextPoseOptions, FitViewToBoundsOptions, type FreeformOptions, GHOST_STROKE, type GateLayerOptions, type GeometryProjection, GestureBinding, type GridLayerOpts, type GridSlotConfig, Guide, type GuideSnapOptions, type GuidesLayerOpts, HandIcon, type InertiaConfig as HandToolInertiaConfig, type HandleDragPoint, type HeadlessCanvasLike, HotkeyTrigger, type IconProps, type ImageNodeData, type ImageStatus, type IndicatorStyle, type IngestCtx, type IngestionDep, InsertAdapter, InsertBehavior, type InsertDep, type InsertExtras, type InsertNodeFactory, type InsertOp, type InsertOverlayStyle, type Interpolate, type InterpolatorFactory, IntersectIcon, Invoker, KIT_SHAPE_KINDS, type LabelOptions, LassoHitMode, LassoIcon, LassoSelectAdapter, LassoSelectBehavior, type LassoSelectDep, LayerEnumerableAdapter, type LayerSlotValue, type LayersMap, type LayoutDep, type LayoutLine, type LayoutResult, LayoutSnap, LayoutStrategy, type LifecycleAnimation, LineIcon, type LinePoint, type LoopFactory, type LoopOptions, MOVE_ANCHORS, type MarkdownFontOptions, type MatchResult, type MeasureFn, type MeasureTextBoundsOpts, type MeasuredText, MinimapCanvas, type MinimapCanvasProps, type MinimapFit, ModifierState, type MomentumOptions, MoveAdapter, MoveBehavior, type MoveOverlayStyle, NEVER, type NestedHitOpts, type NestedHitTester, type NestedSelectAdapter, type NodeAtPointDep, type NodeClientRect, NodeId, type NodePaintCtx, type NodeProperties, type NodePropertiesEntry, type NodeRouting, type NodeRoutingEntry, type NodeShapeEntry, OngoingHandle, type OpenFilePickerOptions, OrderedAdapter, OriginProjection, type PanBounds, type ParallaxOpts, Path, type PathAnchor, PathBuilder, PathDrawCommand, type PathEditingOverlayStyle, PathFillRule, type PathInWorldPose, type PenAnchor$1 as PenAnchor, PenIcon, type PenPreviewStyle, type PenScratch, type PenSubpath, PencilIcon, type PhysicsHandle, type PhysicsOptions, PickHud, type PickHudProps, type PickTopMostHitAdapter, type PinchZoomToolOpts, type PixelRenderPlan, Point2, type PointInPathOptions, type PointInTextPoseOpts, PointSnapBehavior, PointerContextProvider, type PointerContextValue, type PointerGestureBindings, type PointerGestureCallbackCtx, PointerProviderIfRoot, type PointerSample, type PointerStylusState, type PointerWorldPos, PolygonIcon, PolygonPath, type PolygonPoint, PoseComposition, PoseProjection, type PoseRotation, type PositionedRun, type PressureToWidthOptions, type Pt, RECT_ALIGN_PROJECTION, type RasterImage, type Rect, type RectBounds, RectIcon, RectPath, RectPose, type RegisterNodeShapeOptions, RegisteredOp, RenderLayer, type RenderSceneToCanvasArgs, type RenderSceneToPixelsArgs, type ReorderDirection, ResizeAdapter, ResizeAnchor, type ResizePolicy, ResizePose, type ResolveOnlyResult, ResolvedTextStyle, RotateAdapter, RotateBehavior, type RotateGeometry, RotatedPose, type RotationAffordanceOptions, type RotationHandle, type RotationScratch, type Rule, type RuleCtx, SPRING_PRESETS, type ScalarOrXY, Scene, SceneAdapter, type SceneAdapterSelection, SceneCanvas, type SceneCanvasAdapter, type SceneCanvasApi, type SceneCanvasHit, type SceneCanvasProps, Node$1 as SceneNode, type SceneNodeClientRectOpts, SceneRegistry, type SceneSlotConfig, type SceneToAdapterOptions, SceneViewCanvas, type SceneViewCanvasProps, type SceneViewDrawOne, type ScopedBinding, type ScratchKey, type ScratchStore, type SelectAdapter, type SelectFromLassoOptions, SelectIcon, SelectionApi, SelectionContextProvider, SelectionContextProviderIfRoot, type SelectionContextValue, type SelectionHandlesLayerOpts, type SelectionOutlineLayerOpts, type SelectionOverlayLayerOpts, type SelectionOverlaySlotConfig, type Selector, SerializedScene, type SetPathFields, ShaderProgramHandle, type SharedAnchor, type Simulation, type SimulationForce, type SimulationNode, type SliceDep, type SnapPattern, type SnapPointOptions, SnapStrategy, type SplitByLineOptions, type SpringOptions, type SpringPoseOptions, type SpringPreset, type SpringPresetName, type SpringVertexColorsOptions, type StaggerBuilder, type StaggerDelay, type StaggerFactory, type StaggerOptions, type StaggerPerItem, type StaggerSpringPoseOptions, type StaggerTweenOptions, type StaggerVertexColorsOptions, type StandardSlotConfig, type StandardSlotName, StarIcon, type StarPoint, type StartEditOptions, Stroke, StyledRun, type StylusData, SubtractIcon, type SvgIngestOptions, type SvgUnpacker, type TextEditDep, type TextEditScreenPose, TextIcon, type TextPose, type TextRenderer, TextStyle, TextVerticalAlign, TextureHandle, type ThresholdDragHandle, type ThresholdDragOptions, type TileGridOptions, type TilePatternOpts, Tool, type ToolBundle, ToolCtx, ToolKeybinding, type ToolPref, type ToolPrefBoolean, type ToolPrefBooleanControl, type ToolPrefColor, type ToolPrefCustom, type ToolPrefEnum, type ToolPrefEnumControl, type ToolPrefGroup, type ToolPrefKind, type ToolPrefLeaf, type ToolPrefNumber, type ToolPrefNumberControl, type ToolPrefNumberUnit, type ToolPrefString, type ToolPrefStringControl, type ToolsApi, ToolsDispatcher, type TweenLoopOptions, type TweenOptions, type TweenPoseOptions, type TweenVertexColorsOptions, type UiOngoingControl, UnionIcon, UnitSystem, UnitValue, UnknownIcon, type UseAlignOptions, type UseAlignReturn, type UseAnimatorOptions, type UseAreaSelectOptions, type UseArrayAdapterOptions, type UseBooleansOptions, type UseBooleansReturn, type UseCanvasFocusOptions, type UseDistributeOptions, type UseDistributeReturn, type UseDragGestureOptions, type UseDragRadialOptions, type UseDragRectOptions, type UseEllipseToolOptions, type UseEyedropperToolOptions, type UseGestureDispatcherOptions, type UseGridCellHoverOptions, type UseGridCellHoverReturn, type UseGuidesReturn, type UseHandToolOptions, type UseHandleDragOptions, type UseHandleDragReturn, type UseHoverTrackingArgs, type UseImageToolOptions, UseInsertOptions, type UseInsertToolOptions, type UseKeybindingsOptions, type UseLassoSelectOptions, type UseLassoToolOptions, type UseLineToolOptions, UseMoveOptions, type UseNestedSelectToolOptions, type UsePenToolOptions, type UsePenToolReturn, type UsePencilToolOptions, type UsePointerGesturesOptions, type UsePointerStylusOptions, type UsePolygonToolOptions, type UseRectToolOptions, UseResizeOptions, type UseResizePolicyOptions, type UseRotateOptions, type UseRotateToolOptions, UseSceneOptions, type UseSceneTextEditOptions, type UseSceneTextEditReturn, type UseSceneTrivialOptions, type UseSelectToolOptions, UseSelectionOptions, type UseSimulationOptions, type UseStandardActionsOptions, type UseStarToolOptions, type UseTextEditOptions, type UseTextEditReturn, type UseTextToolOptions, type UseToolsOptions, type UseZoomOptions, type UseZoomReturn, type Vec2, type VertexColorChannel, View, type ViewApi, ViewTransform, type ViewportConfig, ViewportDims, type ViewportZoomOptions, type VisibilityRules, WeaselProvider, type WheelInput, type WheelState, ZoomBound, type ZoomBounds, type ZoomClampOpts, ZoomFactor, aabbCenter, actionIs, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, always, and, animateLifecycle, animateOnSetPose, applyBooleanOp, applyHitExistingGate, applyOpsTo, areaSelectAction, arrayAdapter, bezierCubic, bezierQuadratic, boundsOfPath, buildChromeCtx, buildRuleCtx, buildSceneViewCommands, canBringForward, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, cloneAction, composeAffordanceLayer, composePath, composeSelectionPose, computeFitView, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createAnchorEditOverlayLayer, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDeleteOp, createDispatcher, createGridLayer, createGuidesLayer, createHistory, createInsertOp, createMarkdownRenderer, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createReparentOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetSelectionOp, createSetTextOp, createTextLayer, createTilePattern, createTransformOp, createViewportLayer, cycleVertexColors, decomposePath, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, defineTool, deleteScratch, deriveAlignmentGuides, deriveParallaxView, dispatchApplyBatch, dlog, domPositionToCharOffset, domToRuns, dwarn, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, ellipsePath, enterTextEditAction, enumerateAnchors, evaluate, evaluateEnabled, findNodeShape, findShapeSilhouette, fitTextPose, fitToBounds, fitZoom, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, focused, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getScratch, getStylusData, guideSnapStrategy, hexToRgba, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isDebugEnabled, isEditableTarget, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linePath, linear, makeViewportZoomAction, matchAlignment, matchesKeyBinding, meanScale, measureText, measureTextBounds, mergeAlphaFromPrev, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathPoseDescriptor, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInPath, pointInRotatedRect, pointInTextPose, polygonFromPoints, poseRotationOf, pressureToWidth, rainbowVertexColors, rebuildOp, rectCorners, rectPath, registerContentHandler, registerNodeShape, registerOpFactory, regularPolygonPath, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, scalePathToBounds, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, scratchKey, selectFromLasso, selectFromMarquee, selectionAtLeast, selectionEmpty, selectionIs, setScratch, sliceAction, snap, snapPoint, solidVertexColors, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, starPath, startThresholdDrag, subscribeImageReady, textCommand, tileGrid, toHex8, transformPath, translatePath, translatePolygonInPlace, translatePoseViaDescriptor, tweenPose, tweenVertexColors, unionBounds, unionBoundsPath, useAction, useActionsRegistry, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDepRegistry, useDepSource, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useInsertTool, useKeybindings, useLassoTool, useLineTool, useNestedSelectTool, useOptionalActiveToolContext, useOptionalDepRegistry, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerContext, usePointerGestures, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewTween, useZoom, viewportDragPanAction, viewportZoomAction, when, withAlpha01, withCoord, worldEditToStorage, zoomAt, zoomAtLeast };
8560
+ interface InsertOverlayStyle {
8561
+ fill?: string;
8562
+ stroke?: string;
8563
+ dash?: number[];
8564
+ lineWidth?: number;
8565
+ }
8566
+
8567
+ export { Action, ActionsProp, ActionsProviderIfRoot, ActionsRegistry, Affordance, AffordanceBinding, AffordanceHit, type AlignAdapter, type AlignAnchor, type AlignBounds, type AlignBoundsProjection, type AlignEdge, type AlignMatchResult, type AlignMoveArgs, type AlignmentBehaviorBase, type AnchorHit, type AnimateOnSetPoseOptions, type AnimateToBoundsOptions, type AnimationHandle, type Animator, AnyTool, AreaSelectAdapter, type ArrayAdapter, type ArrayAdapterConfig, BUNDLE_TOOLS, BooleansAdapter, Bounds, BoundsConstraint, type BuildChromeCtxArgs, type BuiltinShapeToolId, type BuiltinToolId, type BuiltinToolOptions, CURVE_REPS, type CanvasExtensionApi, type CanvasFocusReturn, type CanvasHelpers, type CanvasProps, type CanvasSelectionMode, type CanvasSize, type CanvasSizeSnapshot, type CellHighlightLayerOpts, ChromeCtx, ChromeId, ChromeState, type ClampBounds, ClipboardSnapshot, type CoalescedCtx, type ColorInterpolate, type ColorOverride, type ColorOverrideFn, ColorOverrideRegistry, type ColorSpace, type ComposeSelectionPoseOpts, type ComputeFitViewOptions, Condition, ContentHandlerEntry, type CornerResizeAffordanceOptions, type CornerResizeScratch, type CreateChildrenLayerOpts, type CreateParallaxLayerOpts, type CreatePathEditingOverlayLayerOptions, type CreatePathLayerOpts, type CreatePenPreviewLayerOptions, type CreateTextLayerOpts, type CreateViewportLayerOpts, CropIcon, CursorCoordsHud, type CursorCoordsHudProps, type CurveRepKind, type CurveRepresentation, type CustomLayerEntry, type CycleHandle, type CycleVertexColorsOptions, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_THEME, DEFAULT_FILL_COLOR, DEFAULT_FLATTEN_TOLERANCE, DEFAULT_GUIDE_TOLERANCE_PX, DEFAULT_HANDLE_SIZE, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_STROKE_COLOR, DEFAULT_VELOCITY_DECAY, DebugConfig, DebugSink, DebugSnapshot, DebugTheme, type DecayLoopConfig, type DecayOptions, type DefaultTextData, DepRegistryProviderIfRoot, DepSchema, type DeriveAlignmentGuidesOptions, Dims, type Discriminator, Dispatcher, type DistributeAdapter, type DistributeAxis, type DistributeMode, DivideIcon, type DragGestureController, type DragGestureCtx, type DragGestureEndCtx, type DragGesturePhase, type DragGesturePoint, type DragHandleOptions, type DragPayload, type DragRadialController, type DragRadialCtx, type DragRadialEndCtx, type DragRadialPoint, type DragRadialState, type DragRectBounds, type DragRectController, type DragRectCtx, type DragRectEndCtx, type DragRectPoint, DrawCommand, type DropZoneOptions, EASINGS, type EasingFn, type EasingName, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, FillStyle, type FitTextPoseOptions, FitViewToBoundsOptions, type FreeformOptions, GHOST_STROKE, type GateLayerOptions, GeometryProjection, type GestureSource, type GridLayerOpts, type GridSlotConfig, Guide, type GuideSnapOptions, type GuidesLayerOpts, HandIcon, type InertiaConfig as HandToolInertiaConfig, type HandleDragPoint, type HeadlessCanvasLike, HotkeyTrigger, type IconProps, type ImageNodeData, type ImageStatus, type IndicatorStyle, InsertAdapter, InsertBehavior, InsertExtras, type InsertNodeFactory, type InsertOp, type InsertOverlayStyle, type Interpolate, type InterpolatorFactory, IntersectIcon, KIT_SHAPE_KINDS, type LabelOptions, LassoHitMode, LassoIcon, LassoSelectAdapter, LassoSelectBehavior, LayerEnumerableAdapter, type LayerSlotValue, type LayersMap, type LayoutLine, type LayoutResult, LayoutSnap, LayoutStrategy, type LifecycleAnimation, LineIcon, type LinePoint, type LoopFactory, type LoopOptions, MIXED, MOVE_ANCHORS, type MarkdownFontOptions, type MeasureFn, type MeasureTextBoundsOpts, type MeasuredText, MinimapCanvas, type MinimapCanvasProps, type MinimapFit, type Mixed, ModifierState, type MomentumOptions, MoveAdapter, MoveBehavior, type NestedHitOpts, type NestedHitTester, type NodeClientRect, NodeId, type NodePaintCtx, type NodeProperties, type NodePropertiesEntry, type NodeRouting, type NodeRoutingEntry, type NodeShapeEntry, type OpenFilePickerOptions, OrderedAdapter, OriginProjection, type PanBounds, type ParallaxOpts, Path, type PathAnchor, PathBuilder, PathDrawCommand, type PathEditingOverlayStyle, PathFillRule, type PathInWorldPose, type PenAnchor, PenIcon, type PenPreviewStyle, type PenScratch, type PenSubpath, PencilIcon, type PencilPoint, type PhysicsHandle, type PhysicsOptions, PickHud, type PickHudProps, type PickTopMostHitAdapter, type PinchZoomToolOpts, type PixelRenderPlan, type PointInPathOptions, type PointInTextPoseOpts, PointSnapBehavior, PointerProviderIfRoot, type PointerSample, type PointerStylusState, PolygonIcon, PolygonPath, type PolygonPoint, PoseProjection, type PoseRotation, type PositionedRun, type PressureToWidthOptions, type Pt, RECT_ALIGN_PROJECTION, type RangeStyle, type RasterImage, type Rect, RectIcon, RectPath, RectPose, type RegisterNodeShapeOptions, RegisteredOp, RenderLayer, type RenderSceneToCanvasArgs, type RenderSceneToPixelsArgs, type ReorderDirection, ResizeAdapter, ResizeAnchor, ResizePose, ResolvedTextStyle, RotateAdapter, RotateBehavior, type RotateGeometry, RotatedPose, type RotationAffordanceOptions, type RotationHandle, type RotationScratch, Rule, RuleCtx, type RunStylePatch, SPRING_PRESETS, type ScalarOrXY, Scene, SceneAdapter, type SceneAdapterSelection, SceneCanvas, type SceneCanvasAdapter, type SceneCanvasApi, type SceneCanvasHit, type SceneCanvasProps, Node$1 as SceneNode, type SceneNodeClientRectOpts, SceneRegistry, type SceneSlotConfig, type SceneToAdapterOptions, SceneViewCanvas, type SceneViewCanvasProps, type SceneViewDrawOne, type ScratchKey, type ScratchStore, type SelectAdapter, type SelectFromLassoOptions, SelectIcon, SelectionApi, SelectionContextProvider, SelectionContextProviderIfRoot, type SelectionContextValue, type SelectionHandlesLayerOpts, type SelectionOutlineLayerOpts, type SelectionOverlayLayerOpts, type SelectionOverlaySlotConfig, SerializedScene, type SetPathFields, ShaderProgramHandle, type SharedAnchor, type Simulation, type SimulationForce, type SimulationNode, SliceDep, type SnapPattern, type SnapPointOptions, SnapStrategy, type SplitByLineOptions, type SpringOptions, type SpringPoseOptions, type SpringPreset, type SpringPresetName, type SpringVertexColorsOptions, type StaggerBuilder, type StaggerDelay, type StaggerFactory, type StaggerOptions, type StaggerPerItem, type StaggerSpringPoseOptions, type StaggerTweenOptions, type StaggerVertexColorsOptions, type StandardSlotConfig, type StandardSlotName, StarIcon, type StarPoint, type StartEditOptions, Stroke, type StyleKey, StyledRun, type StylusData, SubtractIcon, SvgIngestOptions, type TextEditScreenPose, type TextEditSelection, TextIcon, type TextPose, type TextRenderer, TextStyle, TextVerticalAlign, TextureHandle, type ThresholdDragHandle, type ThresholdDragOptions, type TileGridOptions, type TilePatternOpts, Tool, type ToolBundle, ToolCtx, ToolKeybinding, type ToolPref, type ToolPrefBoolean, type ToolPrefBooleanControl, type ToolPrefColor, type ToolPrefCustom, type ToolPrefEnum, type ToolPrefEnumControl, type ToolPrefGroup, type ToolPrefKind, type ToolPrefLeaf, type ToolPrefNumber, type ToolPrefNumberControl, type ToolPrefNumberUnit, type ToolPrefPaint, type ToolPrefString, type ToolPrefStringControl, type ToolsApi, type TweenLoopOptions, type TweenOptions, type TweenPoseOptions, type TweenVertexColorsOptions, UnionIcon, UnitSystem, UnitValue, UnknownIcon, type UseAlignOptions, type UseAlignReturn, type UseAnimatorOptions, type UseArrayAdapterOptions, type UseBooleansOptions, type UseBooleansReturn, type UseCanvasFocusOptions, type UseDistributeOptions, type UseDistributeReturn, type UseDragGestureOptions, type UseDragRadialOptions, type UseDragRectOptions, type UseEyedropperToolOptions, type UseGestureDispatcherOptions, type UseGridCellHoverOptions, type UseGridCellHoverReturn, type UseGuidesReturn, type UseHandToolOptions, type UseHandleDragOptions, type UseHandleDragReturn, type UseHoverTrackingArgs, type UseImageToolOptions, type UseKeybindingsOptions, type UseLassoSelectOptions, type UseLassoToolOptions, UseMoveOptions, type UsePenToolOptions, type UsePointerStylusOptions, type UsePolygonToolOptions, UseResizeOptions, type UseResizePolicyOptions, type UseRotateOptions, type UseRotateToolOptions, UseSceneOptions, type UseSceneTextEditOptions, type UseSceneTextEditReturn, type UseSceneTrivialOptions, type UseSelectToolOptions, UseSelectionOptions, type UseSimulationOptions, type UseStandardActionsOptions, type UseStarToolOptions, type UseTextEditOptions, type UseTextEditReturn, type UseToolsOptions, type UseZoomOptions, type UseZoomReturn, type Vec2, type VertexColorChannel, View, ViewTransform, type ViewportConfig, ViewportDims, type ViewportZoomOptions, VisibilityRules, WeaselProvider, type WheelInput, type WheelState, ZoomBound, type ZoomBounds, type ZoomClampOpts, ZoomFactor, aabbCenter, actionIs, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, always, and, animateLifecycle, animateOnSetPose, applyHitExistingGate, applyOpsTo, applyStyleToRange, areaSelectAction, arrayAdapter, bezierCubic, bezierQuadratic, boundsOfPath, buildChromeCtx, buildSceneViewCommands, canBringForward, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, cloneAction, composeAffordanceLayer, composePath, composeSelectionPose, computeFitView, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDeleteOp, createGridLayer, createGuidesLayer, createHistory, createInsertOp, createMarkdownRenderer, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createReparentOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetSelectionOp, createSetTextOp, createTextLayer, createTilePattern, createTransformOp, createViewportLayer, cycleVertexColors, decomposePath, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, deleteScratch, deriveAlignmentGuides, deriveParallaxView, dispatchApplyBatch, dlog, domPositionToCharOffset, domToRuns, dwarn, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, ellipsePath, enumerateAnchors, findNodeShape, findShapeSilhouette, fitTextPose, fitToBounds, fitZoom, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, focused, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getScratch, getStylusData, guideSnapStrategy, hexToRgba, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isDebugEnabled, isEditableTarget, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linePath, linear, makeViewportZoomAction, matchAlignment, matchesKeyBinding, meanScale, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathPoseDescriptor, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInPath, pointInRotatedRect, pointInTextPose, polygonFromPoints, poseRotationOf, pressureToWidth, rainbowVertexColors, rebuildOp, rectCorners, rectPath, registerNodeShape, registerOpFactory, regularPolygonPath, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, scalePathToBounds, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, scratchKey, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setScratch, snap, snapPoint, solidVertexColors, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, starPath, startThresholdDrag, styleAtRange, subscribeImageReady, textCommand, tileGrid, toHex8, transformPath, translatePath, translatePolygonInPlace, translatePoseViaDescriptor, tweenPose, tweenVertexColors, unionBounds, unionBoundsPath, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewTween, useZoom, viewportDragPanAction, viewportZoomAction, when, withAlpha01, withCoord, worldEditToStorage, zoomAt, zoomAtLeast };