@weasel-js/core 0.6.0 → 0.7.1

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 +233 -0
  2. package/dist/{DrawCommand-Dl0bXNfS.d.ts → DrawCommand-CJtqqt8H.d.ts} +26 -2
  3. package/dist/{chunk-7V6JEOXE.js → chunk-2J6V527H.js} +10033 -10912
  4. package/dist/chunk-2J6V527H.js.map +1 -0
  5. package/dist/chunk-SYM6RAM4.js +232 -0
  6. package/dist/chunk-SYM6RAM4.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-DOYRTfP0.d.ts +2986 -0
  11. package/dist/index.css +0 -35
  12. package/dist/index.css.map +1 -1
  13. package/dist/index.d.ts +1174 -2481
  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 +30 -5
  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,62 @@
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, s as AffordanceRegion, t as CommonAffordanceScratch, B as BooleansAdapter } from './index-DOYRTfP0.js';
8
+ export { u as ALWAYS, v as ActionDeps, w as ActionDisabledReason, x as ActionEnabledResult, y as ActionEntry, z as ActionsProvider, E as ActiveToolContextProvider, F as ActiveToolContextProviderIfRoot, J as ActiveToolContextProviderProps, K as ActiveToolContextValue, L as AreaSelectDep, M as BindingOpts, N as BindingScope, O as BooleanOp, P as BooleanOpResult, Q as BoundGesture, W as BuildRuleCtxArgs, X as ClipboardIngestCtx, Y as CustomPaintContext, Z as DepName, _ as DepRegistry, $ as DepRegistryProvider, a0 as DispatcherContext, a1 as DragSample, a2 as EditAnchorsDep, a3 as GestureBinding, a4 as ImmediateInvoker, a5 as IngestCtx, a6 as IngestionDep, a7 as InsertDep, a8 as InvocationCtx, a9 as Invoker, aa as LassoSelectDep, ab as LayoutDep, ac as MatchResult, ad as NEVER, ae as NodeAtPointDep, af as OngoingHandle, ag as OngoingInvoker, ah as OngoingOverlay, ai as Point2, aj as PointerContextProvider, ak as PointerContextValue, al as PointerWorldPos, am as ResizePolicy, an as ResolveAllOptions, ao as ResolveOnlyResult, ap as ResolvedCandidate, aq as ScopedBinding, ar as SelectionExtendKey, as as SelectionMode, at as Selector, au as SnapDep, av as SvgUnpacker, aw as TextEditDep, ax as ToolModifiers, ay as ToolPresentation, az as ToolSlot, aA as UiOngoingControl, aB as ViewApi, aC as applyBooleanOp, aD as buildRuleCtx, aE as createDispatcher, aF as describeRule, aG as drawLayers, aH as enterTextEditAction, aI as evaluate, aJ as evaluateEnabled, aK as registerContentHandler, aL as routing, aM as sliceAction, aN as specificity, aO as useAction, aP as useActionsRegistry, aQ as useActiveToolContext, aR as useDepRegistry, aS as useDepSource, aT as useOptionalActiveToolContext, aU as useOptionalDepRegistry, aV as usePointerContext, aW as useSelection } from './index-DOYRTfP0.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, LocalFontOutlinesResult, OutlineFontStyle, OutlineSource, OutlineStatus, OutlineVariant, canQueryLocalFonts, enableLocalFontOutlines, hasFontOutlines, isCanvasFont, listFontOutlines, outlineStatus, registerCanvasFont, registerFont, registerFontOutlines, subscribeGlyphReady, unregisterCanvasFont, unregisterFontOutlines } from '@weasel-js/font';
39
+ import '@weasel-js/modes';
40
+
41
+ /**
42
+ * The kit version a build was compiled from, so an app can report what it is
43
+ * running. Substituted at build time — by `tsup.config.ts` for the published
44
+ * package, and by `scripts/vite-build-info.ts` for anything in this repo that
45
+ * bundles core's source through the workspace aliases.
46
+ *
47
+ * Apps pair this with their own compile timestamp; the kit deliberately does
48
+ * not carry one, since a published package's build date says nothing about
49
+ * when the app embedding it was built.
50
+ */
51
+ /**
52
+ * Semver of `@weasel-js/core` as of the build.
53
+ *
54
+ * Falls back to `'0.0.0-unknown'` when core's source is imported by a bundler
55
+ * that hasn't been given the define — honest about not knowing rather than
56
+ * throwing, since nothing about version reporting should be able to break a
57
+ * consumer's build.
58
+ */
59
+ declare const VERSION: string;
39
60
 
40
61
  /** Round `value` to the nearest multiple of `cellSize`. Returns 0 when the result would be -0. */
41
62
  declare function roundToCell(value: number, cellSize: number): number;
@@ -836,182 +857,6 @@ declare function isEditableTarget(target: EventTarget | null): boolean;
836
857
  */
837
858
  declare function matchesKeyBinding(e: KeyboardEvent, b: KeyBinding): boolean;
838
859
 
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
860
  /**
1016
861
  * Promote a Rule tree to a fluent Condition. The function form evaluates
1017
862
  * the tree against a RuleCtx; combinator methods produce new Conditions
@@ -1079,1375 +924,111 @@ declare const modeIs: (m: string) => Condition;
1079
924
  /** Active mode is one of `modes`. */
1080
925
  declare const modeIn: (modes: readonly string[]) => Condition;
1081
926
  /** Active mode is NOT `m`. */
1082
- declare const modeNot: (m: string) => Condition;
1083
- /** Active mode's `allows` includes capability `cap`. */
1084
- declare const capabilityIs: (cap: string) => Condition;
1085
- /** Active mode's `allows` includes ANY of `caps` (OR). */
1086
- declare const capabilityIn: (caps: readonly string[]) => Condition;
1087
- /** Active mode's `allows` includes ALL of `caps` (AND). */
1088
- declare const capabilityAll: (caps: readonly string[]) => Condition;
1089
- /** Active mode's `allows` does NOT include capability `cap`. */
1090
- declare const capabilityNot: (cap: string) => Condition;
1091
-
1092
- /**
1093
- * Kit-shipped defaults. Merged with the consumer's `chromeVisibility` map
1094
- * at resolve time; consumer entries take precedence per id.
1095
- *
1096
- * Written as literal Rule trees rather than fluent chains so the inputs
1097
- * each rule depends on are immediately visible. The fluent atoms compile
1098
- * to the same trees; either form is valid in a VisibilityRules entry.
1099
- *
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.
1105
- */
1106
- declare const defaultVisibilityRules: VisibilityRules;
1107
-
1108
- /**
1109
- * Build the per-frame visibility check. Merges consumer rules on top
1110
- * of the kit defaults, then closes over `ctx` so each chrome-id
1111
- * lookup runs its rule against the current state.
1112
- *
1113
- * `VisibilityRules` entries may be either fluent `Condition` values
1114
- * (callable) or raw `Rule` trees — we normalize at lookup time.
1115
- *
1116
- * Predicates are O(1) and run once per chrome id per frame; no
1117
- * memoization in v1 — profile before adding any.
1118
- *
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.
1123
- */
1124
- declare function resolveVisibility(consumer: VisibilityRules | undefined, ctx: ChromeCtx | RuleCtx): (id: ChromeId) => boolean;
1125
-
1126
- /**
1127
- * Pure assembler for {@link ChromeCtx}.
1128
- *
1129
- * Called once per frame on the rendering side from whichever surface
1130
- * needs to evaluate visibility rules — `composeAffordanceLayer`,
1131
- * `createSelectionOverlayLayer`, the dispatcher overlay layer, etc.
1132
- *
1133
- * Sources are passed as plain getters / values rather than the live
1134
- * objects they come from. This keeps `chrome-caps` from depending on
1135
- * React, the dispatcher's concrete type, or `ChromeState`'s bounds
1136
- * machinery — the same builder works from a test harness, a Storybook
1137
- * fixture, or `<SceneCanvas>`.
1138
- */
1139
-
1140
- interface BuildChromeCtxArgs {
1141
- focused: boolean;
1142
- selection: readonly NodeId[];
1143
- multiActive: boolean;
1144
- modifiers: ModifierState;
1145
- action: {
1146
- kind: string | null;
1147
- id: string | null;
1148
- };
1149
- hover: NodeId | null;
1150
- view: View;
1151
- }
1152
- declare function buildChromeCtx(args: BuildChromeCtxArgs): ChromeCtx;
1153
-
1154
- /**
1155
- * useHoverTracking — last-hovered NodeId for chrome-caps rules.
1156
- *
1157
- * Attaches a `pointermove` listener to the supplied canvas; on each
1158
- * move, converts client coords → world coords and runs the supplied
1159
- * `getNodeAtPoint`, caching the resulting id on a ref. Cleared on
1160
- * `pointerleave` (pointer left the canvas → nothing is hovered).
1161
- *
1162
- * Returns a stable getter — call once per frame from `buildChromeCtx`.
1163
- *
1164
- * No re-render: the ref updates silently, and the next render that
1165
- * reads it picks up the fresh value. Chrome-caps rules are evaluated
1166
- * during paint, so the hover state is always read fresh.
1167
- */
1168
-
1169
- interface UseHoverTrackingArgs {
1170
- canvasRef: RefObject<HTMLCanvasElement | null>;
1171
- /** Client → world conversion. Same shape as the dispatcher's. */
1172
- clientToWorld: (clientX: number, clientY: number) => {
1173
- x: number;
1174
- y: number;
1175
- };
1176
- /** Topmost-id resolver. Returns null when the world point hits nothing. */
1177
- getNodeAtPoint: (worldX: number, worldY: number) => {
1178
- id: NodeId;
1179
- } | null;
1180
- enabled?: boolean;
1181
- }
1182
- declare function useHoverTracking(args: UseHoverTrackingArgs): () => NodeId | null;
1183
-
1184
- /** Boolean op identifiers — five Pathfinder primaries plus Crop. */
1185
- type BooleanOp = 'union' | 'intersect' | 'subtract' | 'exclude' | 'divide' | 'crop';
1186
- /**
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.
1191
- */
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
-
1309
- /**
1310
- * @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>`.
1348
- *
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`.
1386
- */
1387
- declare function ActiveToolContextProviderIfRoot({ children, }: {
1388
- children: ReactNode;
1389
- }): react_jsx_runtime.JSX.Element;
1390
-
1391
- /**
1392
- * `enterTextEditAction` — immediate Action descriptor for entering in-place
1393
- * text editing on a selected text node.
1394
- *
1395
- * ## Status: REAL
1396
- *
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.
1427
- *
1428
- * ### Future: per-kind target classification (Phase 14e)
1429
- *
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`.
1434
- *
1435
- * ## Migration plan for useTextTool
1436
- *
1437
- * When wiring `useTextTool` to `Tool.bindings`:
1438
- *
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.
1448
- */
1449
-
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
- /**
1482
- * @experimental
1483
- * Static descriptor for the `enterTextEdit` Action.
1484
- *
1485
- * Requires dep-schema entries: `textEdit`, `selection`.
1486
- *
1487
- * No `defaultBinding` / `defaultBinding` — fires only via `Tool.bindings`.
1488
- * Self-guards via `TextEditDep.isTextNode` when provided.
1489
- */
1490
- declare const enterTextEditAction: Action & {
1491
- requires: string[];
1492
- };
1493
-
1494
- /**
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.
1498
- */
1499
- interface SliceDep {
1500
- commit(a: Point2, b: Point2): void;
1501
- }
1502
- /**
1503
- * @experimental
1504
- * Static descriptor for the `slice` Action.
1505
- *
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.
1510
- */
1511
- declare const sliceAction: Action & {
1512
- requires: string[];
1513
- };
1514
-
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
- /**
1547
- * Adapter dep for `areaSelectAction`.
1548
- *
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];
927
+ declare const modeNot: (m: string) => Condition;
928
+ /** Active mode's `allows` includes capability `cap`. */
929
+ declare const capabilityIs: (cap: string) => Condition;
930
+ /** Active mode's `allows` includes ANY of `caps` (OR). */
931
+ declare const capabilityIn: (caps: readonly string[]) => Condition;
932
+ /** Active mode's `allows` includes ALL of `caps` (AND). */
933
+ declare const capabilityAll: (caps: readonly string[]) => Condition;
934
+ /** Active mode's `allows` does NOT include capability `cap`. */
935
+ declare const capabilityNot: (cap: string) => Condition;
936
+
2368
937
  /**
2369
- * @experimental
2370
- * Result of evaluating an Action's `enabled` predicate.
938
+ * Kit-shipped defaults. Merged with the consumer's `chromeVisibility` map
939
+ * at resolve time; consumer entries take precedence per id.
940
+ *
941
+ * Written as literal Rule trees rather than fluent chains so the inputs
942
+ * each rule depends on are immediately visible. The fluent atoms compile
943
+ * to the same trees; either form is valid in a VisibilityRules entry.
944
+ *
945
+ * Gating is written in terms of **capabilities, not mode ids**. A
946
+ * capability rule keeps holding when a new mode is added that permits the
947
+ * same thing; a mode rule has to be found and edited every time. The one
948
+ * exception is the `path-edit.*` chrome below, which is genuinely
949
+ * mode-specific: it's the visual signature of that mode, not a statement
950
+ * about what the user is allowed to do.
951
+ *
952
+ * Chrome that gates on the *absence* of a capability (the selection
953
+ * outline, suppressed while an anchor-editing overlay owns the visuals)
954
+ * uses `capability: { not: … }` for the same reason.
2371
955
  */
2372
- interface ActionEnabledResult {
2373
- enabled: boolean;
2374
- reason?: ActionDisabledReason;
2375
- }
2376
- declare function evaluateEnabled(action: Action, deps?: ActionDeps): ActionEnabledResult;
956
+ declare const defaultVisibilityRules: VisibilityRules;
957
+
2377
958
  /**
2378
- * @experimental
2379
- * Partial override or full descriptor passed via `<SceneCanvas actions={...}>`.
2380
- * `null` disables a default at this id.
959
+ * Build the per-frame visibility check. Merges consumer rules on top
960
+ * of the kit defaults, then closes over `ctx` so each chrome-id
961
+ * lookup runs its rule against the current state.
962
+ *
963
+ * `VisibilityRules` entries may be either fluent `Condition` values
964
+ * (callable) or raw `Rule` trees — we normalize at lookup time.
965
+ *
966
+ * Predicates are O(1) and run once per chrome id per frame; no
967
+ * memoization in v1 — profile before adding any.
968
+ *
969
+ * `ChromeCtx` is the legacy shape; surfaces still on it get `mode='normal'`
970
+ * and `DEFAULT_ALLOWED_CAPABILITIES` filled in here, so a caller that never
971
+ * opted into modality behaves exactly like one sitting in normal mode.
2381
972
  */
