@weasel-js/routing 1.5.2 → 1.6.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.
@@ -3,7 +3,6 @@ import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
4
  import { CursorSpec } from '@weasel-js/cursor';
5
5
  import { ClaimableGesture, GestureSpec, InputEvent, TargetSpec, ParsedModifiers, GestureName, IngestItem } from '@weasel-js/gestures';
6
- import * as react_jsx_runtime from 'react/jsx-runtime';
7
6
  import { Op } from '@weasel-js/history';
8
7
 
9
8
  /**
@@ -235,6 +234,10 @@ interface RuleCtx {
235
234
  * density 1. That is what the kit assumed before this field existed, so
236
235
  * an absent profile is behavior-preserving by construction. */
237
236
  readonly device?: DeviceProfile;
237
+ /** The view the input being dispatched landed in — a view id, or `null`
238
+ * for the surface's own camera. The dispatcher fills it; absent anywhere
239
+ * else a rule is evaluated. */
240
+ readonly viewId?: string | null;
238
241
  }
239
242
  /** The live state a `RuleCtx` is assembled from — the chrome context plus
240
243
  * what the active mode allows. */
@@ -556,6 +559,12 @@ interface InvocationCtx {
556
559
  deltaY: number;
557
560
  deltaZ: number;
558
561
  };
562
+ /** One `pinch` sample: `scale` is a multiplier since the previous sample,
563
+ * `rotation` degrees since it. See `PinchEvent`. */
564
+ pinch?: {
565
+ scale: number;
566
+ rotation: number;
567
+ };
559
568
  multiTouch?: {
560
569
  /** Canvas-local CSS pixels — see `MultitouchEvent.centroid`. Neither
561
570
  * `world` nor `screen`: it is the space `zoomAt` anchors in, so passing
@@ -581,6 +590,12 @@ interface InvocationCtx {
581
590
  key: string;
582
591
  repeat: boolean;
583
592
  };
593
+ /**
594
+ * The view the input landed in, as the surface's view resolver assigned it:
595
+ * a view id, or `null` for the surface's own camera. Absent where no
596
+ * surface routed the input — a UI-driven trigger, a bare dispatcher.
597
+ */
598
+ viewId?: string | null;
584
599
  /**
585
600
  * Per-invocation parameters. Populated by `ActionsRegistry.begin()` for
586
601
  * UI-driven ongoing actions (color picker, opacity slider) so handles can
@@ -611,6 +626,16 @@ interface BindingOpts {
611
626
  * values at commit, the invoker can re-call the thunk inside `onEnd`
612
627
  * via `resolveParams(opts?.params)`. */
613
628
  params?: Record<string, unknown> | (() => Record<string, unknown>);
629
+ /**
630
+ * The views this binding is live in, by id, where `null` is the surface's
631
+ * own camera. Omitted means every view.
632
+ *
633
+ * A binding that names the view an input landed in outranks every binding
634
+ * that does not, whatever their scope tiers: naming a view is the most
635
+ * specific thing a binding can say about where it applies. Without that, a
636
+ * drag bound inside a minimap view loses to the active tool's marquee.
637
+ */
638
+ views?: readonly (string | null)[];
614
639
  }
615
640
  /** Resolve `BindingOpts.params` to a concrete record (calling the thunk if
616
641
  * needed). Returns `undefined` for absent params. */
@@ -932,6 +957,26 @@ interface ActionPresentation {
932
957
  * surfaces derive a label from `defaultBinding` via their own
933
958
  * formatter. */
934
959
  shortcut?: string;
960
+ /** The distinct things a parametric action does, one per set of params —
961
+ * `flip` flips horizontally or vertically, `reorder.forward` moves one step
962
+ * or all the way. A bar, menu or palette shows one entry per variant and
963
+ * triggers the action with that variant's `params`. Omitted = the action
964
+ * is one entry. See `actionItems`. */
965
+ variants?: readonly ActionVariant[];
966
+ }
967
+ /**
968
+ * @experimental
969
+ * One entry a parametric action contributes to a bar, menu or palette.
970
+ */
971
+ interface ActionVariant {
972
+ /** Unique within the action; joined to its id to make the entry's key. */
973
+ key: string;
974
+ label: string;
975
+ /** Falls back to the action's own `icon`. */
976
+ icon?: ReactNode | (() => ReactNode);
977
+ /** Passed to `trigger`, and matched against each binding's
978
+ * `opts.params` to find the variant's shortcuts. */
979
+ params: Readonly<Record<string, unknown>>;
935
980
  }
936
981
  /**
937
982
  * @experimental
@@ -1059,6 +1104,27 @@ interface ActionSource {
1059
1104
  list(): readonly Action[];
1060
1105
  }
1061
1106
 
1107
+ /**
1108
+ * @experimental
1109
+ * The entries an action contributes to a bar, menu or palette — one per
1110
+ * variant, or the action itself when it declares none.
1111
+ */
1112
+
1113
+ /** @experimental One renderable entry: an action, optionally with params. */
1114
+ interface ActionItem {
1115
+ /** `action.id`, or `action.id:variant.key` — unique across a registry. */
1116
+ key: string;
1117
+ action: Action;
1118
+ label: string;
1119
+ icon?: ReactNode | (() => ReactNode);
1120
+ /** What to pass to `trigger(action.id, params)`; absent for a plain action. */
1121
+ params?: Readonly<Record<string, unknown>>;
1122
+ }
1123
+ /** @experimental Expand `action` into the entries a surface renders. An
1124
+ * action without an immediate invoker has none: `trigger` cannot start a
1125
+ * drag, so a button for one would do nothing. */
1126
+ declare function actionItems(action: Action): ActionItem[];
1127
+
1062
1128
  /**
1063
1129
  * @experimental
1064
1130
  * Palette-side evaluation of `Action.enabled`. The dispatcher calls the
@@ -1095,7 +1161,7 @@ interface DepRegistry {
1095
1161
  * consumer registering its own dep sources must be inside it. */
1096
1162
  declare function DepRegistryProvider({ children }: {
1097
1163
  children: ReactNode;
1098
- }): react_jsx_runtime.JSX.Element;
1164
+ }): react.JSX.Element;
1099
1165
  /** The dep registry in scope. Throws outside a `<DepRegistryProvider>`. */
1100
1166
  declare function useDepRegistry(): DepRegistry;
1101
1167
  /**
@@ -1168,15 +1234,22 @@ interface Eligibility {
1168
1234
  * all three collapse to `'top'`.
1169
1235
  */
1170
1236
  type OverlayPosition = 'top' | 'before-selection' | 'after-selection';
1237
+ /** Dep sources an entry provides while it is installed, by dep name. */
1238
+ type ContributionDeps = {
1239
+ [K in DepName]?: () => DepSchema[K];
1240
+ };
1171
1241
  /**
1172
1242
  * What routing reads off an entry: when its bindings are live, which they
1173
- * are, and the actions they name.
1243
+ * are, the actions they name, and the deps those actions read.
1174
1244
  */
1175
1245
  interface ContributionRouting {
1176
1246
  id: string;
1177
1247
  eligibility: Eligibility;
1178
1248
  bindings?: GestureBinding[];
1179
1249
  actions?: Action[];
1250
+ /** Dep sources this entry provides while it is installed. A host installs
1251
+ * them for as long as the entry is registered, whatever its eligibility. */
1252
+ deps?: ContributionDeps;
1180
1253
  }
1181
1254
  /**
1182
1255
  * What an entry draws and how a palette shows it. None of it reaches the
@@ -1505,6 +1578,14 @@ interface DispatcherContext {
1505
1578
  * no eligibility filtering.
1506
1579
  */
1507
1580
  getRuleCtx?: () => RuleCtx | undefined;
1581
+ /**
1582
+ * The view this input landed in — a view id, or `null` for the surface's
1583
+ * own camera. Bindings whose `opts.views` omit it are not live, those naming
1584
+ * it outrank the rest, and it reaches the invoker as `InvocationCtx.viewId`
1585
+ * and rules as `RuleCtx.viewId`. Absent means the host routes no views, which
1586
+ * reads as the root.
1587
+ */
1588
+ viewId?: string | null;
1508
1589
  }
1509
1590
  /**
1510
1591
  * Filter `matches` to only those whose backing action has no `eligible`
@@ -1977,10 +2058,11 @@ declare function scopeBindings(entries: Iterable<Contribution>, state: Eligibili
1977
2058
  * entry order decides which of two same-specificity bindings in one scope
1978
2059
  * tier wins, so it is part of the result, not an accident of it.
1979
2060
  *
1980
- * Throws on a duplicate id rather than dropping one: a feature silently
1981
- * losing its bindings is the failure this registry exists to make loud.
2061
+ * Throws on a duplicate id or dep name rather than dropping one: a feature
2062
+ * silently losing its bindings, or reading another feature's dep, is the
2063
+ * failure this registry exists to make loud.
1982
2064
  */
1983
- declare function mergeContributions(...bundles: readonly Contribution[][]): Contribution[];
2065
+ declare function mergeContributions<T extends Contribution<unknown>>(...bundles: readonly (readonly T[])[]): T[];
1984
2066
 
1985
2067
  /**
1986
2068
  * What a tool declares.
@@ -2455,4 +2537,4 @@ interface DepSchema {
2455
2537
  /** Every dep name declared by anything that has merged into {@link DepSchema}. */
2456
2538
  type DepName = keyof DepSchema;
2457
2539
 
