@weasel-js/routing 1.6.1 → 1.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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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
- 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';
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, h as AffordanceHit, t as AnyTool, e as AnyToolOf, B as BindingOpts, u as BindingScope, v as BindingSource, w as BodyPredicate, x as BoundGesture, y as Bounds, z as BuildRuleCtxArgs, E as COARSE_TARGET_SCALE, F as Condition, G as Conflict, C as Contribution, H as ContributionChrome, I as ContributionDeps, J as ContributionRouting, M as DEFAULT_ALLOWED_CAPABILITIES, N as DEFAULT_DEVICE_PROFILE, P as DebugSink, a as DepName, D as DepRegistry, b as DepSchema, Q as DetectedDeviceFacts, T as DeviceProfile, U as DispatchRecord, V as DispatchRecordInput, g as Dispatcher, W as DispatcherContext, X as DragSample, Y as DroppedCandidate, Z as Eligibility, _ as EligibilityState, $ as GestureBinding, a0 as HandleKind, a1 as HitShape, a2 as HotkeyTrigger, a3 as INGEST_STRING_MIMES, a4 as ImmediateInvoker, a5 as InvocationCtx, a6 as Invoker, K as KernelOverlay, a7 as KeyBinding, a8 as LONG_PRESS_MS, L as LongPressOptions, a9 as LongPressState, aa as LongPressStore, ab as MatchOutcome, ac as MatchResult, ad as ModeShortcutHandlers, ae as ModeSwitchLogEntry, af as ModifierState, ag as NEVER, ah as NodeId, ai as OngoingHandle, aj as OngoingInvoker, ak as OngoingOverlay, O as OverlayPosition, al as OverlayRole, am as OverlaySchema, an as PREDICATE_TARGET, ao as PendingLongPress, ap as PlacedBy, aq as Point2, ar as PointerSession, as as PointerSessionCallbacks, at as PointerSessionCancelReason, au as PointerSessionOptions, av as RankedCandidate, aw as RecordCandidate, ax as RegistryEntry, ay as ResizeAnchor, az as ResolveAllOptions, aA as ResolveOnlyResult, aB as ResolvedCandidate, aC as Rule, R as RuleCtx, f as ScopedBinding, aD as ScratchKey, aE as ScratchStore, aF as SelectionApi, aG as SelectionExtendKey, aH as SelectionMode, aI as Selector, aJ as SpecificityPart, aK as TOOL_OFFHAND_ID, aL as Tool, aM as ToolBounds, aN as ToolCtx, aO as ToolDef, aP as ToolKeybinding, aQ as ToolLifecycleCtx, aR as ToolModifiers, aS as ToolOffhandBindingSpec, aT as ToolPresentation, aU as ToolSlot, aV as TraceLogEntry, aW as UiOngoingControl, aX as View, aY as ViewportGestureKind, aZ as WalkStep, a_ as actionBindings, a$ as actionItems, b0 as buildDepsFromRequires, b1 as buildRouteRegistry, b2 as buildRuleCtx, b3 as buildToolOffhandBindings, b4 as clientToCanvas, b5 as clientToCanvasRect, b6 as createDepRegistry, b7 as createDispatcher, b8 as createLongPressStore, b9 as defineTool, ba as deleteScratch, bb as describeRule, bc as dlog, bd as evaluate, be as evaluateEnabled, bf as findConflicts, bg as getScratch, bh as isAnchor, bi as isAnchorOrControl, bj as isBody, bk as isControlHandle, bl as isEditableTarget, bm as isEmpty, bn as isResizeHandle, bo as isRotateHandle, bp as isSelectedBody, bq as isTool, br as isUnselectedBody, bs as itemsFromClipboardData, bt as itemsFromDataTransfer, bu as itemsFromFiles, bv as liveScope, bw as makeToolOffhandAction, bx as matchBest, by as matchSorted, bz as matchSortedWithBarred, bA as matchesKeyBinding, bB as mergeContributions, bC as modeShortcutSpec, bD as modeShortcuts, bE as offhandKeyFor, bF as openPointerSession, bG as pointerGestureId, bH as recordModeSwitch, bI as reportRouteConflicts, bJ as resolveDeviceProfile, bK as resolveParams, bL as routesForSpec, bM as ruleCanHoldIn, bN as rulesExclusive, bO as scopeBindings, bP as scratchKey, bQ as setScratch, bR as targetConsultsAffordance, bS as validateActionDefaultBinding, bT as validateActionId } from './index-BV53gfyB.js';
2
+ export { ChannelRef, DRAG_THRESHOLD_PX, 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, pastDragThreshold, routeGestureForSpecKind, specKindForRouteGesture, specificity } from '@weasel-js/gestures';
3
3
  import '@weasel-js/modes';
4
4
  import 'react';
5
5
  import '@weasel-js/cursor';
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
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';
1
+ import { isDev } from './chunk-ZGKJWZ4S.js';
2
+ export { ALWAYS, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, GESTURE_DESCRIPTORS, INGEST_STRING_MIMES, LONG_PRESS_MS, 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, createDepRegistry, createDispatcher, createLongPressStore, describeRoute, describeRouteParts, describeRule, dlog, evaluate, findConflicts, formatPhaseAtom, formatRoute, getGestureDescriptor, isEditableTarget, isKnownGestureName, isTool, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, liveScope, makeToolOffhandAction, matchBest, matchKey, matchModifiers, matchPhase, matchSorted, matchSortedWithBarred, matchSpec, matchTarget, matchesKeyBinding, offhandKeyFor, openPointerSession, parseRoute, pointerGestureId, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveParams, routeGestureForSpecKind, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, specKindForRouteGesture, specificity, targetConsultsAffordance, validateActionDefaultBinding, validateActionId } from './chunk-ZGKJWZ4S.js';
3
3
  import { RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from '@weasel-js/gestures';
4
+ export { DRAG_THRESHOLD_PX, pastDragThreshold } from '@weasel-js/gestures';
5
+ import { modeLabel } from '@weasel-js/modes';
4
6
 
5
7
  // src/interactions/actions/actionItems.ts
6
8
  function actionItems(action) {
@@ -115,6 +117,85 @@ function mergeContributions(...bundles) {
115
117
  }
116
118
  return out;
117
119
  }
120
+ var LEAVING = ["exit", "discard", "commit", "cancel"];
121
+ var VERB = {
122
+ entry: "Enter",
123
+ exit: "Exit",
124
+ discard: "Discard",
125
+ commit: "Commit",
126
+ cancel: "Cancel"
127
+ };
128
+ var MODIFIERS = {
129
+ meta: "meta",
130
+ cmd: "meta",
131
+ command: "meta",
132
+ ctrl: "ctrl",
133
+ control: "ctrl",
134
+ alt: "alt",
135
+ option: "alt",
136
+ shift: "shift",
137
+ mod: "mod"
138
+ };
139
+ function modeShortcutSpec(shortcut) {
140
+ const parts = shortcut.split("+");
141
+ const key = parts.pop();
142
+ if (!key) throw new Error(`mode shortcut "${shortcut}" names no key`);
143
+ const mods = {};
144
+ for (const part of parts) {
145
+ const mod = MODIFIERS[part.toLowerCase()];
146
+ if (!mod) throw new Error(`mode shortcut "${shortcut}": unknown modifier "${part}"`);
147
+ mods[mod] = true;
148
+ }
149
+ return Object.keys(mods).length > 0 ? { kind: "key", key, mods } : { kind: "key", key };
150
+ }
151
+ function modeShortcuts(registry, handlers) {
152
+ const actions = [];
153
+ for (const mode of registry.list()) {
154
+ const entry = mode.entry?.shortcut;
155
+ const enter = handlers.enter;
156
+ if (entry && enter) {
157
+ actions.push(shortcutAction(
158
+ mode,
159
+ "entry",
160
+ entry,
161
+ { mode: { not: mode.id } },
162
+ () => registry.current().id !== mode.id && (handlers.canEnter?.(mode.id) ?? true),
163
+ () => enter(mode.id)
164
+ ));
165
+ }
166
+ for (const role of LEAVING) {
167
+ const shortcut = mode[role]?.shortcut;
168
+ const run = handlers[role];
169
+ if (!shortcut || !run) continue;
170
+ actions.push(shortcutAction(
171
+ mode,
172
+ role,
173
+ shortcut,
174
+ { mode: mode.id },
175
+ () => registry.current().id === mode.id,
176
+ run
177
+ ));
178
+ }
179
+ }
180
+ return { id: "modes.shortcuts", eligibility: { always: true }, actions };
181
+ }
182
+ function shortcutAction(mode, role, shortcut, eligible, live, run) {
183
+ return {
184
+ id: `mode.${mode.id}.${role}`,
185
+ label: `${VERB[role]} ${modeLabel(mode)}`,
186
+ scope: "hotkey",
187
+ defaultBinding: {
188
+ ...modeShortcutSpec(shortcut),
189
+ phase: [{ channel: "*", phase: "initial" }]
190
+ },
191
+ eligible,
192
+ // `eligible` is the gate the dispatcher and palettes read, but a canvas
193
+ // with no `modes` evaluates none; without this the key is
194
+ // swallowed in every mode.
195
+ enabled: () => live() ? true : ActionDisabledReason.NotApplicable,
196
+ invoker: { timing: "immediate", run: () => run() }
197
+ };
198
+ }
118
199
  function validateId(id, kind) {
119
200
  if (id.length === 0) {
120
201
  throw new Error(`weasel: ${kind} id may not be empty`);
@@ -157,11 +238,6 @@ function defineTool(def) {
157
238
  };
158
239
  }
159
240
 
160
- // src/tools/defineViewportTool.ts
161
- function defineViewportTool(def) {
162
- return defineTool(def);
163
- }
164
-
165
241
  // src/interactions/scratchKey.ts
166
242
  function scratchKey(name) {
167
243
  return { name };
@@ -180,6 +256,6 @@ function deleteScratch(store, key) {
180
256
  return false;
181
257
  }
182
258
 
183
- export { ActionDisabledReason, actionItems, defineTool, defineViewportTool, deleteScratch, evaluateEnabled, getScratch, isAnchor, isAnchorOrControl, isBody, isControlHandle, isEmpty, isResizeHandle, isRotateHandle, isSelectedBody, isUnselectedBody, mergeContributions, scratchKey, setScratch };
259
+ export { ActionDisabledReason, actionItems, defineTool, deleteScratch, evaluateEnabled, getScratch, isAnchor, isAnchorOrControl, isBody, isControlHandle, isEmpty, isResizeHandle, isRotateHandle, isSelectedBody, isUnselectedBody, mergeContributions, modeShortcutSpec, modeShortcuts, scratchKey, setScratch };
184
260
  //# sourceMappingURL=index.js.map
185
261
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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"]}
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/contributions/modeShortcuts.ts","../src/tools/defineTool.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 — an `engaged` phase entry's cursor,\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","import { modeLabel, type ModeDefinition, type ModeRegistry } from '@weasel-js/modes';\nimport type { KeySpec, ModSpec } from '@weasel-js/gestures';\nimport { ActionDisabledReason, type Action } from '../interactions/actions/action';\nimport type { Rule } from '../eligibility';\nimport type { Contribution } from './types';\n\n/**\n * What each mode shortcut does. A role left out binds nothing, so its key\n * falls through to whatever else the dispatcher has on it.\n */\nexport interface ModeShortcutHandlers {\n /** Enter mode `id` — the `entry` shortcut. */\n enter?: (id: string) => void;\n /** Whether mode `id` can be entered now. A mode that needs a target it\n * doesn't have answers no, and its entry key falls through. Default yes. */\n canEnter?: (id: string) => boolean;\n /** Leave the active soft mode, keeping its session to resume. */\n exit?: () => void;\n /** Leave the active soft mode and throw its session away. */\n discard?: () => void;\n /** Commit the active strict mode's transaction. */\n commit?: () => void;\n /** Abandon the active strict mode's transaction. */\n cancel?: () => void;\n}\n\ntype Role = 'exit' | 'discard' | 'commit' | 'cancel';\nconst LEAVING: readonly Role[] = ['exit', 'discard', 'commit', 'cancel'];\nconst VERB: Record<Role | 'entry', string> = {\n entry: 'Enter', exit: 'Exit', discard: 'Discard', commit: 'Commit', cancel: 'Cancel',\n};\n\nconst MODIFIERS: Record<string, keyof ModSpec> = {\n meta: 'meta', cmd: 'meta', command: 'meta',\n ctrl: 'ctrl', control: 'ctrl',\n alt: 'alt', option: 'alt',\n shift: 'shift',\n mod: 'mod',\n};\n\n/**\n * A `ModeDefinition` shortcut chord — `'Escape'`, `'Meta+T'` — as a key spec.\n * Modifier names are case-insensitive; the last segment is the key.\n */\nexport function modeShortcutSpec(shortcut: string): KeySpec {\n const parts = shortcut.split('+');\n const key = parts.pop();\n if (!key) throw new Error(`mode shortcut \"${shortcut}\" names no key`);\n const mods: ModSpec = {};\n for (const part of parts) {\n const mod = MODIFIERS[part.toLowerCase()];\n if (!mod) throw new Error(`mode shortcut \"${shortcut}\": unknown modifier \"${part}\"`);\n mods[mod] = true;\n }\n return Object.keys(mods).length > 0 ? { kind: 'key', key, mods } : { kind: 'key', key };\n}\n\n/**\n * The registry's declared mode shortcuts as an always-on contribution: one\n * action per shortcut, bound to its chord and gated on the mode it acts on —\n * `exit` / `discard` / `commit` / `cancel` while that mode is active, `entry`\n * while it is not. Install it with `<SceneCanvas ambient>`.\n *\n * The bindings ride the hotkey tier, so leaving a mode outranks what the same\n * key does otherwise (Escape clearing the selection), and they yield while a\n * gesture is in flight, so Escape mid-drag still cancels the drag first. Keys\n * typed into an editable element never reach them.\n */\nexport function modeShortcuts(\n registry: ModeRegistry,\n handlers: ModeShortcutHandlers,\n): Contribution<never> {\n const actions: Action[] = [];\n for (const mode of registry.list()) {\n const entry = mode.entry?.shortcut;\n const enter = handlers.enter;\n if (entry && enter) {\n actions.push(shortcutAction(mode, 'entry', entry, { mode: { not: mode.id } },\n () => registry.current().id !== mode.id && (handlers.canEnter?.(mode.id) ?? true),\n () => enter(mode.id)));\n }\n for (const role of LEAVING) {\n const shortcut = mode[role]?.shortcut;\n const run = handlers[role];\n if (!shortcut || !run) continue;\n actions.push(shortcutAction(mode, role, shortcut, { mode: mode.id },\n () => registry.current().id === mode.id,\n run));\n }\n }\n return { id: 'modes.shortcuts', eligibility: { always: true }, actions };\n}\n\nfunction shortcutAction(\n mode: ModeDefinition,\n role: Role | 'entry',\n shortcut: string,\n eligible: Rule,\n live: () => boolean,\n run: () => void,\n): Action {\n return {\n id: `mode.${mode.id}.${role}`,\n label: `${VERB[role]} ${modeLabel(mode)}`,\n scope: 'hotkey',\n defaultBinding: {\n ...modeShortcutSpec(shortcut),\n phase: [{ channel: '*', phase: 'initial' }],\n },\n eligible,\n // `eligible` is the gate the dispatcher and palettes read, but a canvas\n // with no `modes` evaluates none; without this the key is\n // swallowed in every mode.\n enabled: () => (live() ? true : ActionDisabledReason.NotApplicable),\n invoker: { timing: 'immediate', run: () => run() },\n };\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';\nimport type { KernelOverlay } from '../index';\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 = KernelOverlay>(\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","/**\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"]}
@@ -0,0 +1,275 @@
1
+ import { g as Dispatcher, A as ActionsRegistry, C as Contribution, h as AffordanceHit, L as LongPressOptions, R as RuleCtx, b as DepSchema } from './index-BV53gfyB.js';
2
+ import { RefObject } from 'react';
3
+ import { BodyClassification } from '@weasel-js/gestures';
4
+ import { PaintedCursorState } from '@weasel-js/cursor';
5
+
6
+ /**
7
+ * Everything about handling an input event that depends on *which view* it
8
+ * landed in: the dispatcher that runs it, and the three lookups that resolve
9
+ * a client point against that view's camera.
10
+ *
11
+ * @internal One per view. The hook builds the `id: null` one from its flat
12
+ * options; `views` supplies the rest, and the hook chooses between them per
13
+ * event, so that it — and its listener set — still mounts exactly once.
14
+ */
15
+ interface DispatcherViewTarget {
16
+ /** `null` is the root view — the whole canvas. Matches `ViewTarget.id`. */
17
+ id: string | null;
18
+ dispatcher: Dispatcher;
19
+ affordanceAt: UseGestureDispatcherOptions['affordanceAt'];
20
+ classifyTarget: UseGestureDispatcherOptions['classifyTarget'];
21
+ clientToWorld: UseGestureDispatcherOptions['clientToWorld'];
22
+ /**
23
+ * The deps this view answers for itself — its camera, its selection, and
24
+ * whatever else is per-view rather than per-canvas. Read per event; every
25
+ * name it does not carry still resolves through the canvas registry.
26
+ *
27
+ * This is why a view does not get a `DepRegistryProvider` of its own. The
28
+ * registry is where a consumer registers *sources*, and one per view would
29
+ * fragment that: a consumer overriding `insert` would have to know how many
30
+ * views exist and override each. An overlay keeps one place to register and
31
+ * one authority per dep, with the view claiming only what is genuinely its.
32
+ *
33
+ * Without this a gesture inside a panel pans the whole canvas and selects
34
+ * into the canvas's selection — correct coordinates alone do not make
35
+ * routing correct.
36
+ */
37
+ deps?: () => Partial<DepSchema>;
38
+ /**
39
+ * This view's chrome-caps rule context, for the eligibility filter. The rule
40
+ * table is the surface's, but a rule keyed on selection or the in-flight
41
+ * action answers per view — so an action hidden in one panel must not
42
+ * decline a gesture in another. `undefined` means "nothing gates here", the
43
+ * same answer a surface with no mode registry gives.
44
+ */
45
+ getRuleCtx?: () => RuleCtx | undefined;
46
+ }
47
+ /**
48
+ * The part of `features/viewports`' `ViewResolver` the dispatcher needs: which
49
+ * view a client point belongs to, pinned to the view a gesture began in.
50
+ *
51
+ * @internal Structural on purpose — a `ViewResolver` satisfies it, and the
52
+ * dispatcher stays free of the viewport module.
53
+ */
54
+ interface ViewIdResolver {
55
+ begin(pointerId: number, clientX: number, clientY: number): {
56
+ id: string | null;
57
+ };
58
+ at(pointerId: number | null, clientX: number, clientY: number): {
59
+ id: string | null;
60
+ };
61
+ end(pointerId: number): void;
62
+ }
63
+ /** Per-channel switches for the listeners `useGestureDispatcher` attaches to
64
+ * the canvas element. Omitted or `true` attaches the channel. */
65
+ interface DispatcherChannels {
66
+ /** `pointerdown`, hover `pointermove`, `pointerleave` — press, drag, click,
67
+ * long-press, multi-touch and the hover-cursor pump. */
68
+ pointer?: boolean;
69
+ /** `wheel`. */
70
+ wheel?: boolean;
71
+ /** WebKit's `gesturestart` / `gesturechange` / `gestureend` — Safari's
72
+ * trackpad pinch, dispatched as `pinch` events. */
73
+ pinch?: boolean;
74
+ /** `contextmenu`. Off leaves the browser's native menu in place. */
75
+ contextMenu?: boolean;
76
+ /** OS drop on the element plus `paste` on the window. Off leaves the
77
+ * element a non-drop-target and stops `weasel-dropover` being applied. */
78
+ ingest?: boolean;
79
+ }
80
+ /** Options for `useGestureDispatcher`: the element to listen on, the actions
81
+ * and tools in play, and the hooks that turn raw DOM events into the world
82
+ * coordinates and hit targets bindings match against. */
83
+ interface UseGestureDispatcherOptions {
84
+ /** Ref to the canvas element. Pointer/wheel/multitouch listeners attach here. */
85
+ canvasRef: RefObject<HTMLElement | null>;
86
+ /** Action registry (ActionsRegistry from registry.tsx). */
87
+ actions: ActionsRegistry;
88
+ /** Tool definitions keyed by id. Typically passes an empty Map. */
89
+ entriesById: ReadonlyMap<string, Contribution<unknown>>;
90
+ /** Default true. Set false to opt out of dispatcher wiring (e.g. demos that disable it). */
91
+ enabled?: boolean;
92
+ /**
93
+ * Observer fired whenever the dispatcher synthesizes a double click, in
94
+ * world coordinates. Runs BEFORE the event is dispatched and independently
95
+ * of which binding (if any) handles it.
96
+ *
97
+ * This is deliberately not an Action. `<SceneCanvas onDoubleClick>` is a
98
+ * notification — "the user double-clicked, here's what they hit" — and a
99
+ * notification must not compete with behavior for the gesture. As a binding
100
+ * it would lose to `enterPathEdit` on any body hit and silently never fire.
101
+ * Routing it here keeps a single definition of "double click" (the point of
102
+ * consolidating the kit's three detectors) without giving it
103
+ * first-match-wins semantics it shouldn't have.
104
+ */
105
+ onDoubleClick?: (world: {
106
+ x: number;
107
+ y: number;
108
+ }) => void;
109
+ /**
110
+ * Default true. Set false to leave the window `keydown`/`keyup` listeners
111
+ * unattached so keyboard-bound actions never dispatch — pointer, wheel, and
112
+ * contextmenu channels stay live. `<SceneCanvas>` wires this to
113
+ * `enableKeybindings`, so opting out of keybindings disables the modern
114
+ * dispatcher key path as well as the legacy `useKeybindings` hook.
115
+ *
116
+ * Every dispatcher hears keys on `window`, and a keystroke one of them
117
+ * claims — or anything else calls `preventDefault` on — is not dispatched by
118
+ * the rest. `'first'` listens in the capture phase, so this dispatcher gets
119
+ * the keystroke ahead of every dispatcher listening normally, whatever the
120
+ * mount order: the shape for chrome that owns a key only when it can act.
121
+ */
122
+ keyboard?: boolean | 'first';
123
+ /**
124
+ * Optional affordance classifier. Called with the **world** coordinates of
125
+ * the pointer — this hook applies the routed view's `clientToWorld` before
126
+ * calling it, the same conversion that produces `InputEvent.x`/`y`, so a
127
+ * consumer hit-tests in one space. Returns an `AffordanceHit` when the
128
+ * pointer lands on a known affordance (resize handle, rotate handle, etc.)
129
+ * or `null` when the pointer hit open canvas.
130
+ *
131
+ * The hit is packed into `InputEvent.pointerdown.affordance` and flows
132
+ * through into `InvocationCtx.drag.affordance`. Action invokers that require
133
+ * a specific affordance (e.g. `resizeAction` requires `handle:*`) use this
134
+ * field as a guard in their `start` body.
135
+ *
136
+ * When omitted, `affordance` is always `undefined` — meaning only consumers
137
+ * that explicitly wire a classifier get affordance-gated behavior.
138
+ * `<SceneCanvas>` wires the full chrome→dispatcher bridge.
139
+ */
140
+ affordanceAt?: (worldPoint: {
141
+ x: number;
142
+ y: number;
143
+ }) => AffordanceHit | null;
144
+ /**
145
+ * Optional body classifier. Called on every pointerdown with the world-space
146
+ * coordinates of the pointer. Its result is packed onto the event as
147
+ * `bodyTarget` + `bodyKind`, which `matchTarget` reads to resolve the
148
+ * string-form `TargetSpec` values in `Tool.bindings`.
149
+ *
150
+ * `body` is `'empty'` when nothing is under the pointer, `'selected-body'`
151
+ * when the topmost hit belongs to the current selection, or
152
+ * `'unselected-body'` when it belongs to a node that isn't selected. `kind`
153
+ * is the hit node's semantic kind, when the scene can name it.
154
+ *
155
+ * When omitted, every body-derived target form (`'empty'`,
156
+ * `'selected-body'`, `'unselected-body'`, `kind:<k>`, `kind:<k>:selected`)
157
+ * never matches — bindings using those specs are silently skipped.
158
+ * `<SceneCanvas>` wires this.
159
+ *
160
+ * World coordinates, on every path, for the same reason as `affordanceAt`.
161
+ */
162
+ classifyTarget?: (worldPoint: {
163
+ x: number;
164
+ y: number;
165
+ }) => BodyClassification;
166
+ /**
167
+ * Optional pre-created `Dispatcher`. When provided, this hook pumps events
168
+ * into the supplied instance instead of creating its own. Lets a parent
169
+ * scope (e.g. `<SceneCanvas>`) share one dispatcher between the gesture
170
+ * mounter and other consumers (the preview-ghost layer).
171
+ */
172
+ dispatcher?: Dispatcher;
173
+ /**
174
+ * Converts a client-space pointer position (e.g. `e.clientX`, `e.clientY`)
175
+ * to world-space coordinates. When supplied, every pointer/wheel event's
176
+ * `x`/`y` is converted before the dispatcher builds `InvocationCtx.world`.
177
+ *
178
+ * Without this, `ctx.world` is populated with the raw client coords, which
179
+ * silently breaks any action whose overlay/output uses absolute world
180
+ * positions (marquee, lasso polyline, world-space affordances). Actions
181
+ * that only read `drag.delta` are unaffected because client→world deltas
182
+ * are equal at scale 1, but as soon as a consumer pans/zooms the view the
183
+ * deltas diverge too.
184
+ *
185
+ * `<SceneCanvas>` always wires this via its canvas rect + current view.
186
+ * Tests / harnesses without a view can omit it and continue passing raw
187
+ * coordinates as before.
188
+ */
189
+ clientToWorld?: (clientX: number, clientY: number) => {
190
+ x: number;
191
+ y: number;
192
+ };
193
+ /**
194
+ * Invoked once per pump event (`pointermove`, `pointerup`, `pointercancel`,
195
+ * `key-held` up-phase, `multitouch` move) when a dispatcher-side handle is
196
+ * in flight. Lets the host canvas schedule a redraw so dispatcher-only
197
+ * overlays (marquee, lasso, preview-ghost) repaint on each frame.
198
+ *
199
+ * Required because dispatcher actions don't go through the legacy
200
+ * `tools.dispatcher.onGestureChange` redraw bump that ambient tool drags
201
+ * relied on. Without it, the overlay layer's `draw` never runs between
202
+ * pointerdown and pointerup, and the chrome flashes once at gesture start
203
+ * and then sits frozen until the gesture ends.
204
+ *
205
+ * `<SceneCanvas>` wires this to the canvas's `requestRedraw`. Test
206
+ * harnesses can omit it; their dispatchers won't paint between events but
207
+ * that's already the test contract.
208
+ */
209
+ requestRedraw?: () => void;
210
+ /**
211
+ * The surface's painted-cursor channel, read live because the canvas handle
212
+ * it lives on arrives a render after this hook runs.
213
+ *
214
+ * Given one, a cursor the CSS tier cannot express — sized in world units, or
215
+ * past the size the browser drops the image at — is published here and drawn
216
+ * by the painted-cursor layer instead. Without one, such a cursor resolves to
217
+ * `none` and the pointer shows nothing.
218
+ */
219
+ paintedCursor?: () => PaintedCursorState | undefined;
220
+ /** How touch and pen long-presses are timed, published and felt. */
221
+ longPress?: LongPressOptions;
222
+ /**
223
+ * Thunk returning the live `RuleCtx` for the current frame. When supplied,
224
+ * the dispatcher filters matched candidates by their declared
225
+ * `Action.eligible` rule (omitted => always eligible). `<SceneCanvas>`
226
+ * wires this; tests / harnesses without chrome-caps state can omit it.
227
+ */
228
+ getRuleCtx?: () => RuleCtx | undefined;
229
+ /**
230
+ * Which of the canvas element's input channels to attach. Every one
231
+ * defaults to `true`; naming a channel `false` leaves its listeners
232
+ * unattached, so the element behaves as if the dispatcher were not there.
233
+ *
234
+ * A mount that binds two gestures should not also have to take the rest of
235
+ * the pipeline's side effects: `contextMenu` suppresses the native menu
236
+ * unconditionally, and `ingest` makes the element a valid drop target and
237
+ * styles it while an OS drag hovers. `@weasel-js/labkit`'s loupe mounts on
238
+ * a lab's own canvas host for a wheel and a held key, and turning the other
239
+ * three off is what keeps right-click and file drops working there.
240
+ *
241
+ * The window keyboard channel is `keyboard` instead of an entry here,
242
+ * because `<SceneCanvas enableKeybindings>` drives it.
243
+ */
244
+ channels?: DispatcherChannels;
245
+ /**
246
+ * Routing for a canvas hosting more than one view: the non-root dispatch
247
+ * records to choose between, read fresh per event, and the resolver that
248
+ * chooses. Omit for a single view — then every event runs on the record the
249
+ * flat options above describe, which is what they are: view zero.
250
+ *
251
+ * A resolved id with no live record falls back to the root, so a view that
252
+ * unmounts mid-gesture degrades instead of dropping the event.
253
+ *
254
+ * Keyboard and paste have no coordinates to resolve; they run on the view
255
+ * the last coordinate-bearing event resolved to.
256
+ *
257
+ * @internal
258
+ */
259
+ views?: {
260
+ targets: () => readonly DispatcherViewTarget[];
261
+ resolver: ViewIdResolver;
262
+ };
263
+ }
264
+ /**
265
+ * Attach the input pipeline to a canvas: DOM pointer, wheel, keyboard and
266
+ * multitouch listeners in, normalized input events out, routed through the
267
+ * dispatcher to whichever action a binding names.
268
+ *
269
+ * Everything it needs beyond its options — the active tool, the dep registry —
270
+ * comes from context, so it must be mounted inside `<SceneCanvas>`'s
271
+ * providers.
272
+ */
273
+ declare function useGestureDispatcher(opts: UseGestureDispatcherOptions): void;
274
+
275
+ export { type DispatcherChannels as D, type UseGestureDispatcherOptions as U, type ViewIdResolver as V, type DispatcherViewTarget as a, useGestureDispatcher as u };
@@ -0,0 +1,7 @@
1
+ export { a as DispatcherViewTarget, V as ViewIdResolver } from './internal--mYnGecm.js';
2
+ import './index-BV53gfyB.js';
3
+ import '@weasel-js/modes';
4
+ import 'react';
5
+ import '@weasel-js/cursor';
6
+ import '@weasel-js/gestures';
7
+ import '@weasel-js/history';
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=internal.js.map
3
+ //# sourceMappingURL=internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"internal.js"}