@weasel-js/routing 1.10.1 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,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 ActionParamSchema, v as ActionParams, w as ActionPresentation, x as ActionSource, y as ActionTuning, z as ActionTunings, B as ActionVariant, E as ActionsProp, A as ActionsRegistry, l as AffordanceHit, F as AnyTool, G as AssemblyInputs, H as BindingOpts, I as BindingScope, J as BindingSource, M as BodyPredicate, P as BoundGesture, Q as Bounds, U as BuildRuleCtxArgs, V as COARSE_TARGET_SCALE, W as Condition, X as Conflict, C as Contribution, Y as ContributionChrome, Z as ContributionDeps, _ as ContributionRouting, $ as DEFAULT_ALLOWED_CAPABILITIES, a0 as DEFAULT_DEVICE_PROFILE, a1 as DebugSink, a as DepName, D as DepRegistry, b as DepSchema, a2 as DetectedDeviceFacts, a3 as DeviceProfile, a4 as DispatchRecord, a5 as DispatchRecordInput, k as Dispatcher, a6 as DispatcherContext, a7 as DragSample, a8 as DroppedCandidate, a9 as Eligibility, aa as EligibilityState, ab as GestureBinding, ac as HandleKind, ad as HitShape, ae as HotkeyTrigger, af as INGEST_STRING_MIMES, ag as ImmediateInvoker, ah as InvocationCtx, ai as Invoker, K as KernelOverlay, aj as KeyBinding, L as LongPressOptions, ak as LongPressState, al as LongPressStore, am as MatchOutcome, an as MatchResult, ao as ModeShortcutHandlers, ap as ModeSwitchLogEntry, aq as ModifierState, ar as NEVER, N as NodeId, as as OngoingHandle, at as OngoingInvoker, au as OngoingOverlay, O as OverlayPosition, av as OverlayRole, aw as OverlaySchema, ax as PREDICATE_TARGET, ay as PendingLongPress, az as PlacedBy, aA as Point2, aB as PointerSession, aC as PointerSessionCallbacks, aD as PointerSessionCancelReason, aE as PointerSessionOptions, aF as RankedCandidate, aG as RecordCandidate, aH as RegistryEntry, aI as ResizeAnchor, aJ as ResolveAllOptions, aK as ResolveOnlyResult, aL as ResolvedCandidate, aM as Rule, R as RuleCtx, aN as ScopedBinding, aO as ScratchKey, aP as ScratchStore, aQ as SelectionApi, aR as Selector, aS as SpecificityPart, aT as TOOL_OFFHAND_ID, e as Tool, aU as ToolDef, aV as ToolKeybinding, aW as ToolModifiers, aX as ToolOffhandBindingSpec, aY as ToolPresentation, aZ as ToolScopes, a_ as ToolSlot, a$ as TraceLogEntry, b0 as TunedBinding, b1 as UiOngoingControl, b2 as ViewportGestureKind, b3 as WalkStep, b4 as actionBindings, b5 as actionItems, b6 as assembleEntries, b7 as buildDepsFromRequires, b8 as buildRouteRegistry, b9 as buildRuleCtx, ba as buildToolOffhandBindings, bb as clientToCanvas, bc as clientToCanvasRect, bd as createDepRegistry, be as createDispatcher, bf as createLongPressStore, bg as defineTool, bh as deleteScratch, bi as describeRule, bj as dlog, bk as dwarn, bl as evaluate, bm as evaluateActive, bn as evaluateEnabled, bo as findConflicts, bp as getScratch, bq as heldKeySpec, br as historyKey, bs as isAnchor, bt as isAnchorOrControl, bu as isBody, bv as isControlHandle, bw as isDebugEnabled, bx as isEmpty, by as isPathEditPart, bz as isResizeHandle, bA as isRotateHandle, bB as isSegment, bC as isSelectedBody, bD as isTool, bE as isUnselectedBody, bF as itemTypesFromDataTransfer, bG as itemsFromClipboardData, bH as itemsFromDataTransfer, bI as itemsFromFiles, bJ as liveScope, bK as makeToolOffhandAction, bL as matchBest, bM as matchSorted, bN as matchSortedWithBarred, bO as matchesKeyBinding, bP as mergeContributions, bQ as modeShortcutSpec, bR as modeShortcuts, bS as multiTouchGestureId, bT as offhandKeyFor, bU as openPointerSession, bV as pointerGestureId, bW as recordModeSwitch, bX as reportRouteConflicts, bY as resolveDeviceProfile, bZ as resolveParams, b_ as routesForSpec, b$ as ruleCanHoldIn, c0 as rulesExclusive, c1 as scopeBindings, c2 as scratchKey, c3 as setScratch, c4 as targetConsultsAffordance, c5 as tuneAction, c6 as validateActionDefaultBinding, c7 as validateActionId } from './index-DkOqUxow.js';
1
+ export { s as ALWAYS, A as Action, c as ActionDeps, e as ActionDisabledReason, e as ActionDisabledReasonValue, t as ActionDispatch, v as ActionEnabledResult, w as ActionEntry, x as ActionItem, y as ActionParamSchema, z as ActionParams, E as ActionPresentation, F as ActionSettings, G as ActionSettingsState, H as ActionShortcut, I as ActionSource, J as ActionTuning, P as ActionTunings, f as ActionVariant, Q as ActionsProp, d as ActionsRegistry, p as AffordanceHit, U as AnyTool, V as AssemblyInputs, W as BROWSER_RESERVED_CHORDS, X as BindingOpts, Y as BindingScope, Z as BindingSource, _ as BodyPredicate, B as BoundGesture, $ as Bounds, a0 as BuildRuleCtxArgs, a1 as COARSE_TARGET_SCALE, a2 as Carried, a3 as Carry, a4 as CarryCallbacks, a5 as CarryPayload, a6 as CarryPointer, a7 as CarryStage, a8 as Condition, a9 as Conflict, C as Contribution, aa as ContributionChrome, ab as ContributionDeps, ac as ContributionRouting, ad as DEFAULT_ALLOWED_CAPABILITIES, ae as DEFAULT_DEVICE_PROFILE, af as DebugSink, ag as DepListing, a as DepName, D as DepRegistry, r as DepSchema, b as DepSource, ah as DetectedDeviceFacts, ai as DeviceProfile, aj as DispatchRecord, ak as DispatchRecordInput, q as DispatchTrace, o as Dispatcher, al as DispatcherContext, am as DragSample, an as DroppedCandidate, ao as Eligibility, ap as EligibilityState, aq as GestureBinding, ar as GestureKitSettings, as as HandleKind, at as HitShape, au as HotkeyTrigger, av as INGEST_STRING_MIMES, aw as ImmediateInvoker, ax as InvocationCtx, ay as Invoker, K as KernelOverlay, az as KeyBinding, aA as KeyReach, L as LongPressOptions, aB as LongPressState, aC as LongPressStore, aD as MatchOutcome, aE as MatchResult, g as MenuDef, aF as MenuHit, M as MenuHost, aG as MenuOpenRequest, aH as MenuSources, aI as MenuWhen, aJ as ModeShortcutHandlers, aK as ModeSwitchLogEntry, aL as ModifierRole, aM as ModifierRoles, aN as ModifierState, aO as NEVER, N as NodeId, aP as OngoingHandle, aQ as OngoingInvoker, aR as OngoingOverlay, aS as OpenMenu, O as OverlayPosition, aT as OverlayRole, aU as OverlaySchema, aV as OwnedMenuDef, aW as PREDICATE_TARGET, aX as PendingLongPress, aY as PlacedBy, aZ as Point2, a_ as PointerSession, a$ as PointerSessionCallbacks, b0 as PointerSessionCancelReason, b1 as PointerSessionOptions, b2 as RankedCandidate, b3 as RecordCandidate, b4 as RegistryEntry, b5 as ReservedChord, b6 as ResizeAnchor, b7 as ResolveAllOptions, b8 as ResolveOnlyResult, b9 as ResolvedCandidate, ba as RoleKeys, bb as RoleRebinds, bc as Rule, R as RuleCtx, bd as ScopedBinding, be as ScratchKey, bf as ScratchStore, bg as SelectionApi, bh as Selector, bi as SpecificityPart, bj as TOOL_OFFHAND_ID, i as Tool, bk as ToolDef, bl as ToolEntry, bm as ToolKeybinding, bn as ToolModifiers, bo as ToolOffhandBindingSpec, bp as ToolPresentation, bq as ToolScopes, br as ToolSet, bs as ToolSlot, bt as TraceLogEntry, bu as TraceRing, bv as TunedBinding, bw as UiOngoingControl, bx as ViewportGestureKind, by as WalkStep, bz as actionBindings, bA as actionCursor, bB as actionItemTitle, bC as actionItems, bD as actionSettingsParams, bE as actionShortcuts, bF as actionsTagged, bG as admitIngestMime, bH as assembleEntries, bI as browserReservedChord, bJ as buildDepsFromRequires, bK as buildRouteRegistry, bL as buildRuleCtx, bM as buildToolOffhandBindings, bN as clientToCanvas, bO as clientToCanvasRect, bP as createDepRegistry, bQ as createDispatchTrace, bR as createDispatcher, bS as createLongPressStore, bT as createMenuHost, bU as createTraceRing, bV as defineTool, bW as deleteScratch, bX as describeRule, bY as dlog, bZ as dwarn, b_ as evaluate, b$ as evaluateActive, c0 as evaluateEnabled, c1 as findConflicts, c2 as getScratch, c3 as heldKeySpec, c4 as historyKey, c5 as isAnchor, c6 as isAnchorOrControl, c7 as isBalanceGrip, c8 as isBody, c9 as isControlHandle, ca as isCornerWidget, cb as isDebugEnabled, cc as isEmpty, cd as isPathEditPart, ce as isResizeHandle, cf as isRotateHandle, cg as isSegment, ch as isSelectedBody, ci as isTool, cj as isToolSet, ck as isUnselectedBody, cl as itemTypesFromDataTransfer, cm as itemsFromClipboardData, cn as itemsFromDataTransfer, co as itemsFromFiles, cp as keySpecShortcut, cq as keysHeld, cr as liveScope, cs as makeToolOffhandAction, ct as matchActionItem, cu as matchBest, cv as matchSorted, cw as matchSortedWithBarred, cx as matchesKeyBinding, cy as mergeContributions, cz as mergeMenuDefs, cA as modeShortcutSpec, cB as modeShortcuts, cC as multiTouchGestureId, cD as offhandKeyFor, cE as openPointerSession, cF as overlaysOf, cG as pickMenu, cH as pointerGestureId, cI as readActionSettings, cJ as reportRouteConflicts, cK as resolveDeviceProfile, cL as resolveMenu, cM as resolveParams, cN as resolveSchemaMenu, cO as roleReader, cP as routesForSpec, cQ as ruleCanHoldIn, cR as rulesExclusive, cS as scopeBindings, cT as scratchKey, cU as setScratch, cV as startCarry, cW as taggedActionItems, cX as targetConsultsAffordance, cY as toolIdsOf, cZ as toolSet, c_ as toolSetHas, c$ as tuneAction, d0 as validateActionDefaultBinding, d1 as validateActionId, d2 as withoutTools } from './index-Cb7ehEks.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 '@weasel-js/cursor';
6
+ import '@weasel-js/menu';
7
+ import '@weasel-js/prefs';
8
+ import '@weasel-js/modes';
9
+ import '@weasel-js/icons';
5
10
  import 'react';
