@weasel-js/core 1.4.4 → 1.5.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +1295 -2197
  2. package/README.md +118 -75
  3. package/dist/autoPoseDescriptor-Dr6CwNZK.d.ts +26 -0
  4. package/dist/{chunk-WPM42WJP.js → chunk-3LPQ2XZG.js} +196 -406
  5. package/dist/chunk-3LPQ2XZG.js.map +1 -0
  6. package/dist/{chunk-R3AWPTLZ.js → chunk-GREL4MVO.js} +7593 -8533
  7. package/dist/chunk-GREL4MVO.js.map +1 -0
  8. package/dist/chunk-HAGOFNP5.js +162 -0
  9. package/dist/chunk-HAGOFNP5.js.map +1 -0
  10. package/dist/chunk-LXJDWBEL.js +318 -0
  11. package/dist/chunk-LXJDWBEL.js.map +1 -0
  12. package/dist/{chunk-2VXGHUVL.js → chunk-P6MGECVO.js} +4 -22
  13. package/dist/chunk-P6MGECVO.js.map +1 -0
  14. package/dist/{chunk-PRGBGMH3.js → chunk-XXQ6FLCJ.js} +3 -3
  15. package/dist/chunk-XXQ6FLCJ.js.map +1 -0
  16. package/dist/clipboard.d.ts +2 -3
  17. package/dist/clone.d.ts +3 -2
  18. package/dist/depSchema-BubKMv-2.d.ts +3479 -0
  19. package/dist/{grid-0Pbn5B2C.d.ts → grid-Z_Af3vTl.d.ts} +7 -10
  20. package/dist/index.d.ts +2588 -1556
  21. package/dist/index.js +6 -5
  22. package/dist/insert.d.ts +4 -4
  23. package/dist/insert.js +2 -1
  24. package/dist/insert.js.map +1 -1
  25. package/dist/math-E3rZn4bR.d.ts +282 -0
  26. package/dist/math.d.ts +5 -0
  27. package/dist/math.js +4 -0
  28. package/dist/math.js.map +1 -0
  29. package/dist/move.d.ts +5 -6
  30. package/dist/move.js +4 -6
  31. package/dist/move.js.map +1 -1
  32. package/dist/{options-DbYLImvq.d.ts → options-BPcmwYk7.d.ts} +3 -2
  33. package/dist/{autoPoseDescriptor-DF1SnnSx.d.ts → pointSnapToGrid-CgcK2R_I.d.ts} +11 -32
  34. package/dist/poseDescriptor-PgfVKfa0.d.ts +134 -0
  35. package/dist/renderer.d.ts +9 -4
  36. package/dist/renderer.js +6 -5
  37. package/dist/resize.d.ts +11 -12
  38. package/dist/resize.js +3 -2
  39. package/dist/routing.d.ts +1 -142
  40. package/dist/routing.js +1 -1
  41. package/dist/routing.js.map +1 -1
  42. package/dist/{types-ei3UMl9R.d.ts → types-BHGdrOcu.d.ts} +12 -41
  43. package/dist/{types-DEALFt5F.d.ts → types-BdaK9PcP.d.ts} +10 -4
  44. package/package.json +17 -10
  45. package/dist/DrawCommand-CD-ug3d9.d.ts +0 -332
  46. package/dist/builtins-BXFBXegF.d.ts +0 -840
  47. package/dist/chunk-2VXGHUVL.js.map +0 -1
  48. package/dist/chunk-BL65SHCX.js +0 -573
  49. package/dist/chunk-BL65SHCX.js.map +0 -1
  50. package/dist/chunk-PRGBGMH3.js.map +0 -1
  51. package/dist/chunk-R3AWPTLZ.js.map +0 -1
  52. package/dist/chunk-WPM42WJP.js.map +0 -1
  53. package/dist/geometry-6fCNhAux.d.ts +0 -114
  54. package/dist/path-JEV2c5If.d.ts +0 -48
  55. package/dist/registry-BY-wI9gm.d.ts +0 -4003
  56. package/dist/types-BHK2dkMu.d.ts +0 -172
  57. package/dist/types-bcc7jcUy.d.ts +0 -594
  58. package/dist/view-DSQgxBJB.d.ts +0 -63
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/ops/transform.ts","../src/core/ops/reparent.ts","../src/interactions/scratchKey.ts","../src/interactions/gestures/shared/snap.ts"],"names":[],"mappings":"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","file":"chunk-2VXGHUVL.js","sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface TransformAdapter<TPose> {\n setPose(id: string, pose: TPose): void;\n}\n\n/** @internal */\ninterface TransformArgs<TPose> {\n id: string;\n from: TPose;\n to: TPose;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Op: set an object's pose, inverting back to `from`. `coalesceKey` defaults to\n * `transform:${id}` so successive pose writes to the same node (drag-move,\n * resize, repeated nudges) merge into one undo entry within the history's\n * coalesce window. Pass an explicit key (or a unique one) to opt out. */\nexport function createTransformOp<TPose>(args: TransformArgs<TPose>): Op {\n const { id, from, to, label, coalesceKey = `transform:${id}` } = args;\n return {\n name: 'transform',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPose so consumers that inspect op behavior (tests,\n // overlays) see a consistent \"this op writes (id, to)\" signal. When\n // from and to are structurally identical, additionally report a\n // no-op to history so the entry can be skipped from the undo stack.\n // setPose(id, to) is idempotent in this branch — adapters write the\n // same pose they already had.\n (adapter as TransformAdapter<TPose>).setPose(id, to);\n if (poseShallowEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createTransformOp<TPose>({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<TransformArgs<unknown>>('transform', (args) => createTransformOp(args));\n\nfunction poseShallowEqual<TPose>(a: TPose, b: TPose): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n","import type { Op } from './types';\nimport { registerOpFactory } from './registry';\nimport { captureSlot, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface ReparentAdapter extends OrderedReader {\n setParent(id: string, parentId: string | null): void;\n /** Sibling-order write, paired with `getChildren`. Optional: an adapter\n * without both silently ignores slots and appends. */\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n /** Sibling ordinal under the old parent. Sugar for `fromSlot: { index }`,\n * and only a seed: `apply` re-observes the node's full slot through the\n * adapter and that observation supersedes this. */\n fromIndex?: number;\n /** Sibling ordinal under the new parent. Sugar for `toSlot: { index }`.\n * Omit to append. */\n toIndex?: number;\n /** Slot under the old parent, anchor included. `invert()` forwards it as\n * the destination slot; without it undo re-parents with no position and\n * the node lands last, quietly changing paint order. */\n fromSlot?: Slot;\n /** Slot under the new parent, anchor included. Omit to append. */\n toSlot?: Slot;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Move `id` to `slot` among `parentId`'s children. No-op when the adapter\n * has no ordering seam, when `id` isn't there, or when it's already in\n * place. */\nfunction placeAt(\n a: ReparentAdapter,\n parentId: string | null,\n id: string,\n slot: Slot,\n): void {\n if (!a.getChildren || !a.setChildOrder) return;\n const current = a.getChildren(parentId);\n const from = current.indexOf(id);\n if (from < 0) return;\n const next = current.slice();\n next.splice(from, 1);\n const resolved = resolveSlot(next, slot);\n const to = resolved === undefined ? next.length : Math.max(0, Math.min(resolved, next.length));\n if (from === to) return;\n next.splice(to, 0, id);\n a.setChildOrder(parentId, next);\n}\n\n/** Present when the caller named a destination at all; `undefined` leaves\n * placement to the adapter. */\nfunction optionalSlot(slot: Slot | undefined, index: number | undefined): Slot | undefined {\n if (slot) return slot;\n return index === undefined ? undefined : slotFromIndex(index);\n}\n\n/**\n * Op: change `id`'s parent and sibling slot, inverting back to the prior\n * parent and slot.\n *\n * `coalesceKey` defaults to `reparent:${id}` so successive reparents of the\n * same id batch-merge cleanly.\n */\nexport function createReparentOp(args: ReparentArgs): Op {\n const {\n id, fromParentId, toParentId,\n label = 'Reparent', coalesceKey = `reparent:${id}`,\n } = args;\n let fromSlot = optionalSlot(args.fromSlot, args.fromIndex);\n const toSlot = optionalSlot(args.toSlot, args.toIndex);\n const argsForSerial: ReparentArgs = {\n id, fromParentId, toParentId, fromSlot, toSlot, label, coalesceKey,\n };\n return {\n name: 'reparent',\n args: argsForSerial,\n label,\n coalesceKey,\n apply(adapter) {\n const a = adapter as ReparentAdapter;\n const observed = captureSlot(a, fromParentId, id);\n if (observed) {\n fromSlot = observed;\n argsForSerial.fromSlot = observed;\n }\n a.setParent(id, toParentId);\n if (toSlot) placeAt(a, toParentId, id, toSlot);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n fromSlot: toSlot,\n toSlot: fromSlot,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\n","/**\n * Typed keys for the gesture/behavior scratch store.\n *\n * Gestures expose `ctx.scratch: Record<string, unknown>` as a per-gesture\n * mutable bag that behaviors use to stash state across `onDown` / `onMove`\n * / `onEnd` calls. The store is `unknown`-valued because behaviors are\n * tool-agnostic — a single behavior may run under different tools with\n * different scratch shapes. Without a typing convention every read site\n * has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the\n * shape can silently break readers with no compile-time signal.\n *\n * `ScratchKey<T>` is a phantom-typed string that carries its payload type\n * via a branded interface. `getScratch` and `setScratch` use the brand to\n * narrow / constrain at the typing layer; at runtime the key is just its\n * underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level (or behavior-level) constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n *\n * // Writer (a tool's onMove):\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n *\n * // Reader (a behavior's onEnd):\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\n * // ^? readonly { x: number; y: number }[] | undefined\n * ```\n */\n\n/** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom\n * field carries the value type without occupying any runtime memory. */\nexport interface ScratchKey<T> {\n readonly name: string;\n /** Phantom — never read at runtime. Only present so TypeScript can\n * recover `T` at use sites. */\n readonly __scratchKeyPayload?: (_: T) => T;\n}\n\n/** Compatible scratch store shape. The kit's gesture context's `scratch`\n * field already satisfies this; consumers don't need to construct one. */\nexport type ScratchStore = Record<string, unknown>;\n\n/**\n * Make a typed scratch key. Use module-level constants so the same key\n * is shared by writer and reader.\n *\n * Namespace the name by behavior or feature to avoid collisions\n * (`'behavior.field'` is a good convention). Two keys with the same\n * `name` refer to the same slot at runtime — the type parameter is a\n * compile-time contract, not a uniqueness guarantee.\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n","import type { MoveBehavior, ModifierState, SnapStrategy } from '../types';\nimport {\n RECT_ORIGIN_PROJECTION,\n type OriginProjection,\n} from './strategies/grid';\n\ntype ModKey = keyof ModifierState;\n\n/**\n * Generic snap behavior factory: wraps a `SnapStrategy` and returns a\n * `MoveBehavior` whose `onMove` returns a uniform `GroupTransform`.\n *\n * Implementation:\n * 1. Compute the primary's proposed pose by applying the gesture's\n * `transform` (translate) to `origin.get(primaryId)`.\n * 2. Call `strategy.snap(proposed)`. If null, no-op.\n * 3. Derive the snapped delta via the `OriginProjection`:\n * dx = origin(snapped).x - origin(originPose).x\n * ...so the gesture applies the same delta to every dragged id.\n *\n * The pose-shape-aware delta extraction lives here — gestures stay pose-shape\n * agnostic. Default projection is `{x, y}`-bearing (rect / path / polygon).\n * Pass `origin` for exotic poses.\n */\nexport function snap<TPose>(\n strategy: SnapStrategy<TPose>,\n opts: { bypassKey?: ModKey; origin?: OriginProjection<TPose> } = {},\n): MoveBehavior<TPose> {\n const { bypassKey } = opts;\n const proj: OriginProjection<TPose> = opts.origin\n ?? (RECT_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n return {\n onMove(ctx, transform) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n // The new contract: behaviors see a GroupTransform, not a pose. But\n // strategies still operate on poses. Reconstruct the proposed pose\n // by applying the (translate) transform to the primary's origin.\n if (transform.kind !== 'translate') return;\n const primaryId = ctx.draggedIds[0];\n if (primaryId === undefined) return;\n const originPose = ctx.origin.get(primaryId);\n if (originPose === undefined) return;\n const proposed = proj.translate(originPose, transform.dx, transform.dy);\n const snapped = strategy.snap(proposed, ctx);\n if (snapped === null) return;\n const o0 = proj.getOrigin(originPose);\n const o1 = proj.getOrigin(snapped);\n return {\n transform: { kind: 'translate', dx: o1.x - o0.x, dy: o1.y - o0.y },\n };\n },\n };\n}\n"]}
@@ -1,573 +0,0 @@
1
- import { createContext, useRef, useMemo, useContext, useEffect } from 'react';
2
- import { jsx, Fragment } from 'react/jsx-runtime';
3
- import { isKnownGestureName, getGestureDescriptor, formatRoute, canonicalModifiers, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from '@weasel-js/gestures';
4
- export { GESTURE_DESCRIPTORS, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatPhaseAtom, formatRoute, getGestureDescriptor, isKnownGestureName, parseRoute } from '@weasel-js/gestures';
5
-
6
- // src/interactions/actions/depRegistry.tsx
7
- var DepRegistryContext = createContext(null);
8
- function DepRegistryProvider({ children }) {
9
- const sourcesRef = useRef(/* @__PURE__ */ new Map());
10
- const registry = useMemo(() => ({
11
- register: (name, source) => {
12
- const key = name;
13
- const entry = source;
14
- const stack = sourcesRef.current.get(key);
15
- if (stack) stack.push(entry);
16
- else sourcesRef.current.set(key, [entry]);
17
- let released = false;
18
- return () => {
19
- if (released) return;
20
- released = true;
21
- const cur = sourcesRef.current.get(key);
22
- if (!cur) return;
23
- const i = cur.lastIndexOf(entry);
24
- if (i === -1) return;
25
- cur.splice(i, 1);
26
- if (cur.length === 0) sourcesRef.current.delete(key);
27
- };
28
- },
29
- get: (name) => sourcesRef.current.get(name)?.at(-1)?.()
30
- }), []);
31
- return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: registry, children });
32
- }
33
- function useDepRegistry() {
34
- const r = useContext(DepRegistryContext);
35
- if (r === null) {
36
- throw new Error("useDepRegistry: no DepRegistryProvider in scope. Wrap your tree with <DepRegistryProvider> (typically inside <SceneCanvas>).");
37
- }
38
- return r;
39
- }
40
- function useOptionalDepRegistry() {
41
- return useContext(DepRegistryContext);
42
- }
43
- function useDepSource(name, source) {
44
- const registry = useDepRegistry();
45
- const sourceRef = useRef(source);
46
- sourceRef.current = source;
47
- useEffect(() => {
48
- return registry.register(name, () => sourceRef.current());
49
- }, [name, registry]);
50
- }
51
-
52
- // src/interactions/actions/buildDeps.ts
53
- var DEV = (() => {
54
- try {
55
- return Boolean(import.meta.env?.DEV);
56
- } catch {
57
- return false;
58
- }
59
- })();
60
- function buildDepsFromRequires(action, depRegistry) {
61
- const requires = action.requires ?? [];
62
- const entries = requires.map((name) => [name, depRegistry.get(name)]);
63
- const deps = Object.fromEntries(entries);
64
- if (DEV) {
65
- const declared = new Set(requires);
66
- return new Proxy(deps, {
67
- get(target, prop, receiver) {
68
- if (typeof prop === "string" && !declared.has(prop) && prop !== "then") {
69
- const message = `[weasel:dispatcher] action "${action.id}" read deps.${prop} but did not declare it in \`requires\`. Add \`requires: [...'${prop}']\` to the descriptor or the dep will be undefined at runtime.`;
70
- console.warn(message);
71
- throw new Error(message);
72
- }
73
- return Reflect.get(target, prop, receiver);
74
- }
75
- });
76
- }
77
- return deps;
78
- }
79
- function actionBindings(action) {
80
- const gs = action.defaultBinding;
81
- if (!gs) return [];
82
- const raw = Array.isArray(gs) ? gs : [gs];
83
- return raw.map((entry) => {
84
- const isBoundObj = !("kind" in entry);
85
- const spec = isBoundObj ? entry.spec : entry;
86
- const opts = isBoundObj ? entry.opts : void 0;
87
- return { spec, actionId: action.id, ...opts !== void 0 ? { opts } : {} };
88
- });
89
- }
90
- var ActionDisabledReason = {
91
- SelectionRequired: "selection-required",
92
- SceneEmpty: "scene-empty",
93
- NotApplicable: "not-applicable",
94
- /** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
95
- PredicateThrew: "predicate-threw"
96
- };
97
- var enabledSlowWarned = /* @__PURE__ */ new Set();
98
- var enabledThrewWarned = /* @__PURE__ */ new Set();
99
- var ENABLED_BUDGET_MS = 4;
100
- function evaluateEnabled(action, deps) {
101
- if (!action.enabled) return { enabled: true };
102
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
103
- const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
104
- const start = isDev ? now() : 0;
105
- try {
106
- const result = action.enabled(deps);
107
- if (isDev) {
108
- const elapsed = now() - start;
109
- if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
110
- enabledSlowWarned.add(action.id);
111
- console.warn(
112
- `weasel actions: enabled() for action "${action.id}" took ${elapsed.toFixed(2)}ms (budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast \u2014 see Action.enabled JSDoc.`
113
- );
114
- }
115
- }
116
- if (result === true) return { enabled: true };
117
- return { enabled: false, reason: result };
118
- } catch (e) {
119
- if (isDev && !enabledThrewWarned.has(action.id)) {
120
- enabledThrewWarned.add(action.id);
121
- console.warn(
122
- `weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
123
- e
124
- );
125
- }
126
- return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
127
- }
128
- }
129
- function validateActionId(id) {
130
- if (id.length === 0) {
131
- throw new Error(`weasel: action id may not be empty`);
132
- }
133
- if (RESERVED_ID_PREFIXES.has(id[0])) {
134
- throw new Error(
135
- `weasel: action id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
136
- );
137
- }
138
- if (RESERVED_ID_NAMES.has(id)) {
139
- throw new Error(
140
- `weasel: action id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
141
- );
142
- }
143
- }
144
- function validateActionDefaultBinding(action) {
145
- const gs = action.defaultBinding;
146
- if (!gs) return;
147
- const entries = Array.isArray(gs) ? gs : [gs];
148
- for (const entry of entries) {
149
- const spec = "kind" in entry ? entry : entry.spec;
150
- const phase = spec.phase;
151
- if (phase === void 0) continue;
152
- const atoms = Array.isArray(phase) ? phase : [{ channel: "&", phase }];
153
- for (const atom of atoms) {
154
- if (atom.channel === "&") {
155
- throw new Error(
156
- `weasel: action "${action.id}" defaultBinding uses '&' channel which has no owning tool to resolve to. Use a named tool channel (e.g. '[rect:engaged]') or '*' instead, or move this binding to the tool's own Tool.bindings.`
157
- );
158
- }
159
- }
160
- }
161
- }
162
- function warnSharedScope() {
163
- if (!IS_DEV) return;
164
- console.warn(
165
- "weasel: a second dispatcher claimed this <ActionsProvider>, so only the newest <SceneCanvas> under it will respond to input. Give each canvas its own scope with <WeaselProvider isolate>."
166
- );
167
- }
168
- var ActionsContext = createContext(null);
169
- function useMuted(base) {
170
- const mutedRef = useRef(/* @__PURE__ */ new Map());
171
- const listenersRef = useRef(/* @__PURE__ */ new Set());
172
- const cacheRef = useRef(null);
173
- return useMemo(() => {
174
- if (!base) return null;
175
- const isMuted = (id) => mutedRef.current.has(id);
176
- const notify = () => {
177
- for (const l of listenersRef.current) {
178
- try {
179
- l();
180
- } catch (err) {
181
- console.error("weasel ActionsRegistry: subscriber threw", err);
182
- }
183
- }
184
- };
185
- return {
186
- ...base,
187
- list: () => {
188
- const src = base.list();
189
- if (mutedRef.current.size === 0) return src;
190
- const cached = cacheRef.current;
191
- if (cached && cached.src === src) return cached.out;
192
- const out = Object.freeze(src.filter((a) => !mutedRef.current.has(a.id)));
193
- cacheRef.current = { src, out };
194
- return out;
195
- },
196
- trigger: (id, params) => isMuted(id) ? false : base.trigger(id, params),
197
- begin: (id, params) => isMuted(id) ? null : base.begin(id, params),
198
- subscribe: (listener) => {
199
- const offBase = base.subscribe(listener);
200
- listenersRef.current.add(listener);
201
- return () => {
202
- offBase();
203
- listenersRef.current.delete(listener);
204
- };
205
- },
206
- mute: (id) => {
207
- mutedRef.current.set(id, (mutedRef.current.get(id) ?? 0) + 1);
208
- cacheRef.current = null;
209
- notify();
210
- let released = false;
211
- return () => {
212
- if (released) return;
213
- released = true;
214
- const held = mutedRef.current.get(id);
215
- if (held === void 0) return;
216
- if (held > 1) mutedRef.current.set(id, held - 1);
217
- else mutedRef.current.delete(id);
218
- cacheRef.current = null;
219
- notify();
220
- };
221
- }
222
- };
223
- }, [base]);
224
- }
225
- function ActionsScope({ children }) {
226
- const parent = useActionsRegistry();
227
- const scoped = useMuted(parent);
228
- if (!scoped) return /* @__PURE__ */ jsx(Fragment, { children });
229
- return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: scoped, children });
230
- }
231
- function ActionsProvider({ children }) {
232
- const actionsRef = useRef(/* @__PURE__ */ new Map());
233
- const liveAction = (id) => actionsRef.current.get(id)?.at(-1);
234
- const versionRef = useRef(0);
235
- const cachedRef = useRef([]);
236
- const cachedVerRef = useRef(-1);
237
- const listenersRef = useRef(/* @__PURE__ */ new Set());
238
- const depReg = useOptionalDepRegistry();
239
- const depRegRef = useRef(depReg);
240
- depRegRef.current = depReg;
241
- const warnedRef = useRef(false);
242
- const wiredDepRegRef = useRef(null);
243
- const dispatcherRef = useRef(null);
244
- const store = useMemo(() => {
245
- const snapshot = () => {
246
- const v = versionRef.current;
247
- if (cachedVerRef.current === v) return cachedRef.current;
248
- const out = Object.freeze(
249
- Array.from(actionsRef.current.values(), (stack) => stack[stack.length - 1])
250
- );
251
- cachedRef.current = out;
252
- cachedVerRef.current = v;
253
- return out;
254
- };
255
- const notify = () => {
256
- for (const l of listenersRef.current) {
257
- try {
258
- l();
259
- } catch (err) {
260
- console.error("weasel ActionsRegistry: subscriber threw", err);
261
- }
262
- }
263
- };
264
- return {
265
- register: (action) => {
266
- validateActionId(action.id);
267
- validateActionDefaultBinding(action);
268
- const stack = actionsRef.current.get(action.id);
269
- if (stack) stack.push(action);
270
- else actionsRef.current.set(action.id, [action]);
271
- versionRef.current++;
272
- notify();
273
- let released = false;
274
- return () => {
275
- if (released) return;
276
- released = true;
277
- const cur = actionsRef.current.get(action.id);
278
- if (!cur) return;
279
- const i = cur.lastIndexOf(action);
280
- if (i === -1) return;
281
- cur.splice(i, 1);
282
- if (cur.length === 0) actionsRef.current.delete(action.id);
283
- versionRef.current++;
284
- notify();
285
- };
286
- },
287
- unregister: (id) => {
288
- if (actionsRef.current.delete(id)) {
289
- versionRef.current++;
290
- notify();
291
- }
292
- },
293
- list: () => snapshot(),
294
- trigger: (id, params) => {
295
- const a = liveAction(id);
296
- if (!a) return false;
297
- try {
298
- if (a.invoker && a.invoker.timing === "immediate") {
299
- const r = wiredDepRegRef.current ?? depRegRef.current;
300
- const deps = !r ? {} : a.requires ? buildDepsFromRequires(a, r) : {
301
- selection: r.get("selection"),
302
- scene: r.get("scene"),
303
- history: r.get("history"),
304
- view: r.get("view"),
305
- pointer: r.get("pointer"),
306
- activeTool: r.get("activeTool"),
307
- booleansAdapter: r.get("booleansAdapter")
308
- };
309
- a.invoker.run(deps, params);
310
- }
311
- } catch (err) {
312
- console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
313
- }
314
- return true;
315
- },
316
- subscribe: (listener) => {
317
- listenersRef.current.add(listener);
318
- return () => {
319
- listenersRef.current.delete(listener);
320
- };
321
- },
322
- setDispatcher: (d) => {
323
- if (d && dispatcherRef.current && dispatcherRef.current !== d && !warnedRef.current) {
324
- warnedRef.current = true;
325
- warnSharedScope();
326
- }
327
- dispatcherRef.current = d;
328
- return () => {
329
- if (dispatcherRef.current === d) dispatcherRef.current = null;
330
- };
331
- },
332
- setDepRegistry: (r) => {
333
- wiredDepRegRef.current = r;
334
- return () => {
335
- if (wiredDepRegRef.current === r) wiredDepRegRef.current = null;
336
- };
337
- },
338
- begin: (id, params) => {
339
- const disp = dispatcherRef.current;
340
- if (!disp) return null;
341
- const r = wiredDepRegRef.current ?? depRegRef.current;
342
- const a = liveAction(id);
343
- const deps = !r ? {} : a?.requires ? buildDepsFromRequires(a, r) : {
344
- selection: r.get("selection"),
345
- scene: r.get("scene"),
346
- history: r.get("history"),
347
- view: r.get("view"),
348
- pointer: r.get("pointer"),
349
- activeTool: r.get("activeTool"),
350
- booleansAdapter: r.get("booleansAdapter")
351
- };
352
- return disp.beginUiOngoing(id, deps, params);
353
- }
354
- };
355
- }, []);
356
- const registry = useMuted(store);
357
- return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: registry, children });
358
- }
359
- var IS_DEV = (() => {
360
- try {
361
- return Boolean(import.meta.env?.DEV);
362
- } catch {
363
- return false;
364
- }
365
- })();
366
- function useActionsRegistry() {
367
- return useContext(ActionsContext);
368
- }
369
- function useAction(action) {
370
- const reg = useActionsRegistry();
371
- useEffect(() => {
372
- if (!reg) {
373
- if (IS_DEV) {
374
- console.warn(
375
- `useAction("${action.id}"): no <ActionsProvider> is in scope, so the action was not registered and its bindings will never fire.`
376
- );
377
- }
378
- return;
379
- }
380
- return reg.register(action);
381
- }, [reg, action]);
382
- }
383
-
384
- // src/tools/routing/reflection/registry.ts
385
- var PREDICATE_TARGET = "predicate";
386
- var SPEC_KIND_TO_GESTURE = {
387
- key: "keyDown",
388
- "key-held": "keyHeld",
389
- wheel: "wheel",
390
- click: "click",
391
- doubleClick: "dblTap",
392
- contextMenu: "contextMenu",
393
- longPress: "longPress",
394
- drag: "drag",
395
- pointerDown: "pointerDown",
396
- multiTouch: void 0,
397
- multiTouchTap: "multiTouchTap",
398
- drop: "drop",
399
- paste: "paste"
400
- };
401
- function buildRouteRegistry(tools) {
402
- const out = [];
403
- for (const tool of tools) {
404
- for (const binding of tool.bindings ?? []) {
405
- const entry = entryFor(tool.id, binding.spec, binding.actionId);
406
- if (entry) out.push(entry);
407
- }
408
- }
409
- return out;
410
- }
411
- function routeGestureForSpecKind(kind) {
412
- const gesture = SPEC_KIND_TO_GESTURE[kind];
413
- return gesture && isKnownGestureName(gesture) ? gesture : void 0;
414
- }
415
- function routesForSpec(spec) {
416
- const gesture = routeGestureForSpecKind(spec.kind);
417
- if (!gesture) return [];
418
- const descriptor = getGestureDescriptor(gesture);
419
- const modifiers = parseModSpec("mods" in spec ? spec.mods : void 0);
420
- const phases = phaseAtomsForSpec("phase" in spec ? spec.phase : void 0);
421
- const target = descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) ?? "*" : void 0;
422
- const args = descriptor.arg ? routeArgsForSpec(spec, descriptor.arg.default) : [void 0];
423
- return args.map((arg) => formatRoute({ phases, gesture, arg, target, modifiers }));
424
- }
425
- function entryFor(toolId, spec, actionId) {
426
- const gesture = SPEC_KIND_TO_GESTURE[spec.kind];
427
- if (!gesture || !isKnownGestureName(gesture)) return null;
428
- const descriptor = getGestureDescriptor(gesture);
429
- return {
430
- toolId,
431
- actionId,
432
- gesture,
433
- phase: phaseOf("phase" in spec ? spec.phase : void 0),
434
- modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
435
- arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
436
- target: descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) : void 0,
437
- spec
438
- };
439
- }
440
- function phaseOf(phase) {
441
- if (phase === void 0 || phase === "*") return "any";
442
- if (phase === "initial" || phase === "engaged") return phase;
443
- const distinct = new Set(phase.map((atom) => atom.phase));
444
- if (distinct.size !== 1) return "any";
445
- const only = [...distinct][0];
446
- return only === "initial" || only === "engaged" ? only : "any";
447
- }
448
- function parseModSpec(mods) {
449
- if (!mods) return {};
450
- const out = {};
451
- for (const [name, req] of Object.entries(mods)) {
452
- if (req === true) out[name] = "required";
453
- else if (req === "optional") out[name] = "optional";
454
- }
455
- return out;
456
- }
457
- function phaseAtomsForSpec(phase) {
458
- if (phase === void 0) return [{ channel: "&", phase: "*" }];
459
- if (phase === "initial" || phase === "engaged" || phase === "*") {
460
- return [{ channel: "&", phase }];
461
- }
462
- return phase;
463
- }
464
- function routeTargetForSpec(target) {
465
- if (target === void 0) return void 0;
466
- return typeof target === "string" ? target : PREDICATE_TARGET;
467
- }
468
- function routeArgsForSpec(spec, fallback) {
469
- if ("key" in spec) return Array.isArray(spec.key) ? spec.key : [spec.key];
470
- if ("fingers" in spec) return [String(spec.fingers)];
471
- if ("direction" in spec) return [spec.direction ?? fallback];
472
- if ("types" in spec) return [spec.types?.length ? spec.types.join("|") : fallback];
473
- return [fallback];
474
- }
475
- function argOf(spec, fallback) {
476
- const args = routeArgsForSpec(spec, fallback);
477
- return args.length > 1 ? args.join("|") : args[0];
478
- }
479
-
480
- // src/tools/routing/reflection/conflicts.ts
481
- function findConflicts(tools) {
482
- const entries = buildRouteRegistry(tools);
483
- const groups = /* @__PURE__ */ new Map();
484
- for (const entry of entries) {
485
- const key = `${entry.phase}|${entry.gesture}|${entry.arg ?? ""}|${targetKey(entry)}|${canonicalModifiers(entry.modifiers)}`;
486
- const bucket = groups.get(key);
487
- if (bucket) bucket.push(entry);
488
- else groups.set(key, [entry]);
489
- }
490
- const conflicts = [];
491
- for (const bucket of groups.values()) {
492
- if (bucket.length < 2) continue;
493
- const first = bucket[0];
494
- conflicts.push({
495
- phase: first.phase,
496
- gesture: first.gesture,
497
- arg: first.arg,
498
- target: first.target,
499
- modifiers: first.modifiers,
500
- toolIds: bucket.map((e) => e.toolId)
501
- });
502
- }
503
- return conflicts;
504
- }
505
- function targetKey(entry) {
506
- if (entry.target !== PREDICATE_TARGET) return entry.target ?? "";
507
- const spec = entry.spec;
508
- const pred = spec.target;
509
- if (typeof pred !== "object" || pred === null) return PREDICATE_TARGET;
510
- return `${PREDICATE_TARGET}#${predicateId(pred)}`;
511
- }
512
- var PREDICATE_IDS = /* @__PURE__ */ new WeakMap();
513
- var nextPredicateId = 0;
514
- function predicateId(pred) {
515
- let id = PREDICATE_IDS.get(pred);
516
- if (id === void 0) {
517
- id = nextPredicateId++;
518
- PREDICATE_IDS.set(pred, id);
519
- }
520
- return id;
521
- }
522
- function actionAsTool(action) {
523
- return { id: action.id, eligibility: {}, bindings: actionBindings(action) };
524
- }
525
- function findScopedConflicts(scopes) {
526
- const registry = Array.isArray(scopes.registry) ? scopes.registry : Object.values(scopes.registry);
527
- const actions = scopes.actions ?? [];
528
- const ambientActions = actions.filter((a) => a.scope !== "hotkey").map(actionAsTool);
529
- const hotkeyActions = actions.filter((a) => a.scope === "hotkey").map(actionAsTool);
530
- const ambient = [...scopes.ambient ?? [], ...ambientActions];
531
- const out = [];
532
- const seen = /* @__PURE__ */ new Set();
533
- const add = (conflicts) => {
534
- for (const c of conflicts) {
535
- const key = `${c.phase}|${c.gesture}|${c.arg ?? ""}|${c.target ?? ""}|${canonicalModifiers(c.modifiers)}|${c.toolIds.join(",")}`;
536
- if (seen.has(key)) continue;
537
- seen.add(key);
538
- out.push(c);
539
- }
540
- };
541
- for (const tool of [...registry, ...scopes.ambient ?? []]) add(findConflicts([tool]));
542
- if (ambient.length > 1) add(findConflicts(ambient));
543
- const hotkey = [
544
- ...registry.filter((t) => t.def?.hotkey !== void 0),
545
- ...hotkeyActions
546
- ];
547
- if (hotkey.length > 1) add(findConflicts(hotkey));
548
- return out;
549
- }
550
- function formatConflict(conflict) {
551
- const route = formatRoute({
552
- phases: [{ channel: "&", phase: conflict.phase === "any" ? "*" : conflict.phase }],
553
- gesture: conflict.gesture,
554
- arg: conflict.arg,
555
- target: conflict.target,
556
- modifiers: conflict.modifiers
557
- });
558
- return `${route} \u2014 declared by ${conflict.toolIds.join(", ")}`;
559
- }
560
- function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
561
- const conflicts = findScopedConflicts(scopes);
562
- for (const c of conflicts) {
563
- warn(
564
- `[weasel] route conflict: two bindings declare the same (phase, gesture, arg, target, modifiers) tuple in the same scope, so declaration order alone decides which one fires.
565
- ${formatConflict(c)}`
566
- );
567
- }
568
- return conflicts;
569
- }
570
-
571
- export { ActionDisabledReason, ActionsProvider, ActionsScope, DepRegistryProvider, PREDICATE_TARGET, actionBindings, buildDepsFromRequires, buildRouteRegistry, evaluateEnabled, findConflicts, reportRouteConflicts, routeGestureForSpecKind, routesForSpec, useAction, useActionsRegistry, useDepRegistry, useDepSource, useOptionalDepRegistry };
572
- //# sourceMappingURL=chunk-BL65SHCX.js.map
573
- //# sourceMappingURL=chunk-BL65SHCX.js.map