2382
- type ActionEntry = null | Partial<Action> | Action;
973
+ declare function resolveVisibility(consumer: VisibilityRules | undefined, ctx: ChromeCtx | RuleCtx): (id: ChromeId) => boolean;
974
+
2383
975
  /**
2384
- * @experimental
2385
- * Shape of the `actions` prop on `<SceneCanvas>`. `null` disables all defaults.
976
+ * Pure assembler for {@link ChromeCtx}.
977
+ *
978
+ * Called once per frame on the rendering side from whichever surface
979
+ * needs to evaluate visibility rules — `composeAffordanceLayer`,
980
+ * `createSelectionOverlayLayer`, the dispatcher overlay layer, etc.
981
+ *
982
+ * Sources are passed as plain getters / values rather than the live
983
+ * objects they come from. This keeps `chrome-caps` from depending on
984
+ * React, the dispatcher's concrete type, or `ChromeState`'s bounds
985
+ * machinery — the same builder works from a test harness, a Storybook
986
+ * fixture, or `<SceneCanvas>`.
2386
987
  */
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;
988
+
989
+ interface BuildChromeCtxArgs {
990
+ focused: boolean;
991
+ selection: readonly NodeId[];
992
+ multiActive: boolean;
993
+ modifiers: ModifierState;
994
+ action: {
995
+ kind: string | null;
996
+ id: string | null;
997
+ };
998
+ hover: NodeId | null;
999
+ view: View;
2430
1000
  }
1001
+ declare function buildChromeCtx(args: BuildChromeCtxArgs): ChromeCtx;
1002
+
2431
1003
  /**
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).
1004
+ * useHoverTracking — last-hovered NodeId for chrome-caps rules.
1005
+ *
1006
+ * Attaches a `pointermove` listener to the supplied canvas; on each
1007
+ * move, converts client coords → world coords and runs the supplied
1008
+ * `getNodeAtPoint`, caching the resulting id on a ref. Cleared on
1009
+ * `pointerleave` (pointer left the canvas → nothing is hovered).
1010
+ *
1011
+ * Returns a stable getter — call once per frame from `buildChromeCtx`.
1012
+ *
1013
+ * No re-render: the ref updates silently, and the next render that
1014
+ * reads it picks up the fresh value. Chrome-caps rules are evaluated
1015
+ * during paint, so the hover state is always read fresh.
2449
1016
  */
2450
- declare function useAction(action: Action): void;
1017
+
1018
+ interface UseHoverTrackingArgs {
1019
+ canvasRef: RefObject<HTMLCanvasElement | null>;
1020
+ /** Client → world conversion. Same shape as the dispatcher's. */
1021
+ clientToWorld: (clientX: number, clientY: number) => {
1022
+ x: number;
1023
+ y: number;
1024
+ };
1025
+ /** Topmost-id resolver. Returns null when the world point hits nothing. */
1026
+ getNodeAtPoint: (worldX: number, worldY: number) => {
1027
+ id: NodeId;
1028
+ } | null;
1029
+ enabled?: boolean;
1030
+ }
1031
+ declare function useHoverTracking(args: UseHoverTrackingArgs): () => NodeId | null;
2451
1032
 
2452
1033
  /**
2453
1034
  * `moveAction` — first `ongoing`-timing Action descriptor.
@@ -2669,12 +1250,19 @@ declare const areaSelectAction: Action & {
2669
1250
  * delegating to the scene's `add()` with a sensible default data payload for
2670
1251
  * the given `kind`. Override per-consumer for custom node factories.
2671
1252
  *
1253
+ * ## Modifiers and snapping
1254
+ *
1255
+ * Grid snapping comes from the optional `snap` dep, applied to the drag's
1256
+ * start and current point (never to freehand pencil samples). The `line`
1257
+ * kind additionally honors Shift (constrain to 15°) and reads Alt/center as
1258
+ * "mirror the start around the pointer" rather than "grow a symmetric AABB".
1259
+ * All of it resolves in `resolveEndpoints`, which both `overlay()` and
1260
+ * `onEnd()` call — so the live preview and the committed node can't disagree.
1261
+ *
2672
1262
  * ## What this does NOT wire (vs `useInsert`)
2673
1263
  *
2674
- * - `InsertBehavior` pipeline (snap, etc.) — deferred to a later phase.
2675
1264
  * - `pointInsert` fallback for click / sub-threshold drags — not wired; a
2676
1265
  * sub-threshold drag produces no insert.
2677
- * - Live insert overlay — deferred to Phase 7 overlay surface.
2678
1266
  * - `clickOnly` mode — not applicable to the descriptor model.
2679
1267
  *
2680
1268
  * ## Live preview
@@ -2730,17 +1318,32 @@ declare const insertAction: Action & {
2730
1318
  * This avoids ambient double-fire and keeps the action scoped to select-tool
2731
1319
  * context where `classifyTarget` is wired.
2732
1320
  *
2733
- * ## enabled
1321
+ * ## enabled — the anchor-editing fall-through
1322
+ *
1323
+ * There is no "is anything selected?" guard: clearing an already-empty
1324
+ * selection is a safe no-op, and the binding spec is the real gate for
1325
+ * where the click landed.
1326
+ *
1327
+ * What `enabled` *does* do is decline while a path is in anchor-edit mode,
1328
+ * so the dispatcher falls through to `selectAnchorAction`. This is the
1329
+ * click twin of the opt-out `areaSelectAction.start` already performs for
1330
+ * the drag gesture, and it exists for the same reason: `useSelectTool`
1331
+ * binds this action at **active** scope while `selectAnchor` is **ambient**,
1332
+ * so without the gate an anchor that happens to sit over empty canvas
1333
+ * clears the node selection instead of selecting the anchor.
2734
1334
  *
2735
- * Returns `SelectionRequired` disabled reason when nothing is selected, so the
2736
- * dispatcher skips the action on an already-empty canvas (no-op guard).
1335
+ * Capability eligibility (`creates-selection`, which `path-edit` does not
1336
+ * allow) already covers consumers that wired a mode registry. Consumers
1337
+ * without one get no eligibility filtering at all, so this gate is the only
1338
+ * thing separating the two. Both mechanisms are needed — see the header of
1339
+ * `anchorEditing.ts`.
2737
1340
  */
2738
1341
 
2739
1342
  /**
2740
1343
  * @experimental
2741
1344
  * Static descriptor for the `clearSelection` Action.
2742
1345
  *
2743
- * Requires dep-schema entry: `selection`.
1346
+ * Requires dep-schema entries: `selection`, `editAnchors`.
2744
1347
  *
2745
1348
  * Registered via `useStandardActions`; bound via `useSelectTool.bindings`.
2746
1349
  */