6
11
  import '@weasel-js/registry';
7
- import '@weasel-js/modes';
8
- import '@weasel-js/cursor';
package/dist/index.js CHANGED
@@ -1,22 +1,28 @@
1
- import { ActionDisabledReason } from './chunk-VCEIQ64H.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, tuneAction, validateActionDefaultBinding, validateActionId } from './chunk-VCEIQ64H.js';
1
+ import { actionItems, actionItemTitle, ActionDisabledReason } from './chunk-KLBH7VFF.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, actionSettingsParams, actionShortcuts, activatesFocusedControl, admitIngestMime, assembleEntries, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, canonicalModifiers, clientToCanvas, clientToCanvasRect, collapseShiftPairs, createDepRegistry, createDispatchTrace, createDispatcher, createLongPressStore, createMenuHost, createTraceRing, 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, readActionSettings, reportRouteConflicts, resolveDeviceProfile, resolveMenu, resolveParams, resolveSchemaMenu, roleReader, routeGestureForSpecKind, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, specKindForRouteGesture, specificity, startCarry, targetConsultsAffordance, tuneAction, validateActionDefaultBinding, validateActionId } from './chunk-KLBH7VFF.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,9 +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));
44
54
  var isSegment = hitPredicate((target) => kindOf(target) === "segment");