2458
- export { INGEST_STRING_MIMES as $, type ActionsRegistry as A, type BindingOpts as B, type Contribution as C, type Dispatcher as D, type Condition as E, type Conflict as F, type ContributionChrome as G, type ContributionRouting as H, DEFAULT_ALLOWED_CAPABILITIES as I, DEFAULT_DEVICE_PROFILE as J, type DebugSink as K, type DepName as L, type DetectedDeviceFacts as M, type DeviceProfile as N, type OverlayPosition as O, type DispatchLogEntry as P, type DispatcherContext as Q, type RuleCtx as R, type ScopedBinding as S, type Tool as T, type DragSample as U, type Eligibility as V, type EligibilityState as W, type GestureBinding as X, type HandleKind as Y, type HitShape as Z, type HotkeyTrigger as _, type AffordanceHit as a, isAnchorOrControl as a$, type ImmediateInvoker as a0, type InvocationCtx as a1, type Invoker as a2, type KeyBinding as a3, type MatchResult as a4, type ModeSwitchLogEntry as a5, type ModifierState as a6, NEVER as a7, type NodeId as a8, type OngoingHandle as a9, type ToolModifiers as aA, type ToolOffhandBindingSpec as aB, type ToolPresentation as aC, type ToolSlot as aD, type TraceLogEntry as aE, type UiOngoingControl as aF, type View as aG, type ViewportToolDef as aH, actionBindings as aI, buildDepsFromRequires as aJ, buildRouteRegistry as aK, buildRuleCtx as aL, buildToolOffhandBindings as aM, clientToCanvas as aN, clientToCanvasRect as aO, createDispatcher as aP, defineTool as aQ, defineViewportTool as aR, deleteScratch as aS, describeRule as aT, dlog as aU, evaluate as aV, evaluateEnabled as aW, filterEligible as aX, findConflicts as aY, getScratch as aZ, isAnchor as a_, type OngoingInvoker as aa, type OngoingOverlay as ab, type OverlayRole as ac, PREDICATE_TARGET as ad, type Point2 as ae, type PointerSession as af, type PointerSessionCallbacks as ag, type PointerSessionCancelReason as ah, type PointerSessionOptions as ai, type RegistryEntry as aj, type ResizeAnchor as ak, type ResolveAllOptions as al, type ResolveOnlyResult as am, type ResolvedCandidate as an, type Rule as ao, type ScratchKey as ap, type ScratchStore as aq, type SelectionApi as ar, type SelectionExtendKey as as, type SelectionMode as at, type Selector as au, TOOL_OFFHAND_ID as av, type ToolBounds as aw, type ToolCtx as ax, type ToolDef as ay, type ToolKeybinding as az, type DepSchema as b, isBody as b0, isControlHandle as b1, isEditableTarget as b2, isEmpty as b3, isResizeHandle as b4, isRotateHandle as b5, isSelectedBody as b6, isUnselectedBody as b7, itemsFromClipboardData as b8, itemsFromDataTransfer as b9, itemsFromFiles as ba, liveScope as bb, makeToolOffhandAction as bc, matchBest as bd, matchSorted as be, matchesKeyBinding as bf, mergeContributions as bg, offhandKeyFor as bh, openPointerSession as bi, pointerGestureId as bj, recordModeSwitch as bk, reportRouteConflicts as bl, resolveDeviceProfile as bm, resolveParams as bn, routeGestureForSpecKind as bo, routesForSpec as bp, scopeBindings as bq, scratchKey as br, setScratch as bs, specificity as bt, targetConsultsAffordance as bu, validateActionDefaultBinding as bv, validateActionId as bw, type Action as c, type AnyToolOf as d, type DepRegistry as e, DepRegistryProvider as f, useDepSource as g, useOptionalDepRegistry as h, ALWAYS as i, type ActionDeps as j, ActionDisabledReason as k, type ActionDispatch as l, type ActionEnabledResult as m, type ActionEntry as n, type ActionPresentation as o, type ActionSource as p, type ActionsProp as q, type AnyTool as r, type BindingScope as s, type BindingSource as t, useDepRegistry as u, type BodyPredicate as v, type BoundGesture as w, type Bounds as x, type BuildRuleCtxArgs as y, COARSE_TARGET_SCALE as z };
2540
+ export { type HandleKind as $, type ActionsRegistry as A, type BindingOpts as B, type Contribution as C, type Dispatcher as D, type BuildRuleCtxArgs as E, COARSE_TARGET_SCALE as F, type Condition as G, type Conflict as H, type ContributionChrome as I, type ContributionDeps as J, type ContributionRouting as K, DEFAULT_ALLOWED_CAPABILITIES as L, DEFAULT_DEVICE_PROFILE as M, type DebugSink as N, type OverlayPosition as O, type DepName as P, type DetectedDeviceFacts as Q, type RuleCtx as R, type ScopedBinding as S, type Tool as T, type DeviceProfile as U, type DispatchLogEntry as V, type DispatcherContext as W, type DragSample as X, type Eligibility as Y, type EligibilityState as Z, type GestureBinding as _, type AffordanceHit as a, filterEligible as a$, type HitShape as a0, type HotkeyTrigger as a1, INGEST_STRING_MIMES as a2, type ImmediateInvoker as a3, type InvocationCtx as a4, type Invoker as a5, type KeyBinding as a6, type MatchResult as a7, type ModeSwitchLogEntry as a8, type ModifierState as a9, type ToolCtx as aA, type ToolDef as aB, type ToolKeybinding as aC, type ToolModifiers as aD, type ToolOffhandBindingSpec as aE, type ToolPresentation as aF, type ToolSlot as aG, type TraceLogEntry as aH, type UiOngoingControl as aI, type View as aJ, type ViewportToolDef as aK, actionBindings as aL, actionItems as aM, buildDepsFromRequires as aN, buildRouteRegistry as aO, buildRuleCtx as aP, buildToolOffhandBindings as aQ, clientToCanvas as aR, clientToCanvasRect as aS, createDispatcher as aT, defineTool as aU, defineViewportTool as aV, deleteScratch as aW, describeRule as aX, dlog as aY, evaluate as aZ, evaluateEnabled as a_, NEVER as aa, type NodeId as ab, type OngoingHandle as ac, type OngoingInvoker as ad, type OngoingOverlay as ae, type OverlayRole as af, PREDICATE_TARGET as ag, type Point2 as ah, type PointerSession as ai, type PointerSessionCallbacks as aj, type PointerSessionCancelReason as ak, type PointerSessionOptions as al, type RegistryEntry as am, type ResizeAnchor as an, type ResolveAllOptions as ao, type ResolveOnlyResult as ap, type ResolvedCandidate as aq, type Rule as ar, type ScratchKey as as, type ScratchStore as at, type SelectionApi as au, type SelectionExtendKey as av, type SelectionMode as aw, type Selector as ax, TOOL_OFFHAND_ID as ay, type ToolBounds as az, type DepSchema as b, findConflicts as b0, getScratch as b1, isAnchor as b2, isAnchorOrControl as b3, isBody as b4, isControlHandle as b5, isEditableTarget as b6, isEmpty as b7, isResizeHandle as b8, isRotateHandle as b9, validateActionId as bA, isSelectedBody as ba, isUnselectedBody as bb, itemsFromClipboardData as bc, itemsFromDataTransfer as bd, itemsFromFiles as be, liveScope as bf, makeToolOffhandAction as bg, matchBest as bh, matchSorted as bi, matchesKeyBinding as bj, mergeContributions as bk, offhandKeyFor as bl, openPointerSession as bm, pointerGestureId as bn, recordModeSwitch as bo, reportRouteConflicts as bp, resolveDeviceProfile as bq, resolveParams as br, routeGestureForSpecKind as bs, routesForSpec as bt, scopeBindings as bu, scratchKey as bv, setScratch as bw, specificity as bx, targetConsultsAffordance as by, validateActionDefaultBinding as bz, type Action as c, type AnyToolOf as d, type DepRegistry as e, DepRegistryProvider as f, useDepSource as g, useOptionalDepRegistry as h, ALWAYS as i, type ActionDeps as j, ActionDisabledReason as k, type ActionDispatch as l, type ActionEnabledResult as m, type ActionEntry as n, type ActionItem as o, type ActionPresentation as p, type ActionSource as q, type ActionVariant as r, type ActionsProp as s, type AnyTool as t, useDepRegistry as u, type BindingScope as v, type BindingSource as w, type BodyPredicate as x, type BoundGesture as y, type Bounds as z };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- export { i as ALWAYS, c as Action, j as ActionDeps, k as ActionDisabledReason, k as ActionDisabledReasonValue, l as ActionDispatch, m as ActionEnabledResult, n as ActionEntry, o as ActionPresentation, p as ActionSource, q as ActionsProp, A as ActionsRegistry, a as AffordanceHit, r as AnyTool, d as AnyToolOf, B as BindingOpts, s as BindingScope, t as BindingSource, v as BodyPredicate, w as BoundGesture, x as Bounds, y as BuildRuleCtxArgs, z as COARSE_TARGET_SCALE, E as Condition, F as Conflict, C as Contribution, G as ContributionChrome, H as ContributionRouting, I as DEFAULT_ALLOWED_CAPABILITIES, J as DEFAULT_DEVICE_PROFILE, K as DebugSink, L as DepName, b as DepSchema, M as DetectedDeviceFacts, N as DeviceProfile, P as DispatchLogEntry, D as Dispatcher, Q as DispatcherContext, U as DragSample, V as Eligibility, W as EligibilityState, X as GestureBinding, Y as HandleKind, Z as HitShape, _ as HotkeyTrigger, $ as INGEST_STRING_MIMES, a0 as ImmediateInvoker, a1 as InvocationCtx, a2 as Invoker, a3 as KeyBinding, a4 as MatchResult, a5 as ModeSwitchLogEntry, a6 as ModifierState, a7 as NEVER, a8 as NodeId, a9 as OngoingHandle, aa as OngoingInvoker, ab as OngoingOverlay, O as OverlayPosition, ac as OverlayRole, ad as PREDICATE_TARGET, ae as Point2, af as PointerSession, ag as PointerSessionCallbacks, ah as PointerSessionCancelReason, ai as PointerSessionOptions, aj as RegistryEntry, ak as ResizeAnchor, al as ResolveAllOptions, am as ResolveOnlyResult, an as ResolvedCandidate, ao as Rule, R as RuleCtx, S as ScopedBinding, ap as ScratchKey, aq as ScratchStore, ar as SelectionApi, as as SelectionExtendKey, at as SelectionMode, au as Selector, av as TOOL_OFFHAND_ID, T as Tool, aw as ToolBounds, ax as ToolCtx, ay as ToolDef, az as ToolKeybinding, aA as ToolModifiers, aB as ToolOffhandBindingSpec, aC as ToolPresentation, aD as ToolSlot, aE as TraceLogEntry, aF as UiOngoingControl, aG as View, aH as ViewportToolDef, aI as actionBindings, aJ as buildDepsFromRequires, aK as buildRouteRegistry, aL as buildRuleCtx, aM as buildToolOffhandBindings, aN as clientToCanvas, aO as clientToCanvasRect, aP as createDispatcher, aQ as defineTool, aR as defineViewportTool, aS as deleteScratch, aT as describeRule, aU as dlog, aV as evaluate, aW as evaluateEnabled, aX as filterEligible, aY as findConflicts, aZ as getScratch, a_ as isAnchor, a$ as isAnchorOrControl, b0 as isBody, b1 as isControlHandle, b2 as isEditableTarget, b3 as isEmpty, b4 as isResizeHandle, b5 as isRotateHandle, b6 as isSelectedBody, b7 as isUnselectedBody, b8 as itemsFromClipboardData, b9 as itemsFromDataTransfer, ba as itemsFromFiles, bb as liveScope, bc as makeToolOffhandAction, bd as matchBest, be as matchSorted, bf as matchesKeyBinding, bg as mergeContributions, bh as offhandKeyFor, bi as openPointerSession, bj as pointerGestureId, bk as recordModeSwitch, bl as reportRouteConflicts, bm as resolveDeviceProfile, bn as resolveParams, bo as routeGestureForSpecKind, bp as routesForSpec, bq as scopeBindings, br as scratchKey, bs as setScratch, bt as specificity, bu as targetConsultsAffordance, bv as validateActionDefaultBinding, bw as validateActionId } from './index-D4hKg8yu.js';
1
+ export { i as ALWAYS, c as Action, j as ActionDeps, k as ActionDisabledReason, k as ActionDisabledReasonValue, l as ActionDispatch, m as ActionEnabledResult, n as ActionEntry, o as ActionItem, p as ActionPresentation, q as ActionSource, r as ActionVariant, s as ActionsProp, A as ActionsRegistry, a as AffordanceHit, t as AnyTool, d as AnyToolOf, B as BindingOpts, v as BindingScope, w as BindingSource, x as BodyPredicate, y as BoundGesture, z as Bounds, E as BuildRuleCtxArgs, F as COARSE_TARGET_SCALE, G as Condition, H as Conflict, C as Contribution, I as ContributionChrome, J as ContributionDeps, K as ContributionRouting, L as DEFAULT_ALLOWED_CAPABILITIES, M as DEFAULT_DEVICE_PROFILE, N as DebugSink, P as DepName, b as DepSchema, Q as DetectedDeviceFacts, U as DeviceProfile, V as DispatchLogEntry, D as Dispatcher, W as DispatcherContext, X as DragSample, Y as Eligibility, Z as EligibilityState, _ as GestureBinding, $ as HandleKind, a0 as HitShape, a1 as HotkeyTrigger, a2 as INGEST_STRING_MIMES, a3 as ImmediateInvoker, a4 as InvocationCtx, a5 as Invoker, a6 as KeyBinding, a7 as MatchResult, a8 as ModeSwitchLogEntry, a9 as ModifierState, aa as NEVER, ab as NodeId, ac as OngoingHandle, ad as OngoingInvoker, ae as OngoingOverlay, O as OverlayPosition, af as OverlayRole, ag as PREDICATE_TARGET, ah as Point2, ai as PointerSession, aj as PointerSessionCallbacks, ak as PointerSessionCancelReason, al as PointerSessionOptions, am as RegistryEntry, an as ResizeAnchor, ao as ResolveAllOptions, ap as ResolveOnlyResult, aq as ResolvedCandidate, ar as Rule, R as RuleCtx, S as ScopedBinding, as as ScratchKey, at as ScratchStore, au as SelectionApi, av as SelectionExtendKey, aw as SelectionMode, ax as Selector, ay as TOOL_OFFHAND_ID, T as Tool, az as ToolBounds, aA as ToolCtx, aB as ToolDef, aC as ToolKeybinding, aD as ToolModifiers, aE as ToolOffhandBindingSpec, aF as ToolPresentation, aG as ToolSlot, aH as TraceLogEntry, aI as UiOngoingControl, aJ as View, aK as ViewportToolDef, aL as actionBindings, aM as actionItems, aN as buildDepsFromRequires, aO as buildRouteRegistry, aP as buildRuleCtx, aQ as buildToolOffhandBindings, aR as clientToCanvas, aS as clientToCanvasRect, aT as createDispatcher, aU as defineTool, aV as defineViewportTool, aW as deleteScratch, aX as describeRule, aY as dlog, aZ as evaluate, a_ as evaluateEnabled, a$ as filterEligible, b0 as findConflicts, b1 as getScratch, b2 as isAnchor, b3 as isAnchorOrControl, b4 as isBody, b5 as isControlHandle, b6 as isEditableTarget, b7 as isEmpty, b8 as isResizeHandle, b9 as isRotateHandle, ba as isSelectedBody, bb as isUnselectedBody, bc as itemsFromClipboardData, bd as itemsFromDataTransfer, be as itemsFromFiles, bf as liveScope, bg as makeToolOffhandAction, bh as matchBest, bi as matchSorted, bj as matchesKeyBinding, bk as mergeContributions, bl as offhandKeyFor, bm as openPointerSession, bn as pointerGestureId, bo as recordModeSwitch, bp as reportRouteConflicts, bq as resolveDeviceProfile, br as resolveParams, bs as routeGestureForSpecKind, bt as routesForSpec, bu as scopeBindings, bv as scratchKey, bw as setScratch, bx as specificity, by as targetConsultsAffordance, bz as validateActionDefaultBinding, bA as validateActionId } from './index-CKmbNpwl.js';
2
2
  export { ChannelRef, DescribeRouteOptions, GESTURE_DESCRIPTORS, GestureArgSpec, GestureDescriptor, GestureName, IngestItem, InputEvent, ModRequirement, ModifierKey, ParsedModifiers, ParsedRoute, PhaseAtom, PhaseContext, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, RouteDescriptionPart, RouteFieldName, RouteTermLabel, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatPhaseAtom, formatRoute, getGestureDescriptor, isKnownGestureName, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, parseRoute } from '@weasel-js/gestures';
