@weasel-js/core 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/dist/{chunk-7F3SDUJ4.js → chunk-2J6V527H.js} +1555 -1151
- package/dist/chunk-2J6V527H.js.map +1 -0
- package/dist/{chunk-FSZEXVCR.js → chunk-SYM6RAM4.js} +66 -5
- package/dist/chunk-SYM6RAM4.js.map +1 -0
- package/dist/{index-D31EADQG.d.ts → index-DOYRTfP0.d.ts} +156 -17
- package/dist/index.d.ts +352 -26
- package/dist/index.js +2 -2
- package/dist/renderer.d.ts +26 -2
- package/dist/renderer.js +2 -2
- package/dist/routing.d.ts +1 -1
- package/dist/routing.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-7F3SDUJ4.js.map +0 -1
- package/dist/chunk-FSZEXVCR.js.map +0 -1
|
@@ -19,11 +19,14 @@ __export(routing_exports, {
|
|
|
19
19
|
describeRoute: () => describeRoute,
|
|
20
20
|
describeRouteParts: () => describeRouteParts,
|
|
21
21
|
findConflicts: () => findConflicts,
|
|
22
|
+
findScopedConflicts: () => findScopedConflicts,
|
|
23
|
+
formatConflict: () => formatConflict,
|
|
22
24
|
formatPhaseAtom: () => formatPhaseAtom,
|
|
23
25
|
formatRoute: () => formatRoute,
|
|
24
26
|
getGestureDescriptor: () => getGestureDescriptor,
|
|
25
27
|
isKnownGestureName: () => isKnownGestureName,
|
|
26
|
-
parseRoute: () => parseRoute
|
|
28
|
+
parseRoute: () => parseRoute,
|
|
29
|
+
reportRouteConflicts: () => reportRouteConflicts
|
|
27
30
|
});
|
|
28
31
|
|
|
29
32
|
// src/tools/routing/defineTool.ts
|
|
@@ -145,7 +148,7 @@ function findConflicts(tools) {
|
|
|
145
148
|
const entries = buildRouteRegistry(tools);
|
|
146
149
|
const groups = /* @__PURE__ */ new Map();
|
|
147
150
|
for (const entry of entries) {
|
|
148
|
-
const key = `${entry.phase}|${entry.gesture}|${entry.arg ?? ""}|${entry
|
|
151
|
+
const key = `${entry.phase}|${entry.gesture}|${entry.arg ?? ""}|${targetKey(entry)}|${canonicalModifiers(entry.modifiers)}`;
|
|
149
152
|
const bucket = groups.get(key);
|
|
150
153
|
if (bucket) bucket.push(entry);
|
|
151
154
|
else groups.set(key, [entry]);
|
|
@@ -165,7 +168,65 @@ function findConflicts(tools) {
|
|
|
165
168
|
}
|
|
166
169
|
return conflicts;
|
|
167
170
|
}
|
|
171
|
+
function targetKey(entry) {
|
|
172
|
+
if (entry.target !== PREDICATE_TARGET) return entry.target ?? "";
|
|
173
|
+
const spec = entry.spec;
|
|
174
|
+
const pred = spec.target;
|
|
175
|
+
if (typeof pred !== "object" || pred === null) return PREDICATE_TARGET;
|
|
176
|
+
return `${PREDICATE_TARGET}#${predicateId(pred)}`;
|
|
177
|
+
}
|
|
178
|
+
var PREDICATE_IDS = /* @__PURE__ */ new WeakMap();
|
|
179
|
+
var nextPredicateId = 0;
|
|
180
|
+
function predicateId(pred) {
|
|
181
|
+
let id = PREDICATE_IDS.get(pred);
|
|
182
|
+
if (id === void 0) {
|
|
183
|
+
id = nextPredicateId++;
|
|
184
|
+
PREDICATE_IDS.set(pred, id);
|
|
185
|
+
}
|
|
186
|
+
return id;
|
|
187
|
+
}
|
|
188
|
+
function findScopedConflicts(scopes) {
|
|
189
|
+
const registry = Array.isArray(scopes.registry) ? scopes.registry : Object.values(scopes.registry);
|
|
190
|
+
const ambient = scopes.ambient ?? [];
|
|
191
|
+
const out = [];
|
|
192
|
+
const seen = /* @__PURE__ */ new Set();
|
|
193
|
+
const add = (conflicts) => {
|
|
194
|
+
for (const c of conflicts) {
|
|
195
|
+
const key = `${c.phase}|${c.gesture}|${c.arg ?? ""}|${c.target ?? ""}|${canonicalModifiers(c.modifiers)}|${c.toolIds.join(",")}`;
|
|
196
|
+
if (seen.has(key)) continue;
|
|
197
|
+
seen.add(key);
|
|
198
|
+
out.push(c);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
for (const tool of [...registry, ...ambient]) add(findConflicts([tool]));
|
|
202
|
+
if (ambient.length > 1) add(findConflicts(ambient));
|
|
203
|
+
const hotkeyTools = registry.filter(
|
|
204
|
+
(t) => t.def?.hotkey !== void 0
|
|
205
|
+
);
|
|
206
|
+
if (hotkeyTools.length > 1) add(findConflicts(hotkeyTools));
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
function formatConflict(conflict) {
|
|
210
|
+
const route = formatRoute({
|
|
211
|
+
phases: [{ channel: "&", phase: conflict.phase === "any" ? "*" : conflict.phase }],
|
|
212
|
+
gesture: conflict.gesture,
|
|
213
|
+
arg: conflict.arg,
|
|
214
|
+
target: conflict.target,
|
|
215
|
+
modifiers: conflict.modifiers
|
|
216
|
+
});
|
|
217
|
+
return `${route} \u2014 declared by ${conflict.toolIds.join(", ")}`;
|
|
218
|
+
}
|
|
219
|
+
function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
|
|
220
|
+
const conflicts = findScopedConflicts(scopes);
|
|
221
|
+
for (const c of conflicts) {
|
|
222
|
+
warn(
|
|
223
|
+
`[weasel] route conflict: two bindings declare the same (phase, gesture, arg, target, modifiers) tuple in the same scope, so declaration order alone decides which one fires.
|
|
224
|
+
${formatConflict(c)}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return conflicts;
|
|
228
|
+
}
|
|
168
229
|
|
|
169
|
-
export { PREDICATE_TARGET, buildRouteRegistry, defineTool, defineViewportTool, findConflicts, routing_exports };
|
|
170
|
-
//# sourceMappingURL=chunk-
|
|
171
|
-
//# sourceMappingURL=chunk-
|
|
230
|
+
export { PREDICATE_TARGET, buildRouteRegistry, defineTool, defineViewportTool, findConflicts, findScopedConflicts, formatConflict, reportRouteConflicts, routing_exports };
|
|
231
|
+
//# sourceMappingURL=chunk-SYM6RAM4.js.map
|
|
232
|
+
//# sourceMappingURL=chunk-SYM6RAM4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/routing/index.ts","../src/tools/routing/defineTool.ts","../src/tools/routing/defineViewportTool.ts","../src/tools/routing/reflection/registry.ts","../src/tools/routing/reflection/conflicts.ts"],"names":[],"mappings":"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","file":"chunk-SYM6RAM4.js","sourcesContent":["export type { ToolDef, ViewportToolDef, ToolKeybinding } from './types';\nexport { parseRoute, formatRoute, formatPhaseAtom, collapseShiftPairs, describeRoute, describeRouteParts, canonicalModifiers, ROUTE_TERMS, ROUTE_FIELD_DEFINITIONS, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from './routeGrammar';\nexport type { ParsedRoute, ParsedModifiers, ModifierKey, ModRequirement, PhaseAtom, ChannelRef, DescribeRouteOptions, RouteDescriptionPart, RouteTermLabel, RouteFieldName } from './routeGrammar';\nexport { getGestureDescriptor, isKnownGestureName, GESTURE_DESCRIPTORS } from './gestures';\nexport type { GestureName, GestureDescriptor, GestureArgSpec } from './gestures';\nexport { defineTool } from './defineTool';\nexport { defineViewportTool } from './defineViewportTool';\n\n// Reflection consumers — registry / conflict checker / debug overlay.\nexport * from './reflection';\n","// src/tools/routing/defineTool.ts\nimport type { Tool, ToolCtx } from '../types';\nimport type { ToolDef } from './types';\nimport { RESERVED_ID_NAMES, RESERVED_ID_PREFIXES } from './routeGrammar';\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>(\n def: ToolDef<TScratch>,\n): Tool<TScratch> {\n validateId(def.id, 'tool');\n\n // Normalize `string | ((ctx) => string)` to the function form so callers\n // have one shape to deal with. Returns '' (not undefined) so the signature\n // satisfies `Tool.cursor: (ctx) => string`.\n const resolveCursor = (ctx: ToolCtx<TScratch>): string => {\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 capabilities: def.capabilities,\n actions: def.actions,\n def,\n presentation: def.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 };\n}\n","import type { Tool } from '../types';\nimport type { ViewportToolDef } from './types';\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>(\n def: ViewportToolDef<TScratch>,\n): Tool<TScratch> {\n return defineTool<TScratch>(def);\n}\n","import type { Tool } from '../../types';\nimport type { GestureSpec, ModSpec, TargetSpec, PhaseSpec } from '@weasel-js/gestures';\nimport type { ParsedModifiers, ModifierKey } from '../routeGrammar';\nimport { getGestureDescriptor, isKnownGestureName, type GestureName } from '../gestures';\n\n/**\n * One row in the route registry — a single `GestureBinding` on one tool,\n * flattened into the route grammar's vocabulary so the inspector, the\n * conflict checker, and `describeRoute` can all read the same shape.\n *\n * Multiple rows can share (gesture, arg, target, modifiers) if different tools\n * declare them; consumers walk the list and group client-side.\n */\nexport interface RegistryEntry {\n toolId: string;\n /** Which phase the binding's `phase` spec restricts it to. `'any'` when it\n * declares none, declares `'*'`, or declares an atom list whose atoms\n * don't agree on one phase — such a binding fires in either phase, and\n * reporting it as `'initial'` made it collide with genuinely-initial\n * bindings in `findConflicts`' bucket key. */\n phase: 'initial' | 'engaged' | 'any';\n /** Structured v3 modifier requirements. Empty object = \"no modifiers\n * held\" (the strict default). */\n modifiers: ParsedModifiers;\n gesture: GestureName;\n /** Resolved arg value for arg-bearing gestures (wheel direction,\n * key name, multiTouchTap fingers). Undefined for gestures whose\n * descriptor has no `arg`. */\n arg: string | undefined;\n /** Target class for hit-testing gestures. `undefined` when the descriptor\n * has `hasTarget: false` or the spec declares no target;\n * {@link PREDICATE_TARGET} when the spec uses a `kindOf` predicate, which\n * the route grammar has no notation for. */\n target: string | undefined;\n /** The action this binding fires. */\n actionId: string;\n /** The `GestureSpec` this row was flattened from, by reference. Reflection\n * consumers that need something the grammar doesn't capture — the\n * specificity tuple, a `kindOf` predicate identity — read it here rather\n * than re-walking `Tool.bindings`. */\n spec: GestureSpec;\n}\n\n/**\n * Stand-in target for a `{ kindOf }` predicate spec.\n *\n * The route grammar can name target *classes* (`empty`, `selected-body`,\n * `kind:rect`) but not arbitrary predicates, so three distinct select-tool\n * bindings all render as this one token. Narrowing it would mean giving the\n * grammar a way to describe a function, which is a real design question and\n * not one this reflection layer should answer by inventing syntax.\n */\nexport const PREDICATE_TARGET = 'predicate';\n\n/** `GestureSpec.kind` → route-grammar gesture name. `multiTouch` has no\n * route-grammar gesture (only its tap synthesis does), and `drop` / `paste`\n * shipped without grammar names, so specs of those kinds are skipped. */\nconst SPEC_KIND_TO_GESTURE: Record<GestureSpec['kind'], GestureName | undefined> = {\n key: 'keyDown',\n 'key-held': 'keyHeld',\n wheel: 'wheel',\n click: 'click',\n doubleClick: 'dblTap',\n contextMenu: 'contextMenu',\n drag: 'drag',\n pointerDown: 'pointerDown',\n multiTouch: undefined,\n multiTouchTap: 'multiTouchTap',\n drop: undefined,\n paste: undefined,\n};\n\n/**\n * Flatten every tool's `bindings` into route-registry rows.\n *\n * This was `buildActionRegistry`, and it walked `ToolDef.initial` /\n * `.engaged` phase tables — the grammar that no longer exists. It was also\n * blind to `Tool.bindings` the entire time the two lived side by side, which\n * is why the inspector under-reported select by more than half. The rename\n * fixes a second thing: it never had anything to do with the Actions\n * Registry, and reading `buildActionRegistry` next to `ActionsRegistry`\n * suggested otherwise.\n */\nexport function buildRouteRegistry(\n tools: readonly Tool<unknown>[],\n): RegistryEntry[] {\n const out: RegistryEntry[] = [];\n for (const tool of tools) {\n for (const binding of tool.bindings ?? []) {\n const entry = entryFor(tool.id, binding.spec, binding.actionId);\n if (entry) out.push(entry);\n }\n }\n return out;\n}\n\nfunction entryFor(\n toolId: string,\n spec: GestureSpec,\n actionId: string,\n): RegistryEntry | null {\n const gesture = SPEC_KIND_TO_GESTURE[spec.kind];\n if (!gesture || !isKnownGestureName(gesture)) return null;\n const descriptor = getGestureDescriptor(gesture);\n return {\n toolId,\n actionId,\n gesture,\n phase: phaseOf('phase' in spec ? spec.phase : undefined),\n modifiers: parseModSpec('mods' in spec ? spec.mods : undefined),\n arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : undefined,\n target: descriptor.hasTarget\n ? targetOf('target' in spec ? spec.target : undefined)\n : undefined,\n spec,\n };\n}\n\n/** Collapse a `PhaseSpec` to the single phase a binding is restricted to, or\n * `'any'` when it isn't restricted to one.\n *\n * `PhaseAtom.channel` says which channel's phase state the binding reads,\n * not which phase it fires in, so it plays no part in this collapse — only\n * the set of distinct `atom.phase` values matters. */\nfunction phaseOf(phase: PhaseSpec | undefined): 'initial' | 'engaged' | 'any' {\n if (phase === undefined || phase === '*') return 'any';\n if (phase === 'initial' || phase === 'engaged') return phase;\n const distinct = new Set(phase.map((atom) => atom.phase));\n if (distinct.size !== 1) return 'any';\n const only = [...distinct][0];\n return only === 'initial' || only === 'engaged' ? only : 'any';\n}\n\n/** `ModSpec` (per-key tri-state) → `ParsedModifiers`. `false` and absent both\n * mean \"must not be held\", which the parsed form spells as an absent key. */\nfunction parseModSpec(mods: ModSpec | undefined): ParsedModifiers {\n if (!mods) return {};\n const out: ParsedModifiers = {};\n for (const [name, req] of Object.entries(mods)) {\n if (req === true) out[name as ModifierKey] = 'required';\n else if (req === 'optional') out[name as ModifierKey] = 'optional';\n }\n return out;\n}\n\nfunction targetOf(target: TargetSpec | undefined): string | undefined {\n if (target === undefined) return undefined;\n return typeof target === 'string' ? target : PREDICATE_TARGET;\n}\n\nfunction argOf(spec: GestureSpec, fallback: string | undefined): string | undefined {\n if ('key' in spec) {\n return Array.isArray(spec.key) ? spec.key.join('|') : spec.key;\n }\n if ('fingers' in spec) return String(spec.fingers);\n if ('direction' in spec) return spec.direction ?? fallback;\n return fallback;\n}\n\n// Re-export for downstream consumers.\nexport { getGestureDescriptor };\nexport type { GestureName };\n","import type { Tool } from '../../types';\nimport type { ParsedModifiers } from '../routeGrammar';\nimport { canonicalModifiers, formatRoute } from '../routeGrammar';\nimport { buildRouteRegistry, PREDICATE_TARGET, type RegistryEntry, type GestureName } from './registry';\n\n/** Two or more tools declare the same exact (phase, gesture, arg, target,\n * modifiers) tuple — the dispatcher's slot precedence picks one\n * arbitrarily (well, deterministically by slot order, but the author\n * probably didn't intend the duplication). */\nexport interface Conflict {\n phase: 'initial' | 'engaged' | 'any';\n gesture: GestureName;\n arg: string | undefined;\n target: string | undefined;\n modifiers: ParsedModifiers;\n /** All tool ids that registered the same tuple. At least 2 by\n * construction. Order matches the input tools[] order. */\n toolIds: string[];\n}\n\n/** Detect exact-tuple overlaps across a tool registration set.\n *\n * Intentionally NOT flagged:\n * - Broad vs. narrow targets (e.g. an untargeted `click` alongside\n * `click` on `empty`) — the dispatcher's specificity ordering resolves\n * those cleanly, and the broad one is usually the intended fallback.\n * - Different modifier requirements on the same target — they fire on\n * different inputs.\n * - A binding whose action declines via `enabled()` so a lower-priority\n * one can take the gesture. Detecting that intent would mean evaluating\n * the action; consumers can suppress known-intentional compositions in\n * their UI layer.\n *\n * - Two `{ kindOf }` predicate targets. The route grammar renders every\n * predicate as the single token {@link PREDICATE_TARGET}, so bucketing on\n * the rendered target alone reported select's `resize` / `rotate` / `move`\n * drags — three genuinely different predicates — as a three-way conflict.\n * Predicate entries bucket by function identity instead, which means two\n * *separately written but equivalent* predicates go unflagged. That's the\n * right way to be wrong here: this check has to be silent when nothing is\n * wrong or nobody will keep it on.\n *\n * Note that two bindings sharing a tuple on the SAME tool are now possible\n * (bindings are an array, where phase tables were objects with unique\n * keys) — so a conflict may name one tool twice.\n *\n * This is the raw same-tuple detector. Feeding it a whole tool *registry*\n * over-reports, because registry tools take turns in the active slot and\n * can't collide with each other — see {@link findScopedConflicts}.\n */\nexport function findConflicts(\n tools: readonly Tool<unknown>[],\n): Conflict[] {\n const entries = buildRouteRegistry(tools);\n const groups = new Map<string, RegistryEntry[]>();\n for (const entry of entries) {\n const key = `${entry.phase}|${entry.gesture}|${entry.arg ?? ''}|${targetKey(entry)}|${canonicalModifiers(entry.modifiers)}`;\n const bucket = groups.get(key);\n if (bucket) bucket.push(entry);\n else groups.set(key, [entry]);\n }\n const conflicts: Conflict[] = [];\n for (const bucket of groups.values()) {\n if (bucket.length < 2) continue;\n const first = bucket[0];\n conflicts.push({\n phase: first.phase,\n gesture: first.gesture,\n arg: first.arg,\n target: first.target,\n modifiers: first.modifiers,\n toolIds: bucket.map((e) => e.toolId),\n });\n }\n return conflicts;\n}\n\n/** Bucket key for an entry's target slot. Predicate targets all render as\n * the same grammar token, so they're keyed by the predicate's identity —\n * two different functions are two different targets. */\nfunction targetKey(entry: RegistryEntry): string {\n if (entry.target !== PREDICATE_TARGET) return entry.target ?? '';\n const spec = entry.spec as { target?: unknown };\n const pred = spec.target;\n if (typeof pred !== 'object' || pred === null) return PREDICATE_TARGET;\n return `${PREDICATE_TARGET}#${predicateId(pred)}`;\n}\n\nconst PREDICATE_IDS = new WeakMap<object, number>();\nlet nextPredicateId = 0;\n\nfunction predicateId(pred: object): number {\n let id = PREDICATE_IDS.get(pred);\n if (id === undefined) {\n id = nextPredicateId++;\n PREDICATE_IDS.set(pred, id);\n }\n return id;\n}\n\n/**\n * The tool scopes as the dispatcher sees them — which is what decides whether\n * two same-tuple bindings can actually collide.\n */\nexport interface ToolScopes {\n /** Tools eligible for the active slot, keyed by id or as a flat list. */\n registry: readonly Tool<unknown>[] | Readonly<Record<string, Tool<unknown>>>;\n /** Always-on tools. Every one of these is live at once. */\n ambient?: readonly Tool<unknown>[];\n}\n\n/**\n * Detect the same-tuple overlaps that are *reachable* — the ones where the\n * dispatcher really does fall back on declaration order.\n *\n * `matchSorted` walks scopes in strict priority (hotkey > active > ambient)\n * and only sorts by specificity *within* a scope. So a cross-scope tie isn't\n * a tie at all: an ambient tool losing a tuple to the active tool is the\n * documented design, not an accident. Likewise two registry tools sharing a\n * tuple — `rect` and `ellipse` both binding a bare `drag` — can never both be\n * in the active slot, so they never compete.\n *\n * What's left, and what this reports:\n * - a single tool colliding with **itself** (possible since bindings became\n * an array), which is ambiguous in whichever slot it occupies;\n * - two **ambient** tools, all of which are live simultaneously and are\n * ordered only by registration;\n * - two **hotkey-capable** tools, which can stack.\n *\n * Not covered: the actions registry's `defaultBinding`s, which the dispatcher\n * also folds into ambient/hotkey scope. They're assembled somewhere else\n * entirely (`ActionsRegistry`, not `useTools`), so catching tool-vs-action\n * collisions means giving this function a second input it doesn't have yet.\n */\nexport function findScopedConflicts(scopes: ToolScopes): Conflict[] {\n const registry = Array.isArray(scopes.registry)\n ? (scopes.registry as readonly Tool<unknown>[])\n : Object.values(scopes.registry as Readonly<Record<string, Tool<unknown>>>);\n const ambient = scopes.ambient ?? [];\n\n const out: Conflict[] = [];\n const seen = new Set<string>();\n const add = (conflicts: readonly Conflict[]): void => {\n for (const c of conflicts) {\n const key = `${c.phase}|${c.gesture}|${c.arg ?? ''}|${c.target ?? ''}`\n + `|${canonicalModifiers(c.modifiers)}|${c.toolIds.join(',')}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(c);\n }\n };\n\n // Self-collisions: every tool, whichever slot it lands in.\n for (const tool of [...registry, ...ambient]) add(findConflicts([tool]));\n // Ambient tools are all live together.\n if (ambient.length > 1) add(findConflicts(ambient));\n // Hotkey-capable tools can stack on each other. `hotkey` is declared on the\n // authored `ToolDef`, not carried onto the runtime `Tool` — `Tool.def` is\n // the reflection handle for exactly this kind of read, and it's typed\n // `unknown` so consumers cast at the use site.\n const hotkeyTools = registry.filter(\n (t) => (t.def as { hotkey?: unknown } | undefined)?.hotkey !== undefined,\n );\n if (hotkeyTools.length > 1) add(findConflicts(hotkeyTools));\n\n return out;\n}\n\n/**\n * Render a conflict as one line of human-readable text.\n *\n * The tuple is printed through {@link formatRoute}, so the message names the\n * collision in the same grammar the author wrote the binding in — modulo the\n * phase slot, which prints as a bare `'&'`-channel atom because the bucket key\n * collapses channel-bearing phase specs (`sel:engaged` and `&:engaged` collide\n * with each other, and the collapse is what made them collide).\n */\nexport function formatConflict(conflict: Conflict): string {\n const route = formatRoute({\n phases: [{ channel: '&', phase: conflict.phase === 'any' ? '*' : conflict.phase }],\n gesture: conflict.gesture,\n arg: conflict.arg,\n target: conflict.target,\n modifiers: conflict.modifiers,\n });\n return `${route} — declared by ${conflict.toolIds.join(', ')}`;\n}\n\n/**\n * Detect reachable route conflicts in an assembled tool set and report each one.\n *\n * This is the wiring `findConflicts` spent its first life without: the kit\n * could detect the one class of genuine routing ambiguity it has and never\n * looked. Call it once wherever a tool set is assembled (`useTools` does),\n * behind a `process.env.NODE_ENV !== 'production'` guard.\n *\n * **Warn, never throw.** A conflict between a consumer's tool and a kit tool\n * is a design question — sometimes deliberate, since the loser can still take\n * the gesture by declining through `enabled()` — and exploding in a running\n * app is the wrong way to raise it. A conflict between two *kit* tools is\n * always a bug, but the place to fail on that is the kit's own test suite\n * (`canvas/SceneCanvas.routeConflicts.test.tsx`), not a consumer's console.\n *\n * @returns The conflicts found, so callers can dedupe repeat reports.\n */\nexport function reportRouteConflicts(\n scopes: ToolScopes,\n warn: (message: string) => void = (m) => console.warn(m),\n): Conflict[] {\n const conflicts = findScopedConflicts(scopes);\n for (const c of conflicts) {\n warn(\n `[weasel] route conflict: two bindings declare the same (phase, gesture, arg, target, modifiers) tuple `\n + `in the same scope, so declaration order alone decides which one fires.\\n ${formatConflict(c)}`,\n );\n }\n return conflicts;\n}\n"]}
|
|
@@ -122,6 +122,17 @@ interface AffordanceRegion<TScratch = unknown> {
|
|
|
122
122
|
innerY: number;
|
|
123
123
|
innerWidth: number;
|
|
124
124
|
innerHeight: number;
|
|
125
|
+
/** Minimum band thickness outside the inner rect, in **screen**
|
|
126
|
+
* pixels. The framework widens `rx`/`ry` to at least
|
|
127
|
+
* `innerHalfExtent + minBandPx / meanScale(view.scale)` for both
|
|
128
|
+
* paint and hit-test.
|
|
129
|
+
*
|
|
130
|
+
* This exists because the clamp has to know the view and the
|
|
131
|
+
* affordance doesn't: `ChromeState` carries no scale. Expressing the
|
|
132
|
+
* floor in world units instead (which is what the rotate ring used
|
|
133
|
+
* to do) makes the band shrink on screen as you zoom in, until the
|
|
134
|
+
* ring around a small shape is too thin to hover. */
|
|
135
|
+
minBandPx?: number;
|
|
125
136
|
};
|
|
126
137
|
/** Optional paint. World position is derived from `shape` + target
|
|
127
138
|
* transform; visual size stays in screen pixels (so handles don't
|
|
@@ -146,13 +157,16 @@ interface AffordanceRegion<TScratch = unknown> {
|
|
|
146
157
|
kind: 'custom';
|
|
147
158
|
draw: (ctx: CustomPaintContext) => DrawCommand[];
|
|
148
159
|
};
|
|
149
|
-
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
|
|
160
|
+
/** Discriminator a press on this region reports as `AffordanceHit.kind` —
|
|
161
|
+
* the string routing specs match on (`'handle:top-left'`,
|
|
162
|
+
* `'rotate-handle'`, `'anchor:3'`). Omit for regions that only exist
|
|
163
|
+
* inside a consumer-registered layer, where the layer id is the
|
|
164
|
+
* discriminator; `buildAffordanceAt` falls back to
|
|
165
|
+
* `<affordanceId>:<regionId>`. */
|
|
166
|
+
hitKind?: string;
|
|
167
|
+
/** CSS cursor to show while hovering this region. Read by the hover-cursor
|
|
168
|
+
* pump in `useGestureDispatcher` via `AffordanceHit.cursor`, which
|
|
169
|
+
* `buildAffordanceAt` fills in from the region the walk landed on. */
|
|
156
170
|
cursor?: string;
|
|
157
171
|
/** Drag binding produced when this region is hit. Lazily called so
|
|
158
172
|
* affordances don't pay binding-construction cost on every paint frame —
|
|
@@ -196,6 +210,35 @@ interface CustomPaintContext {
|
|
|
196
210
|
interface AffordanceBinding<TScratch = unknown> {
|
|
197
211
|
initialScratch?: TScratch;
|
|
198
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* The fields `buildAffordanceAt` lifts out of a region's `initialScratch`
|
|
215
|
+
* when it turns a region hit into an `AffordanceHit`.
|
|
216
|
+
*
|
|
217
|
+
* Scratch is otherwise opaque — whatever the affordance wants to hand the
|
|
218
|
+
* action that picks up the drag. These few names are the exception: they mean
|
|
219
|
+
* the same thing to every affordance, and the actions that consume them
|
|
220
|
+
* (`resizeAction`, `rotateAction`) read them off `AffordanceHit` rather than
|
|
221
|
+
* out of scratch. An affordance that doesn't set them simply produces a hit
|
|
222
|
+
* without those fields.
|
|
223
|
+
*/
|
|
224
|
+
interface CommonAffordanceScratch {
|
|
225
|
+
/** The node (or `MULTI_RESIZE_TARGET_ID`) this chrome acts on. Becomes
|
|
226
|
+
* `AffordanceHit.targetIds`. */
|
|
227
|
+
targetId?: string;
|
|
228
|
+
/** For resize chrome: which corner stays pinned. Mirrors the kit's
|
|
229
|
+
* `ResizeAnchor`, spelled inline so `affordances/` doesn't take a type
|
|
230
|
+
* dependency on the gesture layer for one field. */
|
|
231
|
+
anchor?: {
|
|
232
|
+
x: 'min' | 'max' | 'free';
|
|
233
|
+
y: 'min' | 'max' | 'free';
|
|
234
|
+
};
|
|
235
|
+
/** World-space invariant point of the transform — the fixed corner for a
|
|
236
|
+
* resize, the pivot for a rotation. */
|
|
237
|
+
fixedPoint?: {
|
|
238
|
+
x: number;
|
|
239
|
+
y: number;
|
|
240
|
+
};
|
|
241
|
+
}
|
|
199
242
|
|
|
200
243
|
/**
|
|
201
244
|
* Canvas size in CSS pixels — passed to `draw` for layers that anchor to
|
|
@@ -253,10 +296,18 @@ interface RenderLayer<TData> {
|
|
|
253
296
|
*/
|
|
254
297
|
space?: 'world' | 'screen';
|
|
255
298
|
/**
|
|
256
|
-
* Optional hit-test
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
299
|
+
* Optional hit-test for **consumer-attached** layers.
|
|
300
|
+
*
|
|
301
|
+
* Only layers registered through `CanvasExtensionApi.registerLayer` are
|
|
302
|
+
* hit-tested: `hitTestExtras` walks them last-registered-first on
|
|
303
|
+
* pointerdown, and `<SceneCanvas>` folds the result into its `affordanceAt`
|
|
304
|
+
* thunk ahead of the kit's own selection chrome. First non-null result
|
|
305
|
+
* wins; null means "I don't claim this hit, try the next layer."
|
|
306
|
+
*
|
|
307
|
+
* Layers that reach the draw stack some other way — a `Tool.overlay`, an
|
|
308
|
+
* entry in the `layers` map — are painted but never hit-tested, so defining
|
|
309
|
+
* `hitTest` on one has no effect. (The kit's own chrome doesn't need it: it
|
|
310
|
+
* goes through `buildAffordanceAt`.)
|
|
260
311
|
*
|
|
261
312
|
* Coordinates are world-space. The `data` arg is the layer's
|
|
262
313
|
* configured data slot (same as `draw`); `view` and `dims` mirror
|
|
@@ -2086,9 +2137,11 @@ declare function specificity(spec: GestureSpec): readonly [number, number, numbe
|
|
|
2086
2137
|
* ## gestureId scheme
|
|
2087
2138
|
* - `key-held` ongoing actions: `key-held-<key>` (e.g. `key-held- ` for Space).
|
|
2088
2139
|
* Chosen because key-held gestures are identified by the held key alone.
|
|
2089
|
-
* - `pointerdown` / drag ongoing actions: `pointer-<pointerId>`,
|
|
2090
|
-
*
|
|
2091
|
-
*
|
|
2140
|
+
* - `pointerdown` / drag ongoing actions: `pointer-<pointerId>`, taken from
|
|
2141
|
+
* the originating DOM `PointerEvent`. Each physical pointer — mouse, each
|
|
2142
|
+
* touch, the stylus — gets its own handle slot. Events with no
|
|
2143
|
+
* `pointerId` (synthesized probes, programmatic drags, most tests) key to
|
|
2144
|
+
* `pointer-mouse`, so a single synthetic pointer behaves as it always has.
|
|
2092
2145
|
* - `multitouch` ongoing actions: `multitouch-<fingers>`.
|
|
2093
2146
|
* - Fallback for any other kind that triggers an ongoing invoker: `ongoing-<kind>`.
|
|
2094
2147
|
*
|
|
@@ -2317,7 +2370,7 @@ interface Dispatcher {
|
|
|
2317
2370
|
* - `kind` — the `OngoingHandle.kind` reported by the in-flight
|
|
2318
2371
|
* handle (e.g. `'marquee'`, `'move'`). `null` when no action is
|
|
2319
2372
|
* in flight OR the handle didn't declare a kind.
|
|
2320
|
-
* - `id` — the dispatcher's internal `gestureId` (`pointer-
|
|
2373
|
+
* - `id` — the dispatcher's internal `gestureId` (`pointer-1`,
|
|
2321
2374
|
* `key-held-Space`, …) — the pointer/key channel the action rode
|
|
2322
2375
|
* in on. `null` when no action is in flight.
|
|
2323
2376
|
*
|
|
@@ -2325,6 +2378,13 @@ interface Dispatcher {
|
|
|
2325
2378
|
* action overlapping a pointer action), the most-recently-started
|
|
2326
2379
|
* handle wins. This matches user intent: the latest interaction is
|
|
2327
2380
|
* the one consumers care about.
|
|
2381
|
+
*
|
|
2382
|
+
* That rule used to be near-vacuous on the pointer side, because every
|
|
2383
|
+
* pointer shared one handle slot and two pointer drags could not coexist.
|
|
2384
|
+
* With per-pointer keying they can — but only via paths that bypass the
|
|
2385
|
+
* multi-pointer policy in `useGestureDispatcher` (which stops a second
|
|
2386
|
+
* finger from opening a drag while a pinch is live), such as a mouse and
|
|
2387
|
+
* a pen used together. Latest-start remains the right answer there.
|
|
2328
2388
|
*/
|
|
2329
2389
|
getActiveAction(): {
|
|
2330
2390
|
kind: string | null;
|
|
@@ -2797,11 +2857,86 @@ interface Conflict {
|
|
|
2797
2857
|
* the action; consumers can suppress known-intentional compositions in
|
|
2798
2858
|
* their UI layer.
|
|
2799
2859
|
*
|
|
2860
|
+
* - Two `{ kindOf }` predicate targets. The route grammar renders every
|
|
2861
|
+
* predicate as the single token {@link PREDICATE_TARGET}, so bucketing on
|
|
2862
|
+
* the rendered target alone reported select's `resize` / `rotate` / `move`
|
|
2863
|
+
* drags — three genuinely different predicates — as a three-way conflict.
|
|
2864
|
+
* Predicate entries bucket by function identity instead, which means two
|
|
2865
|
+
* *separately written but equivalent* predicates go unflagged. That's the
|
|
2866
|
+
* right way to be wrong here: this check has to be silent when nothing is
|
|
2867
|
+
* wrong or nobody will keep it on.
|
|
2868
|
+
*
|
|
2800
2869
|
* Note that two bindings sharing a tuple on the SAME tool are now possible
|
|
2801
2870
|
* (bindings are an array, where phase tables were objects with unique
|
|
2802
2871
|
* keys) — so a conflict may name one tool twice.
|
|
2872
|
+
*
|
|
2873
|
+
* This is the raw same-tuple detector. Feeding it a whole tool *registry*
|
|
2874
|
+
* over-reports, because registry tools take turns in the active slot and
|
|
2875
|
+
* can't collide with each other — see {@link findScopedConflicts}.
|
|
2803
2876
|
*/
|
|
2804
2877
|
declare function findConflicts(tools: readonly Tool<unknown>[]): Conflict[];
|
|
2878
|
+
/**
|
|
2879
|
+
* The tool scopes as the dispatcher sees them — which is what decides whether
|
|
2880
|
+
* two same-tuple bindings can actually collide.
|
|
2881
|
+
*/
|
|
2882
|
+
interface ToolScopes {
|
|
2883
|
+
/** Tools eligible for the active slot, keyed by id or as a flat list. */
|
|
2884
|
+
registry: readonly Tool<unknown>[] | Readonly<Record<string, Tool<unknown>>>;
|
|
2885
|
+
/** Always-on tools. Every one of these is live at once. */
|
|
2886
|
+
ambient?: readonly Tool<unknown>[];
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Detect the same-tuple overlaps that are *reachable* — the ones where the
|
|
2890
|
+
* dispatcher really does fall back on declaration order.
|
|
2891
|
+
*
|
|
2892
|
+
* `matchSorted` walks scopes in strict priority (hotkey > active > ambient)
|
|
2893
|
+
* and only sorts by specificity *within* a scope. So a cross-scope tie isn't
|
|
2894
|
+
* a tie at all: an ambient tool losing a tuple to the active tool is the
|
|
2895
|
+
* documented design, not an accident. Likewise two registry tools sharing a
|
|
2896
|
+
* tuple — `rect` and `ellipse` both binding a bare `drag` — can never both be
|
|
2897
|
+
* in the active slot, so they never compete.
|
|
2898
|
+
*
|
|
2899
|
+
* What's left, and what this reports:
|
|
2900
|
+
* - a single tool colliding with **itself** (possible since bindings became
|
|
2901
|
+
* an array), which is ambiguous in whichever slot it occupies;
|
|
2902
|
+
* - two **ambient** tools, all of which are live simultaneously and are
|
|
2903
|
+
* ordered only by registration;
|
|
2904
|
+
* - two **hotkey-capable** tools, which can stack.
|
|
2905
|
+
*
|
|
2906
|
+
* Not covered: the actions registry's `defaultBinding`s, which the dispatcher
|
|
2907
|
+
* also folds into ambient/hotkey scope. They're assembled somewhere else
|
|
2908
|
+
* entirely (`ActionsRegistry`, not `useTools`), so catching tool-vs-action
|
|
2909
|
+
* collisions means giving this function a second input it doesn't have yet.
|
|
2910
|
+
*/
|
|
2911
|
+
declare function findScopedConflicts(scopes: ToolScopes): Conflict[];
|
|
2912
|
+
/**
|
|
2913
|
+
* Render a conflict as one line of human-readable text.
|
|
2914
|
+
*
|
|
2915
|
+
* The tuple is printed through {@link formatRoute}, so the message names the
|
|
2916
|
+
* collision in the same grammar the author wrote the binding in — modulo the
|
|
2917
|
+
* phase slot, which prints as a bare `'&'`-channel atom because the bucket key
|
|
2918
|
+
* collapses channel-bearing phase specs (`sel:engaged` and `&:engaged` collide
|
|
2919
|
+
* with each other, and the collapse is what made them collide).
|
|
2920
|
+
*/
|
|
2921
|
+
declare function formatConflict(conflict: Conflict): string;
|
|
2922
|
+
/**
|
|
2923
|
+
* Detect reachable route conflicts in an assembled tool set and report each one.
|
|
2924
|
+
*
|
|
2925
|
+
* This is the wiring `findConflicts` spent its first life without: the kit
|
|
2926
|
+
* could detect the one class of genuine routing ambiguity it has and never
|
|
2927
|
+
* looked. Call it once wherever a tool set is assembled (`useTools` does),
|
|
2928
|
+
* behind a `process.env.NODE_ENV !== 'production'` guard.
|
|
2929
|
+
*
|
|
2930
|
+
* **Warn, never throw.** A conflict between a consumer's tool and a kit tool
|
|
2931
|
+
* is a design question — sometimes deliberate, since the loser can still take
|
|
2932
|
+
* the gesture by declining through `enabled()` — and exploding in a running
|
|
2933
|
+
* app is the wrong way to raise it. A conflict between two *kit* tools is
|
|
2934
|
+
* always a bug, but the place to fail on that is the kit's own test suite
|
|
2935
|
+
* (`canvas/SceneCanvas.routeConflicts.test.tsx`), not a consumer's console.
|
|
2936
|
+
*
|
|
2937
|
+
* @returns The conflicts found, so callers can dedupe repeat reports.
|
|
2938
|
+
*/
|
|
2939
|
+
declare function reportRouteConflicts(scopes: ToolScopes, warn?: (message: string) => void): Conflict[];
|
|
2805
2940
|
|
|
2806
2941
|
declare const index_ChannelRef: typeof ChannelRef;
|
|
2807
2942
|
type index_Conflict = Conflict;
|
|
@@ -2826,6 +2961,7 @@ declare const index_RouteFieldName: typeof RouteFieldName;
|
|
|
2826
2961
|
declare const index_RouteTermLabel: typeof RouteTermLabel;
|
|
2827
2962
|
type index_ToolDef<TScratch = void> = ToolDef<TScratch>;
|
|
2828
2963
|
type index_ToolKeybinding = ToolKeybinding;
|
|
2964
|
+
type index_ToolScopes = ToolScopes;
|
|
2829
2965
|
type index_ViewportToolDef<TScratch = void> = ViewportToolDef<TScratch>;
|
|
2830
2966
|
declare const index_buildRouteRegistry: typeof buildRouteRegistry;
|
|
2831
2967
|
declare const index_canonicalModifiers: typeof canonicalModifiers;
|
|
@@ -2835,13 +2971,16 @@ declare const index_defineViewportTool: typeof defineViewportTool;
|
|
|
2835
2971
|
declare const index_describeRoute: typeof describeRoute;
|
|
2836
2972
|
declare const index_describeRouteParts: typeof describeRouteParts;
|
|
2837
2973
|
declare const index_findConflicts: typeof findConflicts;
|
|
2974
|
+
declare const index_findScopedConflicts: typeof findScopedConflicts;
|
|
2975
|
+
declare const index_formatConflict: typeof formatConflict;
|
|
2838
2976
|
declare const index_formatPhaseAtom: typeof formatPhaseAtom;
|
|
2839
2977
|
declare const index_formatRoute: typeof formatRoute;
|
|
2840
2978
|
declare const index_getGestureDescriptor: typeof getGestureDescriptor;
|
|
2841
2979
|
declare const index_isKnownGestureName: typeof isKnownGestureName;
|
|
2842
2980
|
declare const index_parseRoute: typeof parseRoute;
|
|
2981
|
+
declare const index_reportRouteConflicts: typeof reportRouteConflicts;
|
|
2843
2982
|
declare namespace index {
|
|
2844
|
-
export { index_ChannelRef as ChannelRef, type index_Conflict as Conflict, index_DescribeRouteOptions as DescribeRouteOptions, index_GESTURE_DESCRIPTORS as GESTURE_DESCRIPTORS, index_GestureArgSpec as GestureArgSpec, index_GestureDescriptor as GestureDescriptor, index_GestureName as GestureName, index_ModRequirement as ModRequirement, index_ModifierKey as ModifierKey, index_PREDICATE_TARGET as PREDICATE_TARGET, index_ParsedModifiers as ParsedModifiers, index_ParsedRoute as ParsedRoute, index_PhaseAtom as PhaseAtom, index_RESERVED_ID_NAMES as RESERVED_ID_NAMES, index_RESERVED_ID_PREFIXES as RESERVED_ID_PREFIXES, index_ROUTE_FIELD_DEFINITIONS as ROUTE_FIELD_DEFINITIONS, index_ROUTE_TERMS as ROUTE_TERMS, type index_RegistryEntry as RegistryEntry, index_RouteDescriptionPart as RouteDescriptionPart, index_RouteFieldName as RouteFieldName, index_RouteTermLabel as RouteTermLabel, type index_ToolDef as ToolDef, type index_ToolKeybinding as ToolKeybinding, type index_ViewportToolDef as ViewportToolDef, index_buildRouteRegistry as buildRouteRegistry, index_canonicalModifiers as canonicalModifiers, index_collapseShiftPairs as collapseShiftPairs, index_defineTool as defineTool, index_defineViewportTool as defineViewportTool, index_describeRoute as describeRoute, index_describeRouteParts as describeRouteParts, index_findConflicts as findConflicts, index_formatPhaseAtom as formatPhaseAtom, index_formatRoute as formatRoute, index_getGestureDescriptor as getGestureDescriptor, index_isKnownGestureName as isKnownGestureName, index_parseRoute as parseRoute };
|
|
2983
|
+
export { index_ChannelRef as ChannelRef, type index_Conflict as Conflict, index_DescribeRouteOptions as DescribeRouteOptions, index_GESTURE_DESCRIPTORS as GESTURE_DESCRIPTORS, index_GestureArgSpec as GestureArgSpec, index_GestureDescriptor as GestureDescriptor, index_GestureName as GestureName, index_ModRequirement as ModRequirement, index_ModifierKey as ModifierKey, index_PREDICATE_TARGET as PREDICATE_TARGET, index_ParsedModifiers as ParsedModifiers, index_ParsedRoute as ParsedRoute, index_PhaseAtom as PhaseAtom, index_RESERVED_ID_NAMES as RESERVED_ID_NAMES, index_RESERVED_ID_PREFIXES as RESERVED_ID_PREFIXES, index_ROUTE_FIELD_DEFINITIONS as ROUTE_FIELD_DEFINITIONS, index_ROUTE_TERMS as ROUTE_TERMS, type index_RegistryEntry as RegistryEntry, index_RouteDescriptionPart as RouteDescriptionPart, index_RouteFieldName as RouteFieldName, index_RouteTermLabel as RouteTermLabel, type index_ToolDef as ToolDef, type index_ToolKeybinding as ToolKeybinding, type index_ToolScopes as ToolScopes, type index_ViewportToolDef as ViewportToolDef, index_buildRouteRegistry as buildRouteRegistry, index_canonicalModifiers as canonicalModifiers, index_collapseShiftPairs as collapseShiftPairs, index_defineTool as defineTool, index_defineViewportTool as defineViewportTool, index_describeRoute as describeRoute, index_describeRouteParts as describeRouteParts, index_findConflicts as findConflicts, index_findScopedConflicts as findScopedConflicts, index_formatConflict as formatConflict, index_formatPhaseAtom as formatPhaseAtom, index_formatRoute as formatRoute, index_getGestureDescriptor as getGestureDescriptor, index_isKnownGestureName as isKnownGestureName, index_parseRoute as parseRoute, index_reportRouteConflicts as reportRouteConflicts };
|
|
2845
2984
|
}
|
|
2846
2985
|
|
|
2847
|
-
export {
|
|
2986
|
+
export { DepRegistryProvider as $, type Action as A, type BooleansAdapter as B, type Condition as C, type Dims as D, ActiveToolContextProvider as E, ActiveToolContextProviderIfRoot as F, type GeometryProjection as G, type HotkeyTrigger as H, type InsertExtras as I, type ActiveToolContextProviderProps as J, type ActiveToolContextValue as K, type AreaSelectDep as L, type BindingOpts as M, type BindingScope as N, type BooleanOp as O, type BooleanOpResult as P, type BoundGesture as Q, type RenderLayer as R, type SliceDep as S, type Tool as T, type UseSelectionOptions as U, type VisibilityRules as V, type BuildRuleCtxArgs as W, type ClipboardIngestCtx as X, type CustomPaintContext as Y, type DepName as Z, type DepRegistry as _, type Rule as a, type ToolScopes as a$, type DispatcherContext as a0, type DragSample as a1, type EditAnchorsDep as a2, type GestureBinding as a3, type ImmediateInvoker as a4, type IngestCtx as a5, type IngestionDep as a6, type InsertDep as a7, type InvocationCtx as a8, type Invoker as a9, type UiOngoingControl as aA, type ViewApi as aB, applyBooleanOp as aC, buildRuleCtx as aD, createDispatcher as aE, describeRule as aF, drawLayers as aG, enterTextEditAction as aH, evaluate as aI, evaluateEnabled as aJ, registerContentHandler as aK, index as aL, sliceAction as aM, specificity as aN, useAction as aO, useActionsRegistry as aP, useActiveToolContext as aQ, useDepRegistry as aR, useDepSource as aS, useOptionalActiveToolContext as aT, useOptionalDepRegistry as aU, usePointerContext as aV, useSelection as aW, type Conflict as aX, PREDICATE_TARGET as aY, type RegistryEntry as aZ, type ToolDef as a_, type LassoSelectDep as aa, type LayoutDep as ab, type MatchResult as ac, NEVER as ad, type NodeAtPointDep as ae, type OngoingHandle as af, type OngoingInvoker as ag, type OngoingOverlay as ah, type Point2 as ai, PointerContextProvider as aj, type PointerContextValue as ak, type PointerWorldPos as al, type ResizePolicy as am, type ResolveAllOptions as an, type ResolveOnlyResult as ao, type ResolvedCandidate as ap, type ScopedBinding as aq, type SelectionExtendKey as ar, type SelectionMode as as, type Selector as at, type SnapDep as au, type SvgUnpacker as av, type TextEditDep as aw, type ToolModifiers as ax, type ToolPresentation as ay, type ToolSlot as az, type RuleCtx as b, type ViewportToolDef as b0, buildRouteRegistry as b1, defineTool as b2, defineViewportTool as b3, findConflicts as b4, findScopedConflicts as b5, formatConflict as b6, reportRouteConflicts as b7, type ChromeCtx as c, type ChromeId as d, type DepSchema as e, type ActionsRegistry as f, type AffordanceHit as g, type Dispatcher as h, type AnyTool as i, type ToolCtx as j, type ToolKeybinding as k, type AffordanceBinding as l, type ChromeState as m, type SelectionApi as n, type ContentHandlerEntry as o, type SvgIngestOptions as p, type ActionsProp as q, type Affordance as r, type AffordanceRegion as s, type CommonAffordanceScratch as t, ALWAYS as u, type ActionDeps as v, ActionDisabledReason as w, type ActionEnabledResult as x, type ActionEntry as y, ActionsProvider as z };
|