45
- var isPathEditPart = hitPredicate((target) => isAnchorOrControl(target) || isSegment(target));
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));
46
60
 
47
61
  // src/contributions/merge.ts
48
62
  function mergeContributions(...bundles) {
@@ -181,6 +195,7 @@ function defineTool(def) {
181
195
  ...def.hotkey ? { offhand: def.hotkey } : {}
182
196
  },
183
197
  actions: def.actions,
198
+ menus: def.menus,
184
199
  def,
185
200
  presentation: def.presentation,
186
201
  keybinding: def.keybinding,
@@ -193,6 +208,59 @@ function defineTool(def) {
193
208
  };
194
209
  }
195
210
 
211
+ // src/tools/toolSet.ts
212
+ function toolSet(id, members) {
213
+ return { id, members };
214
+ }
215
+ function isToolSet(entry) {
216
+ return typeof entry !== "string";
217
+ }
218
+ function toolIdsOf(entries) {
219
+ const out = [];
220
+ const seen = /* @__PURE__ */ new Set();
221
+ const walk = (list) => {
222
+ for (const entry of list) {
223
+ if (isToolSet(entry)) walk(entry.members);
224
+ else if (!seen.has(entry)) {
225
+ seen.add(entry);
226
+ out.push(entry);
227
+ }
228
+ }
229
+ };
230
+ walk(entries);
231
+ return out;
232
+ }
233
+ function withoutTools(set, ...omit) {
234
+ const gone = new Set(toolIdsOf(omit));
235
+ return { id: `${set.id}-without`, members: toolIdsOf([set]).filter((id) => !gone.has(id)) };
236
+ }
237
+ function toolSetHas(set, id) {
238
+ return toolIdsOf([set]).includes(id);
239
+ }
240
+
241
+ // src/interactions/browserReservedKeys.ts
242
+ var chord = (keys, mods, use) => [...keys].map((key) => ({ key, ...mods, use }));
243
+ var BROWSER_RESERVED_CHORDS = [
244
+ ...chord("nqtwlrpmh", { mod: true }, "new window, quit, new or close tab, address bar, reload, print, minimize, hide"),
245
+ ...chord("ntwrbo", { mod: true, shift: true }, "private window, reopen or close tab, hard reload, bookmarks, bookmark manager"),
246
+ ...chord("ijck", { mod: true, shift: true }, "devtools, console and inspector on Windows and Linux; Firefox Browser and Web Console"),
247
+ ...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"),
248
+ ...chord("fevsbthd", { alt: true }, "menu accelerators and address bar on Windows and Linux"),
249
+ ...chord("ita", { alt: true, shift: true }, "feedback, toolbar focus and accessibility commands")
250
+ ];
251
+ var lower = (k) => (typeof k === "string" ? [k] : [...k]).map((x) => x.toLowerCase());
252
+ var allows = (policy, held) => policy === "optional" || policy === true === held;
253
+ function browserReservedChord(spec) {
254
+ const keys = lower(spec.key);
255
+ const m = spec.mods ?? {};
256
+ const mod = m.mod ?? (m.meta === true || m.ctrl === true ? true : m.meta ?? m.ctrl);
257
+ for (const c of BROWSER_RESERVED_CHORDS) {
258
+ if (!keys.includes(c.key)) continue;
259
+ if (allows(mod, c.mod === true) && allows(m.alt, c.alt === true) && allows(m.shift, c.shift === true)) return c;
260
+ }
261
+ return null;
262
+ }
263
+
196
264
  // src/interactions/scratchKey.ts