3
3
  import '@weasel-js/modes';
4
4
  import 'react';
5
5
  import '@weasel-js/cursor';
6
- import 'react/jsx-runtime';
7
6
  import '@weasel-js/history';
package/dist/index.js CHANGED
@@ -1,7 +1,22 @@
1
- import { isDev } from './chunk-H5AGTB2J.js';
2
- export { ALWAYS, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, GESTURE_DESCRIPTORS, INGEST_STRING_MIMES, NEVER, PREDICATE_TARGET, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, TOOL_OFFHAND_ID, actionBindings, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, canonicalModifiers, clientToCanvas, clientToCanvasRect, collapseShiftPairs, createDispatcher, describeRoute, describeRouteParts, describeRule, dlog, evaluate, filterEligible, findConflicts, formatPhaseAtom, formatRoute, getGestureDescriptor, isEditableTarget, isKnownGestureName, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, liveScope, makeToolOffhandAction, matchBest, matchKey, matchModifiers, matchPhase, matchSorted, matchSpec, matchTarget, matchesKeyBinding, offhandKeyFor, openPointerSession, parseRoute, pointerGestureId, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveParams, routeGestureForSpecKind, routesForSpec, scopeBindings, specificity, targetConsultsAffordance, validateActionDefaultBinding, validateActionId } from './chunk-H5AGTB2J.js';
1
+ import { isDev } from './chunk-VEC3ACV5.js';
2
+ export { ALWAYS, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, GESTURE_DESCRIPTORS, INGEST_STRING_MIMES, NEVER, PREDICATE_TARGET, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, TOOL_OFFHAND_ID, actionBindings, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, canonicalModifiers, clientToCanvas, clientToCanvasRect, collapseShiftPairs, createDispatcher, describeRoute, describeRouteParts, describeRule, dlog, evaluate, filterEligible, findConflicts, formatPhaseAtom, formatRoute, getGestureDescriptor, isEditableTarget, isKnownGestureName, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, liveScope, makeToolOffhandAction, matchBest, matchKey, matchModifiers, matchPhase, matchSorted, matchSpec, matchTarget, matchesKeyBinding, offhandKeyFor, openPointerSession, parseRoute, pointerGestureId, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveParams, routeGestureForSpecKind, routesForSpec, scopeBindings, specificity, targetConsultsAffordance, validateActionDefaultBinding, validateActionId } from './chunk-VEC3ACV5.js';
3
3
  import { RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from '@weasel-js/gestures';
4
4
 
5
+ // src/interactions/actions/actionItems.ts
6
+ function actionItems(action) {
7
+ if (action.invoker?.timing !== "immediate") return [];
8
+ if (!action.variants?.length) {
9
+ return [{ key: action.id, action, label: action.label, icon: action.icon }];
10
+ }
11
+ return action.variants.map((v) => ({
12
+ key: `${action.id}:${v.key}`,
13
+ action,
14
+ label: v.label,
15
+ icon: v.icon ?? action.icon,
16
+ params: v.params
17
+ }));
18
+ }
19
+
5
20
  // src/interactions/actions/action.ts
6
21
  var ActionDisabledReason = {
7
22
  SelectionRequired: "selection-required",
@@ -77,6 +92,7 @@ var isAnchorOrControl = (target) => isAnchor(target) || isControlHandle(target);
77
92
  function mergeContributions(...bundles) {
78
93
  const out = [];
79
94
  const seen = /* @__PURE__ */ new Set();
95
+ const depOwner = /* @__PURE__ */ new Map();
80
96
  for (const bundle of bundles) {
81
97
  for (const entry of bundle) {
82
98
  if (seen.has(entry.id)) {
@@ -85,6 +101,15 @@ function mergeContributions(...bundles) {
85
101
  );
86
102
  }
87
103
  seen.add(entry.id);
104
+ for (const dep of Object.keys(entry.deps ?? {})) {
105
+ const owner = depOwner.get(dep);
106
+ if (owner !== void 0) {
107
+ throw new Error(
108
+ `mergeContributions: dep "${dep}" is provided by both "${owner}" and "${entry.id}". One registry answers each dep name; drop one source.`
109
+ );
110
+ }
111
+ depOwner.set(dep, entry.id);
112
+ }
88
113
  out.push(entry);
89
114
  }
90
115
  }
@@ -155,6 +180,6 @@ function deleteScratch(store, key) {
155
180
  return false;
156
181
  }
157
182
 
158
- export { ActionDisabledReason, defineTool, defineViewportTool, deleteScratch, evaluateEnabled, getScratch, isAnchor, isAnchorOrControl, isBody, isControlHandle, isEmpty, isResizeHandle, isRotateHandle, isSelectedBody, isUnselectedBody, mergeContributions, scratchKey, setScratch };
183
+ export { ActionDisabledReason, actionItems, defineTool, defineViewportTool, deleteScratch, evaluateEnabled, getScratch, isAnchor, isAnchorOrControl, isBody, isControlHandle, isEmpty, isResizeHandle, isRotateHandle, isSelectedBody, isUnselectedBody, mergeContributions, scratchKey, setScratch };
159
184
  //# sourceMappingURL=index.js.map
160
185
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/interactions/actions/action.ts","../src/interactions/actions/actionEnabled.ts","../src/interactions/dispatcher/predicates.ts","../src/contributions/merge.ts","../src/tools/defineTool.ts","../src/tools/defineViewportTool.ts","../src/interactions/scratchKey.ts"],"names":["RESERVED_ID_PREFIXES","RESERVED_ID_NAMES"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @experimental\n * The `Action` descriptor, split along the line the dispatcher reads.\n * `ActionDispatch` is everything routing consults to decide whether an action\n * runs and what happens when it does; `ActionPresentation` is what a palette,\n * menu or toolbar renders. `Action` is both.\n */\nimport type { ReactNode } from 'react';\nimport type { CursorSpec } from '@weasel-js/cursor';\nimport type { ActionDeps, Invoker } from './invoker';\nimport type { BindingSource } from './binding';\nimport type { DepName } from '../../index';\n\n/**\n * @experimental\n * Closed enum of reasons an action might report itself as disabled. The\n * consumer (palette, menu, etc.) maps these symbolic values to display\n * strings via its own label map.\n */\nexport const ActionDisabledReason = {\n SelectionRequired: 'selection-required',\n SceneEmpty: 'scene-empty',\n NotApplicable: 'not-applicable',\n /** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */\n PredicateThrew: 'predicate-threw',\n} as const;\n/** Why an action is unavailable right now. */\nexport type ActionDisabledReason =\n (typeof ActionDisabledReason)[keyof typeof ActionDisabledReason];\n\n/**\n * @experimental\n * What a palette, menu or toolbar renders for an action. No part of this is\n * read during dispatch.\n */\nexport interface ActionPresentation {\n label: string;\n /** Inline-SVG icon for palette / toolbar surfaces. Mirrors\n * `ToolPresentation.icon` so a generic `<ActionBar>` can render from\n * action metadata the same way `<ToolPalette>` renders from tool\n * metadata. May be a static `ReactNode` or a function (rare; useful\n * for state-aware icons like a \"lock\" toggle). */\n icon?: ReactNode | (() => ReactNode);\n /** Grouping key for palette/menu surfaces. Free-form string; the kit\n * ships defaults for `'align'` (six edges/centers), `'distribute'`\n * (two axes), and recommends `'pathfinder'` for boolean ops. */\n group?: string;\n /** Display override for the keyboard shortcut. When omitted, palette\n * surfaces derive a label from `defaultBinding` via their own\n * formatter. */\n shortcut?: string;\n}\n\n/**\n * @experimental\n * What routing consults: the bindings that reach the action, the deps its\n * invoker reads, the gates it passes, and the cursors it claims.\n */\nexport interface ActionDispatch extends BindingSource {\n /** Names of the deps this action's invoker reads (keys of `DepSchema`).\n * The dispatcher (and `trigger`, when `requires` is present) resolves\n * each name against the `DepRegistry` at invocation time and passes the\n * resulting bag to the invoker. Dev builds warn when the invoker reads a\n * dep it didn't declare here — see `buildDepsFromRequires`. */\n requires?: readonly DepName[];\n /** Pluggable invocation strategy. The gesture dispatcher routes matched\n * bindings through `invoker.start` / `invoker.run` depending on timing.\n * All kit-standard descriptors ship one; consumer-supplied actions\n * without an invoker can still register but won't be triggered. */\n invoker?: Invoker;\n /** When set to `'hotkey'`, this action's `defaultBinding` rides the hotkey\n * `BindingScope` instead of the ambient scope — meaning it beats any\n * active-tool binding on the same input shape. Use for tool-switch\n * shortcuts and global held-key triggers. Default: ambient. */\n scope?: 'hotkey';\n /**\n * @experimental\n * Optional predicate the command palette consults when rendering. Return\n * `true` when the action is currently triggerable. Return a reason string\n * (e.g. `'Selection required'`) when disabled — the palette greys out\n * the row, skips it in keyboard nav, ignores clicks, and shows the\n * reason next to the label. Keystroke dispatch (the registered binding)\n * is unaffected; the action's own `run` should self-guard.\n *\n * **Contract:** must be pure (no side effects), fast (< 4ms in dev), and\n * must not throw. If a call throws or exceeds the budget in dev mode,\n * `evaluateEnabled` logs a one-time warning per action id; throws are\n * caught and treated as disabled with reason `'(predicate threw)'`.\n *\n * Snapshot-on-open semantics: the palette evaluates `enabled` once when\n * opened and does NOT re-evaluate on selection changes while open. Live\n * reactive updates are deferred — palette is short-lived.\n *\n * The reason set is a closed enum — to add a new reason, edit\n * `ActionDisabledReason` and the consumer's display map.\n *\n * The optional `deps` argument is the same bag passed to\n * `ImmediateInvoker.run`; callers (`evaluateEnabled` / the ActionBar) may\n * synthesize it from the surrounding `DepRegistry` so predicates can\n * inspect selection / scene / etc. Predicates that don't need deps just\n * ignore the arg.\n *\n * `at` is the world point of the event being routed, when it has one — a\n * click, a press, a long-press. An action that only applies somewhere\n * (on a path segment, say) refuses other points here, so the dispatcher\n * falls through to the next binding and hover prediction shows the\n * action's `cursor` only where it would act. Palettes and keystrokes\n * pass no point; treat absent as \"anywhere\".\n */\n enabled?: (deps?: ActionDeps, at?: { x: number; y: number }) => true | ActionDisabledReason;\n /**\n * Declarative eligibility rule, evaluated against the current\n * `RuleCtx` by the dispatcher before invoking `start()`. Omitted =\n * always eligible.\n *\n * Accepts either a fluent `Condition` (callable with `.rule`) or a\n * raw `Rule` tree; the dispatcher normalizes via `.rule` unwrap.\n *\n * Prefer `capability:`-based rules (e.g. `{ capability: 'transforms-selection' }`)\n * over `mode:` rules — capability rules survive new modes being added\n * that allow the same capability.\n */\n eligible?:\n | import('../../eligibility').Rule\n | import('../../eligibility').Condition;\n /**\n * CSS cursor shown while the pointer hovers a spot where this action\n * would win the drag — or, when the drag's winner declares no cursor,\n * the click. The hover-cursor pump (in `useGestureDispatcher`) runs\n * `Dispatcher.resolveOnly` on each idle pointermove — the same match\n * walk a real press takes — and applies the winning action's `cursor`,\n * so the hint and the actual target stay in sync by construction. Omitted = no override (the active tool's\n * `Tool.cursor` shows). Affordance hits are resolved earlier in the\n * pump via `AffordanceRegion.cursor` and never reach this field.\n *\n * Static value only. Prediction runs `enabled()` but cannot run the\n * invoker, so an action that matches yet bails at `start()` (empty\n * handle) may still show its cursor — keep `enabled` accurate for\n * actions that declare one.\n */\n cursor?: CursorSpec;\n /**\n * CSS cursor shown while THIS action's ongoing handle is in flight —\n * grabbing while panning, `move` while dragging a selection, `crosshair`\n * while pulling a marquee.\n *\n * Separate from `cursor` because the two answer different questions:\n * `cursor` is a prediction (\"a drag from here would pan\"), this is a state\n * (\"you are panning\"). An action can declare either, both, or neither;\n * with only `cursor` set, the hover hint holds for the duration of the\n * gesture.\n *\n * This is where mid-gesture cursors live now. They used to come from the\n * tool side — `ViewportToolDef.engaged.cursor` for a phase-gated string,\n * or a function-form `Tool.cursor` reading the gesture scratch out of the\n * tool-routing dispatcher. Both belonged to a pipeline whose whole job was\n * being taken over by bindings, and neither could describe a cursor for an\n * action a tool doesn't own.\n */\n activeCursor?: CursorSpec;\n}\n\n/**\n * @experimental\n * Single registered action. v1: one binding per action.\n */\nexport interface Action extends ActionDispatch, ActionPresentation {}\n\n/**\n * @experimental\n * The whole of an actions registry the dispatcher reads. It walks the list\n * once per event to build its actionId lookup and never mutates it, so the\n * routing side depends on this rather than on `ActionsRegistry`, whose\n * register / mute / trigger surface is the React provider's.\n */\nexport interface ActionSource {\n list(): readonly Action[];\n}\n","/**\n * @experimental\n * Palette-side evaluation of `Action.enabled`. The dispatcher calls the\n * predicate raw as a gate; this is the surface that also enforces the\n * contract the predicate's JSDoc states — no throwing, under 4ms in dev.\n */\nimport type { ActionDeps } from './invoker';\nimport { ActionDisabledReason, type Action } from './action';\nimport { isDev } from '../../devFlag';\n\n/**\n * @experimental\n * Result of evaluating an Action's `enabled` predicate.\n */\nexport interface ActionEnabledResult {\n enabled: boolean;\n reason?: ActionDisabledReason;\n}\n\nconst enabledSlowWarned = new Set<string>();\nconst enabledThrewWarned = new Set<string>();\nconst ENABLED_BUDGET_MS = 4;\n/**\n * @experimental\n * Safely evaluate an action's `enabled` predicate. Returns `{enabled: true}`\n * when no predicate is supplied. Catches throws and treats them as disabled\n * with reason `'(predicate threw)'`. In dev mode, warns once per action id\n * when a single call exceeds 4ms (single frame at 240fps — generous; real\n * predicates should be sub-millisecond).\n */\nexport function evaluateEnabled(\n action: Action,\n deps?: ActionDeps,\n): ActionEnabledResult {\n if (!action.enabled) return { enabled: true };\n const dev = isDev();\n const now = (typeof performance !== 'undefined' && typeof performance.now === 'function')\n ? () => performance.now()\n : () => Date.now();\n const start = dev ? now() : 0;\n try {\n const result = action.enabled(deps);\n if (dev) {\n const elapsed = now() - start;\n if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {\n enabledSlowWarned.add(action.id);\n console.warn(\n `weasel actions: enabled() for action \"${action.id}\" took ${elapsed.toFixed(2)}ms ` +\n `(budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast — see Action.enabled JSDoc.`,\n );\n }\n }\n if (result === true) return { enabled: true };\n return { enabled: false, reason: result };\n } catch (e) {\n if (dev && !enabledThrewWarned.has(action.id)) {\n enabledThrewWarned.add(action.id);\n console.warn(\n `weasel actions: enabled() for action \"${action.id}\" threw; treating as disabled. ` +\n `The predicate must not throw — see Action.enabled JSDoc.`,\n e,\n );\n }\n return { enabled: false, reason: ActionDisabledReason.PredicateThrew };\n }\n}\n","/**\n * Named predicates for `defaultBinding.target.kindOf`. Each matches the\n * existing `kindOf` signature `(target: unknown, bodyTarget?: string) => boolean`\n * so they drop into action descriptors without a wrapper:\n *\n * target: { kindOf: isAnchor }\n *\n * Single source of truth for \"what does this affordance kind mean\" — adding\n * a new predicate here is what enables every action that needs the same\n * shape to read it. Drift prevention.\n */\n\n// ─── Body-class predicates (read the second arg) ───────────────────────\n\n/** A predicate that resolves from `bodyTarget` alone. The exclusive-claim\n * filter reads `readsAffordance`: without it a body predicate looks like it\n * consults the hit, and survives a claim that should have barred it. */\nexport type BodyPredicate = ((target: unknown, bodyTarget?: string) => boolean)\n & { readsAffordance: false };\n\nfunction bodyPredicate(\n fn: (target: unknown, bodyTarget?: string) => boolean,\n): BodyPredicate {\n return Object.assign(fn, { readsAffordance: false as const });\n}\n\n/** Matches any body hit — selected or unselected node body. */\nexport const isBody: BodyPredicate = bodyPredicate((_target, bodyTarget) =>\n bodyTarget === 'selected-body' || bodyTarget === 'unselected-body');\n\n/** Body hit specifically already in the selection. */\nexport const isSelectedBody: BodyPredicate = bodyPredicate((_target, bodyTarget) =>\n bodyTarget === 'selected-body');\n\n/** Body hit specifically NOT in the selection. */\nexport const isUnselectedBody: BodyPredicate = bodyPredicate((_target, bodyTarget) =>\n bodyTarget === 'unselected-body');\n\n/** Empty-canvas hit — no node beneath cursor. */\nexport const isEmpty: BodyPredicate = bodyPredicate((_target, bodyTarget) =>\n bodyTarget === 'empty');\n\n// ─── Affordance-kind predicates (read the first arg) ───────────────────\n\nfunction kindOf(target: unknown): string | undefined {\n if (typeof target !== 'object' || target === null) return undefined;\n const k = (target as { kind?: unknown }).kind;\n return typeof k === 'string' ? k : undefined;\n}\n\n/** Any corner resize handle — top-left/top-right/bottom-left/bottom-right. */\nexport const isResizeHandle = (target: unknown): boolean => {\n const k = kindOf(target);\n return k !== undefined && k.startsWith('handle:');\n};\n\n/** The rotate-around-center handle above the bounding box. */\nexport const isRotateHandle = (target: unknown): boolean =>\n kindOf(target) === 'rotate-handle';\n\n/** Any path anchor (the on-curve dot). */\nexport const isAnchor = (target: unknown): boolean => {\n const k = kindOf(target);\n return k !== undefined && /^anchor:\\d+$/.test(k);\n};\n\n/** Either control handle (controlIn or controlOut). */\nexport const isControlHandle = (target: unknown): boolean => {\n const k = kindOf(target);\n return k !== undefined && /^(controlIn|controlOut):\\d+$/.test(k);\n};\n\n/** Any of the three anchor-editing affordances (anchor + both controls). */\nexport const isAnchorOrControl = (target: unknown): boolean =>\n isAnchor(target) || isControlHandle(target);\n","import type { Contribution } from './types';\n\n/**\n * Concatenate contribution bundles into one registry, preserving order —\n * entry order decides which of two same-specificity bindings in one scope\n * tier wins, so it is part of the result, not an accident of it.\n *\n * Throws on a duplicate id rather than dropping one: a feature silently\n * losing its bindings is the failure this registry exists to make loud.\n */\nexport function mergeContributions(...bundles: readonly Contribution[][]): Contribution[] {\n const out: Contribution[] = [];\n const seen = new Set<string>();\n for (const bundle of bundles) {\n for (const entry of bundle) {\n if (seen.has(entry.id)) {\n throw new Error(\n `mergeContributions: duplicate contribution id \"${entry.id}\". `\n + `Two bundles registered the same id; rename one or merge them by hand.`,\n );\n }\n seen.add(entry.id);\n out.push(entry);\n }\n }\n return out;\n}\n","// src/tools/defineTool.ts\nimport type { Tool, ToolCtx } from './types';\nimport type { ToolDef } from './routeTypes';\nimport { RESERVED_ID_NAMES, RESERVED_ID_PREFIXES } from '@weasel-js/gestures';\nimport type { CursorSpec } from '@weasel-js/cursor';\n\n/** Validate that `id` is usable as a tool / channel id in the route\n * grammar. Rejects ids that start with a reserved sigil (would shadow\n * future grammar extensions) and ids that collide with phase keywords\n * (would parse as the bare-phase shorthand). */\nfunction validateId(id: string, kind: 'tool' | 'action'): void {\n if (id.length === 0) {\n throw new Error(`weasel: ${kind} id may not be empty`);\n }\n if (RESERVED_ID_PREFIXES.has(id[0]!)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" starts with reserved sigil \"${id[0]}\" ` +\n `(reserved set: ${[...RESERVED_ID_PREFIXES].join(' ')})`,\n );\n }\n if (RESERVED_ID_NAMES.has(id)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" collides with a reserved phase keyword ` +\n `(reserved: ${[...RESERVED_ID_NAMES].join(', ')})`,\n );\n }\n}\n\n/**\n * Build a `Tool<TScratch>` from a declarative `ToolDef<TScratch>`.\n *\n * This used to be a translator: `ToolDef` carried `initial` / `engaged` phase\n * tables of hit-keyed route handlers, and `defineTool` compiled them into the\n * imperative `pointer` / `drag` / `keyboard` / `wheel` handlers the\n * tool-routing dispatcher called. Both ends of that are gone — tools declare\n * `bindings` and the gesture dispatcher routes them — so what remains is\n * identity plumbing plus the id check and the cursor resolver.\n *\n * It stays a function rather than becoming a spread because the id validation\n * and the `initScratch` / `cursor` defaults are worth applying uniformly, and\n * because `Tool.def` gives reflection a handle on the authored form.\n */\nexport function defineTool<TScratch = void, TOverlay = unknown>(\n def: ToolDef<TScratch, TOverlay>,\n): Tool<TScratch, TOverlay> {\n validateId(def.id, 'tool');\n\n // Normalize `CursorSpec | ((ctx) => CursorSpec)` to the function form so\n // callers have one shape to deal with. Returns '' (not undefined) so the\n // signature satisfies `Tool.cursor: (ctx) => CursorSpec`.\n const resolveCursor = (ctx: ToolCtx<TScratch>): CursorSpec => {\n if (def.cursor == null) return '';\n return typeof def.cursor === 'function' ? def.cursor(ctx) : def.cursor;\n };\n\n return {\n id: def.id,\n eligibility: {\n focus: true,\n capabilities: def.capabilities,\n ...(def.hotkey ? { offhand: def.hotkey } : {}),\n },\n actions: def.actions,\n def,\n presentation: def.presentation as Tool<TScratch>['presentation'],\n keybinding: def.keybinding,\n onActivate: def.onActivate,\n onDeactivate: def.onDeactivate,\n initScratch: def.initScratch ?? (() => null as unknown as TScratch),\n cursor: resolveCursor,\n bindings: def.bindings,\n overlay: def.overlay,\n overlayPosition: def.overlayPosition,\n };\n}\n","import type { Tool } from './types';\nimport type { ViewportToolDef } from './routeTypes';\nimport { defineTool } from './defineTool';\n\n/**\n * Define a tool that acts on the viewport rather than the scene.\n *\n * This used to do real work: `ViewportPhaseDef` was a narrowed `PhaseDef`\n * (no click routes, drag restricted to the function form), and the factory\n * lifted it back to the permissive shape before handing it to `defineTool`.\n * With phase tables gone there is no shape left to narrow — a viewport tool\n * declares `bindings` like any other, pointing at `viewport.*` actions.\n *\n * It survives as an authoring signal. `defineViewportTool` at the top of a\n * hook says \"this tool moves the camera, not the drawing\", which is worth\n * more than the type gymnastics it replaced.\n */\nexport function defineViewportTool<TScratch = void, TOverlay = unknown>(\n def: ViewportToolDef<TScratch, TOverlay>,\n): Tool<TScratch, TOverlay> {\n return defineTool<TScratch, TOverlay>(def);\n}\n","/**\n * Typed keys for the gesture/behavior scratch store.\n *\n * Gestures expose `ctx.scratch: Record<string, unknown>` as a per-gesture\n * mutable bag that behaviors use to stash state across `onDown` / `onMove`\n * / `onEnd` calls. The store is `unknown`-valued because behaviors are\n * tool-agnostic — a single behavior may run under different tools with\n * different scratch shapes. Without a typing convention every read site\n * has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the\n * shape can silently break readers with no compile-time signal.\n *\n * `ScratchKey<T>` is a phantom-typed string that carries its payload type\n * via a branded interface. `getScratch` and `setScratch` use the brand to\n * narrow / constrain at the typing layer; at runtime the key is just its\n * underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level (or behavior-level) constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n *\n * // Writer (a tool's onMove):\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n *\n * // Reader (a behavior's onEnd):\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\n * // ^? readonly { x: number; y: number }[] | undefined\n * ```\n */\n\n/** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom\n * field carries the value type without occupying any runtime memory. */\nexport interface ScratchKey<T> {\n readonly name: string;\n /** Phantom — never read at runtime. Only present so TypeScript can\n * recover `T` at use sites. */\n readonly __scratchKeyPayload?: (_: T) => T;\n}\n\n/** Compatible scratch store shape. The kit's gesture context's `scratch`\n * field already satisfies this; consumers don't need to construct one. */\nexport type ScratchStore = Record<string, unknown>;\n\n/**\n * Make a typed scratch key. Use module-level constants so the same key\n * is shared by writer and reader.\n *\n * Namespace the name by behavior or feature to avoid collisions\n * (`'behavior.field'` is a good convention). Two keys with the same\n * `name` refer to the same slot at runtime — the type parameter is a\n * compile-time contract, not a uniqueness guarantee.\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n"]}
1
+ {"version":3,"sources":["../src/interactions/actions/actionItems.ts","../src/interactions/actions/action.ts","../src/interactions/actions/actionEnabled.ts","../src/interactions/dispatcher/predicates.ts","../src/contributions/merge.ts","../src/tools/defineTool.ts","../src/tools/defineViewportTool.ts","../src/interactions/scratchKey.ts"],"names":["RESERVED_ID_PREFIXES","RESERVED_ID_NAMES"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @experimental\n * The entries an action contributes to a bar, menu or palette — one per\n * variant, or the action itself when it declares none.\n */\nimport type { ReactNode } from 'react';\nimport type { Action } from './action';\n\n/** @experimental One renderable entry: an action, optionally with params. */\nexport interface ActionItem {\n /** `action.id`, or `action.id:variant.key` — unique across a registry. */\n key: string;\n action: Action;\n label: string;\n icon?: ReactNode | (() => ReactNode);\n /** What to pass to `trigger(action.id, params)`; absent for a plain action. */\n params?: Readonly<Record<string, unknown>>;\n}\n\n/** @experimental Expand `action` into the entries a surface renders. An\n * action without an immediate invoker has none: `trigger` cannot start a\n * drag, so a button for one would do nothing. */\nexport function actionItems(action: Action): ActionItem[] {\n if (action.invoker?.timing !== 'immediate') return [];\n if (!action.variants?.length) {\n return [{ key: action.id, action, label: action.label, icon: action.icon }];\n }\n return action.variants.map((v) => ({\n key: `${action.id}:${v.key}`,\n action,\n label: v.label,\n icon: v.icon ?? action.icon,\n params: v.params,\n }));\n}\n","/**\n * @experimental\n * The `Action` descriptor, split along the line the dispatcher reads.\n * `ActionDispatch` is everything routing consults to decide whether an action\n * runs and what happens when it does; `ActionPresentation` is what a palette,\n * menu or toolbar renders. `Action` is both.\n */\nimport type { ReactNode } from 'react';\nimport type { CursorSpec } from '@weasel-js/cursor';\nimport type { ActionDeps, Invoker } from './invoker';\nimport type { BindingSource } from './binding';\nimport type { DepName } from '../../index';\n\n/**\n * @experimental\n * Closed enum of reasons an action might report itself as disabled. The\n * consumer (palette, menu, etc.) maps these symbolic values to display\n * strings via its own label map.\n */\nexport const ActionDisabledReason = {\n SelectionRequired: 'selection-required',\n SceneEmpty: 'scene-empty',\n NotApplicable: 'not-applicable',\n /** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */\n PredicateThrew: 'predicate-threw',\n} as const;\n/** Why an action is unavailable right now. */\nexport type ActionDisabledReason =\n (typeof ActionDisabledReason)[keyof typeof ActionDisabledReason];\n\n/**\n * @experimental\n * What a palette, menu or toolbar renders for an action. No part of this is\n * read during dispatch.\n */\nexport interface ActionPresentation {\n label: string;\n /** Inline-SVG icon for palette / toolbar surfaces. Mirrors\n * `ToolPresentation.icon` so a generic `<ActionBar>` can render from\n * action metadata the same way `<ToolPalette>` renders from tool\n * metadata. May be a static `ReactNode` or a function (rare; useful\n * for state-aware icons like a \"lock\" toggle). */\n icon?: ReactNode | (() => ReactNode);\n /** Grouping key for palette/menu surfaces. Free-form string; the kit\n * ships defaults for `'align'` (six edges/centers), `'distribute'`\n * (two axes), and recommends `'pathfinder'` for boolean ops. */\n group?: string;\n /** Display override for the keyboard shortcut. When omitted, palette\n * surfaces derive a label from `defaultBinding` via their own\n * formatter. */\n shortcut?: string;\n /** The distinct things a parametric action does, one per set of params —\n * `flip` flips horizontally or vertically, `reorder.forward` moves one step\n * or all the way. A bar, menu or palette shows one entry per variant and\n * triggers the action with that variant's `params`. Omitted = the action\n * is one entry. See `actionItems`. */\n variants?: readonly ActionVariant[];\n}\n\n/**\n * @experimental\n * One entry a parametric action contributes to a bar, menu or palette.\n */\nexport interface ActionVariant {\n /** Unique within the action; joined to its id to make the entry's key. */\n key: string;\n label: string;\n /** Falls back to the action's own `icon`. */\n icon?: ReactNode | (() => ReactNode);\n /** Passed to `trigger`, and matched against each binding's\n * `opts.params` to find the variant's shortcuts. */\n params: Readonly<Record<string, unknown>>;\n}\n\n/**\n * @experimental\n * What routing consults: the bindings that reach the action, the deps its\n * invoker reads, the gates it passes, and the cursors it claims.\n */\nexport interface ActionDispatch extends BindingSource {\n /** Names of the deps this action's invoker reads (keys of `DepSchema`).\n * The dispatcher (and `trigger`, when `requires` is present) resolves\n * each name against the `DepRegistry` at invocation time and passes the\n * resulting bag to the invoker. Dev builds warn when the invoker reads a\n * dep it didn't declare here — see `buildDepsFromRequires`. */\n requires?: readonly DepName[];\n /** Pluggable invocation strategy. The gesture dispatcher routes matched\n * bindings through `invoker.start` / `invoker.run` depending on timing.\n * All kit-standard descriptors ship one; consumer-supplied actions\n * without an invoker can still register but won't be triggered. */\n invoker?: Invoker;\n /** When set to `'hotkey'`, this action's `defaultBinding` rides the hotkey\n * `BindingScope` instead of the ambient scope — meaning it beats any\n * active-tool binding on the same input shape. Use for tool-switch\n * shortcuts and global held-key triggers. Default: ambient. */\n scope?: 'hotkey';\n /**\n * @experimental\n * Optional predicate the command palette consults when rendering. Return\n * `true` when the action is currently triggerable. Return a reason string\n * (e.g. `'Selection required'`) when disabled — the palette greys out\n * the row, skips it in keyboard nav, ignores clicks, and shows the\n * reason next to the label. Keystroke dispatch (the registered binding)\n * is unaffected; the action's own `run` should self-guard.\n *\n * **Contract:** must be pure (no side effects), fast (< 4ms in dev), and\n * must not throw. If a call throws or exceeds the budget in dev mode,\n * `evaluateEnabled` logs a one-time warning per action id; throws are\n * caught and treated as disabled with reason `'(predicate threw)'`.\n *\n * Snapshot-on-open semantics: the palette evaluates `enabled` once when\n * opened and does NOT re-evaluate on selection changes while open. Live\n * reactive updates are deferred — palette is short-lived.\n *\n * The reason set is a closed enum — to add a new reason, edit\n * `ActionDisabledReason` and the consumer's display map.\n *\n * The optional `deps` argument is the same bag passed to\n * `ImmediateInvoker.run`; callers (`evaluateEnabled` / the ActionBar) may\n * synthesize it from the surrounding `DepRegistry` so predicates can\n * inspect selection / scene / etc. Predicates that don't need deps just\n * ignore the arg.\n *\n * `at` is the world point of the event being routed, when it has one — a\n * click, a press, a long-press. An action that only applies somewhere\n * (on a path segment, say) refuses other points here, so the dispatcher\n * falls through to the next binding and hover prediction shows the\n * action's `cursor` only where it would act. Palettes and keystrokes\n * pass no point; treat absent as \"anywhere\".\n */\n enabled?: (deps?: ActionDeps, at?: { x: number; y: number }) => true | ActionDisabledReason;\n /**\n * Declarative eligibility rule, evaluated against the current\n * `RuleCtx` by the dispatcher before invoking `start()`. Omitted =\n * always eligible.\n *\n * Accepts either a fluent `Condition` (callable with `.rule`) or a\n * raw `Rule` tree; the dispatcher normalizes via `.rule` unwrap.\n *\n * Prefer `capability:`-based rules (e.g. `{ capability: 'transforms-selection' }`)\n * over `mode:` rules — capability rules survive new modes being added\n * that allow the same capability.\n */\n eligible?:\n | import('../../eligibility').Rule\n | import('../../eligibility').Condition;\n /**\n * CSS cursor shown while the pointer hovers a spot where this action\n * would win the drag — or, when the drag's winner declares no cursor,\n * the click. The hover-cursor pump (in `useGestureDispatcher`) runs\n * `Dispatcher.resolveOnly` on each idle pointermove — the same match\n * walk a real press takes — and applies the winning action's `cursor`,\n * so the hint and the actual target stay in sync by construction. Omitted = no override (the active tool's\n * `Tool.cursor` shows). Affordance hits are resolved earlier in the\n * pump via `AffordanceRegion.cursor` and never reach this field.\n *\n * Static value only. Prediction runs `enabled()` but cannot run the\n * invoker, so an action that matches yet bails at `start()` (empty\n * handle) may still show its cursor — keep `enabled` accurate for\n * actions that declare one.\n */\n cursor?: CursorSpec;\n /**\n * CSS cursor shown while THIS action's ongoing handle is in flight —\n * grabbing while panning, `move` while dragging a selection, `crosshair`\n * while pulling a marquee.\n *\n * Separate from `cursor` because the two answer different questions:\n * `cursor` is a prediction (\"a drag from here would pan\"), this is a state\n * (\"you are panning\"). An action can declare either, both, or neither;\n * with only `cursor` set, the hover hint holds for the duration of the\n * gesture.\n *\n * This is where mid-gesture cursors live now. They used to come from the\n * tool side — `ViewportToolDef.engaged.cursor` for a phase-gated string,\n * or a function-form `Tool.cursor` reading the gesture scratch out of the\n * tool-routing dispatcher. Both belonged to a pipeline whose whole job was\n * being taken over by bindings, and neither could describe a cursor for an\n * action a tool doesn't own.\n */\n activeCursor?: CursorSpec;\n}\n\n/**\n * @experimental\n * Single registered action. v1: one binding per action.\n */\nexport interface Action extends ActionDispatch, ActionPresentation {}\n\n/**\n * @experimental\n * The whole of an actions registry the dispatcher reads. It walks the list\n * once per event to build its actionId lookup and never mutates it, so the\n * routing side depends on this rather than on `ActionsRegistry`, whose\n * register / mute / trigger surface is the React provider's.\n */\nexport interface ActionSource {\n list(): readonly Action[];\n}\n","/**\n * @experimental\n * Palette-side evaluation of `Action.enabled`. The dispatcher calls the\n * predicate raw as a gate; this is the surface that also enforces the\n * contract the predicate's JSDoc states — no throwing, under 4ms in dev.\n */\nimport type { ActionDeps } from './invoker';\nimport { ActionDisabledReason, type Action } from './action';\nimport { isDev } from '../../devFlag';\n\n/**\n * @experimental\n * Result of evaluating an Action's `enabled` predicate.\n */\nexport interface ActionEnabledResult {\n enabled: boolean;\n reason?: ActionDisabledReason;\n}\n\nconst enabledSlowWarned = new Set<string>();\nconst enabledThrewWarned = new Set<string>();\nconst ENABLED_BUDGET_MS = 4;\n/**\n * @experimental\n * Safely evaluate an action's `enabled` predicate. Returns `{enabled: true}`\n * when no predicate is supplied. Catches throws and treats them as disabled\n * with reason `'(predicate threw)'`. In dev mode, warns once per action id\n * when a single call exceeds 4ms (single frame at 240fps — generous; real\n * predicates should be sub-millisecond).\n */\nexport function evaluateEnabled(\n action: Action,\n deps?: ActionDeps,\n): ActionEnabledResult {\n if (!action.enabled) return { enabled: true };\n const dev = isDev();\n const now = (typeof performance !== 'undefined' && typeof performance.now === 'function')\n ? () => performance.now()\n : () => Date.now();\n const start = dev ? now() : 0;\n try {\n const result = action.enabled(deps);\n if (dev) {\n const elapsed = now() - start;\n if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {\n enabledSlowWarned.add(action.id);\n console.warn(\n `weasel actions: enabled() for action \"${action.id}\" took ${elapsed.toFixed(2)}ms ` +\n `(budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast — see Action.enabled JSDoc.`,\n );\n }\n }\n if (result === true) return { enabled: true };\n return { enabled: false, reason: result };\n } catch (e) {\n if (dev && !enabledThrewWarned.has(action.id)) {\n enabledThrewWarned.add(action.id);\n console.warn(\n `weasel actions: enabled() for action \"${action.id}\" threw; treating as disabled. ` +\n `The predicate must not throw — see Action.enabled JSDoc.`,\n e,\n );\n }\n return { enabled: false, reason: ActionDisabledReason.PredicateThrew };\n }\n}\n","/**\n * Named predicates for `defaultBinding.target.kindOf`. Each matches the\n * existing `kindOf` signature `(target: unknown, bodyTarget?: string) => boolean`\n * so they drop into action descriptors without a wrapper:\n *\n * target: { kindOf: isAnchor }\n *\n * Single source of truth for \"what does this affordance kind mean\" — adding\n * a new predicate here is what enables every action that needs the same\n * shape to read it. Drift prevention.\n */\n\n// ─── Body-class predicates (read the second arg) ───────────────────────\n\n/** A predicate that resolves from `bodyTarget` alone. The exclusive-claim\n * filter reads `readsAffordance`: without it a body predicate looks like it\n * consults the hit, and survives a claim that should have barred it. */\nexport type BodyPredicate = ((target: unknown, bodyTarget?: string) => boolean)\n & { readsAffordance: false };\n\nfunction bodyPredicate(\n fn: (target: unknown, bodyTarget?: string) => boolean,\n): BodyPredicate {\n return Object.assign(fn, { readsAffordance: false as const });\n}\n\n/** Matches any body hit — selected or unselected node body. */\nexport const isBody: BodyPredicate = bodyPredicate((_target, bodyTarget) =>\n bodyTarget === 'selected-body' || bodyTarget === 'unselected-body');\n\n/** Body hit specifically already in the selection. */\nexport const isSelectedBody: BodyPredicate = bodyPredicate((_target, bodyTarget) =>\n bodyTarget === 'selected-body');\n\n/** Body hit specifically NOT in the selection. */\nexport const isUnselectedBody: BodyPredicate = bodyPredicate((_target, bodyTarget) =>\n bodyTarget === 'unselected-body');\n\n/** Empty-canvas hit — no node beneath cursor. */\nexport const isEmpty: BodyPredicate = bodyPredicate((_target, bodyTarget) =>\n bodyTarget === 'empty');\n\n// ─── Affordance-kind predicates (read the first arg) ───────────────────\n\nfunction kindOf(target: unknown): string | undefined {\n if (typeof target !== 'object' || target === null) return undefined;\n const k = (target as { kind?: unknown }).kind;\n return typeof k === 'string' ? k : undefined;\n}\n\n/** Any corner resize handle — top-left/top-right/bottom-left/bottom-right. */\nexport const isResizeHandle = (target: unknown): boolean => {\n const k = kindOf(target);\n return k !== undefined && k.startsWith('handle:');\n};\n\n/** The rotate-around-center handle above the bounding box. */\nexport const isRotateHandle = (target: unknown): boolean =>\n kindOf(target) === 'rotate-handle';\n\n/** Any path anchor (the on-curve dot). */\nexport const isAnchor = (target: unknown): boolean => {\n const k = kindOf(target);\n return k !== undefined && /^anchor:\\d+$/.test(k);\n};\n\n/** Either control handle (controlIn or controlOut). */\nexport const isControlHandle = (target: unknown): boolean => {\n const k = kindOf(target);\n return k !== undefined && /^(controlIn|controlOut):\\d+$/.test(k);\n};\n\n/** Any of the three anchor-editing affordances (anchor + both controls). */\nexport const isAnchorOrControl = (target: unknown): boolean =>\n isAnchor(target) || isControlHandle(target);\n","import type { Contribution } from './types';\n\n/**\n * Concatenate contribution bundles into one registry, preserving order —\n * entry order decides which of two same-specificity bindings in one scope\n * tier wins, so it is part of the result, not an accident of it.\n *\n * Throws on a duplicate id or dep name rather than dropping one: a feature\n * silently losing its bindings, or reading another feature's dep, is the\n * failure this registry exists to make loud.\n */\nexport function mergeContributions<T extends Contribution<unknown>>(\n ...bundles: readonly (readonly T[])[]\n): T[] {\n const out: T[] = [];\n const seen = new Set<string>();\n const depOwner = new Map<string, string>();\n for (const bundle of bundles) {\n for (const entry of bundle) {\n if (seen.has(entry.id)) {\n throw new Error(\n `mergeContributions: duplicate contribution id \"${entry.id}\". `\n + `Two bundles registered the same id; rename one or merge them by hand.`,\n );\n }\n seen.add(entry.id);\n for (const dep of Object.keys(entry.deps ?? {})) {\n const owner = depOwner.get(dep);\n if (owner !== undefined) {\n throw new Error(\n `mergeContributions: dep \"${dep}\" is provided by both \"${owner}\" and \"${entry.id}\". `\n + `One registry answers each dep name; drop one source.`,\n );\n }\n depOwner.set(dep, entry.id);\n }\n out.push(entry);\n }\n }\n return out;\n}\n","// src/tools/defineTool.ts\nimport type { Tool, ToolCtx } from './types';\nimport type { ToolDef } from './routeTypes';\nimport { RESERVED_ID_NAMES, RESERVED_ID_PREFIXES } from '@weasel-js/gestures';\nimport type { CursorSpec } from '@weasel-js/cursor';\n\n/** Validate that `id` is usable as a tool / channel id in the route\n * grammar. Rejects ids that start with a reserved sigil (would shadow\n * future grammar extensions) and ids that collide with phase keywords\n * (would parse as the bare-phase shorthand). */\nfunction validateId(id: string, kind: 'tool' | 'action'): void {\n if (id.length === 0) {\n throw new Error(`weasel: ${kind} id may not be empty`);\n }\n if (RESERVED_ID_PREFIXES.has(id[0]!)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" starts with reserved sigil \"${id[0]}\" ` +\n `(reserved set: ${[...RESERVED_ID_PREFIXES].join(' ')})`,\n );\n }\n if (RESERVED_ID_NAMES.has(id)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" collides with a reserved phase keyword ` +\n `(reserved: ${[...RESERVED_ID_NAMES].join(', ')})`,\n );\n }\n}\n\n/**\n * Build a `Tool<TScratch>` from a declarative `ToolDef<TScratch>`.\n *\n * This used to be a translator: `ToolDef` carried `initial` / `engaged` phase\n * tables of hit-keyed route handlers, and `defineTool` compiled them into the\n * imperative `pointer` / `drag` / `keyboard` / `wheel` handlers the\n * tool-routing dispatcher called. Both ends of that are gone — tools declare\n * `bindings` and the gesture dispatcher routes them — so what remains is\n * identity plumbing plus the id check and the cursor resolver.\n *\n * It stays a function rather than becoming a spread because the id validation\n * and the `initScratch` / `cursor` defaults are worth applying uniformly, and\n * because `Tool.def` gives reflection a handle on the authored form.\n */\nexport function defineTool<TScratch = void, TOverlay = unknown>(\n def: ToolDef<TScratch, TOverlay>,\n): Tool<TScratch, TOverlay> {\n validateId(def.id, 'tool');\n\n // Normalize `CursorSpec | ((ctx) => CursorSpec)` to the function form so\n // callers have one shape to deal with. Returns '' (not undefined) so the\n // signature satisfies `Tool.cursor: (ctx) => CursorSpec`.\n const resolveCursor = (ctx: ToolCtx<TScratch>): CursorSpec => {\n if (def.cursor == null) return '';\n return typeof def.cursor === 'function' ? def.cursor(ctx) : def.cursor;\n };\n\n return {\n id: def.id,\n eligibility: {\n focus: true,\n capabilities: def.capabilities,\n ...(def.hotkey ? { offhand: def.hotkey } : {}),\n },\n actions: def.actions,\n def,\n presentation: def.presentation as Tool<TScratch>['presentation'],\n keybinding: def.keybinding,\n onActivate: def.onActivate,\n onDeactivate: def.onDeactivate,\n initScratch: def.initScratch ?? (() => null as unknown as TScratch),\n cursor: resolveCursor,\n bindings: def.bindings,\n overlay: def.overlay,\n overlayPosition: def.overlayPosition,\n };\n}\n","import type { Tool } from './types';\nimport type { ViewportToolDef } from './routeTypes';\nimport { defineTool } from './defineTool';\n\n/**\n * Define a tool that acts on the viewport rather than the scene.\n *\n * This used to do real work: `ViewportPhaseDef` was a narrowed `PhaseDef`\n * (no click routes, drag restricted to the function form), and the factory\n * lifted it back to the permissive shape before handing it to `defineTool`.\n * With phase tables gone there is no shape left to narrow — a viewport tool\n * declares `bindings` like any other, pointing at `viewport.*` actions.\n *\n * It survives as an authoring signal. `defineViewportTool` at the top of a\n * hook says \"this tool moves the camera, not the drawing\", which is worth\n * more than the type gymnastics it replaced.\n */\nexport function defineViewportTool<TScratch = void, TOverlay = unknown>(\n def: ViewportToolDef<TScratch, TOverlay>,\n): Tool<TScratch, TOverlay> {\n return defineTool<TScratch, TOverlay>(def);\n}\n","/**\n * Typed keys for the gesture/behavior scratch store.\n *\n * Gestures expose `ctx.scratch: Record<string, unknown>` as a per-gesture\n * mutable bag that behaviors use to stash state across `onDown` / `onMove`\n * / `onEnd` calls. The store is `unknown`-valued because behaviors are\n * tool-agnostic — a single behavior may run under different tools with\n * different scratch shapes. Without a typing convention every read site\n * has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the\n * shape can silently break readers with no compile-time signal.\n *\n * `ScratchKey<T>` is a phantom-typed string that carries its payload type\n * via a branded interface. `getScratch` and `setScratch` use the brand to\n * narrow / constrain at the typing layer; at runtime the key is just its\n * underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level (or behavior-level) constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n *\n * // Writer (a tool's onMove):\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n *\n * // Reader (a behavior's onEnd):\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\n * // ^? readonly { x: number; y: number }[] | undefined\n * ```\n */\n\n/** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom\n * field carries the value type without occupying any runtime memory. */\nexport interface ScratchKey<T> {\n readonly name: string;\n /** Phantom — never read at runtime. Only present so TypeScript can\n * recover `T` at use sites. */\n readonly __scratchKeyPayload?: (_: T) => T;\n}\n\n/** Compatible scratch store shape. The kit's gesture context's `scratch`\n * field already satisfies this; consumers don't need to construct one. */\nexport type ScratchStore = Record<string, unknown>;\n\n/**\n * Make a typed scratch key. Use module-level constants so the same key\n * is shared by writer and reader.\n *\n * Namespace the name by behavior or feature to avoid collisions\n * (`'behavior.field'` is a good convention). Two keys with the same\n * `name` refer to the same slot at runtime — the type parameter is a\n * compile-time contract, not a uniqueness guarantee.\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n"]}
package/dist/react.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { D as Dispatcher, A as ActionsRegistry, T as Tool, a as AffordanceHit, R as RuleCtx, b as DepSchema, c as Action, d as AnyToolOf, O as OverlayPosition, C as Contribution, S as ScopedBinding } from './index-D4hKg8yu.js';
2
- export { e as DepRegistry, f as DepRegistryProvider, u as useDepRegistry, g as useDepSource, h as useOptionalDepRegistry } from './index-D4hKg8yu.js';
1
+ import { D as Dispatcher, A as ActionsRegistry, T as Tool, a as AffordanceHit, R as RuleCtx, b as DepSchema, c as Action, d as AnyToolOf, O as OverlayPosition, C as Contribution, S as ScopedBinding } from './index-CKmbNpwl.js';
2
+ export { e as DepRegistry, f as DepRegistryProvider, u as useDepRegistry, g as useDepSource, h as useOptionalDepRegistry } from './index-CKmbNpwl.js';
3
+ import * as react from 'react';
3
4
  import { RefObject, ReactNode, ReactElement } from 'react';
4
5
  import { BodyClassification } from '@weasel-js/gestures';
5
6
  import { PaintedCursorState } from '@weasel-js/cursor';
6
- import * as react_jsx_runtime from 'react/jsx-runtime';
7
7
  import '@weasel-js/modes';
8
8
  import '@weasel-js/history';
9
9
 
@@ -72,6 +72,9 @@ interface DispatcherChannels {
72
72
  pointer?: boolean;
73
73
  /** `wheel`. */
74
74
  wheel?: boolean;
75
+ /** WebKit's `gesturestart` / `gesturechange` / `gestureend` — Safari's
76
+ * trackpad pinch, dispatched as `pinch` events. */
77
+ pinch?: boolean;
75
78
  /** `contextmenu`. Off leaves the browser's native menu in place. */
76
79
  contextMenu?: boolean;
77
80
  /** OS drop on the element plus `paste` on the window. Off leaves the
@@ -298,6 +301,35 @@ declare function useActionsRegistry(): ActionsRegistry | null;
298
301
  */
299
302
  declare function useAction(action: Action): void;
300
303
 
304
+ /**
305
+ * One UI control's hold on an ongoing action — the handle a color picker, a
306
+ * slider or a swatch drives the action through, the way a drag drives one
307
+ * through a gesture.
308
+ *
309
+ * - `input(params)` opens the action on the first call and moves it on every
310
+ * later one: the live preview.
311
+ * - `commit(params?)` moves it to `params`, if given, and ends it as one
312
+ * undoable edit. With nothing open, a commit carrying params is a whole edit
313
+ * on its own — a click on a swatch, a switch of paint kind.
314
+ * - `cancel()` ends an open edit without keeping it.
315
+ *
316
+ * All three are no-ops when no action is reachable, so a control renders the
317
+ * same with or without a canvas in scope.
318
+ */
319
+ interface OngoingAction {
320
+ input(params: Record<string, unknown>): void;
321
+ commit(params?: Record<string, unknown>): void;
322
+ cancel(): void;
323
+ }
324
+ /**
325
+ * Drive the ongoing action `actionId` from a UI control: {@link OngoingAction}.
326
+ *
327
+ * An edit left open when the control unmounts, or when `actionId` changes, is
328
+ * committed — it is what the user was looking at when the control went away.
329
+ * The returned object is stable for the life of the component.
330
+ */
331
+ declare function useOngoingAction(actionId: string): OngoingAction;
332
+
301
333
  /** Which tool is active, plus the stack of tools temporarily held active by a
302
334
  * hotkey (space-for-hand and the like). The dispatcher reads this to decide
303
335
  * whose bindings are in scope. */
@@ -320,7 +352,7 @@ interface ActiveToolContextProviderProps {
320
352
  initialActive?: string;
321
353
  }
322
354
  /** Provides active-tool state for a canvas. `<SceneCanvas>` mounts one. */
323
- declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react_jsx_runtime.JSX.Element;
355
+ declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react.JSX.Element;
324
356
  /** The active-tool state in scope. Throws outside a provider; use
325
357
  * `useActiveToolContextOptional` where one is not guaranteed. */
326
358
  declare function useActiveToolContext(): ActiveToolContextValue;
@@ -339,7 +371,7 @@ declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
339
371
  */
340
372
  declare function ActiveToolContextProviderIfRoot({ children, }: {
341
373
  children: ReactNode;
342
- }): react_jsx_runtime.JSX.Element;
374
+ }): react.JSX.Element;
343
375
 
344
376
  /** Options for `useTools`: which tools exist, which one starts active, and
345
377
  * which run continuously regardless of the active one. */
@@ -432,4 +464,4 @@ interface ContributionsApi<TOverlay = unknown> {
432
464
  */
433
465
  declare function useContributions<TOverlay = unknown>(opts: UseContributionsOptions<TOverlay>): ContributionsApi<TOverlay>;
434
466
 
435
- export { ActionsProvider, ActionsScope, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, type ActiveToolContextProviderProps, type ActiveToolContextValue, type ContributionsApi, type DispatcherChannels, type DispatcherViewTarget, type ToolsApi, type UseContributionsOptions, type UseGestureDispatcherOptions, type UseToolsOptions, type ViewIdResolver, useAction, useActionsRegistry, useActiveToolContext, useContributions, useGestureDispatcher, useOptionalActiveToolContext, useTools };
467
+ export { ActionsProvider, ActionsScope, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, type ActiveToolContextProviderProps, type ActiveToolContextValue, type ContributionsApi, type DispatcherChannels, type DispatcherViewTarget, type OngoingAction, type ToolsApi, type UseContributionsOptions, type UseGestureDispatcherOptions, type UseToolsOptions, type ViewIdResolver, useAction, useActionsRegistry, useActiveToolContext, useContributions, useGestureDispatcher, useOngoingAction, useOptionalActiveToolContext, useTools };