@weasel-js/routing 1.10.0 → 1.11.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/dist/index.d.ts CHANGED
@@ -1,8 +1,11 @@
1
- export { m as ALWAYS, c as Action, n as ActionDeps, o as ActionDisabledReason, o as ActionDisabledReasonValue, p as ActionDispatch, q as ActionEnabledResult, r as ActionEntry, s as ActionItem, t as ActionPresentation, v as ActionSource, w as ActionVariant, x as ActionsProp, A as ActionsRegistry, l as AffordanceHit, y as AnyTool, z as AssemblyInputs, B as BindingOpts, E as BindingScope, F as BindingSource, G as BodyPredicate, H as BoundGesture, I as Bounds, J as BuildRuleCtxArgs, M as COARSE_TARGET_SCALE, P as Condition, Q as Conflict, C as Contribution, U as ContributionChrome, V as ContributionDeps, W as ContributionRouting, X as DEFAULT_ALLOWED_CAPABILITIES, Y as DEFAULT_DEVICE_PROFILE, Z as DebugSink, a as DepName, D as DepRegistry, b as DepSchema, _ as DetectedDeviceFacts, $ as DeviceProfile, a0 as DispatchRecord, a1 as DispatchRecordInput, k as Dispatcher, a2 as DispatcherContext, a3 as DragSample, a4 as DroppedCandidate, a5 as Eligibility, a6 as EligibilityState, a7 as GestureBinding, a8 as HandleKind, a9 as HitShape, aa as HotkeyTrigger, ab as INGEST_STRING_MIMES, ac as ImmediateInvoker, ad as InvocationCtx, ae as Invoker, K as KernelOverlay, af as KeyBinding, L as LongPressOptions, ag as LongPressState, ah as LongPressStore, ai as MatchOutcome, aj as MatchResult, ak as ModeShortcutHandlers, al as ModeSwitchLogEntry, am as ModifierState, an as NEVER, N as NodeId, ao as OngoingHandle, ap as OngoingInvoker, aq as OngoingOverlay, O as OverlayPosition, ar as OverlayRole, as as OverlaySchema, at as PREDICATE_TARGET, au as PendingLongPress, av as PlacedBy, aw as Point2, ax as PointerSession, ay as PointerSessionCallbacks, az as PointerSessionCancelReason, aA as PointerSessionOptions, aB as RankedCandidate, aC as RecordCandidate, aD as RegistryEntry, aE as ResizeAnchor, aF as ResolveAllOptions, aG as ResolveOnlyResult, aH as ResolvedCandidate, aI as Rule, R as RuleCtx, aJ as ScopedBinding, aK as ScratchKey, aL as ScratchStore, aM as SelectionApi, aN as Selector, aO as SpecificityPart, aP as TOOL_OFFHAND_ID, e as Tool, aQ as ToolDef, aR as ToolKeybinding, aS as ToolModifiers, aT as ToolOffhandBindingSpec, aU as ToolPresentation, aV as ToolScopes, aW as ToolSlot, aX as TraceLogEntry, aY as UiOngoingControl, aZ as ViewportGestureKind, a_ as WalkStep, a$ as actionBindings, b0 as actionItems, b1 as assembleEntries, b2 as buildDepsFromRequires, b3 as buildRouteRegistry, b4 as buildRuleCtx, b5 as buildToolOffhandBindings, b6 as clientToCanvas, b7 as clientToCanvasRect, b8 as createDepRegistry, b9 as createDispatcher, ba as createLongPressStore, bb as defineTool, bc as deleteScratch, bd as describeRule, be as dlog, bf as dwarn, bg as evaluate, bh as evaluateActive, bi as evaluateEnabled, bj as findConflicts, bk as getScratch, bl as heldKeySpec, bm as historyKey, bn as isAnchor, bo as isAnchorOrControl, bp as isBody, bq as isControlHandle, br as isDebugEnabled, bs as isEmpty, bt as isResizeHandle, bu as isRotateHandle, bv as isSelectedBody, bw as isTool, bx as isUnselectedBody, by as itemTypesFromDataTransfer, bz as itemsFromClipboardData, bA as itemsFromDataTransfer, bB as itemsFromFiles, bC as liveScope, bD as makeToolOffhandAction, bE as matchBest, bF as matchSorted, bG as matchSortedWithBarred, bH as matchesKeyBinding, bI as mergeContributions, bJ as modeShortcutSpec, bK as modeShortcuts, bL as multiTouchGestureId, bM as offhandKeyFor, bN as openPointerSession, bO as pointerGestureId, bP as recordModeSwitch, bQ as reportRouteConflicts, bR as resolveDeviceProfile, bS as resolveParams, bT as routesForSpec, bU as ruleCanHoldIn, bV as rulesExclusive, bW as scopeBindings, bX as scratchKey, bY as setScratch, bZ as targetConsultsAffordance, b_ as validateActionDefaultBinding, b$ as validateActionId } from './index-Bm2QEV09.js';
1
+ export { q as ALWAYS, A as Action, c as ActionDeps, e as ActionDisabledReason, e as ActionDisabledReasonValue, r as ActionDispatch, s as ActionEnabledResult, t as ActionEntry, v as ActionItem, w as ActionParamSchema, x as ActionParams, y as ActionPresentation, z as ActionShortcut, E as ActionSource, F as ActionTuning, G as ActionTunings, f as ActionVariant, H as ActionsProp, d as ActionsRegistry, p as AffordanceHit, I as AnyTool, J as AssemblyInputs, P as BROWSER_RESERVED_CHORDS, Q as BindingOpts, U as BindingScope, V as BindingSource, W as BodyPredicate, B as BoundGesture, X as Bounds, Y as BuildRuleCtxArgs, Z as COARSE_TARGET_SCALE, _ as Carry, $ as CarryCallbacks, a0 as CarryPayload, a1 as CarryPointer, a2 as CarryStage, a3 as Condition, a4 as Conflict, C as Contribution, a5 as ContributionChrome, a6 as ContributionDeps, a7 as ContributionRouting, a8 as DEFAULT_ALLOWED_CAPABILITIES, a9 as DEFAULT_DEVICE_PROFILE, aa as DebugSink, a as DepName, D as DepRegistry, b as DepSchema, ab as DetectedDeviceFacts, ac as DeviceProfile, ad as DispatchRecord, ae as DispatchRecordInput, o as Dispatcher, af as DispatcherContext, ag as DragSample, ah as DroppedCandidate, ai as Eligibility, aj as EligibilityState, ak as GestureBinding, al as GestureKitSettings, am as HandleKind, an as HitShape, ao as HotkeyTrigger, ap as INGEST_STRING_MIMES, aq as ImmediateInvoker, ar as InvocationCtx, as as Invoker, K as KernelOverlay, at as KeyBinding, L as LongPressOptions, au as LongPressState, av as LongPressStore, aw as MatchOutcome, ax as MatchResult, g as MenuDef, ay as MenuHit, M as MenuHost, az as MenuOpenRequest, aA as MenuSources, aB as MenuWhen, aC as ModeShortcutHandlers, aD as ModeSwitchLogEntry, aE as ModifierRole, aF as ModifierRoles, aG as ModifierState, aH as NEVER, N as NodeId, aI as OngoingHandle, aJ as OngoingInvoker, aK as OngoingOverlay, aL as OpenMenu, O as OverlayPosition, aM as OverlayRole, aN as OverlaySchema, aO as OwnedMenuDef, aP as PREDICATE_TARGET, aQ as PendingLongPress, aR as PlacedBy, aS as Point2, aT as PointerSession, aU as PointerSessionCallbacks, aV as PointerSessionCancelReason, aW as PointerSessionOptions, aX as RankedCandidate, aY as RecordCandidate, aZ as RegistryEntry, a_ as ReservedChord, a$ as ResizeAnchor, b0 as ResolveAllOptions, b1 as ResolveOnlyResult, b2 as ResolvedCandidate, b3 as RoleKeys, b4 as RoleRebinds, b5 as Rule, R as RuleCtx, b6 as ScopedBinding, b7 as ScratchKey, b8 as ScratchStore, b9 as SelectionApi, ba as Selector, bb as SpecificityPart, bc as TOOL_OFFHAND_ID, i as Tool, bd as ToolDef, be as ToolKeybinding, bf as ToolModifiers, bg as ToolOffhandBindingSpec, bh as ToolPresentation, bi as ToolScopes, bj as ToolSlot, bk as TraceLogEntry, bl as TunedBinding, bm as UiOngoingControl, bn as ViewportGestureKind, bo as WalkStep, bp as actionBindings, bq as actionCursor, br as actionItemTitle, bs as actionItems, bt as actionShortcuts, bu as actionsTagged, bv as assembleEntries, bw as browserReservedChord, bx as buildDepsFromRequires, by as buildRouteRegistry, bz as buildRuleCtx, bA as buildToolOffhandBindings, bB as clientToCanvas, bC as clientToCanvasRect, bD as createDepRegistry, bE as createDispatcher, bF as createLongPressStore, bG as createMenuHost, bH as defineTool, bI as deleteScratch, bJ as describeRule, bK as dlog, bL as dwarn, bM as evaluate, bN as evaluateActive, bO as evaluateEnabled, bP as findConflicts, bQ as getScratch, bR as heldKeySpec, bS as historyKey, bT as isAnchor, bU as isAnchorOrControl, bV as isBalanceGrip, bW as isBody, bX as isControlHandle, bY as isCornerWidget, bZ as isDebugEnabled, b_ as isEmpty, b$ as isPathEditPart, c0 as isResizeHandle, c1 as isRotateHandle, c2 as isSegment, c3 as isSelectedBody, c4 as isTool, c5 as isUnselectedBody, c6 as itemTypesFromDataTransfer, c7 as itemsFromClipboardData, c8 as itemsFromDataTransfer, c9 as itemsFromFiles, ca as keySpecShortcut, cb as keysHeld, cc as liveScope, cd as makeToolOffhandAction, ce as matchActionItem, cf as matchBest, cg as matchSorted, ch as matchSortedWithBarred, ci as matchesKeyBinding, cj as mergeContributions, ck as mergeMenuDefs, cl as modeShortcutSpec, cm as modeShortcuts, cn as multiTouchGestureId, co as offhandKeyFor, cp as openPointerSession, cq as overlaysOf, cr as pickMenu, cs as pointerGestureId, ct as recordModeSwitch, cu as reportRouteConflicts, cv as resolveDeviceProfile, cw as resolveMenu, cx as resolveParams, cy as resolveSchemaMenu, cz as roleReader, cA as routesForSpec, cB as ruleCanHoldIn, cC as rulesExclusive, cD as scopeBindings, cE as scratchKey, cF as setScratch, cG as startCarry, cH as taggedActionItems, cI as targetConsultsAffordance, cJ as tuneAction, cK as validateActionDefaultBinding, cL as validateActionId } from './index-CmfVvwWD.js';
2
2
  export { CONTROL_SELECTOR, ChannelRef, DRAG_THRESHOLD_PX, DescribeRouteOptions, GESTURE_DESCRIPTORS, GestureArgSpec, GestureDescriptor, GestureName, IngestItem, InputEvent, LONG_PRESS_MS, ModRequirement, ModifierKey, ParsedModifiers, ParsedRoute, PhaseAtom, PhaseContext, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, RouteDescriptionPart, RouteFieldName, RouteTermLabel, activatesFocusedControl, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, focusedControlOwnsKey, formatPhaseAtom, formatRoute, getGestureDescriptor, isEditableTarget, isInControlWithin, isKnownGestureName, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, parseRoute, pastDragThreshold, routeGestureForSpecKind, specKindForRouteGesture, specificity } from '@weasel-js/gestures';
