@weasel-js/routing 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/chunk-Y5FHAA4U.js +1504 -0
- package/dist/chunk-Y5FHAA4U.js.map +1 -0
- package/dist/index-BOlkWrA-.d.ts +2446 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +160 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +405 -0
- package/dist/react.js +1304 -0
- package/dist/react.js.map +1 -0
- package/package.json +59 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
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-BOlkWrA-.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';
|
|
3
|
+
import '@weasel-js/modes';
|
|
4
|
+
import 'react';
|
|
5
|
+
import '@weasel-js/cursor';
|
|
6
|
+
import 'react/jsx-runtime';
|
|
7
|
+
import '@weasel-js/history';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { isDev } from './chunk-Y5FHAA4U.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-Y5FHAA4U.js';
|
|
3
|
+
import { RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from '@weasel-js/gestures';
|
|
4
|
+
|
|
5
|
+
// src/interactions/actions/action.ts
|
|
6
|
+
var ActionDisabledReason = {
|
|
7
|
+
SelectionRequired: "selection-required",
|
|
8
|
+
SceneEmpty: "scene-empty",
|
|
9
|
+
NotApplicable: "not-applicable",
|
|
10
|
+
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
11
|
+
PredicateThrew: "predicate-threw"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/interactions/actions/actionEnabled.ts
|
|
15
|
+
var enabledSlowWarned = /* @__PURE__ */ new Set();
|
|
16
|
+
var enabledThrewWarned = /* @__PURE__ */ new Set();
|
|
17
|
+
var ENABLED_BUDGET_MS = 4;
|
|
18
|
+
function evaluateEnabled(action, deps) {
|
|
19
|
+
if (!action.enabled) return { enabled: true };
|
|
20
|
+
const dev = isDev();
|
|
21
|
+
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
22
|
+
const start = dev ? now() : 0;
|
|
23
|
+
try {
|
|
24
|
+
const result = action.enabled(deps);
|
|
25
|
+
if (dev) {
|
|
26
|
+
const elapsed = now() - start;
|
|
27
|
+
if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
|
|
28
|
+
enabledSlowWarned.add(action.id);
|
|
29
|
+
console.warn(
|
|
30
|
+
`weasel actions: enabled() for action "${action.id}" took ${elapsed.toFixed(2)}ms (budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast \u2014 see Action.enabled JSDoc.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (result === true) return { enabled: true };
|
|
35
|
+
return { enabled: false, reason: result };
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (dev && !enabledThrewWarned.has(action.id)) {
|
|
38
|
+
enabledThrewWarned.add(action.id);
|
|
39
|
+
console.warn(
|
|
40
|
+
`weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
|
|
41
|
+
e
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/interactions/dispatcher/predicates.ts
|
|
49
|
+
function bodyPredicate(fn) {
|
|
50
|
+
return Object.assign(fn, { readsAffordance: false });
|
|
51
|
+
}
|
|
52
|
+
var isBody = bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body" || bodyTarget === "unselected-body");
|
|
53
|
+
var isSelectedBody = bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body");
|
|
54
|
+
var isUnselectedBody = bodyPredicate((_target, bodyTarget) => bodyTarget === "unselected-body");
|
|
55
|
+
var isEmpty = bodyPredicate((_target, bodyTarget) => bodyTarget === "empty");
|
|
56
|
+
function kindOf(target) {
|
|
57
|
+
if (typeof target !== "object" || target === null) return void 0;
|
|
58
|
+
const k = target.kind;
|
|
59
|
+
return typeof k === "string" ? k : void 0;
|
|
60
|
+
}
|
|
61
|
+
var isResizeHandle = (target) => {
|
|
62
|
+
const k = kindOf(target);
|
|
63
|
+
return k !== void 0 && k.startsWith("handle:");
|
|
64
|
+
};
|
|
65
|
+
var isRotateHandle = (target) => kindOf(target) === "rotate-handle";
|
|
66
|
+
var isAnchor = (target) => {
|
|
67
|
+
const k = kindOf(target);
|
|
68
|
+
return k !== void 0 && /^anchor:\d+$/.test(k);
|
|
69
|
+
};
|
|
70
|
+
var isControlHandle = (target) => {
|
|
71
|
+
const k = kindOf(target);
|
|
72
|
+
return k !== void 0 && /^(controlIn|controlOut):\d+$/.test(k);
|
|
73
|
+
};
|
|
74
|
+
var isAnchorOrControl = (target) => isAnchor(target) || isControlHandle(target);
|
|
75
|
+
|
|
76
|
+
// src/contributions/merge.ts
|
|
77
|
+
function mergeContributions(...bundles) {
|
|
78
|
+
const out = [];
|
|
79
|
+
const seen = /* @__PURE__ */ new Set();
|
|
80
|
+
for (const bundle of bundles) {
|
|
81
|
+
for (const entry of bundle) {
|
|
82
|
+
if (seen.has(entry.id)) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`mergeContributions: duplicate contribution id "${entry.id}". Two bundles registered the same id; rename one or merge them by hand.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
seen.add(entry.id);
|
|
88
|
+
out.push(entry);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
function validateId(id, kind) {
|
|
94
|
+
if (id.length === 0) {
|
|
95
|
+
throw new Error(`weasel: ${kind} id may not be empty`);
|
|
96
|
+
}
|
|
97
|
+
if (RESERVED_ID_PREFIXES.has(id[0])) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`weasel: ${kind} id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (RESERVED_ID_NAMES.has(id)) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`weasel: ${kind} id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function defineTool(def) {
|
|
109
|
+
validateId(def.id, "tool");
|
|
110
|
+
const resolveCursor = (ctx) => {
|
|
111
|
+
if (def.cursor == null) return "";
|
|
112
|
+
return typeof def.cursor === "function" ? def.cursor(ctx) : def.cursor;
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
id: def.id,
|
|
116
|
+
eligibility: {
|
|
117
|
+
focus: true,
|
|
118
|
+
capabilities: def.capabilities,
|
|
119
|
+
...def.hotkey ? { offhand: def.hotkey } : {}
|
|
120
|
+
},
|
|
121
|
+
actions: def.actions,
|
|
122
|
+
def,
|
|
123
|
+
presentation: def.presentation,
|
|
124
|
+
keybinding: def.keybinding,
|
|
125
|
+
onActivate: def.onActivate,
|
|
126
|
+
onDeactivate: def.onDeactivate,
|
|
127
|
+
initScratch: def.initScratch ?? (() => null),
|
|
128
|
+
cursor: resolveCursor,
|
|
129
|
+
bindings: def.bindings,
|
|
130
|
+
overlay: def.overlay,
|
|
131
|
+
overlayPosition: def.overlayPosition
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/tools/defineViewportTool.ts
|
|
136
|
+
function defineViewportTool(def) {
|
|
137
|
+
return defineTool(def);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/interactions/scratchKey.ts
|
|
141
|
+
function scratchKey(name) {
|
|
142
|
+
return { name };
|
|
143
|
+
}
|
|
144
|
+
function getScratch(store, key) {
|
|
145
|
+
return store[key.name];
|
|
146
|
+
}
|
|
147
|
+
function setScratch(store, key, value) {
|
|
148
|
+
store[key.name] = value;
|
|
149
|
+
}
|
|
150
|
+
function deleteScratch(store, key) {
|
|
151
|
+
if (Object.prototype.hasOwnProperty.call(store, key.name)) {
|
|
152
|
+
delete store[key.name];
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { ActionDisabledReason, defineTool, defineViewportTool, deleteScratch, evaluateEnabled, getScratch, isAnchor, isAnchorOrControl, isBody, isControlHandle, isEmpty, isResizeHandle, isRotateHandle, isSelectedBody, isUnselectedBody, mergeContributions, scratchKey, setScratch };
|
|
159
|
+
//# sourceMappingURL=index.js.map
|
|
160
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 enabled?: (deps?: ActionDeps) => 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. The hover-cursor pump (in `useGestureDispatcher`)\n * runs `Dispatcher.resolveOnly` on each idle pointermove — the same\n * match walk a real pointerdown takes — and applies the winning\n * action's `cursor`, so the hint and the actual click target stay in\n * 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"]}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
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-BOlkWrA-.js';
|
|
2
|
+
export { e as DepRegistry, f as DepRegistryProvider, u as useDepRegistry, g as useDepSource, h as useOptionalDepRegistry } from './index-BOlkWrA-.js';
|
|
3
|
+
import { RefObject, ReactNode, ReactElement } from 'react';
|
|
4
|
+
import { BodyClassification } from '@weasel-js/gestures';
|
|
5
|
+
import { PaintedCursorState } from '@weasel-js/cursor';
|
|
6
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
7
|
+
import '@weasel-js/modes';
|
|
8
|
+
import '@weasel-js/history';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Everything about handling an input event that depends on *which view* it
|
|
12
|
+
* landed in: the dispatcher that runs it, and the three lookups that resolve
|
|
13
|
+
* a client point against that view's camera.
|
|
14
|
+
*
|
|
15
|
+
* @internal One per view. The hook builds the `id: null` one from its flat
|
|
16
|
+
* options; `views` supplies the rest, and the hook chooses between them per
|
|
17
|
+
* event, so that it — and its listener set — still mounts exactly once.
|
|
18
|
+
*/
|
|
19
|
+
interface DispatcherViewTarget {
|
|
20
|
+
/** `null` is the root view — the whole canvas. Matches `ViewTarget.id`. */
|
|
21
|
+
id: string | null;
|
|
22
|
+
dispatcher: Dispatcher;
|
|
23
|
+
affordanceAt: UseGestureDispatcherOptions['affordanceAt'];
|
|
24
|
+
classifyTarget: UseGestureDispatcherOptions['classifyTarget'];
|
|
25
|
+
clientToWorld: UseGestureDispatcherOptions['clientToWorld'];
|
|
26
|
+
/**
|
|
27
|
+
* The deps this view answers for itself — its camera, its selection, and
|
|
28
|
+
* whatever else is per-view rather than per-canvas. Read per event; every
|
|
29
|
+
* name it does not carry still resolves through the canvas registry.
|
|
30
|
+
*
|
|
31
|
+
* This is why a view does not get a `DepRegistryProvider` of its own. The
|
|
32
|
+
* registry is where a consumer registers *sources*, and one per view would
|
|
33
|
+
* fragment that: a consumer overriding `insert` would have to know how many
|
|
34
|
+
* views exist and override each. An overlay keeps one place to register and
|
|
35
|
+
* one authority per dep, with the view claiming only what is genuinely its.
|
|
36
|
+
*
|
|
37
|
+
* Without this a gesture inside a panel pans the whole canvas and selects
|
|
38
|
+
* into the canvas's selection — correct coordinates alone do not make
|
|
39
|
+
* routing correct.
|
|
40
|
+
*/
|
|
41
|
+
deps?: () => Partial<DepSchema>;
|
|
42
|
+
/**
|
|
43
|
+
* This view's chrome-caps rule context, for the eligibility filter. The rule
|
|
44
|
+
* table is the surface's, but a rule keyed on selection or the in-flight
|
|
45
|
+
* action answers per view — so an action hidden in one panel must not
|
|
46
|
+
* decline a gesture in another. `undefined` means "nothing gates here", the
|
|
47
|
+
* same answer a surface with no mode registry gives.
|
|
48
|
+
*/
|
|
49
|
+
getRuleCtx?: () => RuleCtx | undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The part of `features/viewports`' `ViewResolver` the dispatcher needs: which
|
|
53
|
+
* view a client point belongs to, pinned to the view a gesture began in.
|
|
54
|
+
*
|
|
55
|
+
* @internal Structural on purpose — a `ViewResolver` satisfies it, and the
|
|
56
|
+
* dispatcher stays free of the viewport module.
|
|
57
|
+
*/
|
|
58
|
+
interface ViewIdResolver {
|
|
59
|
+
begin(pointerId: number, clientX: number, clientY: number): {
|
|
60
|
+
id: string | null;
|
|
61
|
+
};
|
|
62
|
+
at(pointerId: number | null, clientX: number, clientY: number): {
|
|
63
|
+
id: string | null;
|
|
64
|
+
};
|
|
65
|
+
end(pointerId: number): void;
|
|
66
|
+
}
|
|
67
|
+
/** Options for `useGestureDispatcher`: the element to listen on, the actions
|
|
68
|
+
* and tools in play, and the hooks that turn raw DOM events into the world
|
|
69
|
+
* coordinates and hit targets bindings match against. */
|
|
70
|
+
interface UseGestureDispatcherOptions {
|
|
71
|
+
/** Ref to the canvas element. Pointer/wheel/multitouch listeners attach here. */
|
|
72
|
+
canvasRef: RefObject<HTMLElement | null>;
|
|
73
|
+
/** Action registry (ActionsRegistry from registry.tsx). */
|
|
74
|
+
actions: ActionsRegistry;
|
|
75
|
+
/** Tool definitions keyed by id. Typically passes an empty Map. */
|
|
76
|
+
toolsById: ReadonlyMap<string, Tool>;
|
|
77
|
+
/** Default true. Set false to opt out of dispatcher wiring (e.g. demos that disable it). */
|
|
78
|
+
enabled?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Observer fired whenever the dispatcher synthesizes a double click, in
|
|
81
|
+
* world coordinates. Runs BEFORE the event is dispatched and independently
|
|
82
|
+
* of which binding (if any) handles it.
|
|
83
|
+
*
|
|
84
|
+
* This is deliberately not an Action. `<SceneCanvas onDoubleClick>` is a
|
|
85
|
+
* notification — "the user double-clicked, here's what they hit" — and a
|
|
86
|
+
* notification must not compete with behavior for the gesture. As a binding
|
|
87
|
+
* it would lose to `enterPathEdit` on any body hit and silently never fire.
|
|
88
|
+
* Routing it here keeps a single definition of "double click" (the point of
|
|
89
|
+
* consolidating the kit's three detectors) without giving it
|
|
90
|
+
* first-match-wins semantics it shouldn't have.
|
|
91
|
+
*/
|
|
92
|
+
onDoubleClick?: (world: {
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
}) => void;
|
|
96
|
+
/**
|
|
97
|
+
* Default true. Set false to leave the window `keydown`/`keyup` listeners
|
|
98
|
+
* unattached so keyboard-bound actions never dispatch — pointer, wheel, and
|
|
99
|
+
* contextmenu channels stay live. `<SceneCanvas>` wires this to
|
|
100
|
+
* `enableKeybindings`, so opting out of keybindings disables the modern
|
|
101
|
+
* dispatcher key path as well as the legacy `useKeybindings` hook.
|
|
102
|
+
*/
|
|
103
|
+
keyboard?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Optional affordance classifier. Called with the **world** coordinates of
|
|
106
|
+
* the pointer — this hook applies the routed view's `clientToWorld` before
|
|
107
|
+
* calling it, the same conversion that produces `InputEvent.x`/`y`, so a
|
|
108
|
+
* consumer hit-tests in one space. Returns an `AffordanceHit` when the
|
|
109
|
+
* pointer lands on a known affordance (resize handle, rotate handle, etc.)
|
|
110
|
+
* or `null` when the pointer hit open canvas.
|
|
111
|
+
*
|
|
112
|
+
* The hit is packed into `InputEvent.pointerdown.affordance` and flows
|
|
113
|
+
* through into `InvocationCtx.drag.affordance`. Action invokers that require
|
|
114
|
+
* a specific affordance (e.g. `resizeAction` requires `handle:*`) use this
|
|
115
|
+
* field as a guard in their `start` body.
|
|
116
|
+
*
|
|
117
|
+
* When omitted, `affordance` is always `undefined` — meaning only consumers
|
|
118
|
+
* that explicitly wire a classifier get affordance-gated behavior.
|
|
119
|
+
* `<SceneCanvas>` wires the full chrome→dispatcher bridge.
|
|
120
|
+
*/
|
|
121
|
+
affordanceAt?: (worldPoint: {
|
|
122
|
+
x: number;
|
|
123
|
+
y: number;
|
|
124
|
+
}) => AffordanceHit | null;
|
|
125
|
+
/**
|
|
126
|
+
* Optional body classifier. Called on every pointerdown with the world-space
|
|
127
|
+
* coordinates of the pointer. Its result is packed onto the event as
|
|
128
|
+
* `bodyTarget` + `bodyKind`, which `matchTarget` reads to resolve the
|
|
129
|
+
* string-form `TargetSpec` values in `Tool.bindings`.
|
|
130
|
+
*
|
|
131
|
+
* `body` is `'empty'` when nothing is under the pointer, `'selected-body'`
|
|
132
|
+
* when the topmost hit belongs to the current selection, or
|
|
133
|
+
* `'unselected-body'` when it belongs to a node that isn't selected. `kind`
|
|
134
|
+
* is the hit node's semantic kind, when the scene can name it.
|
|
135
|
+
*
|
|
136
|
+
* When omitted, every body-derived target form (`'empty'`,
|
|
137
|
+
* `'selected-body'`, `'unselected-body'`, `kind:<k>`, `kind:<k>:selected`)
|
|
138
|
+
* never matches — bindings using those specs are silently skipped.
|
|
139
|
+
* `<SceneCanvas>` wires this.
|
|
140
|
+
*
|
|
141
|
+
* World coordinates, on every path, for the same reason as `affordanceAt`.
|
|
142
|
+
*/
|
|
143
|
+
classifyTarget?: (worldPoint: {
|
|
144
|
+
x: number;
|
|
145
|
+
y: number;
|
|
146
|
+
}) => BodyClassification;
|
|
147
|
+
/**
|
|
148
|
+
* Optional pre-created `Dispatcher`. When provided, this hook pumps events
|
|
149
|
+
* into the supplied instance instead of creating its own. Lets a parent
|
|
150
|
+
* scope (e.g. `<SceneCanvas>`) share one dispatcher between the gesture
|
|
151
|
+
* mounter and other consumers (the preview-ghost layer).
|
|
152
|
+
*/
|
|
153
|
+
dispatcher?: Dispatcher;
|
|
154
|
+
/**
|
|
155
|
+
* Converts a client-space pointer position (e.g. `e.clientX`, `e.clientY`)
|
|
156
|
+
* to world-space coordinates. When supplied, every pointer/wheel event's
|
|
157
|
+
* `x`/`y` is converted before the dispatcher builds `InvocationCtx.world`.
|
|
158
|
+
*
|
|
159
|
+
* Without this, `ctx.world` is populated with the raw client coords, which
|
|
160
|
+
* silently breaks any action whose overlay/output uses absolute world
|
|
161
|
+
* positions (marquee, lasso polyline, world-space affordances). Actions
|
|
162
|
+
* that only read `drag.delta` are unaffected because client→world deltas
|
|
163
|
+
* are equal at scale 1, but as soon as a consumer pans/zooms the view the
|
|
164
|
+
* deltas diverge too.
|
|
165
|
+
*
|
|
166
|
+
* `<SceneCanvas>` always wires this via its canvas rect + current view.
|
|
167
|
+
* Tests / harnesses without a view can omit it and continue passing raw
|
|
168
|
+
* coordinates as before.
|
|
169
|
+
*/
|
|
170
|
+
clientToWorld?: (clientX: number, clientY: number) => {
|
|
171
|
+
x: number;
|
|
172
|
+
y: number;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Invoked once per pump event (`pointermove`, `pointerup`, `pointercancel`,
|
|
176
|
+
* `key-held` up-phase, `multitouch` move) when a dispatcher-side handle is
|
|
177
|
+
* in flight. Lets the host canvas schedule a redraw so dispatcher-only
|
|
178
|
+
* overlays (marquee, lasso, preview-ghost) repaint on each frame.
|
|
179
|
+
*
|
|
180
|
+
* Required because dispatcher actions don't go through the legacy
|
|
181
|
+
* `tools.dispatcher.onGestureChange` redraw bump that ambient tool drags
|
|
182
|
+
* relied on. Without it, the overlay layer's `draw` never runs between
|
|
183
|
+
* pointerdown and pointerup, and the chrome flashes once at gesture start
|
|
184
|
+
* and then sits frozen until the gesture ends.
|
|
185
|
+
*
|
|
186
|
+
* `<SceneCanvas>` wires this to the canvas's `requestRedraw`. Test
|
|
187
|
+
* harnesses can omit it; their dispatchers won't paint between events but
|
|
188
|
+
* that's already the test contract.
|
|
189
|
+
*/
|
|
190
|
+
requestRedraw?: () => void;
|
|
191
|
+
/**
|
|
192
|
+
* The surface's painted-cursor channel, read live because the canvas handle
|
|
193
|
+
* it lives on arrives a render after this hook runs.
|
|
194
|
+
*
|
|
195
|
+
* Given one, a cursor the CSS tier cannot express — sized in world units, or
|
|
196
|
+
* past the size the browser drops the image at — is published here and drawn
|
|
197
|
+
* by the painted-cursor layer instead. Without one, such a cursor resolves to
|
|
198
|
+
* `none` and the pointer shows nothing.
|
|
199
|
+
*/
|
|
200
|
+
paintedCursor?: () => PaintedCursorState | undefined;
|
|
201
|
+
/**
|
|
202
|
+
* Thunk returning the live `RuleCtx` for the current frame. When supplied,
|
|
203
|
+
* the dispatcher filters matched candidates by their declared
|
|
204
|
+
* `Action.eligible` rule (omitted => always eligible). `<SceneCanvas>`
|
|
205
|
+
* wires this; tests / harnesses without chrome-caps state can omit it.
|
|
206
|
+
*/
|
|
207
|
+
getRuleCtx?: () => RuleCtx | undefined;
|
|
208
|
+
/**
|
|
209
|
+
* Routing for a canvas hosting more than one view: the non-root dispatch
|
|
210
|
+
* records to choose between, read fresh per event, and the resolver that
|
|
211
|
+
* chooses. Omit for a single view — then every event runs on the record the
|
|
212
|
+
* flat options above describe, which is what they are: view zero.
|
|
213
|
+
*
|
|
214
|
+
* A resolved id with no live record falls back to the root, so a view that
|
|
215
|
+
* unmounts mid-gesture degrades instead of dropping the event.
|
|
216
|
+
*
|
|
217
|
+
* Keyboard and paste have no coordinates to resolve; they run on the view
|
|
218
|
+
* the last coordinate-bearing event resolved to.
|
|
219
|
+
*
|
|
220
|
+
* @internal
|
|
221
|
+
*/
|
|
222
|
+
views?: {
|
|
223
|
+
targets: () => readonly DispatcherViewTarget[];
|
|
224
|
+
resolver: ViewIdResolver;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Attach the input pipeline to a canvas: DOM pointer, wheel, keyboard and
|
|
229
|
+
* multitouch listeners in, normalized input events out, routed through the
|
|
230
|
+
* dispatcher to whichever action a binding names.
|
|
231
|
+
*
|
|
232
|
+
* Everything it needs beyond its options — the active tool, the dep registry —
|
|
233
|
+
* comes from context, so it must be mounted inside `<SceneCanvas>`'s
|
|
234
|
+
* providers.
|
|
235
|
+
*/
|
|
236
|
+
declare function useGestureDispatcher(opts: UseGestureDispatcherOptions): void;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @experimental
|
|
240
|
+
* The React half of the actions registry: the context, the provider that owns
|
|
241
|
+
* the id → descriptor stack, the mute scope, and the two hooks. The store
|
|
242
|
+
* contract it implements is in `registry.tsx`.
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @experimental
|
|
247
|
+
* A view of the registry in scope that can declare ids not for itself. Wrap
|
|
248
|
+
* anything that should be able to opt out of an action — a second
|
|
249
|
+
* `<SceneCanvas>` sharing the host's `<ActionsProvider>` mounts one — and its
|
|
250
|
+
* `mute` calls stay inside it. Renders nothing of its own.
|
|
251
|
+
*
|
|
252
|
+
* Not a `BindingScope`: that names the tier a binding matches at (hotkey /
|
|
253
|
+
* active / ambient), which this has nothing to do with.
|
|
254
|
+
*/
|
|
255
|
+
declare function ActionsScope({ children }: {
|
|
256
|
+
children: ReactNode;
|
|
257
|
+
}): ReactElement;
|
|
258
|
+
declare function ActionsProvider({ children }: {
|
|
259
|
+
children: ReactNode;
|
|
260
|
+
}): ReactElement;
|
|
261
|
+
declare function useActionsRegistry(): ActionsRegistry | null;
|
|
262
|
+
/**
|
|
263
|
+
* @experimental
|
|
264
|
+
* Register an `Action` for the lifetime of the calling component. No-op (with
|
|
265
|
+
* a dev-only warning) when no `ActionsProvider` is in scope. Re-registers on
|
|
266
|
+
* `action` reference change (consumers should memoize stable identities to
|
|
267
|
+
* avoid churn).
|
|
268
|
+
*/
|
|
269
|
+
declare function useAction(action: Action): void;
|
|
270
|
+
|
|
271
|
+
/** Which tool is active, plus the stack of tools temporarily held active by a
|
|
272
|
+
* hotkey (space-for-hand and the like). The dispatcher reads this to decide
|
|
273
|
+
* whose bindings are in scope. */
|
|
274
|
+
interface ActiveToolContextValue {
|
|
275
|
+
active: string;
|
|
276
|
+
hotkeyStack: string[];
|
|
277
|
+
setActive(id: string): void;
|
|
278
|
+
pushHotkey(id: string): void;
|
|
279
|
+
/**
|
|
280
|
+
* Disengage a hotkey-held tool. With `id`, removes that tool's own entry
|
|
281
|
+
* wherever it now sits — holds do not have to come up in the order they went
|
|
282
|
+
* down, and popping the top would disengage a tool whose key is still held.
|
|
283
|
+
* With no id, pops the top.
|
|
284
|
+
*/
|
|
285
|
+
popHotkey(id?: string): void;
|
|
286
|
+
}
|
|
287
|
+
/** Props for `<ActiveToolContextProvider>`. */
|
|
288
|
+
interface ActiveToolContextProviderProps {
|
|
289
|
+
children: ReactNode;
|
|
290
|
+
initialActive?: string;
|
|
291
|
+
}
|
|
292
|
+
/** Provides active-tool state for a canvas. `<SceneCanvas>` mounts one. */
|
|
293
|
+
declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react_jsx_runtime.JSX.Element;
|
|
294
|
+
/** The active-tool state in scope. Throws outside a provider; use
|
|
295
|
+
* `useActiveToolContextOptional` where one is not guaranteed. */
|
|
296
|
+
declare function useActiveToolContext(): ActiveToolContextValue;
|
|
297
|
+
/**
|
|
298
|
+
* Like `useActiveToolContext`, but returns `null` when no
|
|
299
|
+
* `<ActiveToolContextProvider>` is in scope instead of throwing. Used by
|
|
300
|
+
* `useStandardActions` to preserve its silent-no-op contract when no provider
|
|
301
|
+
* is present.
|
|
302
|
+
*/
|
|
303
|
+
declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
|
|
304
|
+
/**
|
|
305
|
+
* Conditional `<ActiveToolContextProvider>` wrapper. Mounts a provider only
|
|
306
|
+
* when no parent provider is in scope — otherwise renders children unwrapped
|
|
307
|
+
* so callers (e.g. `<WeaselProvider>`, `<SceneCanvas>`) defer to the host's
|
|
308
|
+
* existing scope. Mirrors `ActionsProviderIfRoot` / `DepRegistryProviderIfRoot`.
|
|
309
|
+
*/
|
|
310
|
+
declare function ActiveToolContextProviderIfRoot({ children, }: {
|
|
311
|
+
children: ReactNode;
|
|
312
|
+
}): react_jsx_runtime.JSX.Element;
|
|
313
|
+
|
|
314
|
+
/** Options for `useTools`: which tools exist, which one starts active, and
|
|
315
|
+
* which run continuously regardless of the active one. */
|
|
316
|
+
interface UseToolsOptions<TOverlay = unknown> {
|
|
317
|
+
/** Initial active-slot tool id. Must exist in `registry`. */
|
|
318
|
+
active: string;
|
|
319
|
+
/** Tools eligible for the active slot or hotkey slot. The keys are the
|
|
320
|
+
* tool ids; the values are the tool records. A tool with `hotkey` set
|
|
321
|
+
* is wired into the hotkey slot whenever the engagement state matches. */
|
|
322
|
+
registry: Record<string, AnyToolOf<TOverlay>>;
|
|
323
|
+
/** Always-on tools — listen continuously regardless of active slot. */
|
|
324
|
+
ambient?: AnyToolOf<TOverlay>[];
|
|
325
|
+
}
|
|
326
|
+
/** The tool registry's runtime surface: which tool is active, which is
|
|
327
|
+
* temporarily held by a hotkey, and how to change either. */
|
|
328
|
+
interface ToolsApi<TOverlay = unknown> {
|
|
329
|
+
/** Current active-slot tool id. */
|
|
330
|
+
active: string;
|
|
331
|
+
/** Set the active-slot tool. The gesture dispatcher watches the active
|
|
332
|
+
* tool and cancels any in-flight handle itself. */
|
|
333
|
+
setActive: (id: string) => void;
|
|
334
|
+
/** Currently hotkey-engaged tool id (or `null`). Derived as the top of
|
|
335
|
+
* the hotkey stack for backwards compat with the pre-stack API. */
|
|
336
|
+
hotkeyEngaged: string | null;
|
|
337
|
+
/** Engage a hotkey-slot tool by id. */
|
|
338
|
+
engageHotkey: (id: string) => void;
|
|
339
|
+
/** Disengage the hotkey-slot tool, if any. */
|
|
340
|
+
disengageHotkey: () => void;
|
|
341
|
+
/** All always-on tools, in registration order. */
|
|
342
|
+
ambient: readonly AnyToolOf<TOverlay>[];
|
|
343
|
+
/** Full registry — for userland UI (palette buttons, etc.). */
|
|
344
|
+
registry: Readonly<Record<string, AnyToolOf<TOverlay>>>;
|
|
345
|
+
/** Returns true if a tool with the given id is in the registry or ambient list. */
|
|
346
|
+
has(id: string): boolean;
|
|
347
|
+
/** All overlay layers from currently-engaged tools (active slot, hotkey
|
|
348
|
+
* slot if engaged, all ambient slot tools) that declare `position`.
|
|
349
|
+
* Filters out tools with no `overlay` field. Order: active, then hotkey
|
|
350
|
+
* (if engaged), then ambient (registration order). */
|
|
351
|
+
getActiveOverlays(position?: OverlayPosition): TOverlay[];
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Manages the active tool and hotkey slot.
|
|
355
|
+
*
|
|
356
|
+
* A thin shim over {@link useContributions}: the `registry` / `ambient`
|
|
357
|
+
* arguments are restated as declared eligibility (`focus` / `always`), and
|
|
358
|
+
* assembly happens in one place for tools and non-tool contributions alike.
|
|
359
|
+
*
|
|
360
|
+
* Requires `<ActiveToolContextProvider>` (or `<WeaselProvider>` /
|
|
361
|
+
* `<SceneCanvas>`, which mount one internally) in scope: active/hotkey state
|
|
362
|
+
* lives in the context so the gesture dispatcher and any sibling
|
|
363
|
+
* `useTools` calls all read the same source of truth.
|
|
364
|
+
*
|
|
365
|
+
* **First-mount-wins semantics**: if `opts.active` differs from the context
|
|
366
|
+
* default (`'select'`) on first mount, `useTools` pushes `opts.active` to
|
|
367
|
+
* the context. Subsequent mounts respect whatever the context currently
|
|
368
|
+
* holds (the first caller wins).
|
|
369
|
+
*/
|
|
370
|
+
declare function useTools<TOverlay = unknown>(opts: UseToolsOptions<TOverlay>): ToolsApi<TOverlay>;
|
|
371
|
+
|
|
372
|
+
interface UseContributionsOptions<TOverlay = unknown> {
|
|
373
|
+
/** Every registry entry, in declaration order. Order decides which of two
|
|
374
|
+
* same-specificity bindings in one scope tier wins. */
|
|
375
|
+
entries: readonly Contribution<TOverlay>[];
|
|
376
|
+
/** Desired focused entry id. First-mount-wins against the shared
|
|
377
|
+
* `ActiveToolContext` — see `useTools` for the full semantics. */
|
|
378
|
+
focused: string;
|
|
379
|
+
}
|
|
380
|
+
interface ContributionsApi<TOverlay = unknown> {
|
|
381
|
+
/** Every entry, as passed in. */
|
|
382
|
+
entries: readonly Contribution<TOverlay>[];
|
|
383
|
+
/** Currently focused entry id. */
|
|
384
|
+
focused: string;
|
|
385
|
+
/** Focus an entry. Writes through to the shared `ActiveToolContext`. */
|
|
386
|
+
setFocused: (id: string) => void;
|
|
387
|
+
/** Every live binding, tiered by the declaring entry's eligibility. */
|
|
388
|
+
scopedBindings(): ScopedBinding[];
|
|
389
|
+
/** Overlays of every live entry declaring `position`, ordered active, then
|
|
390
|
+
* hotkey, then ambient. */
|
|
391
|
+
overlays(position?: OverlayPosition): TOverlay[];
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Assembles one registry: every entry's bindings and overlays, each tiered by
|
|
395
|
+
* what the entry declares about its own eligibility rather than by which
|
|
396
|
+
* argument a consumer passed it in.
|
|
397
|
+
*
|
|
398
|
+
* Requires `<ActiveToolContextProvider>` (or `<WeaselProvider>` /
|
|
399
|
+
* `<SceneCanvas>`, which mount one internally): focus and held-key state live
|
|
400
|
+
* in the context so the gesture dispatcher and every sibling caller read the
|
|
401
|
+
* same source of truth.
|
|
402
|
+
*/
|
|
403
|
+
declare function useContributions<TOverlay = unknown>(opts: UseContributionsOptions<TOverlay>): ContributionsApi<TOverlay>;
|
|
404
|
+
|
|
405
|
+
export { ActionsProvider, ActionsScope, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, type ActiveToolContextProviderProps, type ActiveToolContextValue, type ContributionsApi, type DispatcherViewTarget, type ToolsApi, type UseContributionsOptions, type UseGestureDispatcherOptions, type UseToolsOptions, type ViewIdResolver, useAction, useActionsRegistry, useActiveToolContext, useContributions, useGestureDispatcher, useOptionalActiveToolContext, useTools };
|