@@ -2920,40 +1523,43 @@ declare const viewportZoomAction: Action & {
2920
1523
  /**
2921
1524
  * `editAnchorsAction` — ongoing Action descriptor for editing polygon anchors.
2922
1525
  *
2923
- * ## Status: REAL
1526
+ * Owns the whole press-on-an-anchor gesture, because pressing and dragging
1527
+ * an anchor are two outcomes of one interaction rather than two
1528
+ * interactions:
1529
+ *
1530
+ * - **Press** selects. Bare press replaces the anchor selection; Shift
1531
+ * toggles the pressed anchor in or out of it. Pressing an anchor that
1532
+ * is already part of a multi-anchor selection leaves the selection
1533
+ * alone, so you can grab a set and move it.
1534
+ * - **Drag** then moves whatever the press left selected — one anchor,
1535
+ * or every selected anchor together.
1536
+ * - **Dragging a control handle** moves just that handle, mirroring the
1537
+ * opposite one while the anchor is smooth. Alt breaks the mirror.
1538
+ *
1539
+ * Selection lives on the `editAnchors` dep (flat anchor indices) rather
1540
+ * than in this handle's scratch, because the keyboard actions
1541
+ * (`nudgeAnchors`, `deleteAnchors`) and the overlay all need to read it
1542
+ * between gestures.
2924
1543
  *
2925
- * The descriptor is fully operational when the consumer wires:
1544
+ * ## Wiring
1545
+ *
1546
+ * The descriptor is operational when the consumer wires:
2926
1547
  * 1. `buildAffordanceAt` with a `getAnchorState` thunk so the dispatcher
2927
1548
  * classifies anchor/control-handle hits as `anchor:N`, `controlIn:N`,
2928
1549
  * or `controlOut:N` affordances on pointerdown.
2929
1550
  * 2. The `editAnchors` dep in the DepSchema.
2930
1551
  *
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
1552
  * When `ctx.drag.affordance` is absent or is not an anchor/control kind,
2938
1553
  * `start` returns `{}` (no-op) so other bindings can handle the drag.
2939
1554
  *
2940
- * @see useEditAnchors — the React hook this descriptor mirrors.
2941
1555
  * @see buildAffordanceAt — the classifier that produces anchor affordances.
1556
+ * @see anchorEdits — the pure geometry these handlers drive.
2942
1557
  */
2943
1558
 
2944
1559
  /**
2945
1560
  * Static descriptor for the `editAnchors` Action.
2946
1561
  *
2947
1562
  * 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
1563
  */
2958
1564
  declare const editAnchorsAction: Action & {
2959
1565
  requires: string[];
@@ -3130,8 +1736,28 @@ interface UseGestureDispatcherOptions {
3130
1736
  actions: ActionsRegistry;
3131
1737
  /** Tool definitions keyed by id. Typically passes an empty Map. */
3132
1738
  toolsById: ReadonlyMap<string, Tool>;
1739
+ /** Ids (within `toolsById`) of always-on tools, whose bindings assemble at
1740
+ * ambient scope. See `DispatcherContext.ambientToolIds`. */
1741
+ ambientToolIds?: readonly string[];
3133
1742
  /** Default true. Set false to opt out of dispatcher wiring (e.g. demos that disable it). */
3134
1743
  enabled?: boolean;
1744
+ /**
1745
+ * Observer fired whenever the dispatcher synthesizes a double click, in
1746
+ * world coordinates. Runs BEFORE the event is dispatched and independently
1747
+ * of which binding (if any) handles it.
1748
+ *
1749
+ * This is deliberately not an Action. `<SceneCanvas onDoubleClick>` is a
1750
+ * notification — "the user double-clicked, here's what they hit" — and a
1751
+ * notification must not compete with behavior for the gesture. As a binding
1752
+ * it would lose to `enterPathEdit` on any body hit and silently never fire.
1753
+ * Routing it here keeps a single definition of "double click" (the point of
1754
+ * consolidating the kit's three detectors) without giving it
1755
+ * first-match-wins semantics it shouldn't have.
1756
+ */
1757
+ onDoubleClick?: (world: {
1758
+ x: number;
1759
+ y: number;
1760
+ }) => void;
3135
1761
  /**
3136
1762
  * Default true. Set false to leave the window `keydown`/`keyup` listeners
3137
1763
  * unattached so keyboard-bound actions never dispatch — pointer, wheel, and
@@ -3160,23 +1786,25 @@ interface UseGestureDispatcherOptions {
3160
1786
  y: number;
3161
1787
  }) => AffordanceHit | null;
3162
1788
  /**
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.
1789
+ * Optional body classifier. Called on every pointerdown with the world-space
1790
+ * coordinates of the pointer. Its result is packed onto the event as
1791
+ * `bodyTarget` + `bodyKind`, which `matchTarget` reads to resolve the
1792
+ * string-form `TargetSpec` values in `Tool.bindings`.
3167
1793
  *
3168
- * Returns `'empty'` when nothing is under the pointer, `'selected-body'`
1794
+ * `body` is `'empty'` when nothing is under the pointer, `'selected-body'`
3169
1795
  * when the topmost hit belongs to the current selection, or
3170
- * `'unselected-body'` when it belongs to a node that isn't selected.
1796
+ * `'unselected-body'` when it belongs to a node that isn't selected. `kind`
1797
+ * is the hit node's semantic kind, when the scene can name it.
3171
1798
  *
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.
1799
+ * When omitted, every body-derived target form (`'empty'`,
1800
+ * `'selected-body'`, `'unselected-body'`, `kind:<k>`, `kind:<k>:selected`)
1801
+ * never matches bindings using those specs are silently skipped.
1802
+ * `<SceneCanvas>` wires this.
3175
1803
  */
3176
1804
  classifyTarget?: (worldPoint: {
3177
1805
  x: number;
3178
1806
  y: number;
3179
- }) => 'empty' | 'selected-body' | 'unselected-body';
1807
+ }) => BodyClassification;
3180
1808
  /**
3181
1809
  * Optional pre-created `Dispatcher`. When provided, this hook pumps events
3182
1810
  * into the supplied instance instead of creating its own. Lets a parent
@@ -3365,218 +1993,6 @@ declare function useViewAnimation(setView: (v: View) => void): {
3365
1993
  cancel: () => void;
3366
1994
  };
3367
1995
 
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;
3548
- }
3549
- /**
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.
3558
- *
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
- * ```
3564
- */
3565
- declare function usePointerGestures<TMovePose, TResizePose>(options: UsePointerGesturesOptions<TMovePose, TResizePose>): PointerGestureBindings;
3566
-
3567
- /**
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.
3571
- *
3572
- * const pen = defineTool({
3573
- * id: 'pen',
3574
- * initScratch: () => ({ anchors: [] as Point[] }),
3575
- * drag: { onMove: (e, ctx) => { ctx.scratch.anchors.push(...); return 'claim'; } },
3576
- * });
3577
- */
3578
- declare function defineTool<TScratch = undefined>(spec: Tool<TScratch>): Tool<TScratch>;
3579
-
3580
1996
  interface UseToolsOptions {
3581
1997
  /** Initial active-slot tool id. Must exist in `registry`. */
3582
1998
  active: string;
@@ -3586,40 +2002,17 @@ interface UseToolsOptions {
3586
2002
  registry: Record<string, AnyTool>;
3587
2003
  /** Always-on tools — listen continuously regardless of active slot. */
3588
2004
  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
2005
  }
3614
2006
  interface ToolsApi {
3615
2007
  /** Current active-slot tool id. */
3616
2008
  active: string;
3617
- /** Set the active-slot tool. Cancels any in-flight gesture. */
2009
+ /** Set the active-slot tool. The gesture dispatcher watches the active
2010
+ * tool and cancels any in-flight handle itself. */
3618
2011
  setActive: (id: string) => void;
3619
2012
  /** Currently hotkey-engaged tool id (or `null`). Derived as the top of
3620
2013
  * the hotkey stack for backwards compat with the pre-stack API. */
3621
2014
  hotkeyEngaged: string | null;
3622
- /** Engage a hotkey-slot tool by id. No-op if a gesture is in flight. */
2015
+ /** Engage a hotkey-slot tool by id. */
3623
2016
  engageHotkey: (id: string) => void;
3624
2017
  /** Disengage the hotkey-slot tool, if any. */
3625
2018
  disengageHotkey: () => void;
@@ -3627,13 +2020,6 @@ interface ToolsApi {
3627
2020
  ambient: readonly AnyTool[];
3628
2021
  /** Full registry — for userland UI (palette buttons, etc.). */
3629
2022
  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
2023
  /** Returns true if a tool with the given id is in the registry or ambient list. */
3638
2024
  has(id: string): boolean;
3639
2025
  /** All overlay layers from currently-engaged tools (active slot, hotkey
@@ -3643,7 +2029,12 @@ interface ToolsApi {
3643
2029
  getActiveOverlays(): RenderLayer<unknown>[];
3644
2030
  }
3645
2031
  /**
3646
- * Manages the active tool, hotkey slot, and gesture dispatcher.
2032
+ * Manages the active tool and hotkey slot.
2033
+ *
2034
+ * It used to also own a dispatcher: `useTools` constructed the tool-routing
2035
+ * dispatcher and `<Canvas>` pumped DOM events into it. Input now belongs
2036
+ * entirely to `useGestureDispatcher`, so what's left here is slot state plus
2037
+ * the overlay roll-up.
3647
2038
  *
3648
2039
  * Requires `<ActiveToolContextProvider>` (or `<WeaselProvider>` /
3649
2040
  * `<SceneCanvas>`, which mount one internally) in scope: active/hotkey state
@@ -3664,10 +2055,20 @@ interface UseKeybindingsOptions {
3664
2055
  * `tools.active` was when the hook first ran (i.e. the initial active
3665
2056
  * tool). Pass `null` to disable Escape-returns-to-default behavior. */
3666
2057
  defaultTool?: string | null;
2058
+ /**
2059
+ * Gate for keyboard tool activation: return false to refuse a tool the
2060
+ * active mode doesn't allow. `<SceneCanvas>` wires this from
2061
+ * `getActiveMode` + each tool's `capabilities`, using the same predicate
2062
+ * `ToolPalette` uses to grey a button out — so the grey-out becomes a
2063
+ * guarantee rather than a hint.
2064
+ *
2065
+ * Omit for consumers with no mode registry: every tool stays activatable.
2066
+ */
2067
+ isToolEligible?: (toolId: string) => boolean;
3667
2068
  }
3668
2069
  declare function useKeybindings(tools: ToolsApi, options?: UseKeybindingsOptions): void;
3669
2070
 
3670
- type ToolPrefKind = 'number' | 'boolean' | 'string' | 'enum' | 'color';
2071
+ type ToolPrefKind = 'number' | 'boolean' | 'string' | 'enum' | 'color' | 'paint';
3671
2072
  interface ToolPrefBase<K extends string, Value> {
3672
2073
  kind: K;
3673
2074
  /** Human-readable label. */
@@ -3734,7 +2135,22 @@ interface ToolPrefColor extends ToolPrefBase<'color', string> {
3734
2135
  */
3735
2136
  interface ToolPrefCustom extends ToolPrefBase<string, unknown> {
3736
2137
  }
3737
- type ToolPref = ToolPrefNumber | ToolPrefBoolean | ToolPrefString | ToolPrefEnum | ToolPrefColor;
2138
+ /**
2139
+ * A whole `FillStyle`, not a color inside one. Use it wherever the value is
2140
+ * the tagged paint union — a solid color, a pattern, a gradient — rather
2141
+ * than a hex string.
2142
+ *
2143
+ * Addressing `…fill.color` instead reads `undefined` off a gradient (so the
2144
+ * control shows its default and claims the text is black) and writes a
2145
+ * hybrid `{ fill: 'gradient', stops, color }` that the renderer's structural
2146
+ * `'color' in paint` checks then paint flat solid. The union has to be
2147
+ * edited as a union.
2148
+ */
2149
+ interface ToolPrefPaint extends ToolPrefBase<'paint', unknown> {
2150
+ /** Offer an opacity control alongside the color. */
2151
+ alpha?: boolean;
2152
+ }
2153
+ type ToolPref = ToolPrefNumber | ToolPrefBoolean | ToolPrefString | ToolPrefEnum | ToolPrefColor | ToolPrefPaint;
3738
2154
  /** Built-in or app-defined leaf. */
3739
2155
  type ToolPrefLeaf = ToolPref | ToolPrefCustom;
3740
2156
  /** Nestable group: branch nodes a tool can use to organize its prefs. */
@@ -3744,61 +2160,7 @@ interface ToolPrefGroup {
3744
2160
  children: Record<string, ToolPrefLeaf | ToolPrefGroup>;
3745
2161
  }
3746
2162
 
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> {
2163
+ interface UseSelectToolOptions<TPose> {
3802
2164
  /** Return ids of all objects whose painted body covers (worldX, worldY).
3803
2165
  * Order doesn't matter — the tool collapses parent/child overlap via
3804
2166
  * `pickTopMostHit`. When omitted, defaults to a rect AABB-vs-point scan
@@ -3809,45 +2171,60 @@ interface UseSelectToolOptions<TNode extends {
3809
2171
  /** Optional alt-aware selection-update hit returning the single id the
3810
2172
  * click should act on. */
3811
2173
  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
2174
  /** Project a pose to its AABB. Default: identity. */
3815
2175
  poseBounds?: (pose: TPose) => Bounds;
2176
+ /**
2177
+ * How the default `pickEvery` decides a **leaf** node covers the pointer.
2178
+ * Ignored when `pickEvery` is supplied.
2179
+ *
2180
+ * - `'aabb'` (default) — the pose rect. Cheap, and the historical behavior:
2181
+ * a click anywhere in the bounding box selects the node.
2182
+ * - `'silhouette'` — the pose rect as a pre-test, then the ink the painter
2183
+ * lays down: `findShapeSilhouette` filled or not per the painter's `ink`,
2184
+ * plus its outline widened by the stroke half-width and `pickTolerance`.
2185
+ * A click inside the bounding box but outside the drawn shape misses —
2186
+ * the concave notch of a star, the corner outside an ellipse, the blank
2187
+ * half of a text box — while a click on the thin outline of an unfilled
2188
+ * shape hits.
2189
+ *
2190
+ * Container nodes already consult their silhouette unconditionally (it is
2191
+ * how their clip is derived); this extends the same test to leaves. Not the
2192
+ * default because it changes what a click selects for every existing
2193
+ * consumer, and a painter with no `silhouette` is unaffected either way —
2194
+ * it falls back to the AABB.
2195
+ */
2196
+ leafPicking?: 'aabb' | 'silhouette';
2197
+ /**
2198
+ * Grab slop around a shape's outline, in **world** units. Default 0.
2199
+ *
2200
+ * Only consulted under `leafPicking: 'silhouette'`. World units because
2201
+ * this hook has no view; `<SceneCanvas>` takes a screen-pixel figure
2202
+ * (`geometry.pickTolerancePx`) and divides by scale on its own pick path.
2203
+ * Without some slop a hairline outline is an unhittable target.
2204
+ */
2205
+ pickTolerance?: number;
3816
2206
  /** Move-action options. The move gesture is dispatcher-routed,
3817
2207
  * so only `behaviors` is consumed here — threaded into the move binding's
3818
2208
  * `opts.behaviors`. Other `UseMoveOptions` fields are accepted for API shape
3819
2209
  * but not read by this tool. */
3820
2210
  move?: UseMoveOptions<TPose>;
3821
- /** Legacy `useAreaSelect` options. Ignored now —
3822
- * `areaSelectAction` configuration moved to the action registration. */
3823
- areaSelect?: unknown;
2211
+ /**
2212
+ * When this returns true, a Shift/Meta extend-click must NOT change the
2213
+ * node selection.
2214
+ *
2215
+ * `<SceneCanvas>` wires it to "a path is in anchor-edit mode", where
2216
+ * Shift-click means "add this anchor to the anchor selection". Without
2217
+ * the lock the same click also toggles the edited node out of the node
2218
+ * selection, and the `editAnchors` dep treats that as the edit target
2219
+ * disappearing — so multi-selecting anchors silently exited edit mode.
2220
+ *
2221
+ * Only extend-clicks are locked. A plain click still re-selects, so
2222
+ * clicking a different node exits edit mode as usual.
2223
+ */
2224
+ extendClickLocked?: () => boolean;
3824
2225
  /** Optional debug sink. Reserved for future overlay/affordance hitbox
3825
2226
  * recording. */
3826
2227
  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
2228
  /** Reparent-on-drop behavior for drag-to-move. `'off'` (default)
3852
2229
  * preserves translate-only commits. `'top'` lands the moved nodes at
3853
2230
  * the top of the container under the drop point. `'above'` lands them
@@ -3855,33 +2232,16 @@ interface UseSelectToolOptions<TNode extends {
3855
2232
  * semantics when the hit is itself a container). Requires the
3856
2233
  * `nodeAtPoint` dep to be registered (sourced by `<SceneCanvas>`). */
3857
2234
  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
2235
  }
3866
2236
  /** Intersection of the move + area-select adapter interfaces.
3867
2237
  * Resize / rotate adapters moved to `useResizeTool` / `useRotateTool`. */
3868
2238
  type SelectAdapter<TNode extends {
3869
- id: string;
3870
- }, 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
- };
2239
+ id: string;
2240
+ }, TPose> = MoveAdapter<TNode, TPose> & AreaSelectAdapter;
3881
2241
  /** 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.
2242
+ * - `select.pick` classifies the press (pointerDown) it runs the tool's own
2243
+ * pickBest/pickEvery so click semantics (selection replace, extend,
2244
+ * deferred collapse) live in one place.
3885
2245
  * - drag is owned exclusively by the dispatcher via `Tool.bindings`
3886
2246
  * (selected-body → moveAction, rotate-handle → rotateAction, handle:* →
3887
2247
  * resizeAction, empty → areaSelectAction, click on empty → clearSelection).
@@ -3891,81 +2251,7 @@ type SelectScratch = {
3891
2251
  */
3892
2252
  declare function useSelectTool<TNode extends {
3893
2253
  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>>;
2254
+ }, TPose>(adapter: SelectAdapter<TNode, TPose>, options: UseSelectToolOptions<TPose>): Tool<null>;
3969
2255
 
3970
2256
  interface UseRotateToolOptions<TNode extends {
3971
2257
  id: string;
@@ -4036,7 +2322,10 @@ declare function useRotateTool<TNode extends {
4036
2322
  * (resolve before calling) — this helper doesn't try to detect that.
4037
2323
  *
4038
2324
  * Pure sibling z-order without a parent/child relation is not resolved
4039
- * here yet — see `docs/TODO.md`.
2325
+ * here yet — see "Sibling z-order is unresolved in hit-picking" under
2326
+ * Tools & gestures in `docs/TODO.md`. The shape it wants is an optional
2327
+ * `getZIndex` / `compareZ` on the adapter below, composing with the
2328
+ * parent/child collapse rather than replacing it.
4040
2329
  */
4041
2330
  interface PickTopMostHitAdapter {
4042
2331
  getParent?: (id: string) => string | null;
@@ -4098,40 +2387,16 @@ interface HandScratch {
4098
2387
  */
4099
2388
  declare function useHandTool(opts?: UseHandToolOptions): Tool<HandScratch | null>;
4100
2389
 
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>;
2390
+ /** Text tool. Drag-rect insertion is owned end-to-end by the dispatcher's
2391
+ * `insertAction` (kind: `'text'`); click-on-selected-text-node enters edit
2392
+ * mode via the `enterTextEdit` action.
2393
+ *
2394
+ * Consumers must register both the `insert` dep (e.g. through
2395
+ * `SceneCanvas`'s `useInsertDepSource`) and the `textEdit` dep
2396
+ * (`enterTextEditAction`'s contract see
2397
+ * `src/interactions/actions/defaults/enterTextEdit.ts`). Custom node
2398
+ * factories and hit gating belong on those deps, not on the tool. */
2399
+ declare function useTextTool(): Tool<undefined>;
4135
2400
 
4136
2401
  interface PinchZoomToolOpts {
4137
2402
  min?: number;
@@ -4147,51 +2412,13 @@ interface PinchZoomToolOpts {
4147
2412
  */
4148
2413
  declare function usePinchZoomTool(canvasRef: React.RefObject<HTMLCanvasElement | null>, view: View, setView: (v: View) => void, opts?: PinchZoomToolOpts): void;
4149
2414
 
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
2415
  /**
4189
2416
  * In-progress pen anchor. `outHandle` is set when the anchor was placed via
4190
2417
  * click-drag (smooth anchor); undefined for click-placed corners.
4191
2418
  * `inHandle` is mirrored from the previous anchor's outHandle on segment
4192
2419
  * emission unless `altBroken` is set on that previous anchor.
4193
2420
  */
4194
- interface PenAnchor {
2421
+ interface PenAnchor$1 {
4195
2422
  x: number;
4196
2423
  y: number;
4197
2424
  outHandle?: {
@@ -4207,57 +2434,32 @@ interface PenAnchor {
4207
2434
  altBroken?: boolean;
4208
2435
  }
4209
2436
  interface PenSubpath {
4210
- anchors: PenAnchor[];
2437
+ anchors: PenAnchor$1[];
4211
2438
  closed: boolean;
4212
2439
  }
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. */
2440
+ /**
2441
+ * Mutable scratch shared across pen-tool gestures. The hook keeps a stable
2442
+ * reference to a single instance and `initScratch` returns it on every call,
2443
+ * so click-by-click state survives gesture boundaries and the preview layer
2444
+ * can read the same object.
2445
+ *
2446
+ * ## The pen creates paths and nothing else
2447
+ *
2448
+ * Reshaping an existing path is anchor editing: double-click a path to
2449
+ * enter edit mode, then use the `editAnchors` / `insertPathAnchor` /
2450
+ * `nudgeAnchors` / `deleteAnchors` / `marqueeAnchors` / `cutPathAtAnchor`
2451
+ * Actions.
2452
+ *
2453
+ * The pen used to carry a second, private implementation of all of that,
2454
+ * with its own scratch mode, hit-test override, overlay, and undo
2455
+ * plumbing. It was reachable only through a `getPathObj` option whose
2456
+ * contract required `pose.kind` to be `'polygon'` or `'rect'` — which no
2457
+ * kit-created node has, including the ones the pen itself creates. So it
2458
+ * was dead in every consumer while shadowing the live implementation, and
2459
+ * the two had drifted. Its geometry now lives in
2460
+ * `features/paths/anchorEdits.ts`, driven by those Actions.
2461
+ */
4255
2462
  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
2463
  finishedSubpaths: PenSubpath[];
4262
2464
  current: PenSubpath | null;
4263
2465
  cursor: {
@@ -4266,23 +2468,6 @@ interface PenScratch {
4266
2468
  } | null;
4267
2469
  draggingHandleAt: number | null;
4268
2470
  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
2471
  }
4287
2472
  interface UsePenToolOptions<TPose> {
4288
2473
  /** Wrap a finished PolygonPath in the consumer's pose type. */
@@ -4293,9 +2478,6 @@ interface UsePenToolOptions<TPose> {
4293
2478
  adapter: {
4294
2479
  addNode: (pose: TPose) => string;
4295
2480
  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
2481
  };
4300
2482
  /** Auto-select the new object after commit. Default `true`. */
4301
2483
  autoSelect?: boolean;
@@ -4329,33 +2511,6 @@ interface UsePenToolOptions<TPose> {
4329
2511
  x: number;
4330
2512
  y: number;
4331
2513
  };
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;
4359
2514
  }
4360
2515
  /**
4361
2516
  * Active-slot Tool: click + drag to build a `PolygonPath` Illustrator-style.
@@ -4367,12 +2522,9 @@ interface UsePenToolReturn {
4367
2522
  * discards. Shift constrains the placement-drag handle to 0/45/90/135°;
4368
2523
  * Alt during drag breaks the handle mirror for the next segment.
4369
2524
  *
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.
2525
+ * Reshaping an existing path is not this tool's job see {@link PenScratch}.
4374
2526
  */
4375
- declare function usePenTool<TPose>(options: UsePenToolOptions<TPose>): UsePenToolReturn;
2527
+ declare function usePenTool<TPose>(options: UsePenToolOptions<TPose>): Tool<PenScratch>;
4376
2528
  declare namespace usePenTool {
4377
2529
  var prefs: {
4378
2530
  name: string;
@@ -4388,80 +2540,28 @@ declare namespace usePenTool {
4388
2540
  };
4389
2541
  }
4390
2542
 
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
2543
  /**
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.
2544
+ * Drag-to-draw rectangle tool.
4422
2545
  *
4423
- * Role model for tools that create scene objects: uses `ctx.applyOps` +
4424
- * `createInsertOp` directly rather than routing through adapter.commitInsert.
2546
+ * The gesture is owned end-to-end by the dispatcher: the `drag` binding
2547
+ * routes to `insertAction`, which tracks the live bounds, paints the
2548
+ * preview through its `overlay()` surface, and commits via the `insert`
2549
+ * dep on release. `<SceneCanvas>` sources that dep from
2550
+ * `useInsertDepSource`; consumers wanting a custom node factory override
2551
+ * the dep (`useDepSource('insert', …)`) rather than the tool.
2552
+ *
2553
+ * Grid snapping comes from the `snap` dep, also read by `insertAction` —
2554
+ * see `SnapDep` in `interactions/actions/depSchema.ts`.
4425
2555
  */
4426
- declare function useRectTool<TNode extends {
4427
- id: string;
4428
- }>(options: UseRectToolOptions<TNode>): Tool<null>;
2556
+ declare function useRectTool(): Tool<null>;
4429
2557
 
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
2558
  /**
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`.
2559
+ * Drag-to-draw ellipse tool. Mirrors `useRectTool`: the `drag` binding
2560
+ * routes to `insertAction`, which owns the live preview and commits
2561
+ * through the `insert` dep. Alt toggles from-corner from-center
2562
+ * mid-drag via the action's live modifier read.
4461
2563
  */
4462
- declare function useEllipseTool<TNode extends {
4463
- id: string;
4464
- }>(options: UseEllipseToolOptions<TNode>): Tool<null>;
2564
+ declare function useEllipseTool(): Tool<null>;
4465
2565
 
4466
2566
  interface UseImageToolOptions {
4467
2567
  /** Image source for inserted nodes — a URL, a `blob:` URL, or a
@@ -4488,7 +2588,8 @@ interface UseEyedropperToolOptions {
4488
2588
  * on a real hit; empty-click is a no-op in v1. */
4489
2589
  onPick: (color: string | null) => void;
4490
2590
  /** 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`. */
2591
+ * to sample. Called on click with the id of the topmost node under the
2592
+ * click point. */
4492
2593
  colorOf: (id: string) => string | null;
4493
2594
  /** Override the default `{ key: 'I' }` keybinding. Pass `null` to omit
4494
2595
  * the keybinding entirely (palette-only or hotkey-only wiring). */
@@ -4504,6 +2605,10 @@ interface UseEyedropperToolOptions {
4504
2605
  *
4505
2606
  * Pure-read tool — does NOT mutate the scene. v1 is click-only; drag is
4506
2607
  * unbound (a future drag-to-sample option is additive).
2608
+ *
2609
+ * The node under the click comes from the `nodeAtPoint` dep, so an
2610
+ * eyedropper in a consumer that never registered that dep samples nothing.
2611
+ * `<SceneCanvas>` sources it from the same picker the rest of the kit hits.
4507
2612
  */
4508
2613
  declare function useEyedropperTool(opts: UseEyedropperToolOptions): Tool<null>;
4509
2614
 
@@ -4511,39 +2616,19 @@ interface LinePoint {
4511
2616
  x: number;
4512
2617
  y: number;
4513
2618
  }
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
2619
  /**
4539
- * Click-down → drag → release-place line tool. Commits via the consumer's
4540
- * `create` factory called with the two endpoints. Modifiers:
2620
+ * Click-down → drag → release line tool. The `drag` binding routes to
2621
+ * `insertAction`, which owns the live preview and commits through the
2622
+ * `insert` dep with `{ kind: 'line', a, b }` endpoints (not the AABB
2623
+ * diagonal), so the drag direction is preserved.
2624
+ *
2625
+ * Modifiers are applied by `insertAction`:
4541
2626
  * - shift: constrain to 15° increments
4542
- * - alt: mirror end around start (drag is treated as half-line)
2627
+ * - alt: mirror the end around the start (the drag is treated as a
2628
+ * half-line) — the line-specific reading of the action's
2629
+ * corner ⇄ center origin toggle
4543
2630
  */
4544
- declare function useLineTool<TNode extends {
4545
- id: string;
4546
- }>(options: UseLineToolOptions<TNode>): Tool<LineScratch | null>;
2631
+ declare function useLineTool(): Tool<null>;
4547
2632
 
4548
2633
  /** Option surface for the `lasso-select` action.
4549
2634
  *
@@ -4652,54 +2737,31 @@ declare function useStarTool<TNode extends {
4652
2737
  * `pressure: 0.5` while a button is held, `0` otherwise (per the Pointer
4653
2738
  * Events spec), so a consumer that wants stylus-only modulation should
4654
2739
  * gate on `pointerType` from the originating event — the kit exposes
4655
- * `usePointerStylus()` for that. */
2740
+ * `usePointerStylus()` for that.
2741
+ *
2742
+ * The dispatcher accumulates these on the drag trail and hands the whole
2743
+ * array to `insertAction`, which forwards it as the `pencil` insert
2744
+ * extras' `samples`. A consumer that wants pressure-driven
2745
+ * `Stroke.vertexWidths` reads it off the samples in its own `insert` dep
2746
+ * (`useDepSource('insert', …)`) — see `apps/site/demos/VertexWidthsDemo.tsx`.
2747
+ */
4656
2748
  interface PencilPoint {
4657
2749
  x: number;
4658
2750
  y: number;
4659
- /** 0..1. Optional for backward compat older `create` factories that
4660
- * treat samples as `{x,y}` keep working. */
2751
+ /** 0..1. Absent when the originating event carried no pressure. */
4661
2752
  pressure?: number;
4662
2753
  /** Degrees, ±90. Zero for mouse/touch. */
4663
2754
  tiltX?: number;
4664
2755
  /** Degrees, ±90. Zero for mouse/touch. */
4665
2756
  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
2757
  }
4693
2758
  /**
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.
2759
+ * Freehand pencil tool. The `drag` binding routes to `insertAction`,
2760
+ * which accumulates the pointer trail and commits it as
2761
+ * `{ kind: 'pencil', samples }`. The kit's default `insert` dep runs
2762
+ * `schneiderFit` over the samples to produce a cubic-Bezier path.
4699
2763
  */
4700
- declare function usePencilTool<TNode extends {
4701
- id: string;
4702
- }>(options: UsePencilToolOptions<TNode>): Tool<PencilScratch | null>;
2764
+ declare function usePencilTool(): Tool<null>;
4703
2765
 
4704
2766
  /** Standard slot names — render in this canonical order.
4705
2767
  * `cellHighlight` is internal: emitted from the `grid` slot's nested
@@ -4743,9 +2805,24 @@ interface CanvasExtensionApi {
4743
2805
  readonly element: HTMLCanvasElement | null;
4744
2806
  requestRedraw(): void;
4745
2807
  /** 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`). */
2808
+ * draw stack and, if it implements `hitTest`, in {@link hitTestExtras}. */
4748
2809
  registerLayer(layer: RenderLayer<unknown>): () => void;
2810
+ /**
2811
+ * Hit-test the registered layers (topmost-first, last-registered wins) at a
2812
+ * world-space point. Returns the id of the layer that claimed the point and
2813
+ * whatever its `hitTest` resolved, or `null` when none did.
2814
+ *
2815
+ * `<SceneCanvas>` folds this into the `affordanceAt` thunk it hands the
2816
+ * gesture dispatcher — ahead of the kit's own selection chrome, since
2817
+ * registered layers draw on top — so a hit surfaces to actions as an
2818
+ * `AffordanceHit` with kind `layer:<id>` and the binding's `initialScratch`
2819
+ * as its `payload`. A layer's owner binds a `kindOf` predicate on that kind
2820
+ * to claim the gesture; see `@weasel-js/hud` for the worked example.
2821
+ */
2822
+ hitTestExtras(worldX: number, worldY: number): {
2823
+ layerId: string;
2824
+ binding: AffordanceBinding;
2825
+ } | null;
4749
2826
  /** Feed external content into the ingestion pipeline imperatively — the
4750
2827
  * same content-handler registry that OS drop and clipboard paste hit.
4751
2828
  * `input` may be raw `File[]` (e.g. from `openFilePicker`) or
@@ -4775,6 +2852,52 @@ interface SceneCanvasApi extends CanvasExtensionApi {
4775
2852
  }): void;
4776
2853
  }
4777
2854
 
2855
+ /**
2856
+ * Union math behind `CanvasHelpers.getGestureBounds()`.
2857
+ *
2858
+ * `getGestureBounds` answers "where is the thing the user is dragging right
2859
+ * now, in total?" — a question every other lookup on `CanvasHelpers` can't
2860
+ * answer because they're keyed by node id, and a drag-to-insert has no node
2861
+ * yet. Canvas collects the parts (preview bounds per in-flight id, plus any
2862
+ * id-less nascent-insert AABBs) and folds them here.
2863
+ *
2864
+ * Kept free of tool / dispatcher types so it stays a pure geometry fold that
2865
+ * both `<Canvas>` and its tests can call without a render.
2866
+ */
2867
+
2868
+ /**
2869
+ * Everything `<Canvas>` needs to know about in-flight gestures that it can't
2870
+ * see for itself.
2871
+ *
2872
+ * `<Canvas>` is deliberately dispatcher-agnostic — it owns the union math and
2873
+ * the `CanvasHelpers` contract, but the live gesture state lives in the
2874
+ * gesture dispatcher that `<SceneCanvas>` creates. This is the one seam
2875
+ * between them (`createGestureSource` in `SceneCanvas/dispatcherGestureBounds.ts`
2876
+ * builds it); bare `<Canvas>` consumers leave it unwired and get the
2877
+ * "no gesture in flight" answers.
2878
+ *
2879
+ * @public
2880
+ */
2881
+ interface GestureSource {
2882
+ /**
2883
+ * Ids the in-flight gesture is previewing, so the canvas can resolve each
2884
+ * one's preview bounds.
2885
+ *
2886
+ * Wider than `previewIdsExtra`, which honors `previewHidesSource: false`
2887
+ * because its job is deciding whose committed paint to suppress: a clone
2888
+ * ghost hides nothing yet still proposes content at the drag target.
2889
+ */
2890
+ ids(): Iterable<string> | null;
2891
+ /** World-space AABBs of in-flight gestures that have no scene id at all —
2892
+ * a drag-to-insert, pre-commit. */
2893
+ bounds(): Iterable<Bounds> | null;
2894
+ /** Fires whenever the above can have changed. See
2895
+ * `CanvasHelpers.subscribeGestures` for the published guarantee. */
2896
+ subscribe(fn: () => void): () => void;
2897
+ /** Monotonic counter bumped on the same events `subscribe` fires on. */
2898
+ getVersion(): number;
2899
+ }
2900
+
4778
2901
  /**
4779
2902
  * Selection overlay primitives — render layers for drawing selection
4780
2903
  * outlines and corner resize handles in world space.
@@ -5146,16 +3269,6 @@ interface CanvasProps<TNode extends {
5146
3269
  * force-graph renderers or read-only viewers).
5147
3270
  */
5148
3271
  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
3272
  /**
5160
3273
  * Pose↔bounds projection for non-rect `TPose` types. When supplied, drives
5161
3274
  * the default `boundsOf` fallback and the selection-overlay bounds source so
@@ -5291,6 +3404,14 @@ interface CanvasProps<TNode extends {
5291
3404
  * through geometry.getBounds(pose).
5292
3405
  */
5293
3406
  previewBoundsExtra?: (id: string) => Bounds | null;
3407
+ /**
3408
+ * In-flight gesture state `<Canvas>` can't see for itself. Backs the
3409
+ * `getGestureBounds` / `subscribeGestures` / `getGestureVersion` trio on
3410
+ * `helpersRef`; wired by `<SceneCanvas>` from the gesture dispatcher
3411
+ * (`createGestureSource`). Leaving it unwired is fine — those three then
3412
+ * report "no gesture in flight" and never fire.
3413
+ */
3414
+ gestureSource?: GestureSource;
5294
3415
  /**
5295
3416
  * Pinch-zoom DOM listener attachment for the canvas surface. When supplied,
5296
3417
  * `<Canvas>` calls `usePinchZoomTool` with `canvasRef` so two-finger pinch
@@ -5346,19 +3467,6 @@ interface CanvasProps<TNode extends {
5346
3467
  * HUD shows the same best-candidate SceneCanvas would pick.
5347
3468
  */
5348
3469
  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
3470
  /**
5363
3471
  * Optional mode-owned decoration layer. When supplied, Canvas inserts it
5364
3472
  * between the scene-render slot and the tool-overlay slot so decoration
@@ -5384,16 +3492,73 @@ interface CanvasProps<TNode extends {
5384
3492
  * predicate that closes over per-frame `ChromeCtx`. Omit to leave
5385
3493
  * every chrome element visible (pre-chrome-caps behavior).
5386
3494
  */
5387
- getIsVisible?: () => (id: string) => boolean;
5388
- }
5389
- /** Live overlay-aware lookups exposed to custom layers via `helpersRef`. */
5390
- interface CanvasHelpers<TPose> {
5391
- /** Pose currently displayed for `id` — drag/resize/rotate overlay if active,
5392
- * otherwise the committed pose from the adapter. Returns `null` if the id
5393
- * isn't known. */
5394
- getEffectivePose(id: string): TPose | null;
5395
- /** Overlay-aware bounds for `id`. */
5396
- getEffectiveBounds(id: string): Bounds | null;
3495
+ getIsVisible?: () => (id: string) => boolean;
3496
+ }
3497
+ /** Live overlay-aware lookups exposed to custom layers via `helpersRef`. */
3498
+ interface CanvasHelpers<TPose> {
3499
+ /** Pose currently displayed for `id` — drag/resize/rotate overlay if active,
3500
+ * otherwise the committed pose from the adapter. Returns `null` if the id
3501
+ * isn't known. */
3502
+ getEffectivePose(id: string): TPose | null;
3503
+ /** Overlay-aware bounds for `id`. */
3504
+ getEffectiveBounds(id: string): Bounds | null;
3505
+ /**
3506
+ * World-space AABB of everything the in-flight gesture proposes — the
3507
+ * displaced poses of nodes being moved / resized / rotated / cloned, plus
3508
+ * any nascent insert that has no scene node yet. `null` when no gesture is
3509
+ * in flight.
3510
+ *
3511
+ * This reports the *gesture*, not the document: committed content the
3512
+ * gesture isn't touching is excluded, so a consumer that wants the union
3513
+ * with the rest of the scene still walks its own ids through
3514
+ * `getEffectiveBounds`. It exists because every other lookup here is keyed
3515
+ * by node id, which can't answer "where is the shape the user is drawing
3516
+ * right now" — a drag-to-insert has no id until pointer-up.
3517
+ *
3518
+ * Select-only gestures are deliberately excluded: a marquee or lasso has
3519
+ * geometry but proposes no content, and a consumer sizing itself to the
3520
+ * gesture must not grow because the user swept a selection rectangle.
3521
+ *
3522
+ * The result is a plain AABB — never rotated. Rotated parts are folded in
3523
+ * by their rotated extent (a union of several oriented boxes has no single
3524
+ * orientation to report).
3525
+ */
3526
+ getGestureBounds(): Bounds | null;
3527
+ /**
3528
+ * Subscribe to the gesture layer's change signal — the other half of the
3529
+ * `useSyncExternalStore` contract for everything on this object that moves
3530
+ * during a drag (`getEffectivePose`, `getEffectiveBounds`,
3531
+ * `getGestureBounds`). Returns an unsubscribe.
3532
+ *
3533
+ * Fires once per dispatcher pump: gesture start, every pointermove that
3534
+ * reaches an in-flight handle, end, and cancel — plus UI-driven ongoing
3535
+ * actions (a slider bound to an ongoing action pumps the same way). It
3536
+ * fires on the pump, not on a diff: a pump that changed nothing observable
3537
+ * still notifies, so don't hang expensive work directly off the callback.
3538
+ *
3539
+ * It does **not** cover committed scene edits (subscribe to the scene for
3540
+ * those) or previews a consumer's own tool publishes from React state
3541
+ * (that tool re-renders on its own).
3542
+ *
3543
+ * Without a gesture source wired — a bare `<Canvas>` — this is a no-op
3544
+ * subscription that never fires.
3545
+ */
3546
+ subscribeGestures(fn: () => void): () => void;
3547
+ /**
3548
+ * Monotonic counter bumped on exactly the events `subscribeGestures` fires
3549
+ * on. Pair the two for `useSyncExternalStore`:
3550
+ *
3551
+ * ```ts
3552
+ * const gestureVersion = useSyncExternalStore(
3553
+ * useCallback((cb) => helpersRef.current?.subscribeGestures(cb) ?? (() => {}), []),
3554
+ * () => helpersRef.current?.getGestureVersion() ?? 0,
3555
+ * );
3556
+ * ```
3557
+ *
3558
+ * Starts at 0 and only increases. `0` is also what a bare `<Canvas>` with
3559
+ * no gesture source reports, forever.
3560
+ */
3561
+ getGestureVersion(): number;
5397
3562
  /** Returns the live ChromeState built once per render. Affordances and
5398
3563
  * custom layers that need overlay-aware selection state (selection ids,
5399
3564
  * bounds, multi-union AABB, modifier flags) read from this. */
@@ -5863,27 +4028,6 @@ interface UseRotateOptions<TPose> {
5863
4028
  pivot?: 'each' | 'union';
5864
4029
  }
5865
4030
 
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
4031
  /**
5888
4032
  * Built-in shape tool ids handled by `useBuiltinShapeTools`. Each maps to a
5889
4033
  * kit tool hook + a default `create` that produces a leaf node compatible
@@ -5907,10 +4051,13 @@ interface BuiltinToolOptions {
5907
4051
  mode?: LassoHitMode;
5908
4052
  };
5909
4053
  /** 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. */
4054
+ *
4055
+ * Registered as the `snap` dep, which `insertAction` applies to the
4056
+ * drag start and current point so the live preview and the committed
4057
+ * geometry agree — and which the pen tool reads for its own anchor
4058
+ * placement. Covers every drag-to-insert tool (rect / ellipse / line /
4059
+ * polygon / star / text); freehand pencil samples are deliberately left
4060
+ * unsnapped. */
5914
4061
  snapPoint?: (p: {
5915
4062
  x: number;
5916
4063
  y: number;
@@ -6081,7 +4228,13 @@ interface NodeShapeEntry<TData = unknown, TPose = unknown> {
6081
4228
  matches(node: Node$1<TData, string, TPose>): boolean;
6082
4229
  /** Emits the draw commands for the node's primary visual. `ctx` is an
6083
4230
  * optional per-call paint context (see `NodePaintCtx`); painters that
6084
- * don't need it can keep a two-argument signature. */
4231
+ * don't need it can keep a two-argument signature.
4232
+ *
4233
+ * **The returned array belongs to the painter.** Callers must treat it as
4234
+ * immutable and copy before appending — a painter is free to memoize its
4235
+ * command list (`kit:shape` and `kit:path` do), and an in-place `push`
4236
+ * would grow that list on every frame. `defaultDrawOne` copies before
4237
+ * adding its label overlay for exactly this reason. */
6085
4238
  paint(node: Node$1<TData, string, TPose>, pose: TPose, ctx?: NodePaintCtx): DrawCommand[];
6086
4239
  /** Optional: derive the node's silhouette path from its pose.
6087
4240
  * Used by clipping (when the container has no explicit
@@ -6089,6 +4242,26 @@ interface NodeShapeEntry<TData = unknown, TPose = unknown> {
6089
4242
  * and by SVG export. Painters whose visual has no meaningful closed
6090
4243
  * silhouette (e.g. text) leave this undefined. */
6091
4244
  silhouette?(node: Node$1<TData, string, TPose>, pose: TPose): Path | null;
4245
+ /** Optional: how the silhouette is inked — whether the interior is filled,
4246
+ * and how wide the outline is. Read by picking, so that an unfilled shape
4247
+ * is grabbable by its outline rather than by its empty middle.
4248
+ *
4249
+ * This is declared separately from `paint` rather than read back off the
4250
+ * emitted draw commands because picking runs on every pointer move, and
4251
+ * `paint` is allowed to be expensive (`kit:text` lays out glyphs). Keep it
4252
+ * to cheap field reads.
4253
+ *
4254
+ * Painters that leave it undefined are treated as `{ filled: true,
4255
+ * strokeWidth: 0 }` — the pre-`ink` behavior, where the whole silhouette
4256
+ * interior is grabbable and the outline adds nothing. */
4257
+ ink?(node: Node$1<TData, string, TPose>, pose: TPose): NodeInk | null;
4258
+ }
4259
+ /** How a painter inks its silhouette. See {@link NodeShapeEntry.ink}. */
4260
+ interface NodeInk {
4261
+ /** True when the silhouette's interior is painted, and so grabbable. */
4262
+ filled: boolean;
4263
+ /** Stroke width in world units. `0` for no stroke. */
4264
+ strokeWidth: number;
6092
4265
  }
6093
4266
  interface RegisterNodeShapeOptions {
6094
4267
  /** `'high'` puts the painter ahead of all normally-registered ones (so
@@ -6113,6 +4286,47 @@ declare function findNodeShape<TData, TPose>(node: Node$1<TData, string, TPose>)
6113
4286
  * draws. (`paint()` is unaffected — it applies rotation via the render wrap,
6114
4287
  * not the silhouette, so there is no double-rotation.) */
6115
4288
  declare function findShapeSilhouette<TData, TPose>(node: Node$1<TData, string, TPose>, pose: TPose): Path | null;
4289
+ /** Find the painter for `node` and ask how it inks its silhouette. Returns
4290
+ * the painter's declared {@link NodeInk}, or `null` when no painter matches
4291
+ * or it declares none — callers substitute {@link DEFAULT_INK}. */
4292
+ declare function findShapeInk<TData, TPose>(node: Node$1<TData, string, TPose>, pose: TPose): NodeInk | null;
4293
+ /** Options for {@link shapeCoversPoint}. */
4294
+ interface ShapeCoversPointOptions {
4295
+ /** Extra grab distance around the outline, in **world** units. Callers
4296
+ * derive it from a screen-pixel slop and the view scale, the same way
4297
+ * affordance hit radii work.
4298
+ *
4299
+ * Without slop a hairline is a mathematically zero-width target: the
4300
+ * stroke of a 1px outline is half a world unit wide at scale 1, which no
4301
+ * one can hit. Defaults to `0` so a caller that hasn't thought about the
4302
+ * view still gets exact geometry rather than a wrong guess. */
4303
+ tolerance?: number;
4304
+ }
4305
+ /**
4306
+ * Does the shape `node` actually paints cover the world point?
4307
+ *
4308
+ * The pose rect says a node covers its whole bounding box. That is wrong for
4309
+ * everything that is not a rectangle: the concave notch of a star, the corner
4310
+ * outside an ellipse, the blank right half of a text box. This asks the
4311
+ * painter's silhouette instead, which is the same boundary used for clipping
4312
+ * and SVG export, so "what you can click" and "what is drawn" answer together.
4313
+ *
4314
+ * "What is drawn" includes the *ink*, not just the boundary. A shape whose
4315
+ * interior isn't filled — an outlined rect, a pencil stroke, a bare line — is
4316
+ * grabbable along its outline and not through its empty middle, which is the
4317
+ * opposite of what a fill test alone answers. The outline's grab width is the
4318
+ * stroke's half-width plus `tolerance`.
4319
+ *
4320
+ * A painter with no `silhouette`, or one that returns `null` for this node
4321
+ * (`kit:text` does, for a node with no non-blank lines), reports `true` —
4322
+ * "no opinion", leaving the caller's own AABB test as the answer. Callers
4323
+ * should keep that AABB test as a cheap pre-filter; this is the refinement,
4324
+ * not a replacement.
4325
+ *
4326
+ * Rotation is already baked by `findShapeSilhouette`, so the point is in
4327
+ * plain world coordinates.
4328
+ */
4329
+ declare function shapeCoversPoint<TData, TPose>(node: Node$1<TData, string, TPose>, pose: TPose, x: number, y: number, opts?: ShapeCoversPointOptions): boolean;
6116
4330
  /** Snapshot of the current painters in evaluation order — `'high'` tier
6117
4331
  * first, then `'normal'`. Useful for debugging which painter handles a
6118
4332
  * given node. */
@@ -6240,6 +4454,35 @@ type SceneCanvasProps<TData, TLayer extends string, TPose> = Omit<CanvasProps<No
6240
4454
  * pre-collapsing to one id. Matches `Canvas`'s `pickEvery` shape. */
6241
4455
  pickEvery?: (worldX: number, worldY: number) => string | string[] | null;
6242
4456
  boundsOf?: (id: string) => Bounds | null;
4457
+ /**
4458
+ * What "the pointer is on this node" means for the default body-pick.
4459
+ *
4460
+ * - `'pose'` — the node's pose rect, rotation honored. What every
4461
+ * consumer got before `'shape'` existed, and now the opt-out.
4462
+ * - `'shape'` (default) — the pose rect as a pre-filter, then the ink the painter
4463
+ * actually lays down: its silhouette (`findShapeSilhouette`) filled
4464
+ * or not per the painter's `ink`, plus its outline widened by the
4465
+ * stroke half-width and `pickTolerancePx`. A click in the concave
4466
+ * notch of a star, in the corner outside an ellipse, or in the blank
4467
+ * half of a text box falls through to whatever is beneath; a click on
4468
+ * the thin outline of an unfilled shape hits it.
4469
+ *
4470
+ * Painters with no silhouette are unaffected — they keep the pose-rect
4471
+ * answer either way, so this can never make a node unreachable.
4472
+ *
4473
+ * Ignored when `pickEvery` is supplied: that override owns the test.
4474
+ */
4475
+ picking?: 'pose' | 'shape';
4476
+ /**
4477
+ * Grab slop around a shape's outline, in **screen** pixels. Default 4.
4478
+ *
4479
+ * Screen pixels rather than world units so the target keeps its
4480
+ * apparent size at any zoom. It widens the outline test under
4481
+ * `picking: 'shape'` (a 1px hairline is otherwise a half-world-unit
4482
+ * target, which is unhittable), and it grows the pose-rect pre-filter
4483
+ * so those outline hits survive it. Set `0` for exact geometry.
4484
+ */
4485
+ pickTolerancePx?: number;
6243
4486
  };
6244
4487
  selectTool?: {
6245
4488
  move?: UseMoveOptions<TPose>;
@@ -6252,10 +4495,6 @@ type SceneCanvasProps<TData, TLayer extends string, TPose> = Omit<CanvasProps<No
6252
4495
  rotate?: UseRotateOptions<TPose> | false;
6253
4496
  snap?: SnapStrategy<TPose>;
6254
4497
  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
4498
  /** Override the body-pick used on click/pointerdown. Alt-aware: receives
6260
4499
  * the live alt state + current selection so consumers can implement
6261
4500
  * alt-cycling through an overlapping stack. Default: top-most hit
@@ -6397,15 +4636,6 @@ type SceneCanvasProps<TData, TLayer extends string, TPose> = Omit<CanvasProps<No
6397
4636
  * the default select. If you supply your own `tools` prop, this is
6398
4637
  * ignored — wire `ambient` through your own `useTools` call instead. */
6399
4638
  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
4639
  /** Viewport feature wiring.
6410
4640
  *
6411
4641
  * - `inertia`, `pinchZoom`, `animatedZoom` are opt-in: pass `true`
@@ -6737,10 +4967,11 @@ declare const inferredNodeRouting: readonly NodeRoutingEntry[];
6737
4967
  * registry: a declarative schema of the kind's editable properties.
6738
4968
  *
6739
4969
  * 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.
4970
+ * `data.fill`, `data.style.fontSize`) — a path of any depth rooted at
4971
+ * `pose` or `data` — so schema consumers (weasel-ui `SelectionPanel`)
4972
+ * can read/aggregate/write generically with no per-kind code. Group
4973
+ * keys are organizational only; they do not contribute to the node
4974
+ * path.
6744
4975
  *
6745
4976
  * Kind names share the routing trait's vocabulary — an entry registered
6746
4977
  * as `'rect'` describes nodes `NodeRouting.classify` maps to `'rect'`.
@@ -7353,6 +5584,70 @@ declare function composeAffordanceLayer(id: string, label: string, affordances:
7353
5584
  }, isVisible?: (id: string) => boolean): AffordanceBinding | null;
7354
5585
  };
7355
5586
 
5587
+ /**
5588
+ * The one affordance hit-test walk.
5589
+ *
5590
+ * The kit used to hit-test selection chrome two ways: this region walk
5591
+ * (reached through `composeAffordanceLayer`'s `RenderLayer.hitTest`) and a
5592
+ * hand-written classifier in `canvas/affordanceAt.ts` that re-derived corner
5593
+ * positions, the rotate ring, and anchor points from `ChromeState` itself.
5594
+ * Both read the same state and answered the same question, so the geometry
5595
+ * lived twice and could disagree — and only the hand-written side ran for the
5596
+ * kit's own chrome, which is why `AffordanceRegion.cursor` was declared, set,
5597
+ * and never consumed.
5598
+ *
5599
+ * Now there is one walk. `composeAffordanceLayer.hitTest` delegates here, and
5600
+ * so does `buildAffordanceAt`, so an affordance's `regions()` is the single
5601
+ * source of truth for where its chrome is and what a press on it means.
5602
+ */
5603
+
5604
+ /** What the walk found: the region, plus the ids needed to describe it. */
5605
+ interface AffordanceRegionHit {
5606
+ /** `Affordance.id` — also the chrome-caps visibility id. */
5607
+ affordanceId: string;
5608
+ /** `AffordanceRegion.id`. */
5609
+ regionId: string;
5610
+ /** The region itself, so callers can read `hitKind` / `cursor`. */
5611
+ region: AffordanceRegion;
5612
+ /** Result of the region's `bind()`, called exactly once. */
5613
+ binding: AffordanceBinding;
5614
+ }
5615
+ /**
5616
+ * Walk affordances for a world-space hit, topmost first.
5617
+ *
5618
+ * **Across affordances**: order mirrors paint. Affordances are drawn first →
5619
+ * last (bottom → top), so the walk runs last → first and stops at the first
5620
+ * one that claims the point. Layering wins outright — a nearer region on a
5621
+ * lower affordance does not beat a farther one on top of it.
5622
+ *
5623
+ * **Within an affordance**: the *nearest* hit region wins, ties going to the
5624
+ * later-declared one. Both hit-testers this replaced took the first region
5625
+ * they found instead, in opposite orders, which is only invisible while
5626
+ * regions don't overlap. They do: the four corner handles of a selection
5627
+ * smaller than the hit radius all contain the same click, and "first in the
5628
+ * table" silently answered top-left (or bottom-right) no matter which handle
5629
+ * the pointer was actually on.
5630
+ *
5631
+ * `isVisible` (when supplied) gates by the same chrome id used at paint time,
5632
+ * so chrome that isn't drawn can't be hit either.
5633
+ */
5634
+ declare function hitAffordanceRegions(affordances: readonly Affordance[], wx: number, wy: number, state: ChromeState, view: View, isVisible?: (id: string) => boolean): AffordanceRegionHit | null;
5635
+ /**
5636
+ * An annulus region's effective outer semi-axes, with `minBandPx` applied.
5637
+ *
5638
+ * The affordance declares the natural ellipse (typically the smallest one
5639
+ * containing the inner rect) plus a screen-space floor on band thickness;
5640
+ * this is where the floor becomes world units, because this is where the view
5641
+ * is known. Paint calls it too, so the visible ring and the hoverable ring
5642
+ * are the same ring.
5643
+ */
5644
+ declare function annulusSemiAxes(shape: Extract<AffordanceRegion['shape'], {
5645
+ kind: 'annulus';
5646
+ }>, view: View): {
5647
+ rx: number;
5648
+ ry: number;
5649
+ };
5650
+
7356
5651
  interface CornerResizeAffordanceOptions {
7357
5652
  /** Hit radius (screen-px) for the corner handles. Default 8. */
7358
5653
  handleHitRadius?: number;
@@ -7361,12 +5656,19 @@ interface CornerResizeAffordanceOptions {
7361
5656
  fill?: string;
7362
5657
  stroke?: string;
7363
5658
  }
7364
- interface CornerResizeScratch {
5659
+ interface CornerResizeScratch extends CommonAffordanceScratch {
7365
5660
  /** Resize anchor identifying the OPPOSITE corner (the one that stays
7366
5661
  * fixed). Matches the kit's existing ResizeAnchor convention. */
7367
5662
  anchor: ResizeAnchor;
7368
5663
  /** Id of the resize target. In multi-mode this is `MULTI_RESIZE_TARGET_ID`. */
7369
5664
  targetId: string;
5665
+ /** The fixed corner in **world** coords, with the target's rotation already
5666
+ * applied. `resizeAction` scales from this point, and it has no access to
5667
+ * the target transform, so the affordance resolves it here. */
5668
+ fixedPoint: {
5669
+ x: number;
5670
+ y: number;
5671
+ };
7370
5672
  }
7371
5673
  /**
7372
5674
  * @experimental
@@ -7400,10 +5702,15 @@ interface RotationAffordanceOptions {
7400
5702
  /** Cursor while hovering the ring. Defaults to `'grab'`. */
7401
5703
  cursor?: string;
7402
5704
  }
7403
- interface RotationScratch {
5705
+ interface RotationScratch extends CommonAffordanceScratch {
7404
5706
  /** Id of the rotation target — single selection id, or
7405
5707
  * `MULTI_RESIZE_TARGET_ID` for multi-selection. */
7406
5708
  targetId: string;
5709
+ /** Rotation pivot in world coords — the AABB (or union AABB) center. */
5710
+ fixedPoint: {
5711
+ x: number;
5712
+ y: number;
5713
+ };
7407
5714
  }
7408
5715
  /**
7409
5716
  * @experimental
@@ -7424,6 +5731,61 @@ interface RotationScratch {
7424
5731
  */
7425
5732
  declare function createRotationAffordance(opts?: RotationAffordanceOptions): Affordance;
7426
5733
 
5734
+ /**
5735
+ * Path anchor + control-handle affordances.
5736
+ *
5737
+ * These were the last piece of selection chrome whose geometry lived only
5738
+ * inside `canvas/affordanceAt.ts`'s hand-written classifier. Expressing them
5739
+ * as regions puts every kit affordance behind one hit-test walk, which is
5740
+ * what lets `AffordanceRegion.cursor` and nearest-region picking apply here
5741
+ * too rather than only to the chrome that happened to be declarative already.
5742
+ */
5743
+
5744
+ /**
5745
+ * Live anchor-editing state, read fresh on every hit-test.
5746
+ *
5747
+ * Anchors are not derivable from `ChromeState` — they come off the editable
5748
+ * path behind whatever the consumer's `editAnchors` dep exposes — so this
5749
+ * thunk is the seam. It's called on every pointer event, so it must be cheap
5750
+ * (O(1) field reads); enumeration happens inside `regions()`.
5751
+ */
5752
+ interface AnchorState {
5753
+ /** Id of the path currently in anchor-edit mode, or `null`. Control handles
5754
+ * are only hittable on the path that is being edited. */
5755
+ editingId: string | null;
5756
+ /** Current editable path for a node id. Non-polygon values are ignored. */
5757
+ getPose(id: string): unknown;
5758
+ }
5759
+ interface PathAnchorAffordanceOptions {
5760
+ /** Hit radius (screen-px) for anchor and control points. Default 8. */
5761
+ hitRadius?: number;
5762
+ /** Cursor while hovering an anchor or control. Defaults to `'pointer'`. */
5763
+ cursor?: string;
5764
+ }
5765
+ interface AnchorScratch extends CommonAffordanceScratch {
5766
+ /** Node id of the path the anchor belongs to. */
5767
+ targetId: string;
5768
+ /** Sequential anchor index in path walk order. */
5769
+ anchorIndex: number;
5770
+ /** Which of the anchor's three grabbable points this is. */
5771
+ part: 'anchor' | 'controlIn' | 'controlOut';
5772
+ }
5773
+ /** Chrome-caps id gating both returned affordances. */
5774
+ declare const PATH_ANCHOR_CHROME_ID = "path-edit.anchors";
5775
+ /**
5776
+ * Anchor + control affordances for the selected paths, bottom → top.
5777
+ *
5778
+ * Returns **two** affordances rather than one so that a control handle beats
5779
+ * an anchor regardless of which is nearer — the same preference `hitAnchor`
5780
+ * encodes, and the same one the rendering has (controls draw on top). Within
5781
+ * each of the two, nearest wins. They deliberately share one id: chrome-caps
5782
+ * gates anchor editing as a single piece of chrome, and splitting the id
5783
+ * would invent a visibility rule nothing asked for.
5784
+ *
5785
+ * Order matters — spread this into an affordance list in the returned order.
5786
+ */
5787
+ declare function createPathAnchorAffordances(getAnchorState: () => AnchorState | null, opts?: PathAnchorAffordanceOptions): Affordance[];
5788
+
7427
5789
  /** Pluggable text-painting strategy. The default fills white at `(x, y)`; markdown renderers replace this. */
7428
5790
  type TextRenderer = (ctx: CanvasRenderingContext2D, text: string, x: number, y: number) => void;
7429
5791
  /** Visual options for `renderLabel`. */
@@ -7505,6 +5867,17 @@ declare function createMarkdownRenderer(ctx: CanvasRenderingContext2D, text: str
7505
5867
  * already matches `style` (use `fontString(style)`).
7506
5868
  */
7507
5869
 
5870
+ /**
5871
+ * Advance width of `text` in world units, tracking included.
5872
+ *
5873
+ * `letter-spacing` is not part of the CSS `font` shorthand, so a context
5874
+ * whose `font` was set from `fontString(style)` measures glyphs only. The
5875
+ * GL path (`layoutRuns`) adds `letterSpacing` after **every** code point
5876
+ * including the last, matching CSS, so this does too — and every 2D-side
5877
+ * width has to go through here or the two paths disagree about where a line
5878
+ * breaks.
5879
+ */
5880
+ declare function measuredWidth(ctx: CanvasRenderingContext2D, text: string, style: ResolvedTextStyle): number;
7508
5881
  /** Result of `measureText`: wrapped lines, per-line source offsets, and total block height. */
7509
5882
  interface MeasuredText {
7510
5883
  lines: string[];
@@ -7617,6 +5990,70 @@ declare function pointInTextPose(x: number, y: number, pose: TextPose, opts?: Po
7617
5990
  */
7618
5991
  declare function caretIndexAt(ctx: CanvasRenderingContext2D, x: number, y: number, pose: TextPose): number;
7619
5992
 
5993
+ /**
5994
+ * Polygon-vs-axis-aligned-rect tests. Pure functions; no React/kit deps.
5995
+ *
5996
+ * `polygon` is a closed polyline given as an ordered vertex array; the
5997
+ * closing edge from `polygon[N-1]` to `polygon[0]` is implicit. Even-odd
5998
+ * fill rule (matches `pointInPath`).
5999
+ */
6000
+ interface Vec2 {
6001
+ x: number;
6002
+ y: number;
6003
+ }
6004
+ interface Rect {
6005
+ x: number;
6006
+ y: number;
6007
+ width: number;
6008
+ height: number;
6009
+ }
6010
+
6011
+ /**
6012
+ * Where a text node's lines actually sit inside its pose box.
6013
+ *
6014
+ * A text pose is a *wrap box*, not a bounding box: `"Away"` in a 309-unit-wide
6015
+ * box leaves most of the box empty, and anything that treats the pose as the
6016
+ * node's extent — picking, lasso, clipping, SVG export — claims that empty
6017
+ * space. `textLineBoxes` returns the per-line rectangles instead.
6018
+ *
6019
+ * The numbers come from `layoutRuns`, the same walk that positions the glyphs,
6020
+ * through the same `resolveTextStyle` → `resolveRuns` → `layoutRuns` chain as
6021
+ * `textCommand` — so the boxes cannot drift from what is painted. In
6022
+ * particular they honor `align` (a centered line reports its own span, not the
6023
+ * wrap width) and `verticalAlign` (the block shifts inside `[y, y + height]`
6024
+ * exactly as `drawText` shifts the quads).
6025
+ *
6026
+ * These are line boxes, not ink boxes: each is `fontSize * lineHeight` tall
6027
+ * from the pen's line top. Ink can escape vertically at a `lineHeight` low
6028
+ * enough — see the `bounds` note at the end of `layoutRuns` — which is the
6029
+ * right trade for hit-testing, where a box that hugged the ink would make
6030
+ * an `x` harder to click than an `X`.
6031
+ */
6032
+
6033
+ /** Options for {@link textLineBoxes}. */
6034
+ interface TextLineBoxesOpts {
6035
+ /** Grow every box by this much on all four sides (world units). Default 0.
6036
+ * Picking wants a little slack so a single hairline row of text is still
6037
+ * grabbable; clipping and export want none. */
6038
+ padding?: number;
6039
+ /** Keep boxes for blank lines (zero width). Default `false` — a blank line
6040
+ * covers no area, so for hit-testing and silhouettes it is noise. Pass
6041
+ * `true` when the indices have to line up with the wrapped lines. */
6042
+ includeEmpty?: boolean;
6043
+ /**
6044
+ * Wrap width. Default `pose.width`, which is what `createTextLayer` passes
6045
+ * and what `TextPose` means by its box.
6046
+ *
6047
+ * Pass `Infinity` for a node painted by the built-in `kit:text` painter:
6048
+ * that painter deliberately does **not** forward `maxWidth` (see
6049
+ * `NodeShape.ts`), so its text does not wrap, and boxes computed with a
6050
+ * finite width would wrap where the paint did not.
6051
+ */
6052
+ maxWidth?: number;
6053
+ }
6054
+ /** Per-line rectangles for a text pose, in world units, in layout order. */
6055
+ declare function textLineBoxes(pose: TextPose, opts?: TextLineBoxesOpts): Rect[];
6056
+
7620
6057
  /**
7621
6058
  * Resize a text node's pose to fit its content. Pure helper — does not own
7622
6059
  * scene state. Call it whenever the text, style, or relevant axis changes,
@@ -7651,6 +6088,82 @@ interface FitTextPoseOptions {
7651
6088
  /** Recompute a `TextPose`'s `width`/`height` to fit its content; pure helper, doesn't mutate scene state. */
7652
6089
  declare function fitTextPose(ctx: CanvasRenderingContext2D, pose: TextPose, opts?: FitTextPoseOptions): TextPose;
7653
6090
 
6091
+ /**
6092
+ * The kit-wide "these values disagree" sentinel. Used wherever a value is
6093
+ * aggregated across more than one source and the sources don't agree — e.g.
6094
+ * a UI panel aggregating a property across a multi-node selection, or text
6095
+ * styling aggregated across the runs in a character range. One symbol, one
6096
+ * `isMixed` check, regardless of what's being aggregated.
6097
+ */
6098
+ declare const MIXED: unique symbol;
6099
+ type Mixed = typeof MIXED;
6100
+
6101
+ /**
6102
+ * Range-addressed styling over `StyledRun[]`. The panel addresses node paths;
6103
+ * a caret addresses a character range — these are the functions for the
6104
+ * second case. Pure and React-free so the semantics are unit-testable, the
6105
+ * same split `SelectionPanel` makes between `model.ts` and its component.
6106
+ *
6107
+ * Character offsets index the concatenated run text (`runsToPlainText`), and
6108
+ * a range is half-open: `[start, end)`. Ranges are clamped, never wrapped —
6109
+ * a negative `start` clamps to 0, an `end` past the text clamps to its
6110
+ * length, and a collapsed or inverted range (`start >= end`) is empty.
6111
+ * Offsets are UTF-16 code units, as everywhere else in this feature, so a
6112
+ * boundary can bisect a surrogate pair (`[1, 2)` over `'a👍b'` splits the
6113
+ * thumbs-up into lone surrogates). A caret-derived range never does this.
6114
+ *
6115
+ * **Run-level flags are additive over the node's `TextStyle`: a run can
6116
+ * turn `bold` / `italic` / `underline` / `strikethrough` on, never off.**
6117
+ * So a flag is stored only when true, and absent reads as `false`. For
6118
+ * `bold` this falls out of the model — node weight is numeric, and
6119
+ * `run.bold ? 700 : baseWeight` has nowhere to put "not bold." For the
6120
+ * boolean decorations it does not: both levels are booleans, so a tri-state
6121
+ * (`true` / `false` / inherit) is expressible and we are collapsing it by
6122
+ * choice, to keep one canonical form per styling. Resolution must therefore
6123
+ * read decorations as `run.underline || style.underline` — a `??` would
6124
+ * make "select a word in an underlined node, hit U to turn it off" look
6125
+ * supported by the type while this file discards the `false` that expresses it.
6126
+ *
6127
+ * Copies here are shallow: a `fill` object is shared by reference between
6128
+ * the input runs, the patch, every run the patch touched, and the value
6129
+ * `styleAtRange` reports. Treat `fill` values as immutable.
6130
+ */
6131
+
6132
+ /** Every styleable key of a run, each either a concrete value or MIXED (the
6133
+ * runs in the range disagree at that key). */
6134
+ type RangeStyle = {
6135
+ [K in Exclude<keyof StyledRun, 'text'>]?: StyledRun[K] | Mixed;
6136
+ };
6137
+ /**
6138
+ * What `applyStyleToRange` writes. `text` is deliberately not part of it —
6139
+ * this is a styling operation, not an edit.
6140
+ */
6141
+ type RunStylePatch = Partial<Omit<StyledRun, 'text'>>;
6142
+ /** Every key of a run that carries styling — i.e. everything but `text`. */
6143
+ type StyleKey = Exclude<keyof StyledRun, 'text'>;
6144
+ /**
6145
+ * The styling shared by every run overlapping `[start, end)`: a concrete
6146
+ * value where the range agrees, `MIXED` where it doesn't. Keys no run
6147
+ * in range sets are absent (they inherit the node's `TextStyle`) — except
6148
+ * the additive flags, which read as `false` rather than `undefined` since a
6149
+ * run cannot un-set them. An empty range reads as `{}`. A reported `fill`
6150
+ * is the run's own object, not a copy — treat it as immutable.
6151
+ */
6152
+ declare function styleAtRange(runs: readonly StyledRun[], start: number, end: number): RangeStyle;
6153
+ /**
6154
+ * Write `patch` over `[start, end)`, splitting runs at the boundaries and
6155
+ * coalescing neighbors that end up identical. A patch value of `undefined`
6156
+ * or `false` deletes the key instead of storing it — see the additive-flags
6157
+ * contract at the top of the file.
6158
+ *
6159
+ * Returns a new array of new runs; neither the input array nor its runs are
6160
+ * mutated (though a nested `fill` is shared by reference). Normalization —
6161
+ * merging identical neighbors, dropping empty runs — applies to the whole
6162
+ * array, not just the patched span, and to every call, including one whose
6163
+ * range is empty and so patches nothing.
6164
+ */
6165
+ declare function applyStyleToRange(runs: readonly StyledRun[], start: number, end: number, patch: RunStylePatch): StyledRun[];
6166
+
7654
6167
  /**
7655
6168
  * In-place text editing via a contenteditable overlay positioned over the
7656
6169
  * text node's screen-space pose. Enter / blur commit; Shift+Enter inserts a
@@ -7662,17 +6175,51 @@ declare function fitTextPose(ctx: CanvasRenderingContext2D, pose: TextPose, opts
7662
6175
  * (`isHidden: (n) => n.id === editingId`) so the overlay isn't drawn twice.
7663
6176
  */
7664
6177
 
6178
+ /**
6179
+ * The caret's character range within the text being edited. Half-open
6180
+ * `[start, end)` over the concatenated run text, normalized so `start <= end`
6181
+ * regardless of which way the user dragged. `start === end` is a collapsed
6182
+ * caret — a real position, not the absence of one, which is why the hook
6183
+ * reports `null` rather than a zero-width range when there is no caret.
6184
+ */
6185
+ interface TextEditSelection {
6186
+ start: number;
6187
+ end: number;
6188
+ }
7665
6189
  /** Screen-space pose passed to `useTextEdit` so the overlay can be placed and sized in CSS pixels. */
7666
6190
  interface TextEditScreenPose {
7667
- /** Top-left in CSS pixels relative to `container`. */
6191
+ /** Top-left in CSS pixels relative to `container`. Always screen pixels,
6192
+ * including when `zoom` is set — the scale is anchored at this point, not
6193
+ * translated by it. */
7668
6194
  x: number;
7669
6195
  y: number;
6196
+ /** Pre-scale — CSS pixels, or world units when `zoom` is set. */
7670
6197
  width: number;
7671
6198
  height: number;
7672
- /** Effective on-screen font size (style.fontSize * zoom). */
6199
+ /** Pre-scale font size: `style.fontSize * zoom` when `zoom` is omitted, the
6200
+ * world-unit `style.fontSize` when it is set. */
7673
6201
  fontSize: number;
7674
6202
  /** Effective on-screen line height multiplier (defaults to style.lineHeight). */
7675
6203
  lineHeight?: number;
6204
+ /**
6205
+ * CSS scale applied to the overlay (`transform: scale(zoom)`, anchored at
6206
+ * its top-left). Every other size on this pose, and every typographic
6207
+ * metric the hook writes, is then **pre-scale** — pass world units and the
6208
+ * transform does the world→screen conversion.
6209
+ *
6210
+ * This is the only way run-level typography can be correct at a zoom other
6211
+ * than 1. `runsToDom` emits run `fontSize` / `letterSpacing` in world units
6212
+ * and `domToRuns` reads them straight back; threading a scale through the
6213
+ * writer and its inverse through the reader would go lossy on fractional
6214
+ * zooms. Scaling the whole overlay instead leaves that serializer pure and
6215
+ * scales node-level and run-level values by the same factor for free.
6216
+ *
6217
+ * Omit it (the default, `1`) and the pose is plain screen pixels — the hook
6218
+ * then infers the world→screen factor from `fontSize / style.fontSize` to
6219
+ * scale node-level `letterSpacing`, and run-level overrides are left
6220
+ * unscaled. Correct only at zoom 1.
6221
+ */
6222
+ zoom?: number;
7676
6223
  }
7677
6224
  /** Options for `useTextEdit`. */
7678
6225
  interface UseTextEditOptions {
@@ -7697,9 +6244,27 @@ interface UseTextEditOptions {
7697
6244
  * Optional: commit rich-text runs back to the node. When omitted, only
7698
6245
  * `setText` is called with the plain-text form on commit. When provided,
7699
6246
  * commit calls both `setText` (with `runsToPlainText(runs)`) and
7700
- * `setRuns(id, runs)`.
6247
+ * `setRuns(id, runs)` — but only when runs are actually in play: the node
6248
+ * already had some, or the edit produced styling. A plain-text edit of a
6249
+ * plain-text node still calls `setText` alone, so a node that has never
6250
+ * been styled doesn't grow a single-run `runs` array just for being edited.
7701
6251
  */
7702
6252
  setRuns?: (id: string, runs: StyledRun[]) => void;
6253
+ /**
6254
+ * Is `el` part of the editor's own chrome — a character-options bar, a
6255
+ * color popover, anything whose whole purpose is to style the text being
6256
+ * edited? Focus moving into one does not end the edit.
6257
+ *
6258
+ * Without this, the controls the feature exists for are exactly what
6259
+ * destroys it: clicking a size field blurs the overlay, blur commits, and
6260
+ * the caret the control was about to act on is gone. Toggle buttons can
6261
+ * dodge it by `preventDefault()`-ing their own mousedown, but a field the
6262
+ * user has to type into cannot.
6263
+ *
6264
+ * Chrome focus does not disturb the reported `selection` either — see
6265
+ * `UseTextEditReturn.selection`.
6266
+ */
6267
+ isEditorChrome?: (el: Element) => boolean;
7703
6268
  }
7704
6269
  /** Options for `useTextEdit().startEdit`. */
7705
6270
  interface StartEditOptions {
@@ -7716,6 +6281,37 @@ interface UseTextEditReturn {
7716
6281
  cancelEdit: () => void;
7717
6282
  commit: () => void;
7718
6283
  isEditing: (id: string) => boolean;
6284
+ /**
6285
+ * The caret's character range, or `null` when nothing is being edited. A
6286
+ * collapsed caret reports `{ start: n, end: n }`, so `null` and "caret at
6287
+ * n" stay distinguishable — a character-styling control routes the
6288
+ * collapsed case to the node's `TextStyle` instead of to a range.
6289
+ *
6290
+ * Follows the DOM selection, which browsers (and jsdom) report from a task
6291
+ * rather than synchronously; anything this hook writes itself updates it
6292
+ * synchronously. A DOM selection that leaves the overlay does **not** clear
6293
+ * it: that is what happens when the user clicks a styling control, and
6294
+ * reporting `null` there would read as "collapsed caret" and send the
6295
+ * control's patch to the node instead of to the range. It is cleared on
6296
+ * `startEdit` and when the edit ends.
6297
+ */
6298
+ selection: TextEditSelection | null;
6299
+ /**
6300
+ * The styling shared by every run in `selection` — a concrete value where
6301
+ * the range agrees, `MIXED` where it doesn't. `null` exactly when
6302
+ * `selection` is `null`. A collapsed caret reports `{}`: no run is in
6303
+ * range, so the range reader has nothing to say and the node's style is
6304
+ * what applies.
6305
+ */
6306
+ rangeStyle: RangeStyle | null;
6307
+ /**
6308
+ * Write `patch` over `selection`. A no-op with no active edit, with a
6309
+ * collapsed caret (there is no range to style — patch the node's
6310
+ * `TextStyle` instead), or with an empty patch. The caret survives, so a
6311
+ * second style can be applied without re-selecting, and `rangeStyle`
6312
+ * reflects the write before this returns.
6313
+ */
6314
+ applyStyleToSelection: (patch: RunStylePatch) => void;
7719
6315
  }
7720
6316
  /** In-place text editing via a contenteditable overlay positioned over the text node's screen-space pose. */
7721
6317
  declare function useTextEdit(opts: UseTextEditOptions): UseTextEditReturn;
@@ -7748,8 +6344,10 @@ declare function unionBounds<TPose extends RectPose>(poses: Iterable<TPose>): Re
7748
6344
  * (`getText` / `getStyle` / `getRuns` / `setText` / `setRuns`).
7749
6345
  *
7750
6346
  * 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
6347
+ * the node's pose (typed `RectPose`). Pass `view` and it projects that box
6348
+ * through the viewport; omit it and world units are handed through as
6349
+ * screen pixels, which is correct only for an unpanned, unzoomed canvas.
6350
+ * Consumers with non-rect poses should drop down to raw `useTextEdit` and
7753
6351
  * supply their own `getScreenPose`.
7754
6352
  */
7755
6353
 
@@ -7775,6 +6373,25 @@ interface UseSceneTextEditOptions<TData> {
7775
6373
  setRuns?: (data: TData, runs: StyledRun[]) => TData;
7776
6374
  /** Fallback fontSize when `style.fontSize` is unset. Default `16`. */
7777
6375
  defaultFontSize?: number;
6376
+ /**
6377
+ * Current viewport. Supply it on a canvas that pans or zooms: the overlay
6378
+ * is then positioned at the node's projected screen origin and CSS-scaled
6379
+ * by the view, so every typographic metric on it — including the
6380
+ * `fontSize` / `letterSpacing` a *run* carries — stays in world units and
6381
+ * scales together. Omit it and the node's world box is passed through as
6382
+ * screen pixels (correct at `{x: 0, y: 0, scale: 1}`).
6383
+ *
6384
+ * The overlay takes a single scale factor, so a non-uniform view scale is
6385
+ * represented by its `scale.x`; text under `scale.x !== scale.y` will not
6386
+ * match the canvas.
6387
+ */
6388
+ view?: View;
6389
+ /**
6390
+ * Forwarded to `useTextEdit`: is `el` part of the editor's own chrome?
6391
+ * Focus moving into it does not end the edit. Wire it to whatever renders
6392
+ * the character controls.
6393
+ */
6394
+ isEditorChrome?: (el: Element) => boolean;
7778
6395
  }
7779
6396
  /** Return shape extends `UseTextEditReturn` with an `onDoubleClick`
7780
6397
  * binding for the canvas container. Double-clicking inside a text
@@ -7790,10 +6407,12 @@ interface UseSceneTextEditReturn extends UseTextEditReturn {
7790
6407
  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
6408
 
7792
6409
  /**
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))`.
6410
+ * Build a `TextDrawCommand`. `textCommand` takes a plain string and wraps it
6411
+ * in one run; `textCommandFromRuns` takes the styled runs directly. Both
6412
+ * resolve through `resolveTextStyle` + `resolveRuns`, so every emitter of a
6413
+ * text command derives `align` and per-run resolution the same way a
6414
+ * caller that assembles the command by hand is one field away from a
6415
+ * silently different result.
7797
6416
  */
7798
6417
 
7799
6418
  declare function textCommand(x: number, y: number, text: string, style?: TextStyle, maxWidth?: number, height?: number, verticalAlign?: TextVerticalAlign): DrawCommand;
@@ -7823,8 +6442,15 @@ declare function charOffsetToDomPosition(parent: HTMLElement, offset: number): {
7823
6442
  /**
7824
6443
  * Inverse of `charOffsetToDomPosition`. Walks text nodes in document order;
7825
6444
  * 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.
6445
+ *
6446
+ * When `node` is an element the DOM offset indexes *child nodes*, not
6447
+ * characters, so it can't simply be added — `(overlay, 0)` is the start of
6448
+ * the text and `(overlay, childNodes.length)` its end. That shape is not
6449
+ * exotic: `Range.selectNodeContents`, which `useTextEdit` uses for its
6450
+ * select-all caret, produces exactly it. So resolve an element position by
6451
+ * counting the text that precedes the boundary point. A text node can never
6452
+ * *contain* that point (the container isn't one), so each is wholly before
6453
+ * or wholly after it.
7828
6454
  */
7829
6455
  declare function domPositionToCharOffset(parent: HTMLElement, node: Node, offset: number): number;
7830
6456
 
@@ -8072,6 +6698,48 @@ declare function registerOpFactory<A>(name: string, build: (args: A) => Op): voi
8072
6698
  * a no-op placeholder so stack ordering survives across kit version skew). */
8073
6699
  declare function rebuildOp(name: string, args: unknown): Op | null;
8074
6700
 
6701
+ /** @internal */
6702
+ interface HitAdapter<TNode extends {
6703
+ id: string;
6704
+ }, TPose> {
6705
+ getNode: (id: string) => TNode | undefined;
6706
+ getNodes: () => TNode[];
6707
+ getPose: (id: string) => TPose;
6708
+ getParent: (id: string) => string | null;
6709
+ }
6710
+ interface NestedHitOpts<TNode extends {
6711
+ id: string;
6712
+ }, TPose> {
6713
+ /** Compose a child's local pose into world coords given its parent's world
6714
+ * pose. Same shape as `composeRectPose` (the default expectation). */
6715
+ composePose: (parent: TPose, child: TPose) => TPose;
6716
+ /** Derive an axis-aligned bounding rect from a (world-space) pose. Default
6717
+ * reads `x` / `y` / `width` / `height` straight off the pose (matches
6718
+ * `RectPose`). Override for non-rect poses (e.g. paths). */
6719
+ poseBounds?: (pose: TPose) => Bounds;
6720
+ /** Predicate for "this object is a nesting parent body". The leaf scan
6721
+ * skips objects for which this returns true so a click on a parent's
6722
+ * painted body resolves to a child leaf, not the parent itself. Default:
6723
+ * never (treat every object as hittable). */
6724
+ isGroup?: (id: string, obj: TNode | undefined) => boolean;
6725
+ }
6726
+ interface NestedHitTester {
6727
+ /** Outermost-ancestor pick. Suitable as the chrome-level `pickEvery`: a
6728
+ * casual click selects the whole top-level ancestor. Returns `null` on
6729
+ * empty space. */
6730
+ pickOutermost: (worldX: number, worldY: number) => string | null;
6731
+ /** Alt-aware selection-update pick. Without `alt`, returns the outermost
6732
+ * ancestor (same as `pickOutermost`). With `alt`, returns one level
6733
+ * deeper than the deepest currently-selected ancestor in the leaf's
6734
+ * chain — repeated alt-clicks step ancestor → descendant → leaf. With
6735
+ * `alt` and nothing in the chain selected, jumps straight to the leaf.
6736
+ * Plug into `useSelectTool({ pickBest })`. */
6737
+ pickBest: (worldX: number, worldY: number, alt: boolean, selection: readonly string[]) => string | null;
6738
+ }
6739
+ declare function nestedHitTester<TNode extends {
6740
+ id: string;
6741
+ }, TPose>(adapter: HitAdapter<TNode, TPose>, opts: NestedHitOpts<TNode, TPose>): NestedHitTester;
6742
+
8075
6743
  /**
8076
6744
  * Fluent builder for `PolygonPath`. Hides the `Uint8Array` / `Float32Array`
8077
6745
  * encoding behind move/line/curve/close calls and a final `build()`. Use
@@ -8173,6 +6841,44 @@ declare function linePath(a: {
8173
6841
  /** AABB of a `Path`, returned as a `RectPath` for direct reuse with rect-fast-path machinery. */
8174
6842
  declare function boundsOfPath(path: Path): RectPath;
8175
6843
 
6844
+ /**
6845
+ * Counts the path anchors used by the per-anchor coloring surface. An
6846
+ * "anchor" is the destination point of a path command: M, L, C, Q each
6847
+ * contribute one (the (x, y) where the pen ends up); Z contributes none
6848
+ * (it closes back to the subpath's first M). RectPath has four implicit
6849
+ * anchors — the corners — matching its M/L/L/L/Z stroke tessellation.
6850
+ *
6851
+ * Consumers use this to size their per-anchor color array; the renderer
6852
+ * uses it to validate the array length in dev builds.
6853
+ */
6854
+
6855
+ interface PenAnchor {
6856
+ x: number;
6857
+ y: number;
6858
+ inHandle?: {
6859
+ x: number;
6860
+ y: number;
6861
+ };
6862
+ outHandle?: {
6863
+ x: number;
6864
+ y: number;
6865
+ };
6866
+ }
6867
+ /**
6868
+ * Derive a per-subpath anchor model from a PolygonPath. Subpaths split on
6869
+ * every `M` command; a subpath is closed iff it ends with `Z`.
6870
+ *
6871
+ * Cubic-segment control points become the outHandle of the previous anchor
6872
+ * and the inHandle of the next anchor. Quadratic segments are upgraded to
6873
+ * cubics (each control reused for both adjacent handles) — this loses no
6874
+ * geometry. Linear segments produce anchors with no handles.
6875
+ */
6876
+ declare function pathToAnchors(path: PolygonPath): {
6877
+ anchors: PenAnchor[][];
6878
+ closed: boolean[];
6879
+ };
6880
+ declare function countPathAnchors(path: Path): number;
6881
+
8176
6882
  interface Point {
8177
6883
  x: number;
8178
6884
  y: number;
@@ -8198,24 +6904,6 @@ interface PointInPathOptions {
8198
6904
  /** Filled-region hit-test for a `Path`. Rect short-circuits to AABB; polygons run ray-cast / winding per `fillRule`. */
8199
6905
  declare function pointInPath(path: Path, x: number, y: number, opts?: PointInPathOptions): boolean;
8200
6906
 
8201
- /**
8202
- * Polygon-vs-axis-aligned-rect tests. Pure functions; no React/kit deps.
8203
- *
8204
- * `polygon` is a closed polyline given as an ordered vertex array; the
8205
- * closing edge from `polygon[N-1]` to `polygon[0]` is implicit. Even-odd
8206
- * fill rule (matches `pointInPath`).
8207
- */
8208
- interface Vec2 {
8209
- x: number;
8210
- y: number;
8211
- }
8212
- interface Rect {
8213
- x: number;
8214
- y: number;
8215
- width: number;
8216
- height: number;
8217
- }
8218
-
8219
6907
  /**
8220
6908
  * Path-vs-geometry hit-test helpers. Five pure functions covering all
8221
6909
  * combinations of path-vs-point, path-vs-rect, and path-vs-polygon.
@@ -8654,10 +7342,12 @@ declare function createPenPreviewLayer(opts: CreatePenPreviewLayerOptions): Rend
8654
7342
  * `getEditingId()` + `getPose(id)` thunks. Returns `[]` when no editing
8655
7343
  * target is set or the target isn't a polygon — the layer never throws.
8656
7344
  *
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.
7345
+ * Anchors render as small white-filled stroked squares filled solid
7346
+ * when selected; control points as small filled circles connected to
7347
+ * their anchor by a thin stem line. This is the kit's only anchor
7348
+ * chrome; the pen tool's parallel overlay and `apps/draw`'s
7349
+ * mode-decoration painter both drew the same thing from different state
7350
+ * and have been removed.
8661
7351
  *
8662
7352
  * All coordinates are projected via `worldToScreen` so the marker sizes
8663
7353
  * stay constant regardless of zoom.
@@ -8671,7 +7361,26 @@ interface CreatePathEditingOverlayLayerOptions {
8671
7361
  /** Returns the pose for an id, or null if the node has been deleted.
8672
7362
  * Non-polygon poses are tolerated (the layer no-ops on them). */
8673
7363
  getPose(id: string): Path | null;
8674
- /** Optional styling overrides. Defaults match `penEditOverlay`. */
7364
+ /** Flat indices of the selected anchors. Selected anchors render
7365
+ * filled; unselected ones hollow — the standard vector-editor cue for
7366
+ * "these are what the arrow keys and Delete will act on". Omit when
7367
+ * the consumer has no anchor selection to show. */
7368
+ getSelectedAnchors?(): ReadonlySet<number>;
7369
+ /** In-flight anchor-marquee rect in world coords, or null. Drawn as a
7370
+ * rubber band while `marqueeAnchorsAction` is dragging. */
7371
+ getMarquee?(): {
7372
+ x: number;
7373
+ y: number;
7374
+ width: number;
7375
+ height: number;
7376
+ } | null;
7377
+ /** Chrome-caps predicate. The layer asks `'path-edit.anchors'` before
7378
+ * drawing anything, so paint and the anchor hit-test in
7379
+ * `affordanceAt` consult the same rule — otherwise a consumer that
7380
+ * hides the chrome still gets grabbable invisible anchors, or vice
7381
+ * versa. Omit to always draw when an edit target is set. */
7382
+ isVisible?(chromeId: string): boolean;
7383
+ /** Optional styling overrides. */
8675
7384
  style?: PathEditingOverlayStyle;
8676
7385
  }
8677
7386
  interface PathEditingOverlayStyle {
@@ -8685,6 +7394,13 @@ interface PathEditingOverlayStyle {
8685
7394
  * curve / background show through, which matches Figma's "translucent
8686
7395
  * handle dot" idiom. */
8687
7396
  handleDotFill?: string;
7397
+ /** Fill of a selected anchor square. Defaults to the anchor stroke
7398
+ * color, so selection reads as "the marker filled in". */
7399
+ anchorFillSelected?: string;
7400
+ /** Stroke of the marquee rubber band. */
7401
+ marqueeStroke?: string;
7402
+ /** Fill of the marquee rubber band. */
7403
+ marqueeFill?: string;
8688
7404
  }
8689
7405
  declare function createPathEditingOverlayLayer(opts: CreatePathEditingOverlayLayerOptions): RenderLayer<unknown>;
8690
7406
 
@@ -9421,36 +8137,6 @@ interface AnchorHit {
9421
8137
  * are within `threshold` (controls are smaller and rendered on top). */
9422
8138
  declare function hitAnchor(path: PolygonPath, worldX: number, worldY: number, threshold: number): AnchorHit | null;
9423
8139
 
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
8140
  /**
9455
8141
  * Typed keys for the gesture/behavior scratch store.
9456
8142
  *
@@ -10197,4 +8883,11 @@ declare function ExcludeIcon(): react_jsx_runtime.JSX.Element;
10197
8883
  declare function DivideIcon(): react_jsx_runtime.JSX.Element;
10198
8884
  declare function CropIcon(): react_jsx_runtime.JSX.Element;
10199
8885
 
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 };
8886
+ interface InsertOverlayStyle {
8887
+ fill?: string;
8888
+ stroke?: string;
8889
+ dash?: number[];
8890
+ lineWidth?: number;
8891
+ }
8892
+
8893
+ export { Action, ActionsProp, ActionsProviderIfRoot, ActionsRegistry, Affordance, AffordanceBinding, AffordanceHit, AffordanceRegion, type AffordanceRegionHit, type AlignAdapter, type AlignAnchor, type AlignBounds, type AlignBoundsProjection, type AlignEdge, type AlignMatchResult, type AlignMoveArgs, type AlignmentBehaviorBase, type AnchorHit, type AnchorScratch, type AnchorState, 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, CommonAffordanceScratch, 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 NodeInk, type NodePaintCtx, type NodeProperties, type NodePropertiesEntry, type NodeRouting, type NodeRoutingEntry, type NodeShapeEntry, type OpenFilePickerOptions, OrderedAdapter, OriginProjection, PATH_ANCHOR_CHROME_ID, type PanBounds, type ParallaxOpts, Path, type PathAnchor, type PathAnchorAffordanceOptions, 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 ShapeCoversPointOptions, 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 TextLineBoxesOpts, 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, VERSION, 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, annulusSemiAxes, 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, createPathAnchorAffordances, 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, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitZoom, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, focused, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getScratch, getStylusData, guideSnapStrategy, hexToRgba, hitAffordanceRegions, 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, shapeCoversPoint, snap, snapPoint, solidVertexColors, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, starPath, startThresholdDrag, styleAtRange, subscribeImageReady, textCommand, textLineBoxes, 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 };