197
265
  function scratchKey(name) {
198
266
  return { name };
@@ -211,6 +279,6 @@ function deleteScratch(store, key) {
211
279
  return false;
212
280
  }
213
281
 
214
- export { actionItems, defineTool, deleteScratch, getScratch, isAnchor, isAnchorOrControl, isBody, isControlHandle, isEmpty, isPathEditPart, isResizeHandle, isRotateHandle, isSegment, isSelectedBody, isUnselectedBody, mergeContributions, modeShortcutSpec, modeShortcuts, scratchKey, setScratch };
282
+ export { BROWSER_RESERVED_CHORDS, actionsTagged, browserReservedChord, defineTool, deleteScratch, getScratch, isAnchor, isAnchorOrControl, isBalanceGrip, isBody, isControlHandle, isCornerWidget, isEmpty, isPathEditPart, isResizeHandle, isRotateHandle, isSegment, isSelectedBody, isToolSet, isUnselectedBody, matchActionItem, mergeContributions, modeShortcutSpec, modeShortcuts, overlaysOf, scratchKey, setScratch, taggedActionItems, toolIdsOf, toolSet, toolSetHas, withoutTools };
215
283
  //# sourceMappingURL=index.js.map
216
284
  //# 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\n/** The outline of the path being edited, between its anchors. */\nexport const isSegment = hitPredicate((target) => kindOf(target) === 'segment');\n\n/** Anything path edit puts on the path: an anchor, a control, or a segment. */\nexport const isPathEditPart = hitPredicate((target) =>\n isAnchorOrControl(target) || isSegment(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/tools/toolSet.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 /** Where a dragged item would land: a reorder's insertion bar. */\n | 'insert'\n /** Where a dragged item is over and cannot land. */\n | 'refused'\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 * Tool sets: a named group of tool ids, the unit a consumer opts into whole.\n * A set names ids, never tools, because a tool is built by a hook that holds\n * its state; whatever mounts the set supplies the tool for each id.\n */\n\n/** A named group of tool ids. A member is an id or another set, whose ids it takes in order. */\nexport interface ToolSet<TId extends string = string> {\n readonly id: string;\n readonly members: readonly (TId | ToolSet<TId>)[];\n}\n\n/** One entry of a list of tools to mount: an id, or a set of them. */\nexport type ToolEntry<TId extends string = string> = TId | ToolSet<TId>;\n\n/** A set named `id` of `members`, in palette order. */\nexport function toolSet<TId extends string>(id: string, members: readonly ToolEntry<TId>[]): ToolSet<TId> {\n return { id, members };\n}\n\n/** Whether `entry` is a set rather than a single id. */\nexport function isToolSet<TId extends string>(entry: ToolEntry<TId>): entry is ToolSet<TId> {\n return typeof entry !== 'string';\n}\n\n/** Every id `entries` name, sets opened in place, each id once at its first place. */\nexport function toolIdsOf<TId extends string>(entries: readonly ToolEntry<TId>[]): TId[] {\n const out: TId[] = [];\n const seen = new Set<string>();\n const walk = (list: readonly ToolEntry<TId>[]): void => {\n for (const entry of list) {\n if (isToolSet(entry)) walk(entry.members);\n else if (!seen.has(entry)) {\n seen.add(entry);\n out.push(entry);\n }\n }\n };\n walk(entries);\n return out;\n}\n\n/** `set` less every id `omit` names, sets opened. Keeps `set`'s id with a `-without` suffix. */\nexport function withoutTools<TId extends string>(set: ToolSet<TId>, ...omit: readonly ToolEntry<TId>[]): ToolSet<TId> {\n const gone = new Set<string>(toolIdsOf(omit));\n return { id: `${set.id}-without`, members: toolIdsOf([set]).filter((id) => !gone.has(id)) };\n}\n\n/** Whether `set` names `id`, directly or through a set inside it. */\nexport function toolSetHas<TId extends string>(set: ToolSet<TId>, id: string): boolean {\n return (toolIdsOf([set]) as string[]).includes(id);\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('ijck', { mod: true, shift: true }, 'devtools, console and inspector on Windows and Linux; Firefox Browser and Web Console'),\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-DkOqUxow.js';
1
+ import { o as Dispatcher, d as ActionsRegistry, C as Contribution, p as AffordanceHit, q as DispatchTrace, L as LongPressOptions, R as RuleCtx, r as DepSchema } from './index-Cb7ehEks.js';
2
2
  import { RefObject } from 'react';
3
3
  import { BodyClassification } from '@weasel-js/gestures';
4
4
  import { PaintedCursorState, CursorSink } from '@weasel-js/cursor';
@@ -61,6 +61,11 @@ interface DispatcherChannels {
61
61
  /** `pointerdown`, hover `pointermove`, `pointerleave` — press, drag, click,
62
62
  * long-press, multi-touch and the hover-cursor pump. */
63
63
  pointer?: boolean;
64
+ /** `hover` gestures and the hover-cursor pump without the rest of
65
+ * `pointer`: no press opens a gesture or captures the pointer, so the
66
+ * element's own clicks and drags are left alone. Omitted, it follows
67
+ * `pointer`, which carries hovers whenever it is on. */
68
+ hover?: boolean;
64
69
  /** `wheel`. */
65
70
  wheel?: boolean;
66
71
  /** WebKit's `gesturestart` / `gesturechange` / `gestureend` — Safari's
@@ -71,6 +76,9 @@ interface DispatcherChannels {
71
76
  /** OS drop on the element plus `paste` on the window. Off leaves the
72
77
  * element a non-drop-target and stops `weasel-dropover` being applied. */
73
78
  ingest?: boolean;
79
+ /** A carry (`startCarry`) from elsewhere on the page, over the element. Off,
80
+ * the element is not a surface a carry can be released on. */
81
+ carry?: boolean;
74
82
  }
75
83
  /** Options for `useGestureDispatcher`: the element to listen on, the actions
76
84
  * and entries in play, and the hooks that turn raw DOM events into the world
@@ -87,6 +95,13 @@ interface UseGestureDispatcherOptions {
87
95
  canvasRef: RefObject<HTMLElement | null>;
88
96
  /** Action registry (ActionsRegistry from registry.tsx). */
89
97
  actions: ActionsRegistry;
98
+ /**
99
+ * Which actions this dispatcher matches. `'scope'` (default): every action a
100
+ * read from `actions` reaches, down its active scope. `'own'`: only those
101
+ * registered at `actions`' own node — for chrome on a root that canvases
102
+ * nest under.
103
+ */
104
+ hears?: 'scope' | 'own';
90
105
  /** Every entry in play, keyed by id: tools and any other contribution
91
106
  * whose bindings this dispatcher routes. */
92
107
  entriesById: ReadonlyMap<string, Contribution<unknown>>;
@@ -166,6 +181,9 @@ interface UseGestureDispatcherOptions {
166
181
  * mounter and other consumers (the preview-ghost layer).
167
182
  */
168
183
  dispatcher?: Dispatcher;
184
+ /** Where every dispatcher this hook drives records its decisions. Absent,
185
+ * the hook makes one of its own. Published as the `dispatchTrace` dep. */
186
+ trace?: DispatchTrace;
169
187
  /**
170
188
  * Converts a client-space pointer position (e.g. `e.clientX`, `e.clientY`)
171
189
  * to world-space coordinates. When supplied, every pointer/wheel event's
@@ -180,6 +198,18 @@ interface UseGestureDispatcherOptions {
180
198
  x: number;
181
199
  y: number;
182
200
  };
201
+ /**
202
+ * Converts a client point to CSS pixels from the canvas's top-left: where
203
+ * the painted cursor draws, where a wheel or pinch zoom anchors, and where a
204
+ * pending long-press is published. Without this, the element's rect is
205
+ * subtracted — wrong for a canvas under a CSS transform, whose host already
206
+ * owns the mapping. `<SceneCanvas>` wires it from its own camera, so a
207
+ * `clientToWorld` override reaches it.
208
+ */
209
+ clientToLocal?: (clientX: number, clientY: number) => {
210
+ x: number;
211
+ y: number;
212
+ };
183
213
  /**
184
214
  * Called whenever one of this hook's dispatchers notifies its subscribers:
185
215
  * 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-Boc2I3zn.js';
2
- import './index-DkOqUxow.js';
1
+ export { a as DispatcherViewTarget, V as ViewIdResolver } from './internal-Dcyz_cJv.js';
2
+ import './index-Cb7ehEks.js';
3
+ import '@weasel-js/gestures';
3
4
  import '@weasel-js/geom';
5
+ import '@weasel-js/cursor';
6
+ import '@weasel-js/menu';
7
+ import '@weasel-js/prefs';
8
+ import '@weasel-js/modes';
9
+ import '@weasel-js/icons';
4
10
  import 'react';
5
11
  import '@weasel-js/registry';
6
- import '@weasel-js/gestures';
7
- import '@weasel-js/modes';
8
- import '@weasel-js/cursor';
9
12
  import '@weasel-js/select';