3
3
  export { SelectionExtendKey, SelectionMode } from '@weasel-js/select';
4
4
  export { View } from '@weasel-js/geom';
5
- import 'react';
6
- import '@weasel-js/registry';
5
+ import '@weasel-js/menu';
6
+ import '@weasel-js/prefs';
7
7
  import '@weasel-js/modes';
8
+ import '@weasel-js/icons';
8
9
  import '@weasel-js/cursor';
10
+ import 'react';
11
+ import '@weasel-js/registry';
package/dist/index.js CHANGED
@@ -1,22 +1,28 @@
1
- import { ActionDisabledReason } from './chunk-4DR6AJMN.js';
2
- export { ALWAYS, ActionDisabledReason, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, GESTURE_DESCRIPTORS, INGEST_STRING_MIMES, LONG_PRESS_MS, NEVER, PREDICATE_TARGET, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, TOOL_OFFHAND_ID, actionBindings, activatesFocusedControl, assembleEntries, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, canonicalModifiers, clientToCanvas, clientToCanvasRect, collapseShiftPairs, createDepRegistry, createDispatcher, createLongPressStore, describeRoute, describeRouteParts, describeRule, dlog, dwarn, evaluate, evaluateActive, evaluateEnabled, findConflicts, focusedControlOwnsKey, formatPhaseAtom, formatRoute, getGestureDescriptor, heldKeySpec, historyKey, isDebugEnabled, isEditableTarget, isKnownGestureName, isTool, itemTypesFromDataTransfer, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, liveScope, makeToolOffhandAction, matchBest, matchKey, matchModifiers, matchPhase, matchSorted, matchSortedWithBarred, matchSpec, matchTarget, matchesKeyBinding, multiTouchGestureId, offhandKeyFor, openPointerSession, parseRoute, pointerGestureId, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveParams, routeGestureForSpecKind, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, specKindForRouteGesture, specificity, targetConsultsAffordance, validateActionDefaultBinding, validateActionId } from './chunk-4DR6AJMN.js';
1
+ import { actionItems, actionItemTitle, ActionDisabledReason } from './chunk-QBQQIFPI.js';
2
+ export { ALWAYS, ActionDisabledReason, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, GESTURE_DESCRIPTORS, INGEST_STRING_MIMES, LONG_PRESS_MS, NEVER, PREDICATE_TARGET, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, TOOL_OFFHAND_ID, actionBindings, actionCursor, actionItemTitle, actionItems, actionShortcuts, activatesFocusedControl, assembleEntries, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, canonicalModifiers, clientToCanvas, clientToCanvasRect, collapseShiftPairs, createDepRegistry, createDispatcher, createLongPressStore, createMenuHost, describeRoute, describeRouteParts, describeRule, dlog, dwarn, evaluate, evaluateActive, evaluateEnabled, findConflicts, focusedControlOwnsKey, formatPhaseAtom, formatRoute, getGestureDescriptor, heldKeySpec, historyKey, isDebugEnabled, isEditableTarget, isKnownGestureName, isTool, itemTypesFromDataTransfer, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, keySpecShortcut, keysHeld, liveScope, makeToolOffhandAction, matchBest, matchKey, matchModifiers, matchPhase, matchSorted, matchSortedWithBarred, matchSpec, matchTarget, matchesKeyBinding, mergeMenuDefs, multiTouchGestureId, offhandKeyFor, openPointerSession, parseRoute, pickMenu, pointerGestureId, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveMenu, resolveParams, resolveSchemaMenu, roleReader, routeGestureForSpecKind, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, specKindForRouteGesture, specificity, startCarry, targetConsultsAffordance, tuneAction, validateActionDefaultBinding, validateActionId } from './chunk-QBQQIFPI.js';
3
+ import { hasTag, matchQuery } from '@weasel-js/registry';
3
4
  import { bodyPredicate, hitPredicate, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from '@weasel-js/gestures';
4
5
  export { CONTROL_SELECTOR, DRAG_THRESHOLD_PX, isInControlWithin, pastDragThreshold } from '@weasel-js/gestures';
5
6
  import { modeLabel } from '@weasel-js/modes';
6
7
 
7
- // src/interactions/actions/actionItems.ts
8
- function actionItems(action) {
9
- if (action.invoker?.timing !== "immediate") return [];
10
- if (!action.variants?.length) {
11
- return [{ key: action.id, action, label: action.label, icon: action.icon }];
12
- }
13
- return action.variants.map((v) => ({
14
- key: `${action.id}:${v.key}`,
15
- action,
16
- label: v.label,
17
- icon: v.icon ?? action.icon,
18
- params: v.params
19
- }));
8
+ function actionsTagged(actions, tag) {
9
+ return actions.filter((a) => hasTag(a.tags, tag) || (a.variants?.some((v) => hasTag(v.tags, tag)) ?? false));
10
+ }
11
+ function taggedActionItems(actions, tag) {
12
+ return actionsTagged(actions, tag).flatMap(actionItems).filter((i) => hasTag(i.tags, tag));
13
+ }
14
+ function matchActionItem(item, query) {
15
+ return matchQuery(query, { text: [actionItemTitle(item)], tags: item.tags });
16
+ }
17
+
18
+ // src/interactions/actions/overlay.ts
19
+ function overlaysOf(handle) {
20
+ const ov = handle.overlay?.();
21
+ if (!ov) return [];
22
+ return isOverlayList(ov) ? ov : [ov];
23
+ }
24
+ function isOverlayList(ov) {
25
+ return Array.isArray(ov);
20
26
  }
21
27
  var isBody = bodyPredicate((bodyTarget) => bodyTarget === "selected-body" || bodyTarget === "unselected-body");
22
28
  var isSelectedBody = bodyPredicate((bodyTarget) => bodyTarget === "selected-body");
@@ -40,7 +46,17 @@ var isControlHandle = hitPredicate((target) => {
40
46
  const k = kindOf(target);
41
47
  return k !== void 0 && /^(controlIn|controlOut):\d+$/.test(k);
42
48
  });
49
+ var isCornerWidget = hitPredicate((target) => {
50
+ const k = kindOf(target);
51
+ return k !== void 0 && /^corner:\d+$/.test(k);
52
+ });
43
53
  var isAnchorOrControl = hitPredicate((target) => isAnchor(target) || isControlHandle(target));
54
+ var isSegment = hitPredicate((target) => kindOf(target) === "segment");
55
+ var isBalanceGrip = hitPredicate((target) => {
56
+ const k = kindOf(target);
57
+ return k !== void 0 && /^balance:\d+$/.test(k);
58
+ });
59
+ var isPathEditPart = hitPredicate((target) => isAnchorOrControl(target) || isSegment(target) || isBalanceGrip(target) || isCornerWidget(target));
44
60
 
45
61
  // src/contributions/merge.ts
46
62
  function mergeContributions(...bundles) {
@@ -179,6 +195,7 @@ function defineTool(def) {
179
195
  ...def.hotkey ? { offhand: def.hotkey } : {}
180
196
  },
181
197
  actions: def.actions,
198
+ menus: def.menus,
182
199
  def,
183
200
  presentation: def.presentation,
184
201
  keybinding: def.keybinding,
@@ -191,6 +208,28 @@ function defineTool(def) {
191
208
  };
192
209
  }
193
210
 
211
+ // src/interactions/browserReservedKeys.ts
212
+ var chord = (keys, mods, use) => [...keys].map((key) => ({ key, ...mods, use }));
213
+ var BROWSER_RESERVED_CHORDS = [
214
+ ...chord("nqtwlrpmh", { mod: true }, "new window, quit, new or close tab, address bar, reload, print, minimize, hide"),
215
+ ...chord("ntwrbo", { mod: true, shift: true }, "private window, reopen or close tab, hard reload, bookmarks, bookmark manager"),
216
+ ...chord("ijcukbrlemhdwfn", { mod: true, alt: true }, "devtools, console, inspector, view source, bookmarks, reload from origin, downloads, empty caches, minimize all, hide others, Dock, close all, search field"),
217
+ ...chord("fevsbthd", { alt: true }, "menu accelerators and address bar on Windows and Linux"),
218
+ ...chord("ita", { alt: true, shift: true }, "feedback, toolbar focus and accessibility commands")
219
+ ];
220
+ var lower = (k) => (typeof k === "string" ? [k] : [...k]).map((x) => x.toLowerCase());
221
+ var allows = (policy, held) => policy === "optional" || policy === true === held;
222
+ function browserReservedChord(spec) {
223
+ const keys = lower(spec.key);
224
+ const m = spec.mods ?? {};
225
+ const mod = m.mod ?? (m.meta === true || m.ctrl === true ? true : m.meta ?? m.ctrl);
226
+ for (const c of BROWSER_RESERVED_CHORDS) {
227
+ if (!keys.includes(c.key)) continue;
228
+ if (allows(mod, c.mod === true) && allows(m.alt, c.alt === true) && allows(m.shift, c.shift === true)) return c;
229
+ }
230
+ return null;
231
+ }
232
+
194
233
  // src/interactions/scratchKey.ts
195
234
  function scratchKey(name) {
196
235
  return { name };
@@ -209,6 +248,6 @@ function deleteScratch(store, key) {
209
248
  return false;
210
249
  }
211
250
 
212
- export { actionItems, defineTool, deleteScratch, getScratch, isAnchor, isAnchorOrControl, isBody, isControlHandle, isEmpty, isResizeHandle, isRotateHandle, isSelectedBody, isUnselectedBody, mergeContributions, modeShortcutSpec, modeShortcuts, scratchKey, setScratch };
251
+ export { BROWSER_RESERVED_CHORDS, actionsTagged, browserReservedChord, defineTool, deleteScratch, getScratch, isAnchor, isAnchorOrControl, isBalanceGrip, isBody, isControlHandle, isCornerWidget, isEmpty, isPathEditPart, isResizeHandle, isRotateHandle, isSegment, isSelectedBody, isUnselectedBody, matchActionItem, mergeContributions, modeShortcutSpec, modeShortcuts, overlaysOf, scratchKey, setScratch, taggedActionItems };
213
252
  //# sourceMappingURL=index.js.map
214
253
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/interactions/actions/actionItems.ts","../src/interactions/dispatcher/predicates.ts","../src/contributions/merge.ts","../src/contributions/modeShortcuts.ts","../src/tools/defineTool.ts","../src/interactions/scratchKey.ts"],"names":["RESERVED_ID_PREFIXES","RESERVED_ID_NAMES"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @experimental\n * The entries an action contributes to a bar, menu or palette — one per\n * variant, or the action itself when it declares none.\n */\nimport type { ReactNode } from 'react';\nimport type { Action } from './action';\n\n/** @experimental One renderable entry: an action, optionally with params. */\nexport interface ActionItem {\n /** `action.id`, or `action.id:variant.key` — unique across a registry. */\n key: string;\n action: Action;\n label: string;\n icon?: ReactNode | (() => ReactNode);\n /** What to pass to `trigger(action.id, params)`; absent for a plain action. */\n params?: Readonly<Record<string, unknown>>;\n}\n\n/** @experimental Expand `action` into the entries a surface renders. An\n * action without an immediate invoker has none: `trigger` cannot start a\n * drag, so a button for one would do nothing. */\nexport function actionItems(action: Action): ActionItem[] {\n if (action.invoker?.timing !== 'immediate') return [];\n if (!action.variants?.length) {\n return [{ key: action.id, action, label: action.label, icon: action.icon }];\n }\n return action.variants.map((v) => ({\n key: `${action.id}:${v.key}`,\n action,\n label: v.label,\n icon: v.icon ?? action.icon,\n params: v.params,\n }));\n}\n","/**\n * Named predicates for `defaultBinding.target.kindOf`. Each matches the\n * `TargetPredicate` shape, declaring whether it reads the hit,\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 */\nimport { bodyPredicate, hitPredicate, type TargetPredicate } from '@weasel-js/gestures';\n\n// ─── Body-class predicates (read the second arg) ───────────────────────\n\n/** A predicate that resolves from `bodyTarget` alone. */\nexport type BodyPredicate = TargetPredicate & { readsAffordance: false };\n\n/** Matches any body hit — selected or unselected node body. */\nexport const isBody: BodyPredicate = bodyPredicate((bodyTarget) =>\n bodyTarget === 'selected-body' || bodyTarget === 'unselected-body');\n\n/** Body hit specifically already in the selection. */\nexport const isSelectedBody: BodyPredicate = bodyPredicate((bodyTarget) =>\n bodyTarget === 'selected-body');\n\n/** Body hit specifically NOT in the selection. */\nexport const isUnselectedBody: BodyPredicate = bodyPredicate((bodyTarget) =>\n bodyTarget === 'unselected-body');\n\n/** Empty-canvas hit — no node beneath cursor. */\nexport const isEmpty: BodyPredicate = bodyPredicate((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 = hitPredicate((target) => {\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 = hitPredicate((target) =>\n kindOf(target) === 'rotate-handle');\n\n/** Any path anchor (the on-curve dot). */\nexport const isAnchor = hitPredicate((target) => {\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 = hitPredicate((target) => {\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 = hitPredicate((target) =>\n isAnchor(target) || isControlHandle(target));\n","import type { Contribution } from './types';\n\n/**\n * Concatenate contribution bundles into one registry, preserving order —\n * entry order decides which of two same-specificity bindings in one scope\n * tier wins, so it is part of the result, not an accident of it.\n *\n * Throws on a duplicate id or dep name rather than dropping one: a feature\n * silently losing its bindings, or reading another feature's dep, is the\n * failure this registry exists to make loud.\n */\nexport function mergeContributions<T extends Contribution<unknown>>(\n ...bundles: readonly (readonly T[])[]\n): T[] {\n const out: T[] = [];\n const seen = new Set<string>();\n const depOwner = new Map<string, string>();\n for (const bundle of bundles) {\n for (const entry of bundle) {\n if (seen.has(entry.id)) {\n throw new Error(\n `mergeContributions: duplicate contribution id \"${entry.id}\". `\n + `Two bundles registered the same id; rename one or merge them by hand.`,\n );\n }\n seen.add(entry.id);\n for (const dep of Object.keys(entry.deps ?? {})) {\n const owner = depOwner.get(dep);\n if (owner !== undefined) {\n throw new Error(\n `mergeContributions: dep \"${dep}\" is provided by both \"${owner}\" and \"${entry.id}\". `\n + `One registry answers each dep name; drop one source.`,\n );\n }\n depOwner.set(dep, entry.id);\n }\n out.push(entry);\n }\n }\n return out;\n}\n","import { modeLabel, type ModeDefinition, type ModeRegistry } from '@weasel-js/modes';\nimport type { KeySpec, ModSpec } from '@weasel-js/gestures';\nimport { ActionDisabledReason, type Action } from '../interactions/actions/action';\nimport type { Rule } from '../eligibility';\nimport type { Contribution } from './types';\n\n/**\n * What each mode shortcut does. A role left out binds nothing, so its key\n * falls through to whatever else the dispatcher has on it.\n */\nexport interface ModeShortcutHandlers {\n /** Enter mode `id` — the `entry` shortcut. */\n enter?: (id: string) => void;\n /** Whether mode `id` can be entered now. A mode that needs a target it\n * doesn't have answers no, and its entry key falls through. Default yes. */\n canEnter?: (id: string) => boolean;\n /** Leave the active soft mode, keeping its session to resume. */\n exit?: () => void;\n /** Leave the active soft mode and throw its session away. */\n discard?: () => void;\n /** Commit the active strict mode's transaction. */\n commit?: () => void;\n /** Abandon the active strict mode's transaction. */\n cancel?: () => void;\n}\n\ntype Role = 'exit' | 'discard' | 'commit' | 'cancel';\nconst LEAVING: readonly Role[] = ['exit', 'discard', 'commit', 'cancel'];\nconst VERB: Record<Role | 'entry', string> = {\n entry: 'Enter', exit: 'Exit', discard: 'Discard', commit: 'Commit', cancel: 'Cancel',\n};\n\nconst MODIFIERS: Record<string, keyof ModSpec> = {\n meta: 'meta', cmd: 'meta', command: 'meta',\n ctrl: 'ctrl', control: 'ctrl',\n alt: 'alt', option: 'alt',\n shift: 'shift',\n mod: 'mod',\n};\n\n/**\n * A `ModeDefinition` shortcut chord — `'Escape'`, `'Meta+T'` — as a key spec.\n * Modifier names are case-insensitive; the last segment is the key.\n */\nexport function modeShortcutSpec(shortcut: string): KeySpec {\n const parts = shortcut.split('+');\n const key = parts.pop();\n if (!key) throw new Error(`mode shortcut \"${shortcut}\" names no key`);\n const mods: ModSpec = {};\n for (const part of parts) {\n const mod = MODIFIERS[part.toLowerCase()];\n if (!mod) throw new Error(`mode shortcut \"${shortcut}\": unknown modifier \"${part}\"`);\n mods[mod] = true;\n }\n return Object.keys(mods).length > 0 ? { kind: 'key', key, mods } : { kind: 'key', key };\n}\n\n/**\n * The registry's declared mode shortcuts as an always-on contribution: one\n * action per shortcut, bound to its chord and gated on the mode it acts on —\n * `exit` / `discard` / `commit` / `cancel` while that mode is active, `entry`\n * while it is not. Install it with `<SceneCanvas ambient>`.\n *\n * An entry key one mode shares with another mode's leaving key is reported as\n * a route conflict unless `canEnter` is given: that is what keeps the entry\n * key from firing inside the other mode.\n *\n * The bindings ride the hotkey tier, so leaving a mode outranks what the same\n * key does otherwise (Escape clearing the selection), and they yield while a\n * gesture is in flight, so Escape mid-drag still cancels the drag first. Keys\n * typed into an editable element never reach them.\n */\nexport function modeShortcuts(\n registry: ModeRegistry,\n handlers: ModeShortcutHandlers,\n): Contribution<never> {\n const actions: Action[] = [];\n for (const mode of registry.list()) {\n const entry = mode.entry?.shortcut;\n const enter = handlers.enter;\n if (entry && enter) {\n // Without `canEnter` an entry key is live in every other mode, so it does not yield there.\n actions.push(shortcutAction(mode, 'entry', entry, { mode: { not: mode.id } },\n () => registry.current().id !== mode.id && (handlers.canEnter?.(mode.id) ?? true),\n () => enter(mode.id), handlers.canEnter !== undefined));\n }\n for (const role of LEAVING) {\n const shortcut = mode[role]?.shortcut;\n const run = handlers[role];\n if (!shortcut || !run) continue;\n actions.push(shortcutAction(mode, role, shortcut, { mode: mode.id },\n () => registry.current().id === mode.id,\n run, true));\n }\n }\n return { id: 'modes.shortcuts', eligibility: { always: true }, actions };\n}\n\nfunction shortcutAction(\n mode: ModeDefinition,\n role: Role | 'entry',\n shortcut: string,\n eligible: Rule,\n live: () => boolean,\n run: () => void,\n yields: boolean,\n): Action {\n return {\n id: `mode.${mode.id}.${role}`,\n label: `${VERB[role]} ${modeLabel(mode)}`,\n scope: 'hotkey',\n defaultBinding: [{\n spec: { ...modeShortcutSpec(shortcut), phase: [{ channel: '*', phase: 'initial' }] },\n opts: { yields },\n }],\n eligible,\n // `eligible` is the gate the dispatcher and palettes read, but a canvas\n // with no `modes` evaluates none; without this the key is\n // swallowed in every mode.\n enabled: () => (live() ? true : ActionDisabledReason.NotApplicable),\n invoker: { timing: 'immediate', run: () => run() },\n };\n}\n","// src/tools/defineTool.ts\nimport type { Tool } from './types';\nimport type { ToolDef } from './routeTypes';\nimport { RESERVED_ID_NAMES, RESERVED_ID_PREFIXES } from '@weasel-js/gestures';\nimport type { CursorSpec } from '@weasel-js/cursor';\nimport type { KernelOverlay } from '../index';\n\n/** Validate that `id` is usable as a tool / channel id in the route\n * grammar. Rejects ids that start with a reserved sigil (would shadow\n * future grammar extensions) and ids that collide with phase keywords\n * (would parse as the bare-phase shorthand). */\nfunction validateId(id: string, kind: 'tool' | 'action'): void {\n if (id.length === 0) {\n throw new Error(`weasel: ${kind} id may not be empty`);\n }\n if (RESERVED_ID_PREFIXES.has(id[0]!)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" starts with reserved sigil \"${id[0]}\" ` +\n `(reserved set: ${[...RESERVED_ID_PREFIXES].join(' ')})`,\n );\n }\n if (RESERVED_ID_NAMES.has(id)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" collides with a reserved phase keyword ` +\n `(reserved: ${[...RESERVED_ID_NAMES].join(', ')})`,\n );\n }\n}\n\n/**\n * Build a `Tool` from a declarative `ToolDef`: validates the id, applies the\n * `cursor` default, and keeps the authored form on `Tool.def` for reflection.\n */\nexport function defineTool<TOverlay = KernelOverlay>(\n def: ToolDef<TOverlay>,\n): Tool<TOverlay> {\n validateId(def.id, 'tool');\n\n const resolveCursor = (): CursorSpec => {\n if (def.cursor == null) return '';\n return typeof def.cursor === 'function' ? def.cursor() : 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,\n keybinding: def.keybinding,\n onActivate: def.onActivate,\n onDeactivate: def.onDeactivate,\n cursor: resolveCursor,\n bindings: def.bindings,\n overlay: def.overlay,\n overlayPosition: def.overlayPosition,\n };\n}\n","/**\n * Typed keys for the gesture/behavior scratch store, `ctx.scratch`.\n * `ScratchKey<T>` carries its payload type at the typing layer; at runtime\n * the key is just its underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\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. Two keys with the same `name` refer to the same\n * slot at runtime — the type parameter is a compile-time contract, not a\n * uniqueness guarantee — so namespace the name (`'behavior.field'`).\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n"]}
1
+ {"version":3,"sources":["../src/interactions/actions/actionTags.ts","../src/interactions/actions/overlay.ts","../src/interactions/dispatcher/predicates.ts","../src/contributions/merge.ts","../src/contributions/modeShortcuts.ts","../src/tools/defineTool.ts","../src/interactions/browserReservedKeys.ts","../src/interactions/scratchKey.ts"],"names":["RESERVED_ID_PREFIXES","RESERVED_ID_NAMES"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Finding actions by tag: gathering the ones that carry it, and a person's\n * search matching it as it matches a label.\n */\nimport { hasTag, matchQuery, type QueryMatch } from '@weasel-js/registry';\nimport type { Action } from './action';\nimport { actionItems, actionItemTitle, type ActionItem } from './actionItems';\n\n/** The actions carrying `tag`, themselves or on any variant, in\n * the order given. Drags included, which have no entries to show. */\nexport function actionsTagged(actions: readonly Action[], tag: string): Action[] {\n return actions.filter((a) => hasTag(a.tags, tag) || (a.variants?.some((v) => hasTag(v.tags, tag)) ?? false));\n}\n\n/** The entries carrying `tag`: what a bar or a menu built from\n * a tag shows. A variant that does not carry it is left out. */\nexport function taggedActionItems(actions: readonly Action[], tag: string): ActionItem[] {\n return actionsTagged(actions, tag).flatMap(actionItems).filter((i) => hasTag(i.tags, tag));\n}\n\n/** Whether `item` answers a search for `query`, by its palette title or\n * by a tag, and which. */\nexport function matchActionItem(item: ActionItem, query: string): QueryMatch | null {\n return matchQuery(query, { text: [actionItemTitle(item)], tags: item.tags });\n}\n","/** The chrome an in-flight gesture paints beside the scene. */\nimport type { OngoingHandle } from './invoker';\n\n/**\n * What a `polyline` overlay is, in a word — the narrowest thing an action can\n * say about how its run should look without naming a paint.\n *\n * The named roles are the kit's; a painter maps each to a style and falls back\n * to plain chrome for one it does not know, so a package outside core can coin\n * its own and still be drawn.\n */\nexport type OverlayRole =\n /** Generic in-flight chrome — the marquee/lasso paint. */\n | 'chrome'\n /** A cut the gesture would make. */\n | 'cut'\n /** An edge the gesture would author between two points. */\n | 'connector'\n /** The stretch of a path a soft-selection drag reaches. */\n | 'falloff'\n /** What a lock holds a dragged point to — a line, a circle. */\n | 'lock'\n | (string & {});\n\n/**\n * What an in-flight gesture proposes to paint *beside* the scene, returned by\n * `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a\n * shape being dragged out, the line a slice would cut along.\n *\n * Every variant is world geometry and nothing else — no `DrawCommand`, no\n * color, no coordinate space to choose — so a surface rendering the same\n * gesture some other way can still read it. The painter owns every style\n * decision.\n */\nexport type OngoingOverlay =\n | {\n kind: 'marquee';\n start: { x: number; y: number };\n current: { x: number; y: number };\n shiftHeld: boolean;\n }\n | {\n kind: 'lasso';\n vertices: ReadonlyArray<{ x: number; y: number }>;\n current: { x: number; y: number };\n shiftHeld: boolean;\n }\n | {\n /**\n * An open run of world points the gesture is proposing — a cut line, a\n * routed connector. `role` is the whole of what an action says about\n * appearance.\n */\n kind: 'polyline';\n /** At least two points. Anything shorter is dropped in resolution. */\n points: ReadonlyArray<{ x: number; y: number }>;\n /** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */\n role?: OverlayRole;\n }\n | {\n /**\n * A round brush dragged through world points: the area it has swept so\n * far, and its footprint at the last point. The radius is in screen\n * pixels, so the footprint keeps its size at every zoom.\n */\n kind: 'brush';\n /** The brush's path so far, at least one point; the last is where it is now. */\n points: ReadonlyArray<{ x: number; y: number }>;\n radiusPx: number;\n }\n | {\n /**\n * Live insert-drag preview. Pre-commit there is no scene node to ghost\n * via `previewIds()`/`previewPose()`, so insert paints its preview\n * through the overlay instead.\n *\n * `bounds` is the AABB of the current drag (start/current normalized).\n * `extras` is the per-kind extras the action already collected; the\n * overlay renderer narrows on `shape` and casts the field shape it\n * expects.\n */\n kind: 'insertPreview';\n /** Which preview the dispatcher's overlay draws. The kit's own shape\n * kinds are the ones it knows how to render; a consumer-defined kind\n * is legal and simply gets no live preview. */\n shape: string;\n bounds: { x: number; y: number; width: number; height: number };\n extras: unknown;\n /** World-space point to paint a small \"anchor\" dot at, marking the\n * click point — for radial shapes (polygon/star), where no vertex\n * sits on it, and for any shape in center mode. */\n anchorPoint?: { x: number; y: number };\n /** World affines the shape will also be drawn under once it lands, by\n * the container it is drawn into: the preview is painted under each. */\n copies?: readonly (readonly [number, number, number, number, number, number])[];\n };\n\n/** A handle's overlays as a list: empty when it has none. */\nexport function overlaysOf(handle: OngoingHandle): readonly OngoingOverlay[] {\n const ov = handle.overlay?.();\n if (!ov) return [];\n return isOverlayList(ov) ? ov : [ov];\n}\n\nfunction isOverlayList(ov: OngoingOverlay | readonly OngoingOverlay[]): ov is readonly OngoingOverlay[] {\n return Array.isArray(ov);\n}\n","/**\n * Named predicates for `defaultBinding.target.kindOf`. Each matches the\n * `TargetPredicate` shape, declaring whether it reads the hit,\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 */\nimport { bodyPredicate, hitPredicate, type TargetPredicate } from '@weasel-js/gestures';\n\n// ─── Body-class predicates (read the second arg) ───────────────────────\n\n/** A predicate that resolves from `bodyTarget` alone. */\nexport type BodyPredicate = TargetPredicate & { readsAffordance: false };\n\n/** Matches any body hit — selected or unselected node body. */\nexport const isBody: BodyPredicate = bodyPredicate((bodyTarget) =>\n bodyTarget === 'selected-body' || bodyTarget === 'unselected-body');\n\n/** Body hit specifically already in the selection. */\nexport const isSelectedBody: BodyPredicate = bodyPredicate((bodyTarget) =>\n bodyTarget === 'selected-body');\n\n/** Body hit specifically NOT in the selection. */\nexport const isUnselectedBody: BodyPredicate = bodyPredicate((bodyTarget) =>\n bodyTarget === 'unselected-body');\n\n/** Empty-canvas hit — no node beneath cursor. */\nexport const isEmpty: BodyPredicate = bodyPredicate((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 resize handle: a corner (`handle:top-left`, …) or an edge (`handle:top`, …). */\nexport const isResizeHandle = hitPredicate((target) => {\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 = hitPredicate((target) =>\n kindOf(target) === 'rotate-handle');\n\n/** Any path anchor (the on-curve dot). */\nexport const isAnchor = hitPredicate((target) => {\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 = hitPredicate((target) => {\n const k = kindOf(target);\n return k !== undefined && /^(controlIn|controlOut):\\d+$/.test(k);\n});\n\n/** A corner-shape widget, set inside a corner anchor of the edited path. */\nexport const isCornerWidget = hitPredicate((target) => {\n const k = kindOf(target);\n return k !== undefined && /^corner:\\d+$/.test(k);\n});\n\n/** Any of the three anchor-editing affordances (anchor + both controls). */\nexport const isAnchorOrControl = hitPredicate((target) =>\n isAnchor(target) || isControlHandle(target));\n\n/** The outline of the path being edited, between its anchors. */\nexport const isSegment = hitPredicate((target) => kindOf(target) === 'segment');\n\n/** The grip midway along a curved segment's balance line, `balance:N`, where\n * N is the flat index of the anchor the segment starts at. */\nexport const isBalanceGrip = hitPredicate((target) => {\n const k = kindOf(target);\n return k !== undefined && /^balance:\\d+$/.test(k);\n});\n\n/** Anything path edit puts on the path: an anchor, a control, a segment, a\n * segment's balance grip, or a corner widget. */\nexport const isPathEditPart = hitPredicate((target) =>\n isAnchorOrControl(target) || isSegment(target) || isBalanceGrip(target) || isCornerWidget(target));\n","import type { Contribution } from './types';\n\n/**\n * Concatenate contribution bundles into one registry, preserving order —\n * entry order decides which of two same-specificity bindings in one scope\n * tier wins, so it is part of the result, not an accident of it.\n *\n * Throws on a duplicate id or dep name rather than dropping one: a feature\n * silently losing its bindings, or reading another feature's dep, is the\n * failure this registry exists to make loud.\n */\nexport function mergeContributions<T extends Contribution<unknown>>(\n ...bundles: readonly (readonly T[])[]\n): T[] {\n const out: T[] = [];\n const seen = new Set<string>();\n const depOwner = new Map<string, string>();\n for (const bundle of bundles) {\n for (const entry of bundle) {\n if (seen.has(entry.id)) {\n throw new Error(\n `mergeContributions: duplicate contribution id \"${entry.id}\". `\n + `Two bundles registered the same id; rename one or merge them by hand.`,\n );\n }\n seen.add(entry.id);\n for (const dep of Object.keys(entry.deps ?? {})) {\n const owner = depOwner.get(dep);\n if (owner !== undefined) {\n throw new Error(\n `mergeContributions: dep \"${dep}\" is provided by both \"${owner}\" and \"${entry.id}\". `\n + `One registry answers each dep name; drop one source.`,\n );\n }\n depOwner.set(dep, entry.id);\n }\n out.push(entry);\n }\n }\n return out;\n}\n","import { modeLabel, type ModeDefinition, type ModeRegistry } from '@weasel-js/modes';\nimport type { KeySpec, ModSpec } from '@weasel-js/gestures';\nimport { ActionDisabledReason, type Action } from '../interactions/actions/action';\nimport type { Rule } from '../eligibility';\nimport type { Contribution } from './types';\n\n/**\n * What each mode shortcut does. A role left out binds nothing, so its key\n * falls through to whatever else the dispatcher has on it.\n */\nexport interface ModeShortcutHandlers {\n /** Enter mode `id` — the `entry` shortcut. */\n enter?: (id: string) => void;\n /** Whether mode `id` can be entered now. A mode that needs a target it\n * doesn't have answers no, and its entry key falls through. Default yes. */\n canEnter?: (id: string) => boolean;\n /** Leave the active soft mode, keeping its session to resume. */\n exit?: () => void;\n /** Leave the active soft mode and throw its session away. */\n discard?: () => void;\n /** Commit the active strict mode's transaction. */\n commit?: () => void;\n /** Abandon the active strict mode's transaction. */\n cancel?: () => void;\n}\n\ntype Role = 'exit' | 'discard' | 'commit' | 'cancel';\nconst LEAVING: readonly Role[] = ['exit', 'discard', 'commit', 'cancel'];\nconst VERB: Record<Role | 'entry', string> = {\n entry: 'Enter', exit: 'Exit', discard: 'Discard', commit: 'Commit', cancel: 'Cancel',\n};\n\nconst MODIFIERS: Record<string, keyof ModSpec> = {\n meta: 'meta', cmd: 'meta', command: 'meta',\n ctrl: 'ctrl', control: 'ctrl',\n alt: 'alt', option: 'alt',\n shift: 'shift',\n mod: 'mod',\n};\n\n/**\n * A `ModeDefinition` shortcut chord — `'Escape'`, `'Meta+T'` — as a key spec.\n * Modifier names are case-insensitive; the last segment is the key.\n */\nexport function modeShortcutSpec(shortcut: string): KeySpec {\n const parts = shortcut.split('+');\n const key = parts.pop();\n if (!key) throw new Error(`mode shortcut \"${shortcut}\" names no key`);\n const mods: ModSpec = {};\n for (const part of parts) {\n const mod = MODIFIERS[part.toLowerCase()];\n if (!mod) throw new Error(`mode shortcut \"${shortcut}\": unknown modifier \"${part}\"`);\n mods[mod] = true;\n }\n return Object.keys(mods).length > 0 ? { kind: 'key', key, mods } : { kind: 'key', key };\n}\n\n/**\n * The registry's declared mode shortcuts as an always-on contribution: one\n * action per shortcut, bound to its chord and gated on the mode it acts on —\n * `exit` / `discard` / `commit` / `cancel` while that mode is active, `entry`\n * while it is not. Install it with `<SceneCanvas ambient>`.\n *\n * An entry key one mode shares with another mode's leaving key is reported as\n * a route conflict unless `canEnter` is given: that is what keeps the entry\n * key from firing inside the other mode.\n *\n * The bindings ride the hotkey tier, so leaving a mode outranks what the same\n * key does otherwise (Escape clearing the selection), and they yield while a\n * gesture is in flight, so Escape mid-drag still cancels the drag first. Keys\n * typed into an editable element never reach them.\n */\nexport function modeShortcuts(\n registry: ModeRegistry,\n handlers: ModeShortcutHandlers,\n): Contribution<never> {\n const actions: Action[] = [];\n for (const mode of registry.list()) {\n const entry = mode.entry?.shortcut;\n const enter = handlers.enter;\n if (entry && enter) {\n // Without `canEnter` an entry key is live in every other mode, so it does not yield there.\n actions.push(shortcutAction(mode, 'entry', entry, { mode: { not: mode.id } },\n () => registry.current().id !== mode.id && (handlers.canEnter?.(mode.id) ?? true),\n () => enter(mode.id), handlers.canEnter !== undefined));\n }\n for (const role of LEAVING) {\n const shortcut = mode[role]?.shortcut;\n const run = handlers[role];\n if (!shortcut || !run) continue;\n actions.push(shortcutAction(mode, role, shortcut, { mode: mode.id },\n () => registry.current().id === mode.id,\n run, true));\n }\n }\n return { id: 'modes.shortcuts', eligibility: { always: true }, actions };\n}\n\nfunction shortcutAction(\n mode: ModeDefinition,\n role: Role | 'entry',\n shortcut: string,\n eligible: Rule,\n live: () => boolean,\n run: () => void,\n yields: boolean,\n): Action {\n return {\n id: `mode.${mode.id}.${role}`,\n label: `${VERB[role]} ${modeLabel(mode)}`,\n scope: 'hotkey',\n defaultBinding: [{\n spec: { ...modeShortcutSpec(shortcut), phase: [{ channel: '*', phase: 'initial' }] },\n opts: { yields },\n }],\n eligible,\n // `eligible` is the gate the dispatcher and palettes read, but a canvas\n // with no `modes` evaluates none; without this the key is\n // swallowed in every mode.\n enabled: () => (live() ? true : ActionDisabledReason.NotApplicable),\n invoker: { timing: 'immediate', run: () => run() },\n };\n}\n","// src/tools/defineTool.ts\nimport type { Tool } from './types';\nimport type { ToolDef } from './routeTypes';\nimport { RESERVED_ID_NAMES, RESERVED_ID_PREFIXES } from '@weasel-js/gestures';\nimport type { CursorSpec } from '@weasel-js/cursor';\nimport type { KernelOverlay } from '../index';\n\n/** Validate that `id` is usable as a tool / channel id in the route\n * grammar. Rejects ids that start with a reserved sigil (would shadow\n * future grammar extensions) and ids that collide with phase keywords\n * (would parse as the bare-phase shorthand). */\nfunction validateId(id: string, kind: 'tool' | 'action'): void {\n if (id.length === 0) {\n throw new Error(`weasel: ${kind} id may not be empty`);\n }\n if (RESERVED_ID_PREFIXES.has(id[0]!)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" starts with reserved sigil \"${id[0]}\" ` +\n `(reserved set: ${[...RESERVED_ID_PREFIXES].join(' ')})`,\n );\n }\n if (RESERVED_ID_NAMES.has(id)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" collides with a reserved phase keyword ` +\n `(reserved: ${[...RESERVED_ID_NAMES].join(', ')})`,\n );\n }\n}\n\n/**\n * Build a `Tool` from a declarative `ToolDef`: validates the id, applies the\n * `cursor` default, and keeps the authored form on `Tool.def` for reflection.\n */\nexport function defineTool<TOverlay = KernelOverlay>(\n def: ToolDef<TOverlay>,\n): Tool<TOverlay> {\n validateId(def.id, 'tool');\n\n const resolveCursor = (): CursorSpec => {\n if (def.cursor == null) return '';\n return typeof def.cursor === 'function' ? def.cursor() : 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 menus: def.menus,\n def,\n presentation: def.presentation,\n keybinding: def.keybinding,\n onActivate: def.onActivate,\n onDeactivate: def.onDeactivate,\n cursor: resolveCursor,\n bindings: def.bindings,\n overlay: def.overlay,\n overlayPosition: def.overlayPosition,\n };\n}\n","/**\n * Key chords a major browser or the desktop under it takes before a page can:\n * reload, devtools, tab and window commands, menu accelerators. A binding on\n * one never fires for some users, so kit defaults steer clear of them.\n *\n * `mod` is Cmd on macOS and Ctrl elsewhere, as in a `KeySpec`. The list is\n * the union across Chrome, Safari, Firefox and Edge on macOS, Windows and\n * Linux, and errs toward listing a chord.\n */\nimport type { KeySpec } from '@weasel-js/gestures';\n\n/** One reserved chord, and what takes it. */\nexport interface ReservedChord {\n key: string;\n mod?: boolean;\n alt?: boolean;\n shift?: boolean;\n /** Who takes it, for a message. */\n use: string;\n}\n\nconst chord = (keys: string, mods: Omit<ReservedChord, 'key' | 'use'>, use: string): ReservedChord[] =>\n [...keys].map((key) => ({ key, ...mods, use }));\n\n/** Every chord the module's header describes, one entry per key. */\nexport const BROWSER_RESERVED_CHORDS: readonly ReservedChord[] = [\n ...chord('nqtwlrpmh', { mod: true }, 'new window, quit, new or close tab, address bar, reload, print, minimize, hide'),\n ...chord('ntwrbo', { mod: true, shift: true }, 'private window, reopen or close tab, hard reload, bookmarks, bookmark manager'),\n ...chord('ijcukbrlemhdwfn', { mod: true, alt: true }, 'devtools, console, inspector, view source, bookmarks, reload from origin, downloads, empty caches, minimize all, hide others, Dock, close all, search field'),\n ...chord('fevsbthd', { alt: true }, 'menu accelerators and address bar on Windows and Linux'),\n ...chord('ita', { alt: true, shift: true }, 'feedback, toolbar focus and accessibility commands'),\n];\n\nconst lower = (k: string | readonly string[]): string[] => (typeof k === 'string' ? [k] : [...k]).map((x) => x.toLowerCase());\n\n/** Whether a modifier policy can be satisfied with the modifier held (`true`)\n * or released (`false`). */\nconst allows = (policy: boolean | 'optional' | undefined, held: boolean): boolean =>\n policy === 'optional' || (policy === true) === held;\n\n/** The first reserved chord `spec` can fire on, or `null` when none. */\nexport function browserReservedChord(spec: KeySpec): ReservedChord | null {\n const keys = lower(spec.key);\n const m = spec.mods ?? {};\n const mod = m.mod ?? (m.meta === true || m.ctrl === true ? true : m.meta ?? m.ctrl);\n for (const c of BROWSER_RESERVED_CHORDS) {\n if (!keys.includes(c.key)) continue;\n if (allows(mod, c.mod === true) && allows(m.alt, c.alt === true) && allows(m.shift, c.shift === true)) return c;\n }\n return null;\n}\n","/**\n * Typed keys for the gesture/behavior scratch store, `ctx.scratch`.\n * `ScratchKey<T>` carries its payload type at the typing layer; at runtime\n * the key is just its underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\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. Two keys with the same `name` refer to the same\n * slot at runtime — the type parameter is a compile-time contract, not a\n * uniqueness guarantee — so namespace the name (`'behavior.field'`).\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { k as Dispatcher, A as ActionsRegistry, C as Contribution, l as AffordanceHit, L as LongPressOptions, R as RuleCtx, b as DepSchema } from './index-Bm2QEV09.js';
1
+ import { o as Dispatcher, d as ActionsRegistry, C as Contribution, p as AffordanceHit, L as LongPressOptions, R as RuleCtx, b as DepSchema } from './index-CmfVvwWD.js';
2
2
  import { RefObject } from 'react';
3
3
  import { BodyClassification } from '@weasel-js/gestures';
4
4
  import { PaintedCursorState, CursorSink } from '@weasel-js/cursor';
@@ -71,6 +71,9 @@ interface DispatcherChannels {
71
71
  /** OS drop on the element plus `paste` on the window. Off leaves the
72
72
  * element a non-drop-target and stops `weasel-dropover` being applied. */
73
73
  ingest?: boolean;
74
+ /** A carry (`startCarry`) from elsewhere on the page, over the element. Off,
75
+ * the element is not a surface a carry can be released on. */
76
+ carry?: boolean;
74
77
  }
75
78
  /** Options for `useGestureDispatcher`: the element to listen on, the actions
76
79
  * and entries in play, and the hooks that turn raw DOM events into the world
@@ -87,6 +90,13 @@ interface UseGestureDispatcherOptions {
87
90
  canvasRef: RefObject<HTMLElement | null>;
88
91
  /** Action registry (ActionsRegistry from registry.tsx). */
89
92
  actions: ActionsRegistry;
93
+ /**
94
+ * Which actions this dispatcher matches. `'scope'` (default): every action a
95
+ * read from `actions` reaches, down its active scope. `'own'`: only those
96
+ * registered at `actions`' own node — for chrome on a root that canvases
97
+ * nest under.
98
+ */
99
+ hears?: 'scope' | 'own';
90
100
  /** Every entry in play, keyed by id: tools and any other contribution
91
101
  * whose bindings this dispatcher routes. */
92
102
  entriesById: ReadonlyMap<string, Contribution<unknown>>;
@@ -180,6 +190,18 @@ interface UseGestureDispatcherOptions {
180
190
  x: number;
181
191
  y: number;
182
192
  };
193
+ /**
194
+ * Converts a client point to CSS pixels from the canvas's top-left: where
195
+ * the painted cursor draws, where a wheel or pinch zoom anchors, and where a
196
+ * pending long-press is published. Without this, the element's rect is
197
+ * subtracted — wrong for a canvas under a CSS transform, whose host already
198
+ * owns the mapping. `<SceneCanvas>` wires it from its own camera, so a
199
+ * `clientToWorld` override reaches it.
200
+ */
201
+ clientToLocal?: (clientX: number, clientY: number) => {
202
+ x: number;
203
+ y: number;
204
+ };
183
205
  /**
184
206
  * Called whenever one of this hook's dispatchers notifies its subscribers:
185
207
  * once for each input event, cancel, or UI-driven step that found or left a
@@ -1,9 +1,12 @@
1
- export { a as DispatcherViewTarget, V as ViewIdResolver } from './internal-D0rDmi7t.js';
2
- import './index-Bm2QEV09.js';
1
+ export { a as DispatcherViewTarget, V as ViewIdResolver } from './internal-21M1rTVo.js';
2
+ import './index-CmfVvwWD.js';
3
+ import '@weasel-js/menu';
4
+ import '@weasel-js/prefs';
3
5
  import '@weasel-js/geom';
4
- import 'react';
5
- import '@weasel-js/registry';
6
6
  import '@weasel-js/modes';
7
- import '@weasel-js/cursor';
8
7
  import '@weasel-js/gestures';
8
+ import '@weasel-js/icons';
9
+ import '@weasel-js/cursor';
10
+ import 'react';
11
+ import '@weasel-js/registry';
9
12
  import '@weasel-js/select';
package/dist/react.d.ts CHANGED
@@ -1,12 +1,15 @@
1
- import { U as UseGestureDispatcherOptions } from './internal-D0rDmi7t.js';
2
- export { D as DispatcherChannels } from './internal-D0rDmi7t.js';
1
+ import { U as UseGestureDispatcherOptions } from './internal-21M1rTVo.js';
2
+ export { D as DispatcherChannels } from './internal-21M1rTVo.js';
3
3
  import * as react from 'react';
4
4
  import { ReactNode, ReactElement } from 'react';
5
- import { D as DepRegistry, a as DepName, b as DepSchema, S as ScopeNode, A as ActionsRegistry, c as Action, d as DepNode, T as ToolNode, N as NodeId, K as KernelOverlay, C as Contribution, e as Tool, O as OverlayPosition } from './index-Bm2QEV09.js';
6
- export { f as ActiveToolContextProvider, g as ActiveToolContextProviderIfRoot, h as ActiveToolContextProviderProps, i as ActiveToolContextValue, u as useActiveToolContext, j as useOptionalActiveToolContext } from './index-Bm2QEV09.js';
5
+ import { D as DepRegistry, a as DepName, b as DepSchema, S as ScopeNode, A as Action, c as ActionDeps, M as MenuHost, d as ActionsRegistry, e as ActionDisabledReason, B as BoundGesture, f as ActionVariant, g as MenuDef, h as DepNode, T as ToolNode, N as NodeId, K as KernelOverlay, C as Contribution, i as Tool, O as OverlayPosition } from './index-CmfVvwWD.js';
6
+ export { j as ActiveToolContextProvider, k as ActiveToolContextProviderIfRoot, l as ActiveToolContextProviderProps, m as ActiveToolContextValue, u as useActiveToolContext, n as useOptionalActiveToolContext } from './index-CmfVvwWD.js';
7
+ import { IconRef } from '@weasel-js/icons';
7
8
  import { ModeRegistry } from '@weasel-js/modes';
8
9
  import '@weasel-js/gestures';
9
10
  import '@weasel-js/cursor';
11
+ import '@weasel-js/menu';
12
+ import '@weasel-js/prefs';
10
13
  import '@weasel-js/geom';
11
14
  import '@weasel-js/registry';
12
15
  import '@weasel-js/select';
@@ -49,20 +52,34 @@ declare class ActionsNode extends ScopeNode<ActionsNode> {
49
52
  private readonly contextDeps;
50
53
  private readonly actions;
51
54
  private readonly muted;
55
+ private readonly tunings;
52
56
  private readonly dispatchers;
53
57
  private cache;
58
+ private host;
54
59
  /** Called on `activate`, so a scope's dep and tool nodes move with it. */
55
60
  readonly onActivate: (() => void)[];
56
61
  constructor(parent: ActionsNode | null, descends: boolean, contextDeps: () => DepRegistry | null);
57
62
  private resolve;
63
+ /** Every tuning for `id` from the root down to here, root first. */
64
+ private tuningsOf;
65
+ /** `a` under every tuning for its id from the root down to here. */
66
+ private tuned;
58
67
  private snapshot;
59
68
  private deps;
69
+ /** The actions registered at this node itself, live, unmuted and tuned —
70
+ * none from the scopes below it. */
71
+ ownActions(): readonly Action[];
72
+ /** The deps `a` is invoked with from here: the active scope's, which is
73
+ * what `trigger` and `begin` run it with. */
74
+ depsOf(a: Action): ActionDeps;
75
+ /** The menu host of this node's tree, made by its root on first ask. Rows
76
+ * resolve and run against the active scope, as chrome's `trigger` does. */
77
+ menuHost(): MenuHost;
60
78
  private dispatcher;
61
79
  activate(): void;
62
80
  readonly registry: ActionsRegistry;
63
81
  }
64
82
  /**
65
- * @experimental
66
83
  * Mounts a root `ActionsRegistry` for its lifetime. Children call
67
84
  * `useActionsRegistry()` or `useAction()` to participate. Mounts no input
68
85
  * listener of its own — the gesture dispatcher owns input.
@@ -71,12 +88,10 @@ declare function ActionsProvider({ children }: {
71
88
  children: ReactNode;
72
89
  }): ReactElement;
73
90
  /**
74
- * @experimental
75
91
  * Returns the `ActionsRegistry` in scope, or `null` when there is none.
76
92
  */
77
93
  declare function useActionsRegistry(): ActionsRegistry | null;
78
94
  /**
79
- * @experimental
80
95
  * Register an `Action` for the lifetime of the calling component. No-op (with
81
96
  * a dev-only warning) when no `ActionsProvider` is in scope. Re-registers on
82
97
  * `action` reference change (consumers should memoize stable identities to
@@ -84,8 +99,37 @@ declare function useActionsRegistry(): ActionsRegistry | null;
84
99
  */
85
100
  declare function useAction(action: Action): void;
86
101
 
102
+ /** What a command offers besides its id, label and callback. */
103
+ interface CommandOptions {
104
+ /** True when the command can run, else why not. Read each time it is asked. */
105
+ enabled?: () => true | ActionDisabledReason;
106
+ /** Whether it reads as held down. Read each time it is asked. */
107
+ active?: () => boolean;
108
+ /** The gestures that run it. The `{ spec, opts: { params } }` form gives one variant its own key. */
109
+ keys?: BoundGesture | readonly BoundGesture[];
110
+ /** Shown beside the label. */
111
+ icon?: IconRef;
112
+ /** Words a palette finds it by and a tagged bar or menu gathers it under. */
113
+ tags?: readonly string[];
114
+ /** One entry each in a bar, menu or palette; `run` receives the chosen one's params. */
115
+ variants?: readonly ActionVariant[];
116
+ }
117
+ /**
118
+ * Register `run` as an action under `id` on the nearest `<ActionsProvider>`
119
+ * for the calling component's lifetime, so a menu, toolbar, palette or key
120
+ * can run it. Callbacks and option functions may be new on every render: the
121
+ * latest are read when used, and the action is replaced only when `id`,
122
+ * `label`, `icon`, `keys`, `tags` or `variants` change by value.
123
+ */
124
+ declare function useCommand(id: string, label: string, run: (params?: Readonly<Record<string, unknown>>) => void, options?: CommandOptions): void;
125
+
126
+ /** The menu host in scope: the nearest `<ActionsProvider>`'s, else the one a
127
+ * canvas with no provider above it makes. Null outside both. */
128
+ declare function useMenuHost(): MenuHost | null;
129
+ /** Declare menus for the calling component's lifetime. Memoize `defs`. */
130
+ declare function useMenus(defs: readonly MenuDef[], owner?: string | null): void;
131
+
87
132
  /**
88
- * @experimental
89
133
  * Input scopes and yokes: the tiers between a root registry and a canvas.
90
134
  */
91
135
 
@@ -97,21 +141,20 @@ interface InputScopeProps {
97
141
  children: ReactNode;
98
142
  }
99
143
  /**
100
- * @experimental
101
144
  * One canvas's input: actions, deps and a tool of its own, under whichever
102
145
  * registries are in scope (or its yoke's). What the canvas registers stays
103
146
  * here; what it looks up is answered here first, then by the tiers above.
104
- * With nothing in scope it is a root.
147
+ * With nothing in scope it is a root, and makes the menu host
148
+ * `<ActionsProvider>` would have.
105
149
  *
106
150
  * Chrome above calls `trigger` / `begin` / `useActiveToolContext` on its own
107
151
  * registries, and those reach the active scope: the one last activated — by a
108
- * pointerdown or wheel — or the newest where none has been. Keys are stricter:
152
+ * pointerdown, wheel or contextmenu — or the newest where none has been. Keys are stricter:
109
153
  * one that reaches the page goes to the canvas last pressed or scrolled in,
110
154
  * across every root on the page, and to none before the first.
111
155
  */
112
156
  declare function InputScope({ yoke, children }: InputScopeProps): ReactElement;
113
157
  /**
114
- * @experimental
115
158
  * Make a yoke for canvases to join, under the registries in scope where it is
116
159
  * called. It lives as long as the calling component.
117
160
  */
@@ -122,7 +165,6 @@ interface YokeProps {
122
165
  children: ReactNode;
123
166
  }
124
167
  /**
125
- * @experimental
126
168
  * Canvases that share input on purpose: the active tool, the gesture in
127
169
  * flight, history, and yoke-wide actions. Made once with `useYoke()`, and
128
170
  * passed to each canvas that joins it (`yoke={yoke}`) and to
@@ -138,7 +180,6 @@ interface Yoke {
138
180
  readonly tools: ToolNode;
139
181
  }
140
182
  /**
141
- * @experimental
142
183
  * Put `value` in scope for what is not a canvas — a toolbar, a status bar — so
143
184
  * its actions, deps and tool reads reach the yoke and, through it, the member
144
185
  * canvas last used.
@@ -174,11 +215,12 @@ interface OngoingAction {
174
215
  *
175
216
  * An edit left open when the control unmounts, or when `actionId` changes, is
176
217
  * committed — it is what the user was looking at when the control went away.
218
+ * On unmount that happens a microtask later.
177
219
  * The returned object is stable for the life of the component.
178
220
  */
179
221
  declare function useOngoingAction(actionId: string): OngoingAction;
180
222
 
181
- /** @experimental World-space pointer position and the view it is over — a
223
+ /** World-space pointer position and the view it is over — a
182
224
  * view id, or `null` for a surface's own camera. `null` as a whole when the
183
225
  * pointer is over no publishing surface. */
184
226
  type PointerWorldPos = {
@@ -186,7 +228,7 @@ type PointerWorldPos = {
186
228
  worldY: number;
187
229
  viewId: string | null;
188
230
  } | null;
189
- /** @experimental */
231
+ /** A pointer store: the latest position, a way to publish one, and change notification. */
190
232
  interface PointerContextValue {
191
233
  /** The latest position. */
192
234
  get(): PointerWorldPos;
@@ -199,10 +241,9 @@ interface PointerContextValue {
199
241
  /** `get()` as a stable thunk, for hooks taking a drop-point option. */
200
242
  readonly getDropPoint: () => PointerWorldPos;
201
243
  }
202
- /** @experimental A pointer store with no provider around it. */
244
+ /** A pointer store with no provider around it. */
203
245
  declare function createPointerStore(): PointerContextValue;
204
246
  /**
205
- * @experimental
206
247
  * Wrap the part of the React tree that should share one pointer.
207
248
  * `<SceneCanvas>` mounts one when none is in scope, so mount it yourself only
208
249
  * when two surfaces should share a pointer. Pass `store` to publish into one
@@ -212,15 +253,14 @@ declare function PointerContextProvider({ children, store }: {
212
253
  children: ReactNode;
213
254
  store?: PointerContextValue;
214
255
  }): ReactNode;
215
- /** @experimental The surrounding pointer store, or `null` when no provider is
256
+ /** The surrounding pointer store, or `null` when no provider is
216
257
  * in scope. */
217
258
  declare function usePointerContext(): PointerContextValue | null;
218
- /** @experimental The pointer, re-rendering the caller whenever it changes.
259
+ /** The pointer, re-rendering the caller whenever it changes.
219
260
  * `null` outside a provider. */
220
261
  declare function usePointerPosition(): PointerWorldPos;
221
262
 
222
263
  /**
223
- * @experimental
224
264
  * SelectionContext — an ambient context that publishes "what's currently
225
265
  * selected" so non-canvas UI (command palette, status bar) can read it without
226
266
  * threading a selection prop through the tree.
@@ -230,7 +270,7 @@ declare function usePointerPosition(): PointerWorldPos;
230
270
  * last-writer-wins. `<SceneCanvas>` publishes automatically.
231
271
  */
232
272
 
233
- /** @experimental */
273
+ /** The ambient selection store a canvas publishes into and non-canvas UI reads. */
234
274
  interface SelectionContextValue {
235
275
  /** The most-recently-published selection. Empty array when no canvas
236
276
  * has published or when the active canvas's selection is empty. */
@@ -244,7 +284,6 @@ interface SelectionContextValue {
244
284
  publishSelection(ids: readonly NodeId[], kinds?: readonly (string | undefined)[]): void;
245
285
  }
246
286
  /**
247
- * @experimental
248
287
  * Wrap the part of the React tree that should share a selection context.
249
288
  * Usually placed at the demo / app root, alongside `<ActionsProvider>`.
250
289
  */
@@ -252,13 +291,11 @@ declare function SelectionContextProvider({ children }: {
252
291
  children: ReactNode;
253
292
  }): ReactNode;
254
293
  /**
255
- * @experimental
256
294
  * The current selection-context value, or `null` when no
257
295
  * `<SelectionContextProvider>` is in scope.
258
296
  */
259
297
  declare function useSelectionContext(): SelectionContextValue | null;
260
298
  /**
261
- * @experimental
262
299
  * Mounts a `<SelectionContextProvider>` only when no parent provider is in
263
300
  * scope; otherwise renders children unwrapped.
264
301
  */
@@ -266,7 +303,6 @@ declare function SelectionContextProviderIfRoot({ children, }: {
266
303
  children: ReactNode;
267
304
  }): ReactNode;
268
305
  /**
269
- * @experimental
270
306
  * Publishes `ids` into the surrounding `<SelectionContextProvider>` whenever
271
307
  * their content changes, and clears the slot on unmount unless another
272
308
  * publisher has written it since. No-op when there's no provider in scope.
@@ -376,4 +412,4 @@ declare function useContributions<TOverlay = KernelOverlay>(opts: UseContributio
376
412
  */
377
413
  declare function useOffhandAction(entries: readonly Contribution<unknown>[]): void;
378
414
 
379
- export { ActionsProvider, type ContributionsApi, DepRegistry, DepRegistryProvider, InputScope, type InputScopeProps, type OngoingAction, PointerContextProvider, type PointerContextValue, type PointerWorldPos, SelectionContextProvider, SelectionContextProviderIfRoot, type SelectionContextValue, type ToolsApi, type UseContributionsOptions, UseGestureDispatcherOptions, type UseToolsOptions, Yoke, type YokeProps, createPointerStore, useAction, useActionsRegistry, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useOffhandAction, useOngoingAction, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
415
+ export { ActionsProvider, type CommandOptions, type ContributionsApi, DepRegistry, DepRegistryProvider, InputScope, type InputScopeProps, type OngoingAction, PointerContextProvider, type PointerContextValue, type PointerWorldPos, SelectionContextProvider, SelectionContextProviderIfRoot, type SelectionContextValue, type ToolsApi, type UseContributionsOptions, UseGestureDispatcherOptions, type UseToolsOptions, Yoke, type YokeProps, createPointerStore, useAction, useActionsRegistry, useCommand, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useMenuHost, useMenus, useOffhandAction, useOngoingAction, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };