@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/{chunk-VCEIQ64H.js → chunk-KLBH7VFF.js} +740 -87
- package/dist/chunk-KLBH7VFF.js.map +1 -0
- package/dist/{index-DkOqUxow.d.ts → index-Cb7ehEks.d.ts} +1689 -1177
- package/dist/index.d.ts +6 -3
- package/dist/index.js +85 -17
- package/dist/index.js.map +1 -1
- package/dist/{internal-Boc2I3zn.d.ts → internal-Dcyz_cJv.d.ts} +31 -1
- package/dist/internal.d.ts +8 -5
- package/dist/react.d.ts +61 -29
- package/dist/react.js +467 -232
- package/dist/react.js.map +1 -1
- package/package.json +13 -9
- package/dist/chunk-VCEIQ64H.js.map +0 -1
package/dist/react.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { createDepRegistry, createDispatcher, assembleEntries, liveScope,
|
|
2
|
-
import { createContext, useState, useContext, useEffect, useRef, useCallback,
|
|
1
|
+
import { createDepRegistry, createDispatchTrace, createDispatcher, assembleEntries, liveScope, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, ScopeNode, tuneAction, createMenuHost, pushOwner, evaluateEnabled, tunedParams, validateActionId, validateActionDefaultBinding, isTool, depNodeOf, DepNode, buildDepsFromRequires, clientToCanvas, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, addCarrySurface, carryGestureId, anyBinding, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer, actionCursor } from './chunk-KLBH7VFF.js';
|
|
2
|
+
import { createContext, useState, useContext, useEffect, useMemo, useRef, useCallback, useSyncExternalStore } from 'react';
|
|
3
3
|
import { isApplePlatform, heldPointers, createPointerRecognizer, isInControlWithin, longPressEvents, stylusOf } from '@weasel-js/gestures';
|
|
4
|
-
import { useLatest, useStableByContent, sameList } from '@weasel-js/react';
|
|
4
|
+
import { isDev, useLatest, useKitSettings, useStableByContent, sameList } from '@weasel-js/react';
|
|
5
5
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
6
6
|
import { createCursorSink, resolveCursorTier } from '@weasel-js/cursor';
|
|
7
|
-
import { createEmitter } from '@weasel-js/registry';
|
|
7
|
+
import { deepEqual, createEmitter } from '@weasel-js/registry';
|
|
8
8
|
import { activeModeOf } from '@weasel-js/modes';
|
|
9
9
|
|
|
10
10
|
var ToolNode = class extends ScopeNode {
|
|
@@ -108,9 +108,280 @@ function useDepSource(name, source) {
|
|
|
108
108
|
const registry = useDepRegistry();
|
|
109
109
|
const sourceRef = useLatest(source);
|
|
110
110
|
useEffect(() => {
|
|
111
|
-
return registry.register(name, () => sourceRef.current());
|
|
111
|
+
return registry.register(name, (below) => sourceRef.current(below));
|
|
112
112
|
}, [name, registry, sourceRef]);
|
|
113
113
|
}
|
|
114
|
+
var MenuHostContext = createContext(null);
|
|
115
|
+
function useMenuHost() {
|
|
116
|
+
return useContext(MenuHostContext);
|
|
117
|
+
}
|
|
118
|
+
function useMenus(defs, owner = null) {
|
|
119
|
+
const host = useMenuHost();
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!host) return;
|
|
122
|
+
const releases = defs.map((d) => host.define(d, owner));
|
|
123
|
+
return () => {
|
|
124
|
+
for (const r of releases) r();
|
|
125
|
+
};
|
|
126
|
+
}, [host, defs, owner]);
|
|
127
|
+
}
|
|
128
|
+
var ActionsContext = createContext(null);
|
|
129
|
+
function depsFor(a, r) {
|
|
130
|
+
if (!r) return {};
|
|
131
|
+
if (a.requires) return buildDepsFromRequires(a, r);
|
|
132
|
+
return {
|
|
133
|
+
selection: r.get("selection"),
|
|
134
|
+
scene: r.get("scene"),
|
|
135
|
+
history: r.get("history"),
|
|
136
|
+
view: r.get("view"),
|
|
137
|
+
activeTool: r.get("activeTool"),
|
|
138
|
+
booleansAdapter: r.get("booleansAdapter")
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
var ActionsNode = class extends ScopeNode {
|
|
142
|
+
constructor(parent, descends, contextDeps) {
|
|
143
|
+
super(parent, descends);
|
|
144
|
+
this.contextDeps = contextDeps;
|
|
145
|
+
NODES.set(this.registry, this);
|
|
146
|
+
}
|
|
147
|
+
contextDeps;
|
|
148
|
+
actions = /* @__PURE__ */ new Map();
|
|
149
|
+
muted = /* @__PURE__ */ new Map();
|
|
150
|
+
tunings = /* @__PURE__ */ new Map();
|
|
151
|
+
dispatchers = [];
|
|
152
|
+
cache = null;
|
|
153
|
+
host = null;
|
|
154
|
+
/** Called on `activate`, so a scope's dep and tool nodes move with it. */
|
|
155
|
+
onActivate = [];
|
|
156
|
+
resolve(id) {
|
|
157
|
+
for (const n of this.chain()) {
|
|
158
|
+
if (n.muted.has(id)) return void 0;
|
|
159
|
+
const a = n.actions.get(id)?.at(-1);
|
|
160
|
+
if (a) return this.tuned(a);
|
|
161
|
+
}
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
/** Every tuning for `id` from the root down to here, root first. */
|
|
165
|
+
tuningsOf(id) {
|
|
166
|
+
const tunings = [];
|
|
167
|
+
for (const n of this.chain()) {
|
|
168
|
+
const stack = n.tunings.get(id);
|
|
169
|
+
if (stack) tunings.unshift(...stack);
|
|
170
|
+
}
|
|
171
|
+
return tunings;
|
|
172
|
+
}
|
|
173
|
+
/** `a` under every tuning for its id from the root down to here. */
|
|
174
|
+
tuned(a) {
|
|
175
|
+
const tunings = this.tuningsOf(a.id);
|
|
176
|
+
return tunings.length ? tuneAction(a, tunings) : a;
|
|
177
|
+
}
|
|
178
|
+
snapshot() {
|
|
179
|
+
const leaf = this.start();
|
|
180
|
+
const cached = this.cache;
|
|
181
|
+
if (cached && cached.version === this.tree.version && cached.leaf === leaf) return cached.list;
|
|
182
|
+
const ids = /* @__PURE__ */ new Set();
|
|
183
|
+
for (const n of leaf.chain()) for (const id of n.actions.keys()) ids.add(id);
|
|
184
|
+
const out = [];
|
|
185
|
+
for (const id of ids) {
|
|
186
|
+
const a = leaf.resolve(id);
|
|
187
|
+
if (a) out.push(a);
|
|
188
|
+
}
|
|
189
|
+
const list = Object.freeze(out);
|
|
190
|
+
this.cache = { version: this.tree.version, leaf, list };
|
|
191
|
+
return list;
|
|
192
|
+
}
|
|
193
|
+
deps() {
|
|
194
|
+
for (const n of this.chain()) {
|
|
195
|
+
const r = n.contextDeps();
|
|
196
|
+
if (r) return r;
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
/** The actions registered at this node itself, live, unmuted and tuned —
|
|
201
|
+
* none from the scopes below it. */
|
|
202
|
+
ownActions() {
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const [id, stack] of this.actions) {
|
|
205
|
+
if (stack.length > 0 && !this.muted.has(id)) out.push(this.tuned(stack.at(-1)));
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
/** The deps `a` is invoked with from here: the active scope's, which is
|
|
210
|
+
* what `trigger` and `begin` run it with. */
|
|
211
|
+
depsOf(a) {
|
|
212
|
+
return depsFor(a, this.start().deps());
|
|
213
|
+
}
|
|
214
|
+
/** The menu host of this node's tree, made by its root on first ask. Rows
|
|
215
|
+
* resolve and run against the active scope, as chrome's `trigger` does. */
|
|
216
|
+
menuHost() {
|
|
217
|
+
const root = [...this.chain()].at(-1);
|
|
218
|
+
return root.host ??= createMenuHost({
|
|
219
|
+
actions: () => root.registry.list(),
|
|
220
|
+
depsFor: (a) => root.depsOf(a),
|
|
221
|
+
trigger: (id, params) => root.registry.trigger(id, params),
|
|
222
|
+
activeTool: () => root.start().deps()?.get("activeTool")?.active ?? null
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
dispatcher() {
|
|
226
|
+
for (const n of this.chain()) {
|
|
227
|
+
const d = n.dispatchers.at(-1);
|
|
228
|
+
if (d) return d;
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
activate() {
|
|
233
|
+
keyHolder = this;
|
|
234
|
+
super.activate();
|
|
235
|
+
for (const f of this.onActivate) f();
|
|
236
|
+
}
|
|
237
|
+
registry = {
|
|
238
|
+
register: (action) => {
|
|
239
|
+
validateActionId(action.id);
|
|
240
|
+
validateActionDefaultBinding(action);
|
|
241
|
+
const stack = this.actions.get(action.id);
|
|
242
|
+
if (stack) stack.push(action);
|
|
243
|
+
else this.actions.set(action.id, [action]);
|
|
244
|
+
this.changed();
|
|
245
|
+
let released = false;
|
|
246
|
+
return () => {
|
|
247
|
+
if (released) return;
|
|
248
|
+
released = true;
|
|
249
|
+
const cur = this.actions.get(action.id);
|
|
250
|
+
if (!cur) return;
|
|
251
|
+
const i = cur.lastIndexOf(action);
|
|
252
|
+
if (i === -1) return;
|
|
253
|
+
cur.splice(i, 1);
|
|
254
|
+
if (cur.length === 0) {
|
|
255
|
+
queueMicrotask(() => {
|
|
256
|
+
if (cur.length === 0 && this.actions.get(action.id) === cur) this.actions.delete(action.id);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
this.changed();
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
unregister: (id) => {
|
|
263
|
+
if (this.actions.delete(id)) this.changed();
|
|
264
|
+
},
|
|
265
|
+
mute: (id) => {
|
|
266
|
+
this.muted.set(id, (this.muted.get(id) ?? 0) + 1);
|
|
267
|
+
this.changed();
|
|
268
|
+
let released = false;
|
|
269
|
+
return () => {
|
|
270
|
+
if (released) return;
|
|
271
|
+
released = true;
|
|
272
|
+
const held = this.muted.get(id);
|
|
273
|
+
if (held === void 0) return;
|
|
274
|
+
if (held > 1) this.muted.set(id, held - 1);
|
|
275
|
+
else this.muted.delete(id);
|
|
276
|
+
this.changed();
|
|
277
|
+
};
|
|
278
|
+
},
|
|
279
|
+
tune: (id, tuning) => {
|
|
280
|
+
let stack = this.tunings.get(id);
|
|
281
|
+
if (!stack) this.tunings.set(id, stack = []);
|
|
282
|
+
const release = pushOwner(stack, tuning);
|
|
283
|
+
this.changed();
|
|
284
|
+
let released = false;
|
|
285
|
+
return () => {
|
|
286
|
+
if (released) return;
|
|
287
|
+
released = true;
|
|
288
|
+
release();
|
|
289
|
+
if (stack.length === 0) this.tunings.delete(id);
|
|
290
|
+
this.changed();
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
paramsOf: (id, given) => tunedParams(this.tuningsOf(id), given),
|
|
294
|
+
list: () => this.snapshot(),
|
|
295
|
+
trigger: (id, params) => {
|
|
296
|
+
const leaf = this.start();
|
|
297
|
+
const a = leaf.resolve(id);
|
|
298
|
+
if (!a?.invoker || a.invoker.timing !== "immediate") return false;
|
|
299
|
+
const deps = leaf.depsOf(a);
|
|
300
|
+
if (!evaluateEnabled(a, deps, pointOf(params)).enabled) return false;
|
|
301
|
+
try {
|
|
302
|
+
a.invoker.run(deps, params);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
},
|
|
308
|
+
preview: (id, params) => {
|
|
309
|
+
const leaf = this.start();
|
|
310
|
+
const a = leaf.resolve(id);
|
|
311
|
+
if (!a?.preview) return NO_PREVIEW;
|
|
312
|
+
const deps = leaf.depsOf(a);
|
|
313
|
+
if (!evaluateEnabled(a, deps, pointOf(params)).enabled) return NO_PREVIEW;
|
|
314
|
+
let stop;
|
|
315
|
+
try {
|
|
316
|
+
stop = a.preview(deps, params);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
console.error(`weasel ActionsRegistry: preview of "${id}" threw`, err);
|
|
319
|
+
return NO_PREVIEW;
|
|
320
|
+
}
|
|
321
|
+
if (typeof stop !== "function") return NO_PREVIEW;
|
|
322
|
+
let down = false;
|
|
323
|
+
return () => {
|
|
324
|
+
if (down) return;
|
|
325
|
+
down = true;
|
|
326
|
+
try {
|
|
327
|
+
stop();
|
|
328
|
+
} catch (err) {
|
|
329
|
+
console.error(`weasel ActionsRegistry: preview of "${id}" threw as it ended`, err);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
subscribe: (listener) => this.tree.listeners.subscribe(listener),
|
|
334
|
+
setDispatcher: (d) => pushOwner(this.dispatchers, d),
|
|
335
|
+
begin: (id, params) => {
|
|
336
|
+
const leaf = this.start();
|
|
337
|
+
const a = leaf.resolve(id);
|
|
338
|
+
const disp = leaf.dispatcher();
|
|
339
|
+
if (!a || !disp) return null;
|
|
340
|
+
return disp.beginUiOngoing(id, leaf.depsOf(a), params);
|
|
341
|
+
},
|
|
342
|
+
activate: () => this.activate(),
|
|
343
|
+
isActive: () => this.isActive(),
|
|
344
|
+
holdsKeys: () => keyHolder === this,
|
|
345
|
+
dropKeys: () => {
|
|
346
|
+
if (keyHolder === this) keyHolder = null;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
var NO_PREVIEW = () => {
|
|
351
|
+
};
|
|
352
|
+
function pointOf(params) {
|
|
353
|
+
const { worldX: x, worldY: y } = params ?? {};
|
|
354
|
+
return typeof x === "number" && typeof y === "number" ? { x, y } : void 0;
|
|
355
|
+
}
|
|
356
|
+
var keyHolder = null;
|
|
357
|
+
var NODES = /* @__PURE__ */ new WeakMap();
|
|
358
|
+
function actionsNodeOf(registry) {
|
|
359
|
+
return registry ? NODES.get(registry) ?? null : null;
|
|
360
|
+
}
|
|
361
|
+
function ActionsProvider({ children }) {
|
|
362
|
+
const depReg = useOptionalDepRegistry();
|
|
363
|
+
const depRegRef = useLatest(depReg);
|
|
364
|
+
const node = useMemo(() => new ActionsNode(null, true, () => depRegRef.current), [depRegRef]);
|
|
365
|
+
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: node.registry, children: /* @__PURE__ */ jsx(MenuHostContext.Provider, { value: node.menuHost(), children }) });
|
|
366
|
+
}
|
|
367
|
+
var IS_DEV = isDev();
|
|
368
|
+
function useActionsRegistry() {
|
|
369
|
+
return useContext(ActionsContext);
|
|
370
|
+
}
|
|
371
|
+
function useAction(action) {
|
|
372
|
+
const reg = useActionsRegistry();
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
if (!reg) {
|
|
375
|
+
if (IS_DEV) {
|
|
376
|
+
console.warn(
|
|
377
|
+
`useAction("${action.id}"): no <ActionsProvider> is in scope, so the action was not registered and its bindings will never fire.`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
return reg.register(action);
|
|
383
|
+
}, [reg, action]);
|
|
384
|
+
}
|
|
114
385
|
function useToolLifecycle(opts) {
|
|
115
386
|
const { active, hotkeyStack } = opts;
|
|
116
387
|
const liveRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -148,6 +419,7 @@ function useToolLifecycle(opts) {
|
|
|
148
419
|
function createDispatchCore(src) {
|
|
149
420
|
const { canvas } = src;
|
|
150
421
|
let routedId = null;
|
|
422
|
+
let over = false;
|
|
151
423
|
const target = () => {
|
|
152
424
|
if (routedId === null) return src.root();
|
|
153
425
|
return src.views()?.targets().find((t) => t.id === routedId) ?? src.root();
|
|
@@ -184,16 +456,22 @@ function createDispatchCore(src) {
|
|
|
184
456
|
return fn ? fn(cx, cy) : { x: cx, y: cy };
|
|
185
457
|
},
|
|
186
458
|
toCanvasLocal: (cx, cy) => {
|
|
459
|
+
const own = src.clientToLocal();
|
|
460
|
+
if (own) return own(cx, cy);
|
|
187
461
|
if (!canvas) return { x: cx, y: cy };
|
|
188
462
|
const [x, y] = clientToCanvas(canvas, cx, cy);
|
|
189
463
|
return { x, y };
|
|
190
464
|
},
|
|
191
|
-
dispatch: (ev, on) => {
|
|
465
|
+
dispatch: (ev, on, reach) => {
|
|
192
466
|
const d = on ?? dispatcherNow();
|
|
193
467
|
src.uses(d);
|
|
194
|
-
return d.handleInput(ev, ctxNow());
|
|
468
|
+
return d.handleInput(ev, reach ? { ...ctxNow(), keyReach: reach } : ctxNow());
|
|
195
469
|
},
|
|
196
470
|
holdsKeys: () => canvas ? src.actions().holdsKeys() : src.actions().isActive(),
|
|
471
|
+
pointerOver: () => over,
|
|
472
|
+
setPointerOver: (next) => {
|
|
473
|
+
over = canvas !== null && next;
|
|
474
|
+
},
|
|
197
475
|
activate: () => src.actions().activate(),
|
|
198
476
|
disposed: src.disposed
|
|
199
477
|
};
|
|
@@ -243,8 +521,65 @@ function underPointer(view, mods, w) {
|
|
|
243
521
|
shiftKey: mods.shiftKey,
|
|
244
522
|
...affordance !== void 0 ? { affordance } : {},
|
|
245
523
|
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
246
|
-
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
524
|
+
...body?.kind !== void 0 ? { bodyKind: body.kind } : {},
|
|
525
|
+
...body?.doubleClick !== void 0 ? { doubleClickBody: doubleClickBodyOf(body.doubleClick) } : {}
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function doubleClickBodyOf(named) {
|
|
529
|
+
return { bodyTarget: named.body, ...named.kind !== void 0 ? { bodyKind: named.kind } : {} };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/interactions/dispatcher/channels/carry.ts
|
|
533
|
+
function carryInput(core) {
|
|
534
|
+
const { canvas } = core;
|
|
535
|
+
if (!canvas) return () => {
|
|
247
536
|
};
|
|
537
|
+
const pinned = /* @__PURE__ */ new Map();
|
|
538
|
+
return addCarrySurface({
|
|
539
|
+
element: canvas,
|
|
540
|
+
receive(stage, payload, at, pointerId) {
|
|
541
|
+
if (core.disposed()) return "unhandled";
|
|
542
|
+
const event = () => {
|
|
543
|
+
const w = core.toWorld(at.clientX, at.clientY);
|
|
544
|
+
const { affordance, bodyTarget, bodyKind } = underPointer(core.target(), at, w);
|
|
545
|
+
return {
|
|
546
|
+
kind: "carry",
|
|
547
|
+
stage,
|
|
548
|
+
pointerId,
|
|
549
|
+
type: payload.type,
|
|
550
|
+
value: payload.value,
|
|
551
|
+
x: w.x,
|
|
552
|
+
y: w.y,
|
|
553
|
+
clientX: at.clientX,
|
|
554
|
+
clientY: at.clientY,
|
|
555
|
+
altKey: at.altKey,
|
|
556
|
+
ctrlKey: at.ctrlKey,
|
|
557
|
+
metaKey: at.metaKey,
|
|
558
|
+
shiftKey: at.shiftKey,
|
|
559
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
560
|
+
...bodyTarget !== void 0 ? { bodyTarget } : {},
|
|
561
|
+
...bodyKind !== void 0 ? { bodyKind } : {}
|
|
562
|
+
};
|
|
563
|
+
};
|
|
564
|
+
const routedNow = core.routedView();
|
|
565
|
+
try {
|
|
566
|
+
if (stage === "enter") {
|
|
567
|
+
core.routeAt(null, at.clientX, at.clientY);
|
|
568
|
+
pinned.set(pointerId, core.routedView());
|
|
569
|
+
return core.dispatch(event());
|
|
570
|
+
}
|
|
571
|
+
if (pinned.has(pointerId)) core.setRoutedView(pinned.get(pointerId));
|
|
572
|
+
if (stage === "drop" || stage === "leave") pinned.delete(pointerId);
|
|
573
|
+
if (stage !== "leave" && !core.dispatcherNow().inFlight().has(carryGestureId(pointerId))) {
|
|
574
|
+
core.routeAt(null, at.clientX, at.clientY);
|
|
575
|
+
if (stage === "move") pinned.set(pointerId, core.routedView());
|
|
576
|
+
}
|
|
577
|
+
return core.dispatch(event());
|
|
578
|
+
} finally {
|
|
579
|
+
core.setRoutedView(routedNow);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
});
|
|
248
583
|
}
|
|
249
584
|
|
|
250
585
|
// src/interactions/dispatcher/channels/contextMenu.ts
|
|
@@ -253,6 +588,7 @@ function contextMenuInput(core, takeNativeHold) {
|
|
|
253
588
|
const onContextMenu = (e) => {
|
|
254
589
|
e.preventDefault();
|
|
255
590
|
if (takeNativeHold()) return;
|
|
591
|
+
core.activate();
|
|
256
592
|
routeAt(null, e.clientX, e.clientY);
|
|
257
593
|
const w = toWorld(e.clientX, e.clientY);
|
|
258
594
|
const ev = {
|
|
@@ -290,6 +626,15 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
|
|
|
290
626
|
};
|
|
291
627
|
let last = null;
|
|
292
628
|
let facts = null;
|
|
629
|
+
let heldHeard = null;
|
|
630
|
+
const hears = () => {
|
|
631
|
+
if (pointersHeld() === 0) {
|
|
632
|
+
heldHeard = null;
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
heldHeard ??= anyBinding(ctxNow(), (s) => s.kind === "hover" && s.held !== void 0);
|
|
636
|
+
return heldHeard;
|
|
637
|
+
};
|
|
293
638
|
const resolve = () => {
|
|
294
639
|
if (!canvas || !last) return;
|
|
295
640
|
if (pointersHeld() > 0 || dispatcherNow().inFlight().size > 0) {
|
|
@@ -307,9 +652,10 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
|
|
|
307
652
|
const ctx = ctxNow();
|
|
308
653
|
const d = dispatcherNow();
|
|
309
654
|
const press = { kind: "pointerdown", x: w.x, y: w.y, clientX: h.clientX, clientY: h.clientY, ...where };
|
|
310
|
-
if (
|
|
655
|
+
if (ctx.trace?.recording()) ctx.trace.setLive(() => d.explain(press, ctx));
|
|
311
656
|
const click = { kind: "click", x: w.x, y: w.y, pressX: w.x, pressY: w.y, ...where };
|
|
312
|
-
const
|
|
657
|
+
const cursorOf = (r) => r ? actionCursor(r.action, r.params) : void 0;
|
|
658
|
+
const cursor = cursorOf(d.resolveOnly(press, ctx)) ?? cursorOf(dispatcherNow().resolveOnly(click, ctx));
|
|
313
659
|
override.apply(cursor ?? null);
|
|
314
660
|
};
|
|
315
661
|
const refresh = () => {
|
|
@@ -324,19 +670,22 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
|
|
|
324
670
|
if (!core.disposed()) refresh();
|
|
325
671
|
}, 0);
|
|
326
672
|
};
|
|
673
|
+
const hoverAt = (at) => {
|
|
674
|
+
const w = toWorld(at.clientX, at.clientY);
|
|
675
|
+
const where = underPointer(target(), at, w);
|
|
676
|
+
const from = facts ? { from: whereOf(facts.where) } : {};
|
|
677
|
+
const held = pointersHeld() > 0 ? { held: true } : {};
|
|
678
|
+
facts = { at, where };
|
|
679
|
+
dispatch({ kind: "hover", x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, ...from, ...held });
|
|
680
|
+
};
|
|
327
681
|
return {
|
|
328
682
|
moved(e) {
|
|
329
683
|
last = hoverOf(e);
|
|
330
|
-
if (
|
|
331
|
-
const w = toWorld(e.clientX, e.clientY);
|
|
332
|
-
const where = underPointer(target(), last, w);
|
|
333
|
-
const from = facts ? { from: whereOf(facts.where) } : {};
|
|
334
|
-
facts = { at: last, where };
|
|
335
|
-
dispatch({ kind: "hover", x: w.x, y: w.y, clientX: e.clientX, clientY: e.clientY, ...where, ...from });
|
|
336
|
-
}
|
|
684
|
+
if (hears()) hoverAt(last);
|
|
337
685
|
refresh();
|
|
338
686
|
},
|
|
339
687
|
released(e) {
|
|
688
|
+
heldHeard = null;
|
|
340
689
|
last = hoverOf(e);
|
|
341
690
|
refreshSoon();
|
|
342
691
|
},
|
|
@@ -348,13 +697,16 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
|
|
|
348
697
|
}
|
|
349
698
|
facts = null;
|
|
350
699
|
last = null;
|
|
351
|
-
|
|
700
|
+
ctxNow().trace?.setLive(null);
|
|
352
701
|
paintedCursor()?.clearPointer();
|
|
353
702
|
override.clear();
|
|
354
703
|
},
|
|
355
704
|
onKey(e) {
|
|
356
705
|
if (!last) return;
|
|
706
|
+
const prev = last;
|
|
357
707
|
last = { ...last, altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey };
|
|
708
|
+
const changed = prev.altKey !== last.altKey || prev.ctrlKey !== last.ctrlKey || prev.metaKey !== last.metaKey || prev.shiftKey !== last.shiftKey;
|
|
709
|
+
if (changed && hears()) hoverAt(last);
|
|
358
710
|
refreshSoon();
|
|
359
711
|
},
|
|
360
712
|
clear: override.clear
|
|
@@ -451,22 +803,24 @@ function typedWithAltGraph(e) {
|
|
|
451
803
|
const base = /^(?:Key|Digit)(.)$/.exec(e.code)?.[1];
|
|
452
804
|
return base === void 0 || base.toLowerCase() !== e.key.toLowerCase();
|
|
453
805
|
}
|
|
806
|
+
var inMenu = (t) => t instanceof Element && t.closest('[role="menu"]') !== null;
|
|
454
807
|
function keyboardInput(core) {
|
|
455
808
|
const { dispatch, dispatcherNow } = core;
|
|
456
809
|
const heldKeys = /* @__PURE__ */ new Map();
|
|
457
810
|
const codeOf = (e) => e.code ? { code: e.code } : {};
|
|
458
811
|
const physicalKey = (e) => e.code || e.key.toLowerCase();
|
|
459
812
|
const onKeyDown = (e) => {
|
|
460
|
-
if (e.defaultPrevented || isEditableTarget(e.target) || focusedControlOwnsKey(e)) return;
|
|
461
|
-
|
|
813
|
+
if (e.defaultPrevented || isEditableTarget(e.target) || inMenu(e.target) || focusedControlOwnsKey(e)) return;
|
|
814
|
+
const reach = { held: core.holdsKeys(), over: core.pointerOver() };
|
|
815
|
+
if (!reach.held && !reach.over) return;
|
|
462
816
|
if (e.repeat && heldKeys.has(physicalKey(e))) {
|
|
463
817
|
e.preventDefault();
|
|
464
818
|
return;
|
|
465
819
|
}
|
|
466
|
-
if (press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey })) return;
|
|
467
|
-
if (typedWithAltGraph(e)) press(e, { altKey: false, ctrlKey: false });
|
|
820
|
+
if (press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey }, reach)) return;
|
|
821
|
+
if (typedWithAltGraph(e)) press(e, { altKey: false, ctrlKey: false }, reach);
|
|
468
822
|
};
|
|
469
|
-
const press = (e, mods) => {
|
|
823
|
+
const press = (e, mods, reach) => {
|
|
470
824
|
const keyEv = {
|
|
471
825
|
kind: "key",
|
|
472
826
|
key: e.key,
|
|
@@ -476,7 +830,7 @@ function keyboardInput(core) {
|
|
|
476
830
|
shiftKey: e.shiftKey,
|
|
477
831
|
repeat: e.repeat
|
|
478
832
|
};
|
|
479
|
-
const keyResult = dispatch(keyEv);
|
|
833
|
+
const keyResult = dispatch(keyEv, void 0, reach);
|
|
480
834
|
const heldEv = {
|
|
481
835
|
kind: "key-held",
|
|
482
836
|
key: e.key,
|
|
@@ -487,7 +841,7 @@ function keyboardInput(core) {
|
|
|
487
841
|
shiftKey: e.shiftKey
|
|
488
842
|
};
|
|
489
843
|
const heldOn = dispatcherNow();
|
|
490
|
-
const heldResult = dispatch(heldEv, heldOn);
|
|
844
|
+
const heldResult = dispatch(heldEv, heldOn, reach);
|
|
491
845
|
if (heldResult === "handled") {
|
|
492
846
|
heldKeys.set(physicalKey(e), { key: e.key, ...codeOf(e), on: heldOn });
|
|
493
847
|
}
|
|
@@ -553,6 +907,7 @@ var sampleOf = (e) => ({
|
|
|
553
907
|
clientX: e.clientX,
|
|
554
908
|
clientY: e.clientY,
|
|
555
909
|
target: e.target,
|
|
910
|
+
timeStamp: e.timeStamp,
|
|
556
911
|
altKey: e.altKey,
|
|
557
912
|
ctrlKey: e.ctrlKey,
|
|
558
913
|
metaKey: e.metaKey,
|
|
@@ -620,7 +975,11 @@ function pointerInput(core, opts) {
|
|
|
620
975
|
clock: hold.clock,
|
|
621
976
|
toWorld,
|
|
622
977
|
toSurface: (p) => core.toCanvasLocal(p.x, p.y),
|
|
623
|
-
longPressMs: () => opts.longPress()?.duration,
|
|
978
|
+
longPressMs: () => opts.longPress()?.duration ?? opts.kitSettings().get("gestures.longPressMs"),
|
|
979
|
+
doubleClickMs: () => opts.kitSettings().get("gestures.doubleClickMs"),
|
|
980
|
+
doubleClickPx: () => opts.kitSettings().get("gestures.doubleClickPx"),
|
|
981
|
+
tapPx: () => opts.kitSettings().get("gestures.tapPx"),
|
|
982
|
+
dragPx: () => opts.kitSettings().get("gestures.dragPx"),
|
|
624
983
|
clicks: opts.clicks,
|
|
625
984
|
observer: {
|
|
626
985
|
click: (world) => {
|
|
@@ -765,6 +1124,12 @@ function webkitPinchInput(core) {
|
|
|
765
1124
|
}
|
|
766
1125
|
|
|
767
1126
|
// src/interactions/dispatcher/channels/wheel.ts
|
|
1127
|
+
var LINE_PX = 16;
|
|
1128
|
+
function pixelsPer(e, canvas) {
|
|
1129
|
+
if (e.deltaMode === 1) return LINE_PX;
|
|
1130
|
+
if (e.deltaMode === 2) return canvas?.clientHeight || LINE_PX * 20;
|
|
1131
|
+
return 1;
|
|
1132
|
+
}
|
|
768
1133
|
function wheelInput(core, pinchLive) {
|
|
769
1134
|
const { routeAt, toWorld, target, dispatch } = core;
|
|
770
1135
|
const onWheel = (e) => {
|
|
@@ -777,10 +1142,11 @@ function wheelInput(core, pinchLive) {
|
|
|
777
1142
|
routeAt(null, e.clientX, e.clientY);
|
|
778
1143
|
const local = core.toCanvasLocal(e.clientX, e.clientY);
|
|
779
1144
|
const hitPoint = toWorld(e.clientX, e.clientY);
|
|
1145
|
+
const toPx = pixelsPer(e, core.canvas);
|
|
780
1146
|
const ev = {
|
|
781
1147
|
kind: "wheel",
|
|
782
|
-
deltaX: e.deltaX,
|
|
783
|
-
deltaY: e.deltaY,
|
|
1148
|
+
deltaX: e.deltaX * toPx,
|
|
1149
|
+
deltaY: e.deltaY * toPx,
|
|
784
1150
|
clientX: local.x,
|
|
785
1151
|
clientY: local.y,
|
|
786
1152
|
...underPointer(target(), e, hitPoint)
|
|
@@ -803,6 +1169,7 @@ function attachInput(canvas, src, channels) {
|
|
|
803
1169
|
canvas,
|
|
804
1170
|
root: src.root,
|
|
805
1171
|
views: src.views,
|
|
1172
|
+
clientToLocal: src.clientToLocal,
|
|
806
1173
|
context: src.context,
|
|
807
1174
|
actions: src.actions,
|
|
808
1175
|
uses: () => {
|
|
@@ -817,6 +1184,7 @@ function attachInput(canvas, src, channels) {
|
|
|
817
1184
|
const hover = hoverCursorInput(core, src.paintedCursor, () => held?.count() ?? 0, src.cursorSink);
|
|
818
1185
|
const pointer = pointerInput(core, {
|
|
819
1186
|
longPress: src.longPress,
|
|
1187
|
+
kitSettings: src.kitSettings,
|
|
820
1188
|
onClick: src.onClick,
|
|
821
1189
|
onDoubleClick: src.onDoubleClick,
|
|
822
1190
|
clicks: src.clicks,
|
|
@@ -837,6 +1205,12 @@ function attachInput(canvas, src, channels) {
|
|
|
837
1205
|
listeners.on(canvas, "pointerdown", () => {
|
|
838
1206
|
src.actions().activate();
|
|
839
1207
|
});
|
|
1208
|
+
const setOver = (on) => () => {
|
|
1209
|
+
core.setPointerOver(on);
|
|
1210
|
+
};
|
|
1211
|
+
listeners.on(canvas, "pointerenter", setOver(true));
|
|
1212
|
+
listeners.on(canvas, "pointermove", setOver(true));
|
|
1213
|
+
listeners.on(canvas, "pointerleave", setOver(false));
|
|
840
1214
|
if (channels.pointer) {
|
|
841
1215
|
listeners.on(window, "keydown", hover.onKey);
|
|
842
1216
|
listeners.on(window, "keyup", hover.onKey);
|
|
@@ -844,6 +1218,14 @@ function attachInput(canvas, src, channels) {
|
|
|
844
1218
|
listeners.on(canvas, "pointerdown", pointer.onPointerDown);
|
|
845
1219
|
listeners.on(canvas, "pointermove", pointer.onHoverMove);
|
|
846
1220
|
listeners.on(canvas, "dblclick", pointer.onNativeDoubleClick);
|
|
1221
|
+
} else if (channels.hover) {
|
|
1222
|
+
listeners.on(window, "keydown", hover.onKey);
|
|
1223
|
+
listeners.on(window, "keyup", hover.onKey);
|
|
1224
|
+
listeners.on(canvas, "pointerleave", hover.onLeave);
|
|
1225
|
+
listeners.on(canvas, "pointermove", (e) => {
|
|
1226
|
+
core.routeAt(null, e.clientX, e.clientY);
|
|
1227
|
+
hover.moved(e);
|
|
1228
|
+
});
|
|
847
1229
|
}
|
|
848
1230
|
if (channels.wheel) {
|
|
849
1231
|
listeners.on(canvas, "wheel", wheelInput(core, pinch.live), { passive: false, capture: true });
|
|
@@ -863,8 +1245,10 @@ function attachInput(canvas, src, channels) {
|
|
|
863
1245
|
listeners.on(canvas, "drop", ingest.onDrop);
|
|
864
1246
|
listeners.on(window, "paste", ingest.onPaste);
|
|
865
1247
|
}
|
|
1248
|
+
const offCarry = channels.carry ? carryInput(core) : null;
|
|
866
1249
|
return () => {
|
|
867
1250
|
listeners.removeAll();
|
|
1251
|
+
offCarry?.();
|
|
868
1252
|
canvas?.classList.remove(DROPOVER_CLASS);
|
|
869
1253
|
src.actions().dropKeys();
|
|
870
1254
|
hover.clear();
|
|
@@ -878,29 +1262,39 @@ function attachInput(canvas, src, channels) {
|
|
|
878
1262
|
|
|
879
1263
|
// src/interactions/dispatcher/useGestureDispatcher.tsx
|
|
880
1264
|
function useGestureDispatcher(opts) {
|
|
881
|
-
const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, requestRedraw, paintedCursor, cursorSink, getRuleCtx, onClick, onDoubleClick, views, channels, longPress, claimKeys = false } = opts;
|
|
1265
|
+
const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, trace: traceOpt, clientToWorld, clientToLocal, requestRedraw, paintedCursor, cursorSink, getRuleCtx, onClick, onDoubleClick, views, channels, longPress, claimKeys = false, hears = "scope" } = opts;
|
|
882
1266
|
const pointerChannel = channels?.pointer !== false;
|
|
1267
|
+
const hoverChannel = channels?.hover ?? pointerChannel;
|
|
883
1268
|
const wheelChannel = channels?.wheel !== false;
|
|
884
1269
|
const pinchChannel = channels?.pinch !== false;
|
|
885
1270
|
const contextMenuChannel = channels?.contextMenu !== false;
|
|
886
1271
|
const ingestChannel = channels?.ingest !== false;
|
|
1272
|
+
const carryChannel = channels?.carry !== false;
|
|
887
1273
|
const onClickRef = useLatest(onClick);
|
|
888
1274
|
const onDoubleClickRef = useLatest(onDoubleClick);
|
|
889
1275
|
const activeTool = useActiveToolContext();
|
|
890
1276
|
const depRegistry = useDepRegistry();
|
|
891
1277
|
const [isMac] = useState(isApplePlatform);
|
|
1278
|
+
const [ownTrace] = useState(createDispatchTrace);
|
|
1279
|
+
const trace = traceOpt ?? ownTrace;
|
|
1280
|
+
useEffect(() => depRegistry.register("dispatchTrace", () => trace), [depRegistry, trace]);
|
|
892
1281
|
const dispatcherRef = useRef(null);
|
|
893
1282
|
if (!dispatcherRef.current) {
|
|
894
1283
|
dispatcherRef.current = dispatcherOpt ?? createDispatcher();
|
|
895
1284
|
}
|
|
896
1285
|
const registryRef = useLatest(actions);
|
|
1286
|
+
const heard = useMemo(() => {
|
|
1287
|
+
const node = hears === "own" ? actionsNodeOf(actions) : null;
|
|
1288
|
+
return node ? { list: () => node.ownActions() } : actions;
|
|
1289
|
+
}, [actions, hears]);
|
|
897
1290
|
const ctxRef = useLatest({
|
|
898
|
-
actions,
|
|
1291
|
+
actions: heard,
|
|
899
1292
|
depRegistry,
|
|
900
1293
|
activeToolId: activeTool.active,
|
|
901
1294
|
hotkeyStack: activeTool.hotkeyStack,
|
|
902
1295
|
entriesById,
|
|
903
|
-
isMac
|
|
1296
|
+
isMac,
|
|
1297
|
+
trace
|
|
904
1298
|
});
|
|
905
1299
|
const rootTargetRef = useLatest({
|
|
906
1300
|
id: null,
|
|
@@ -911,6 +1305,7 @@ function useGestureDispatcher(opts) {
|
|
|
911
1305
|
getRuleCtx
|
|
912
1306
|
});
|
|
913
1307
|
const viewsRef = useLatest(views);
|
|
1308
|
+
const clientToLocalRef = useLatest(clientToLocal);
|
|
914
1309
|
const allDispatchers = useCallback(() => [
|
|
915
1310
|
rootTargetRef.current.dispatcher,
|
|
916
1311
|
...(viewsRef.current?.targets() ?? []).map((t) => t.dispatcher)
|
|
@@ -918,6 +1313,7 @@ function useGestureDispatcher(opts) {
|
|
|
918
1313
|
const requestRedrawRef = useLatest(requestRedraw);
|
|
919
1314
|
const cursorRef = useLatest({ painted: paintedCursor, sink: cursorSink });
|
|
920
1315
|
const longPressRef = useLatest(longPress);
|
|
1316
|
+
const kitSettingsRef = useLatest(useKitSettings());
|
|
921
1317
|
const clicksRef = useRef({ last: null });
|
|
922
1318
|
useToolLifecycle({
|
|
923
1319
|
entriesById,
|
|
@@ -936,6 +1332,7 @@ function useGestureDispatcher(opts) {
|
|
|
936
1332
|
return attachInput(canvasRef.current, {
|
|
937
1333
|
root: () => rootTargetRef.current,
|
|
938
1334
|
views: () => viewsRef.current,
|
|
1335
|
+
clientToLocal: () => clientToLocalRef.current,
|
|
939
1336
|
context: () => ctxRef.current,
|
|
940
1337
|
actions: () => registryRef.current,
|
|
941
1338
|
dispatchers: allDispatchers,
|
|
@@ -945,28 +1342,35 @@ function useGestureDispatcher(opts) {
|
|
|
945
1342
|
paintedCursor: () => cursorRef.current.painted?.(),
|
|
946
1343
|
cursorSink: () => cursorRef.current.sink?.(),
|
|
947
1344
|
longPress: () => longPressRef.current,
|
|
1345
|
+
kitSettings: () => kitSettingsRef.current,
|
|
948
1346
|
onClick: () => onClickRef.current,
|
|
949
1347
|
onDoubleClick: () => onDoubleClickRef.current,
|
|
950
1348
|
clicks: clicksRef.current
|
|
951
1349
|
}, {
|
|
952
1350
|
keyboard,
|
|
953
1351
|
pointer: pointerChannel,
|
|
1352
|
+
hover: hoverChannel,
|
|
954
1353
|
wheel: wheelChannel,
|
|
955
1354
|
pinch: pinchChannel,
|
|
956
1355
|
contextMenu: contextMenuChannel,
|
|
957
|
-
ingest: ingestChannel
|
|
1356
|
+
ingest: ingestChannel,
|
|
1357
|
+
carry: carryChannel
|
|
958
1358
|
});
|
|
959
1359
|
}, [
|
|
960
1360
|
enabled,
|
|
961
1361
|
keyboard,
|
|
962
1362
|
canvasRef,
|
|
963
1363
|
pointerChannel,
|
|
1364
|
+
hoverChannel,
|
|
964
1365
|
wheelChannel,
|
|
965
1366
|
pinchChannel,
|
|
966
1367
|
contextMenuChannel,
|
|
967
1368
|
ingestChannel,
|
|
1369
|
+
carryChannel,
|
|
968
1370
|
allDispatchers,
|
|
1371
|
+
clientToLocalRef,
|
|
969
1372
|
ctxRef,
|
|
1373
|
+
kitSettingsRef,
|
|
970
1374
|
longPressRef,
|
|
971
1375
|
onClickRef,
|
|
972
1376
|
onDoubleClickRef,
|
|
@@ -977,206 +1381,36 @@ function useGestureDispatcher(opts) {
|
|
|
977
1381
|
viewsRef
|
|
978
1382
|
]);
|
|
979
1383
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
cache = null;
|
|
1005
|
-
/** Called on `activate`, so a scope's dep and tool nodes move with it. */
|
|
1006
|
-
onActivate = [];
|
|
1007
|
-
resolve(id) {
|
|
1008
|
-
for (const n of this.chain()) {
|
|
1009
|
-
if (n.muted.has(id)) return void 0;
|
|
1010
|
-
const a = n.actions.get(id)?.at(-1);
|
|
1011
|
-
if (a) return this.tuned(a);
|
|
1012
|
-
}
|
|
1013
|
-
return void 0;
|
|
1014
|
-
}
|
|
1015
|
-
/** `a` under every tuning for its id from the root down to here. */
|
|
1016
|
-
tuned(a) {
|
|
1017
|
-
const tunings = [];
|
|
1018
|
-
for (const n of this.chain()) {
|
|
1019
|
-
const stack = n.tunings.get(a.id);
|
|
1020
|
-
if (stack) tunings.unshift(...stack);
|
|
1021
|
-
}
|
|
1022
|
-
return tunings.length ? tuneAction(a, tunings) : a;
|
|
1023
|
-
}
|
|
1024
|
-
snapshot() {
|
|
1025
|
-
const leaf = this.start();
|
|
1026
|
-
const cached = this.cache;
|
|
1027
|
-
if (cached && cached.version === this.tree.version && cached.leaf === leaf) return cached.list;
|
|
1028
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1029
|
-
for (const n of leaf.chain()) for (const id of n.actions.keys()) ids.add(id);
|
|
1030
|
-
const out = [];
|
|
1031
|
-
for (const id of ids) {
|
|
1032
|
-
const a = leaf.resolve(id);
|
|
1033
|
-
if (a) out.push(a);
|
|
1034
|
-
}
|
|
1035
|
-
const list = Object.freeze(out);
|
|
1036
|
-
this.cache = { version: this.tree.version, leaf, list };
|
|
1037
|
-
return list;
|
|
1038
|
-
}
|
|
1039
|
-
deps() {
|
|
1040
|
-
for (const n of this.chain()) {
|
|
1041
|
-
const r = n.contextDeps();
|
|
1042
|
-
if (r) return r;
|
|
1043
|
-
}
|
|
1044
|
-
return null;
|
|
1045
|
-
}
|
|
1046
|
-
dispatcher() {
|
|
1047
|
-
for (const n of this.chain()) {
|
|
1048
|
-
const d = n.dispatchers.at(-1);
|
|
1049
|
-
if (d) return d;
|
|
1050
|
-
}
|
|
1051
|
-
return null;
|
|
1052
|
-
}
|
|
1053
|
-
activate() {
|
|
1054
|
-
keyHolder = this;
|
|
1055
|
-
super.activate();
|
|
1056
|
-
for (const f of this.onActivate) f();
|
|
1057
|
-
}
|
|
1058
|
-
registry = {
|
|
1059
|
-
register: (action) => {
|
|
1060
|
-
validateActionId(action.id);
|
|
1061
|
-
validateActionDefaultBinding(action);
|
|
1062
|
-
const stack = this.actions.get(action.id);
|
|
1063
|
-
if (stack) stack.push(action);
|
|
1064
|
-
else this.actions.set(action.id, [action]);
|
|
1065
|
-
this.changed();
|
|
1066
|
-
let released = false;
|
|
1067
|
-
return () => {
|
|
1068
|
-
if (released) return;
|
|
1069
|
-
released = true;
|
|
1070
|
-
const cur = this.actions.get(action.id);
|
|
1071
|
-
if (!cur) return;
|
|
1072
|
-
const i = cur.lastIndexOf(action);
|
|
1073
|
-
if (i === -1) return;
|
|
1074
|
-
cur.splice(i, 1);
|
|
1075
|
-
if (cur.length === 0) {
|
|
1076
|
-
queueMicrotask(() => {
|
|
1077
|
-
if (cur.length === 0 && this.actions.get(action.id) === cur) this.actions.delete(action.id);
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
|
-
this.changed();
|
|
1081
|
-
};
|
|
1082
|
-
},
|
|
1083
|
-
unregister: (id) => {
|
|
1084
|
-
if (this.actions.delete(id)) this.changed();
|
|
1085
|
-
},
|
|
1086
|
-
mute: (id) => {
|
|
1087
|
-
this.muted.set(id, (this.muted.get(id) ?? 0) + 1);
|
|
1088
|
-
this.changed();
|
|
1089
|
-
let released = false;
|
|
1090
|
-
return () => {
|
|
1091
|
-
if (released) return;
|
|
1092
|
-
released = true;
|
|
1093
|
-
const held = this.muted.get(id);
|
|
1094
|
-
if (held === void 0) return;
|
|
1095
|
-
if (held > 1) this.muted.set(id, held - 1);
|
|
1096
|
-
else this.muted.delete(id);
|
|
1097
|
-
this.changed();
|
|
1098
|
-
};
|
|
1099
|
-
},
|
|
1100
|
-
tune: (id, tuning) => {
|
|
1101
|
-
let stack = this.tunings.get(id);
|
|
1102
|
-
if (!stack) this.tunings.set(id, stack = []);
|
|
1103
|
-
const release = pushOwner(stack, tuning);
|
|
1104
|
-
this.changed();
|
|
1105
|
-
let released = false;
|
|
1106
|
-
return () => {
|
|
1107
|
-
if (released) return;
|
|
1108
|
-
released = true;
|
|
1109
|
-
release();
|
|
1110
|
-
if (stack.length === 0) this.tunings.delete(id);
|
|
1111
|
-
this.changed();
|
|
1112
|
-
};
|
|
1113
|
-
},
|
|
1114
|
-
list: () => this.snapshot(),
|
|
1115
|
-
trigger: (id, params) => {
|
|
1116
|
-
const leaf = this.start();
|
|
1117
|
-
const a = leaf.resolve(id);
|
|
1118
|
-
if (!a?.invoker || a.invoker.timing !== "immediate") return false;
|
|
1119
|
-
const deps = depsFor(a, leaf.deps());
|
|
1120
|
-
if (!evaluateEnabled(a, deps).enabled) return false;
|
|
1121
|
-
try {
|
|
1122
|
-
a.invoker.run(deps, params);
|
|
1123
|
-
} catch (err) {
|
|
1124
|
-
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
1125
|
-
}
|
|
1126
|
-
return true;
|
|
1127
|
-
},
|
|
1128
|
-
subscribe: (listener) => this.tree.listeners.subscribe(listener),
|
|
1129
|
-
setDispatcher: (d) => pushOwner(this.dispatchers, d),
|
|
1130
|
-
begin: (id, params) => {
|
|
1131
|
-
const leaf = this.start();
|
|
1132
|
-
const a = leaf.resolve(id);
|
|
1133
|
-
const disp = leaf.dispatcher();
|
|
1134
|
-
if (!a || !disp) return null;
|
|
1135
|
-
return disp.beginUiOngoing(id, depsFor(a, leaf.deps()), params);
|
|
1136
|
-
},
|
|
1137
|
-
activate: () => this.activate(),
|
|
1138
|
-
isActive: () => this.isActive(),
|
|
1139
|
-
holdsKeys: () => keyHolder === this,
|
|
1140
|
-
dropKeys: () => {
|
|
1141
|
-
if (keyHolder === this) keyHolder = null;
|
|
1142
|
-
}
|
|
1143
|
-
};
|
|
1144
|
-
};
|
|
1145
|
-
var keyHolder = null;
|
|
1146
|
-
var NODES = /* @__PURE__ */ new WeakMap();
|
|
1147
|
-
function actionsNodeOf(registry) {
|
|
1148
|
-
return registry ? NODES.get(registry) ?? null : null;
|
|
1149
|
-
}
|
|
1150
|
-
function ActionsProvider({ children }) {
|
|
1151
|
-
const depReg = useOptionalDepRegistry();
|
|
1152
|
-
const depRegRef = useLatest(depReg);
|
|
1153
|
-
const node = useMemo(() => new ActionsNode(null, true, () => depRegRef.current), [depRegRef]);
|
|
1154
|
-
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: node.registry, children });
|
|
1155
|
-
}
|
|
1156
|
-
var IS_DEV = (() => {
|
|
1157
|
-
try {
|
|
1158
|
-
return Boolean(import.meta.env?.DEV);
|
|
1159
|
-
} catch {
|
|
1160
|
-
return false;
|
|
1161
|
-
}
|
|
1162
|
-
})();
|
|
1163
|
-
function useActionsRegistry() {
|
|
1164
|
-
return useContext(ActionsContext);
|
|
1384
|
+
function useCommand(id, label, run, options) {
|
|
1385
|
+
const latest = useLatest({ run, options });
|
|
1386
|
+
const keys = useStableByContent(options?.keys, deepEqual);
|
|
1387
|
+
const tags = useStableByContent(options?.tags, deepEqual);
|
|
1388
|
+
const variants = useStableByContent(options?.variants, deepEqual);
|
|
1389
|
+
const hasEnabled = options?.enabled !== void 0;
|
|
1390
|
+
const hasActive = options?.active !== void 0;
|
|
1391
|
+
const icon = options?.icon;
|
|
1392
|
+
const action = useMemo(() => {
|
|
1393
|
+
const read = () => latest.current.options;
|
|
1394
|
+
const a = {
|
|
1395
|
+
id,
|
|
1396
|
+
label,
|
|
1397
|
+
invoker: { timing: "immediate", run: (_deps, params) => latest.current.run(params) }
|
|
1398
|
+
};
|
|
1399
|
+
if (keys !== void 0) a.defaultBinding = bindingOf(keys);
|
|
1400
|
+
if (tags !== void 0) a.tags = tags;
|
|
1401
|
+
if (variants !== void 0) a.variants = variants;
|
|
1402
|
+
if (hasEnabled) a.enabled = () => read()?.enabled?.() ?? true;
|
|
1403
|
+
if (hasActive) a.active = () => read()?.active?.() ?? false;
|
|
1404
|
+
if (icon !== void 0) a.icon = icon;
|
|
1405
|
+
return a;
|
|
1406
|
+
}, [id, label, icon, keys, tags, variants, hasEnabled, hasActive, latest]);
|
|
1407
|
+
useAction(action);
|
|
1165
1408
|
}
|
|
1166
|
-
function
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
if (!reg) {
|
|
1170
|
-
if (IS_DEV) {
|
|
1171
|
-
console.warn(
|
|
1172
|
-
`useAction("${action.id}"): no <ActionsProvider> is in scope, so the action was not registered and its bindings will never fire.`
|
|
1173
|
-
);
|
|
1174
|
-
}
|
|
1175
|
-
return;
|
|
1176
|
-
}
|
|
1177
|
-
return reg.register(action);
|
|
1178
|
-
}, [reg, action]);
|
|
1409
|
+
function bindingOf(keys) {
|
|
1410
|
+
if (isList(keys)) return [...keys];
|
|
1411
|
+
return "spec" in keys ? [keys] : keys;
|
|
1179
1412
|
}
|
|
1413
|
+
var isList = (keys) => Array.isArray(keys);
|
|
1180
1414
|
function useNodes(descends, ownsTool, parent) {
|
|
1181
1415
|
const parentActions = useActionsRegistry();
|
|
1182
1416
|
const parentDeps = useOptionalDepRegistry();
|
|
@@ -1200,7 +1434,8 @@ function useNodes(descends, ownsTool, parent) {
|
|
|
1200
1434
|
return nodes;
|
|
1201
1435
|
}
|
|
1202
1436
|
function Provide({ nodes, children }) {
|
|
1203
|
-
|
|
1437
|
+
const menuHost = useContext(MenuHostContext) ?? nodes.actions.menuHost();
|
|
1438
|
+
return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: nodes.deps.registry, children: /* @__PURE__ */ jsx(ActionsContext.Provider, { value: nodes.actions.registry, children: /* @__PURE__ */ jsx(ActiveToolNodeContext.Provider, { value: nodes.tools, children: /* @__PURE__ */ jsx(MenuHostContext.Provider, { value: menuHost, children }) }) }) });
|
|
1204
1439
|
}
|
|
1205
1440
|
function InputScope({ yoke, children }) {
|
|
1206
1441
|
const nodes = useNodes(false, yoke === void 0, yoke);
|
|
@@ -1533,6 +1768,6 @@ function useTools(opts) {
|
|
|
1533
1768
|
);
|
|
1534
1769
|
}
|
|
1535
1770
|
|
|
1536
|
-
export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, InputScope, PointerContextProvider, SelectionContextProvider, SelectionContextProviderIfRoot, Yoke, createPointerStore, useAction, useActionsRegistry, useActiveToolContext, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useOffhandAction, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
|
|
1771
|
+
export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, InputScope, PointerContextProvider, SelectionContextProvider, SelectionContextProviderIfRoot, Yoke, createPointerStore, useAction, useActionsRegistry, useActiveToolContext, useCommand, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useMenuHost, useMenus, useOffhandAction, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
|
|
1537
1772
|
//# sourceMappingURL=react.js.map
|
|
1538
1773
|
//# sourceMappingURL=react.js.map
|