@weasel-js/routing 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4DR6AJMN.js → chunk-QBQQIFPI.js} +625 -49
- package/dist/chunk-QBQQIFPI.js.map +1 -0
- package/dist/{index-Bm2QEV09.d.ts → index-CmfVvwWD.d.ts} +722 -276
- package/dist/index.d.ts +6 -3
- package/dist/index.js +55 -16
- package/dist/index.js.map +1 -1
- package/dist/{internal-D0rDmi7t.d.ts → internal-21M1rTVo.d.ts} +23 -1
- package/dist/internal.d.ts +8 -5
- package/dist/react.d.ts +63 -27
- package/dist/react.js +535 -268
- package/dist/react.js.map +1 -1
- package/package.json +13 -9
- package/dist/chunk-4DR6AJMN.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,
|
|
3
|
-
import { isApplePlatform, createPointerRecognizer, isInControlWithin, longPressEvents, stylusOf } from '@weasel-js/gestures';
|
|
4
|
-
import { useLatest, useStableByContent, sameList } from '@weasel-js/react';
|
|
1
|
+
import { createDepRegistry, createDispatcher, assembleEntries, liveScope, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, ScopeNode, tuneAction, createMenuHost, pushOwner, evaluateEnabled, tunedParams, validateActionId, validateActionDefaultBinding, isTool, depNodeOf, DepNode, buildDepsFromRequires, clientToCanvas, DEV, publishLiveDispatch, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, addCarrySurface, gestureIdFor, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer, actionCursor } from './chunk-QBQQIFPI.js';
|
|
2
|
+
import { createContext, useState, useContext, useEffect, useMemo, useRef, useCallback, useSyncExternalStore } from 'react';
|
|
3
|
+
import { isApplePlatform, heldPointers, createPointerRecognizer, isInControlWithin, longPressEvents, stylusOf } from '@weasel-js/gestures';
|
|
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 {
|
|
@@ -111,6 +111,277 @@ function useDepSource(name, source) {
|
|
|
111
111
|
return registry.register(name, () => sourceRef.current());
|
|
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());
|
|
@@ -133,7 +404,7 @@ function useToolLifecycle(opts) {
|
|
|
133
404
|
const next = new Set(active === null ? hotkeyStack : [active, ...hotkeyStack]);
|
|
134
405
|
liveRef.current = next;
|
|
135
406
|
for (const id of prev) if (!next.has(id)) fire(id, "onDeactivate");
|
|
136
|
-
if (prevActiveRef.current !== active) onSwitchRef.current?.();
|
|
407
|
+
if (prevActiveRef.current !== null && prevActiveRef.current !== active) onSwitchRef.current?.();
|
|
137
408
|
prevActiveRef.current = active;
|
|
138
409
|
for (const id of next) if (!prev.has(id)) fire(id, "onActivate");
|
|
139
410
|
});
|
|
@@ -184,6 +455,8 @@ function createDispatchCore(src) {
|
|
|
184
455
|
return fn ? fn(cx, cy) : { x: cx, y: cy };
|
|
185
456
|
},
|
|
186
457
|
toCanvasLocal: (cx, cy) => {
|
|
458
|
+
const own = src.clientToLocal();
|
|
459
|
+
if (own) return own(cx, cy);
|
|
187
460
|
if (!canvas) return { x: cx, y: cy };
|
|
188
461
|
const [x, y] = clientToCanvas(canvas, cx, cy);
|
|
189
462
|
return { x, y };
|
|
@@ -232,6 +505,56 @@ function dispatcherWatch(onChange) {
|
|
|
232
505
|
};
|
|
233
506
|
}
|
|
234
507
|
|
|
508
|
+
// src/interactions/dispatcher/channels/carry.ts
|
|
509
|
+
function carryInput(core) {
|
|
510
|
+
const { canvas } = core;
|
|
511
|
+
if (!canvas) return () => {
|
|
512
|
+
};
|
|
513
|
+
const pinned = /* @__PURE__ */ new Map();
|
|
514
|
+
return addCarrySurface({
|
|
515
|
+
element: canvas,
|
|
516
|
+
receive(stage, payload, at, pointerId) {
|
|
517
|
+
if (core.disposed()) return "unhandled";
|
|
518
|
+
const event = () => {
|
|
519
|
+
const w = core.toWorld(at.clientX, at.clientY);
|
|
520
|
+
return {
|
|
521
|
+
kind: "carry",
|
|
522
|
+
stage,
|
|
523
|
+
pointerId,
|
|
524
|
+
type: payload.type,
|
|
525
|
+
value: payload.value,
|
|
526
|
+
x: w.x,
|
|
527
|
+
y: w.y,
|
|
528
|
+
clientX: at.clientX,
|
|
529
|
+
clientY: at.clientY,
|
|
530
|
+
altKey: at.altKey,
|
|
531
|
+
ctrlKey: at.ctrlKey,
|
|
532
|
+
metaKey: at.metaKey,
|
|
533
|
+
shiftKey: at.shiftKey
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
const routedNow = core.routedView();
|
|
537
|
+
try {
|
|
538
|
+
if (stage === "enter") {
|
|
539
|
+
core.routeAt(null, at.clientX, at.clientY);
|
|
540
|
+
pinned.set(pointerId, core.routedView());
|
|
541
|
+
return core.dispatch(event());
|
|
542
|
+
}
|
|
543
|
+
if (pinned.has(pointerId)) core.setRoutedView(pinned.get(pointerId));
|
|
544
|
+
if (stage !== "move") pinned.delete(pointerId);
|
|
545
|
+
const ev = event();
|
|
546
|
+
if (stage === "drop" && !core.dispatcherNow().inFlight().has(gestureIdFor(ev))) {
|
|
547
|
+
core.routeAt(null, at.clientX, at.clientY);
|
|
548
|
+
return core.dispatch(event());
|
|
549
|
+
}
|
|
550
|
+
return core.dispatch(ev);
|
|
551
|
+
} finally {
|
|
552
|
+
core.setRoutedView(routedNow);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
235
558
|
// src/interactions/dispatcher/underPointer.ts
|
|
236
559
|
function underPointer(view, mods, w) {
|
|
237
560
|
const affordance = view.affordanceAt?.(w) ?? void 0;
|
|
@@ -243,9 +566,13 @@ function underPointer(view, mods, w) {
|
|
|
243
566
|
shiftKey: mods.shiftKey,
|
|
244
567
|
...affordance !== void 0 ? { affordance } : {},
|
|
245
568
|
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
246
|
-
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
569
|
+
...body?.kind !== void 0 ? { bodyKind: body.kind } : {},
|
|
570
|
+
...body?.doubleClick !== void 0 ? { doubleClickBody: doubleClickBodyOf(body.doubleClick) } : {}
|
|
247
571
|
};
|
|
248
572
|
}
|
|
573
|
+
function doubleClickBodyOf(named) {
|
|
574
|
+
return { bodyTarget: named.body, ...named.kind !== void 0 ? { bodyKind: named.kind } : {} };
|
|
575
|
+
}
|
|
249
576
|
|
|
250
577
|
// src/interactions/dispatcher/channels/contextMenu.ts
|
|
251
578
|
function contextMenuInput(core, takeNativeHold) {
|
|
@@ -253,6 +580,7 @@ function contextMenuInput(core, takeNativeHold) {
|
|
|
253
580
|
const onContextMenu = (e) => {
|
|
254
581
|
e.preventDefault();
|
|
255
582
|
if (takeNativeHold()) return;
|
|
583
|
+
core.activate();
|
|
256
584
|
routeAt(null, e.clientX, e.clientY);
|
|
257
585
|
const w = toWorld(e.clientX, e.clientY);
|
|
258
586
|
const ev = {
|
|
@@ -268,6 +596,11 @@ function contextMenuInput(core, takeNativeHold) {
|
|
|
268
596
|
};
|
|
269
597
|
return onContextMenu;
|
|
270
598
|
}
|
|
599
|
+
var whereOf = ({ affordance, bodyTarget, bodyKind }) => ({
|
|
600
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
601
|
+
...bodyTarget !== void 0 ? { bodyTarget } : {},
|
|
602
|
+
...bodyKind !== void 0 ? { bodyKind } : {}
|
|
603
|
+
});
|
|
271
604
|
var hoverOf = (e) => ({
|
|
272
605
|
clientX: e.clientX,
|
|
273
606
|
clientY: e.clientY,
|
|
@@ -304,7 +637,8 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
|
|
|
304
637
|
const press = { kind: "pointerdown", x: w.x, y: w.y, clientX: h.clientX, clientY: h.clientY, ...where };
|
|
305
638
|
if (DEV) publishLiveDispatch(() => d.explain(press, ctx));
|
|
306
639
|
const click = { kind: "click", x: w.x, y: w.y, pressX: w.x, pressY: w.y, ...where };
|
|
307
|
-
const
|
|
640
|
+
const cursorOf = (r) => r ? actionCursor(r.action, r.params) : void 0;
|
|
641
|
+
const cursor = cursorOf(d.resolveOnly(press, ctx)) ?? cursorOf(dispatcherNow().resolveOnly(click, ctx));
|
|
308
642
|
override.apply(cursor ?? null);
|
|
309
643
|
};
|
|
310
644
|
const refresh = () => {
|
|
@@ -319,15 +653,17 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
|
|
|
319
653
|
if (!core.disposed()) refresh();
|
|
320
654
|
}, 0);
|
|
321
655
|
};
|
|
656
|
+
const hoverAt = (at) => {
|
|
657
|
+
const w = toWorld(at.clientX, at.clientY);
|
|
658
|
+
const where = underPointer(target(), at, w);
|
|
659
|
+
const from = facts ? { from: whereOf(facts.where) } : {};
|
|
660
|
+
facts = { at, where };
|
|
661
|
+
dispatch({ kind: "hover", x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, ...from });
|
|
662
|
+
};
|
|
322
663
|
return {
|
|
323
664
|
moved(e) {
|
|
324
665
|
last = hoverOf(e);
|
|
325
|
-
if (pointersHeld() === 0)
|
|
326
|
-
const w = toWorld(e.clientX, e.clientY);
|
|
327
|
-
const where = underPointer(target(), last, w);
|
|
328
|
-
facts = { at: last, where };
|
|
329
|
-
dispatch({ kind: "hover", x: w.x, y: w.y, clientX: e.clientX, clientY: e.clientY, ...where });
|
|
330
|
-
}
|
|
666
|
+
if (pointersHeld() === 0) hoverAt(last);
|
|
331
667
|
refresh();
|
|
332
668
|
},
|
|
333
669
|
released(e) {
|
|
@@ -335,6 +671,12 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
|
|
|
335
671
|
refreshSoon();
|
|
336
672
|
},
|
|
337
673
|
onLeave() {
|
|
674
|
+
if (facts) {
|
|
675
|
+
const { at, where } = facts;
|
|
676
|
+
const w = toWorld(at.clientX, at.clientY);
|
|
677
|
+
dispatch({ kind: "hover", off: true, x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, from: whereOf(where) });
|
|
678
|
+
}
|
|
679
|
+
facts = null;
|
|
338
680
|
last = null;
|
|
339
681
|
if (DEV) publishLiveDispatch(null);
|
|
340
682
|
paintedCursor()?.clearPointer();
|
|
@@ -342,7 +684,10 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
|
|
|
342
684
|
},
|
|
343
685
|
onKey(e) {
|
|
344
686
|
if (!last) return;
|
|
687
|
+
const prev = last;
|
|
345
688
|
last = { ...last, altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey };
|
|
689
|
+
const changed = prev.altKey !== last.altKey || prev.ctrlKey !== last.ctrlKey || prev.metaKey !== last.metaKey || prev.shiftKey !== last.shiftKey;
|
|
690
|
+
if (changed && pointersHeld() === 0) hoverAt(last);
|
|
346
691
|
refreshSoon();
|
|
347
692
|
},
|
|
348
693
|
clear: override.clear
|
|
@@ -439,13 +784,14 @@ function typedWithAltGraph(e) {
|
|
|
439
784
|
const base = /^(?:Key|Digit)(.)$/.exec(e.code)?.[1];
|
|
440
785
|
return base === void 0 || base.toLowerCase() !== e.key.toLowerCase();
|
|
441
786
|
}
|
|
787
|
+
var inMenu = (t) => t instanceof Element && t.closest('[role="menu"]') !== null;
|
|
442
788
|
function keyboardInput(core) {
|
|
443
789
|
const { dispatch, dispatcherNow } = core;
|
|
444
790
|
const heldKeys = /* @__PURE__ */ new Map();
|
|
445
791
|
const codeOf = (e) => e.code ? { code: e.code } : {};
|
|
446
792
|
const physicalKey = (e) => e.code || e.key.toLowerCase();
|
|
447
793
|
const onKeyDown = (e) => {
|
|
448
|
-
if (e.defaultPrevented || isEditableTarget(e.target) || focusedControlOwnsKey(e)) return;
|
|
794
|
+
if (e.defaultPrevented || isEditableTarget(e.target) || inMenu(e.target) || focusedControlOwnsKey(e)) return;
|
|
449
795
|
if (!core.holdsKeys()) return;
|
|
450
796
|
if (e.repeat && heldKeys.has(physicalKey(e))) {
|
|
451
797
|
e.preventDefault();
|
|
@@ -608,7 +954,11 @@ function pointerInput(core, opts) {
|
|
|
608
954
|
clock: hold.clock,
|
|
609
955
|
toWorld,
|
|
610
956
|
toSurface: (p) => core.toCanvasLocal(p.x, p.y),
|
|
611
|
-
longPressMs: () => opts.longPress()?.duration,
|
|
957
|
+
longPressMs: () => opts.longPress()?.duration ?? opts.kitSettings().get("gestures.longPressMs"),
|
|
958
|
+
doubleClickMs: () => opts.kitSettings().get("gestures.doubleClickMs"),
|
|
959
|
+
doubleClickPx: () => opts.kitSettings().get("gestures.doubleClickPx"),
|
|
960
|
+
tapPx: () => opts.kitSettings().get("gestures.tapPx"),
|
|
961
|
+
dragPx: () => opts.kitSettings().get("gestures.dragPx"),
|
|
612
962
|
clicks: opts.clicks,
|
|
613
963
|
observer: {
|
|
614
964
|
click: (world) => {
|
|
@@ -678,7 +1028,6 @@ function pointerInput(core, opts) {
|
|
|
678
1028
|
hold.routeToPress();
|
|
679
1029
|
return recognizer.takeHold();
|
|
680
1030
|
},
|
|
681
|
-
heldCount: () => recognizer.heldCount(),
|
|
682
1031
|
cancelHeld() {
|
|
683
1032
|
for (const session of [...sessions.values()]) session.cancel();
|
|
684
1033
|
},
|
|
@@ -754,6 +1103,12 @@ function webkitPinchInput(core) {
|
|
|
754
1103
|
}
|
|
755
1104
|
|
|
756
1105
|
// src/interactions/dispatcher/channels/wheel.ts
|
|
1106
|
+
var LINE_PX = 16;
|
|
1107
|
+
function pixelsPer(e, canvas) {
|
|
1108
|
+
if (e.deltaMode === 1) return LINE_PX;
|
|
1109
|
+
if (e.deltaMode === 2) return canvas?.clientHeight || LINE_PX * 20;
|
|
1110
|
+
return 1;
|
|
1111
|
+
}
|
|
757
1112
|
function wheelInput(core, pinchLive) {
|
|
758
1113
|
const { routeAt, toWorld, target, dispatch } = core;
|
|
759
1114
|
const onWheel = (e) => {
|
|
@@ -766,10 +1121,11 @@ function wheelInput(core, pinchLive) {
|
|
|
766
1121
|
routeAt(null, e.clientX, e.clientY);
|
|
767
1122
|
const local = core.toCanvasLocal(e.clientX, e.clientY);
|
|
768
1123
|
const hitPoint = toWorld(e.clientX, e.clientY);
|
|
1124
|
+
const toPx = pixelsPer(e, core.canvas);
|
|
769
1125
|
const ev = {
|
|
770
1126
|
kind: "wheel",
|
|
771
|
-
deltaX: e.deltaX,
|
|
772
|
-
deltaY: e.deltaY,
|
|
1127
|
+
deltaX: e.deltaX * toPx,
|
|
1128
|
+
deltaY: e.deltaY * toPx,
|
|
773
1129
|
clientX: local.x,
|
|
774
1130
|
clientY: local.y,
|
|
775
1131
|
...underPointer(target(), e, hitPoint)
|
|
@@ -783,14 +1139,101 @@ function wheelInput(core, pinchLive) {
|
|
|
783
1139
|
return onWheel;
|
|
784
1140
|
}
|
|
785
1141
|
|
|
1142
|
+
// src/interactions/dispatcher/attachInput.ts
|
|
1143
|
+
function attachInput(canvas, src, channels) {
|
|
1144
|
+
let disposed = false;
|
|
1145
|
+
const redraw = dispatcherWatch(src.requestRedraw);
|
|
1146
|
+
redraw.sync(src.dispatchers());
|
|
1147
|
+
const core = createDispatchCore({
|
|
1148
|
+
canvas,
|
|
1149
|
+
root: src.root,
|
|
1150
|
+
views: src.views,
|
|
1151
|
+
clientToLocal: src.clientToLocal,
|
|
1152
|
+
context: src.context,
|
|
1153
|
+
actions: src.actions,
|
|
1154
|
+
uses: () => {
|
|
1155
|
+
redraw.sync(src.dispatchers());
|
|
1156
|
+
},
|
|
1157
|
+
disposed: () => disposed
|
|
1158
|
+
});
|
|
1159
|
+
const keys = keyboardInput(core);
|
|
1160
|
+
const pinch = webkitPinchInput(core);
|
|
1161
|
+
const ingest = ingestInput(core);
|
|
1162
|
+
const held = canvas ? heldPointers(canvas) : null;
|
|
1163
|
+
const hover = hoverCursorInput(core, src.paintedCursor, () => held?.count() ?? 0, src.cursorSink);
|
|
1164
|
+
const pointer = pointerInput(core, {
|
|
1165
|
+
longPress: src.longPress,
|
|
1166
|
+
kitSettings: src.kitSettings,
|
|
1167
|
+
onClick: src.onClick,
|
|
1168
|
+
onDoubleClick: src.onDoubleClick,
|
|
1169
|
+
clicks: src.clicks,
|
|
1170
|
+
hover
|
|
1171
|
+
});
|
|
1172
|
+
const onWindowBlur = () => {
|
|
1173
|
+
pinch.end();
|
|
1174
|
+
pointer.cancelHeld();
|
|
1175
|
+
keys.releaseAll();
|
|
1176
|
+
};
|
|
1177
|
+
const listeners = listenerSet();
|
|
1178
|
+
const keyCapture = channels.keyboard === "first";
|
|
1179
|
+
if (channels.keyboard) {
|
|
1180
|
+
listeners.on(window, "keydown", keys.onKeyDown, keyCapture);
|
|
1181
|
+
listeners.on(window, "keyup", keys.onKeyUp, keyCapture);
|
|
1182
|
+
}
|
|
1183
|
+
listeners.on(window, "blur", onWindowBlur);
|
|
1184
|
+
listeners.on(canvas, "pointerdown", () => {
|
|
1185
|
+
src.actions().activate();
|
|
1186
|
+
});
|
|
1187
|
+
if (channels.pointer) {
|
|
1188
|
+
listeners.on(window, "keydown", hover.onKey);
|
|
1189
|
+
listeners.on(window, "keyup", hover.onKey);
|
|
1190
|
+
listeners.on(canvas, "pointerleave", hover.onLeave);
|
|
1191
|
+
listeners.on(canvas, "pointerdown", pointer.onPointerDown);
|
|
1192
|
+
listeners.on(canvas, "pointermove", pointer.onHoverMove);
|
|
1193
|
+
listeners.on(canvas, "dblclick", pointer.onNativeDoubleClick);
|
|
1194
|
+
}
|
|
1195
|
+
if (channels.wheel) {
|
|
1196
|
+
listeners.on(canvas, "wheel", wheelInput(core, pinch.live), { passive: false, capture: true });
|
|
1197
|
+
}
|
|
1198
|
+
if (channels.pinch) {
|
|
1199
|
+
listeners.on(canvas, "gesturestart", pinch.onGestureStart);
|
|
1200
|
+
listeners.on(canvas, "gesturechange", pinch.onGestureChange);
|
|
1201
|
+
listeners.on(canvas, "gestureend", pinch.onGestureEnd);
|
|
1202
|
+
}
|
|
1203
|
+
if (channels.contextMenu) {
|
|
1204
|
+
listeners.on(canvas, "contextmenu", contextMenuInput(core, pointer.takeNativeHold));
|
|
1205
|
+
}
|
|
1206
|
+
if (channels.ingest) {
|
|
1207
|
+
listeners.on(canvas, "dragenter", ingest.onDragOver);
|
|
1208
|
+
listeners.on(canvas, "dragover", ingest.onDragOver);
|
|
1209
|
+
listeners.on(canvas, "dragleave", ingest.onDragLeave);
|
|
1210
|
+
listeners.on(canvas, "drop", ingest.onDrop);
|
|
1211
|
+
listeners.on(window, "paste", ingest.onPaste);
|
|
1212
|
+
}
|
|
1213
|
+
const offCarry = channels.carry ? carryInput(core) : null;
|
|
1214
|
+
return () => {
|
|
1215
|
+
listeners.removeAll();
|
|
1216
|
+
offCarry?.();
|
|
1217
|
+
canvas?.classList.remove(DROPOVER_CLASS);
|
|
1218
|
+
src.actions().dropKeys();
|
|
1219
|
+
hover.clear();
|
|
1220
|
+
disposed = true;
|
|
1221
|
+
pointer.dispose();
|
|
1222
|
+
held?.release();
|
|
1223
|
+
redraw.stop();
|
|
1224
|
+
for (const d of src.dispatchers()) d.cancelAll("cancel");
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
|
|
786
1228
|
// src/interactions/dispatcher/useGestureDispatcher.tsx
|
|
787
1229
|
function useGestureDispatcher(opts) {
|
|
788
|
-
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;
|
|
1230
|
+
const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, clientToLocal, requestRedraw, paintedCursor, cursorSink, getRuleCtx, onClick, onDoubleClick, views, channels, longPress, claimKeys = false, hears = "scope" } = opts;
|
|
789
1231
|
const pointerChannel = channels?.pointer !== false;
|
|
790
1232
|
const wheelChannel = channels?.wheel !== false;
|
|
791
1233
|
const pinchChannel = channels?.pinch !== false;
|
|
792
1234
|
const contextMenuChannel = channels?.contextMenu !== false;
|
|
793
1235
|
const ingestChannel = channels?.ingest !== false;
|
|
1236
|
+
const carryChannel = channels?.carry !== false;
|
|
794
1237
|
const onClickRef = useLatest(onClick);
|
|
795
1238
|
const onDoubleClickRef = useLatest(onDoubleClick);
|
|
796
1239
|
const activeTool = useActiveToolContext();
|
|
@@ -801,8 +1244,12 @@ function useGestureDispatcher(opts) {
|
|
|
801
1244
|
dispatcherRef.current = dispatcherOpt ?? createDispatcher();
|
|
802
1245
|
}
|
|
803
1246
|
const registryRef = useLatest(actions);
|
|
1247
|
+
const heard = useMemo(() => {
|
|
1248
|
+
const node = hears === "own" ? actionsNodeOf(actions) : null;
|
|
1249
|
+
return node ? { list: () => node.ownActions() } : actions;
|
|
1250
|
+
}, [actions, hears]);
|
|
804
1251
|
const ctxRef = useLatest({
|
|
805
|
-
actions,
|
|
1252
|
+
actions: heard,
|
|
806
1253
|
depRegistry,
|
|
807
1254
|
activeToolId: activeTool.active,
|
|
808
1255
|
hotkeyStack: activeTool.hotkeyStack,
|
|
@@ -818,6 +1265,7 @@ function useGestureDispatcher(opts) {
|
|
|
818
1265
|
getRuleCtx
|
|
819
1266
|
});
|
|
820
1267
|
const viewsRef = useLatest(views);
|
|
1268
|
+
const clientToLocalRef = useLatest(clientToLocal);
|
|
821
1269
|
const allDispatchers = useCallback(() => [
|
|
822
1270
|
rootTargetRef.current.dispatcher,
|
|
823
1271
|
...(viewsRef.current?.targets() ?? []).map((t) => t.dispatcher)
|
|
@@ -825,6 +1273,7 @@ function useGestureDispatcher(opts) {
|
|
|
825
1273
|
const requestRedrawRef = useLatest(requestRedraw);
|
|
826
1274
|
const cursorRef = useLatest({ painted: paintedCursor, sink: cursorSink });
|
|
827
1275
|
const longPressRef = useLatest(longPress);
|
|
1276
|
+
const kitSettingsRef = useLatest(useKitSettings());
|
|
828
1277
|
const clicksRef = useRef({ last: null });
|
|
829
1278
|
useToolLifecycle({
|
|
830
1279
|
entriesById,
|
|
@@ -840,85 +1289,32 @@ function useGestureDispatcher(opts) {
|
|
|
840
1289
|
}, [enabled, claimKeys, registryRef]);
|
|
841
1290
|
useEffect(() => {
|
|
842
1291
|
if (!enabled) return;
|
|
843
|
-
|
|
844
|
-
const canvas = canvasRef.current;
|
|
845
|
-
const redraw = dispatcherWatch(() => {
|
|
846
|
-
requestRedrawRef.current?.();
|
|
847
|
-
});
|
|
848
|
-
redraw.sync(allDispatchers());
|
|
849
|
-
const core = createDispatchCore({
|
|
850
|
-
canvas,
|
|
1292
|
+
return attachInput(canvasRef.current, {
|
|
851
1293
|
root: () => rootTargetRef.current,
|
|
852
1294
|
views: () => viewsRef.current,
|
|
1295
|
+
clientToLocal: () => clientToLocalRef.current,
|
|
853
1296
|
context: () => ctxRef.current,
|
|
854
1297
|
actions: () => registryRef.current,
|
|
855
|
-
|
|
856
|
-
|
|
1298
|
+
dispatchers: allDispatchers,
|
|
1299
|
+
requestRedraw: () => {
|
|
1300
|
+
requestRedrawRef.current?.();
|
|
857
1301
|
},
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
const keys = keyboardInput(core);
|
|
861
|
-
const pinch = webkitPinchInput(core);
|
|
862
|
-
const ingest = ingestInput(core);
|
|
863
|
-
const hover = hoverCursorInput(core, () => cursorRef.current.painted?.(), () => pointer.heldCount(), () => cursorRef.current.sink?.());
|
|
864
|
-
const pointer = pointerInput(core, {
|
|
1302
|
+
paintedCursor: () => cursorRef.current.painted?.(),
|
|
1303
|
+
cursorSink: () => cursorRef.current.sink?.(),
|
|
865
1304
|
longPress: () => longPressRef.current,
|
|
1305
|
+
kitSettings: () => kitSettingsRef.current,
|
|
866
1306
|
onClick: () => onClickRef.current,
|
|
867
1307
|
onDoubleClick: () => onDoubleClickRef.current,
|
|
868
|
-
clicks: clicksRef.current
|
|
869
|
-
|
|
1308
|
+
clicks: clicksRef.current
|
|
1309
|
+
}, {
|
|
1310
|
+
keyboard,
|
|
1311
|
+
pointer: pointerChannel,
|
|
1312
|
+
wheel: wheelChannel,
|
|
1313
|
+
pinch: pinchChannel,
|
|
1314
|
+
contextMenu: contextMenuChannel,
|
|
1315
|
+
ingest: ingestChannel,
|
|
1316
|
+
carry: carryChannel
|
|
870
1317
|
});
|
|
871
|
-
const onWindowBlur = () => {
|
|
872
|
-
pinch.end();
|
|
873
|
-
pointer.cancelHeld();
|
|
874
|
-
keys.releaseAll();
|
|
875
|
-
};
|
|
876
|
-
const listeners = listenerSet();
|
|
877
|
-
const keyCapture = keyboard === "first";
|
|
878
|
-
if (keyboard) {
|
|
879
|
-
listeners.on(window, "keydown", keys.onKeyDown, keyCapture);
|
|
880
|
-
listeners.on(window, "keyup", keys.onKeyUp, keyCapture);
|
|
881
|
-
}
|
|
882
|
-
listeners.on(window, "blur", onWindowBlur);
|
|
883
|
-
listeners.on(canvas, "pointerdown", () => {
|
|
884
|
-
registryRef.current.activate();
|
|
885
|
-
});
|
|
886
|
-
if (pointerChannel) {
|
|
887
|
-
listeners.on(window, "keydown", hover.onKey);
|
|
888
|
-
listeners.on(window, "keyup", hover.onKey);
|
|
889
|
-
listeners.on(canvas, "pointerleave", hover.onLeave);
|
|
890
|
-
listeners.on(canvas, "pointerdown", pointer.onPointerDown);
|
|
891
|
-
listeners.on(canvas, "pointermove", pointer.onHoverMove);
|
|
892
|
-
listeners.on(canvas, "dblclick", pointer.onNativeDoubleClick);
|
|
893
|
-
}
|
|
894
|
-
if (wheelChannel) {
|
|
895
|
-
listeners.on(canvas, "wheel", wheelInput(core, pinch.live), { passive: false, capture: true });
|
|
896
|
-
}
|
|
897
|
-
if (pinchChannel) {
|
|
898
|
-
listeners.on(canvas, "gesturestart", pinch.onGestureStart);
|
|
899
|
-
listeners.on(canvas, "gesturechange", pinch.onGestureChange);
|
|
900
|
-
listeners.on(canvas, "gestureend", pinch.onGestureEnd);
|
|
901
|
-
}
|
|
902
|
-
if (contextMenuChannel) {
|
|
903
|
-
listeners.on(canvas, "contextmenu", contextMenuInput(core, pointer.takeNativeHold));
|
|
904
|
-
}
|
|
905
|
-
if (ingestChannel) {
|
|
906
|
-
listeners.on(canvas, "dragenter", ingest.onDragOver);
|
|
907
|
-
listeners.on(canvas, "dragover", ingest.onDragOver);
|
|
908
|
-
listeners.on(canvas, "dragleave", ingest.onDragLeave);
|
|
909
|
-
listeners.on(canvas, "drop", ingest.onDrop);
|
|
910
|
-
listeners.on(window, "paste", ingest.onPaste);
|
|
911
|
-
}
|
|
912
|
-
return () => {
|
|
913
|
-
listeners.removeAll();
|
|
914
|
-
canvas?.classList.remove(DROPOVER_CLASS);
|
|
915
|
-
registryRef.current.dropKeys();
|
|
916
|
-
hover.clear();
|
|
917
|
-
disposed = true;
|
|
918
|
-
pointer.dispose();
|
|
919
|
-
redraw.stop();
|
|
920
|
-
for (const d of allDispatchers()) d.cancelAll("cancel");
|
|
921
|
-
};
|
|
922
1318
|
}, [
|
|
923
1319
|
enabled,
|
|
924
1320
|
keyboard,
|
|
@@ -928,8 +1324,11 @@ function useGestureDispatcher(opts) {
|
|
|
928
1324
|
pinchChannel,
|
|
929
1325
|
contextMenuChannel,
|
|
930
1326
|
ingestChannel,
|
|
1327
|
+
carryChannel,
|
|
931
1328
|
allDispatchers,
|
|
1329
|
+
clientToLocalRef,
|
|
932
1330
|
ctxRef,
|
|
1331
|
+
kitSettingsRef,
|
|
933
1332
|
longPressRef,
|
|
934
1333
|
onClickRef,
|
|
935
1334
|
onDoubleClickRef,
|
|
@@ -940,182 +1339,36 @@ function useGestureDispatcher(opts) {
|
|
|
940
1339
|
viewsRef
|
|
941
1340
|
]);
|
|
942
1341
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
/** Called on `activate`, so a scope's dep and tool nodes move with it. */
|
|
968
|
-
onActivate = [];
|
|
969
|
-
resolve(id) {
|
|
970
|
-
for (const n of this.chain()) {
|
|
971
|
-
if (n.muted.has(id)) return void 0;
|
|
972
|
-
const a = n.actions.get(id)?.at(-1);
|
|
973
|
-
if (a) return a;
|
|
974
|
-
}
|
|
975
|
-
return void 0;
|
|
976
|
-
}
|
|
977
|
-
snapshot() {
|
|
978
|
-
const leaf = this.start();
|
|
979
|
-
const cached = this.cache;
|
|
980
|
-
if (cached && cached.version === this.tree.version && cached.leaf === leaf) return cached.list;
|
|
981
|
-
const ids = /* @__PURE__ */ new Set();
|
|
982
|
-
for (const n of leaf.chain()) for (const id of n.actions.keys()) ids.add(id);
|
|
983
|
-
const out = [];
|
|
984
|
-
for (const id of ids) {
|
|
985
|
-
const a = leaf.resolve(id);
|
|
986
|
-
if (a) out.push(a);
|
|
987
|
-
}
|
|
988
|
-
const list = Object.freeze(out);
|
|
989
|
-
this.cache = { version: this.tree.version, leaf, list };
|
|
990
|
-
return list;
|
|
991
|
-
}
|
|
992
|
-
deps() {
|
|
993
|
-
for (const n of this.chain()) {
|
|
994
|
-
const r = n.contextDeps();
|
|
995
|
-
if (r) return r;
|
|
996
|
-
}
|
|
997
|
-
return null;
|
|
998
|
-
}
|
|
999
|
-
dispatcher() {
|
|
1000
|
-
for (const n of this.chain()) {
|
|
1001
|
-
const d = n.dispatchers.at(-1);
|
|
1002
|
-
if (d) return d;
|
|
1003
|
-
}
|
|
1004
|
-
return null;
|
|
1005
|
-
}
|
|
1006
|
-
activate() {
|
|
1007
|
-
keyHolder = this;
|
|
1008
|
-
super.activate();
|
|
1009
|
-
for (const f of this.onActivate) f();
|
|
1010
|
-
}
|
|
1011
|
-
registry = {
|
|
1012
|
-
register: (action) => {
|
|
1013
|
-
validateActionId(action.id);
|
|
1014
|
-
validateActionDefaultBinding(action);
|
|
1015
|
-
const stack = this.actions.get(action.id);
|
|
1016
|
-
if (stack) stack.push(action);
|
|
1017
|
-
else this.actions.set(action.id, [action]);
|
|
1018
|
-
this.changed();
|
|
1019
|
-
let released = false;
|
|
1020
|
-
return () => {
|
|
1021
|
-
if (released) return;
|
|
1022
|
-
released = true;
|
|
1023
|
-
const cur = this.actions.get(action.id);
|
|
1024
|
-
if (!cur) return;
|
|
1025
|
-
const i = cur.lastIndexOf(action);
|
|
1026
|
-
if (i === -1) return;
|
|
1027
|
-
cur.splice(i, 1);
|
|
1028
|
-
if (cur.length === 0) {
|
|
1029
|
-
queueMicrotask(() => {
|
|
1030
|
-
if (cur.length === 0 && this.actions.get(action.id) === cur) this.actions.delete(action.id);
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
this.changed();
|
|
1034
|
-
};
|
|
1035
|
-
},
|
|
1036
|
-
unregister: (id) => {
|
|
1037
|
-
if (this.actions.delete(id)) this.changed();
|
|
1038
|
-
},
|
|
1039
|
-
mute: (id) => {
|
|
1040
|
-
this.muted.set(id, (this.muted.get(id) ?? 0) + 1);
|
|
1041
|
-
this.changed();
|
|
1042
|
-
let released = false;
|
|
1043
|
-
return () => {
|
|
1044
|
-
if (released) return;
|
|
1045
|
-
released = true;
|
|
1046
|
-
const held = this.muted.get(id);
|
|
1047
|
-
if (held === void 0) return;
|
|
1048
|
-
if (held > 1) this.muted.set(id, held - 1);
|
|
1049
|
-
else this.muted.delete(id);
|
|
1050
|
-
this.changed();
|
|
1051
|
-
};
|
|
1052
|
-
},
|
|
1053
|
-
list: () => this.snapshot(),
|
|
1054
|
-
trigger: (id, params) => {
|
|
1055
|
-
const leaf = this.start();
|
|
1056
|
-
const a = leaf.resolve(id);
|
|
1057
|
-
if (!a?.invoker || a.invoker.timing !== "immediate") return false;
|
|
1058
|
-
const deps = depsFor(a, leaf.deps());
|
|
1059
|
-
if (!evaluateEnabled(a, deps).enabled) return false;
|
|
1060
|
-
try {
|
|
1061
|
-
a.invoker.run(deps, params);
|
|
1062
|
-
} catch (err) {
|
|
1063
|
-
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
1064
|
-
}
|
|
1065
|
-
return true;
|
|
1066
|
-
},
|
|
1067
|
-
subscribe: (listener) => this.tree.listeners.subscribe(listener),
|
|
1068
|
-
setDispatcher: (d) => pushOwner(this.dispatchers, d),
|
|
1069
|
-
begin: (id, params) => {
|
|
1070
|
-
const leaf = this.start();
|
|
1071
|
-
const a = leaf.resolve(id);
|
|
1072
|
-
const disp = leaf.dispatcher();
|
|
1073
|
-
if (!a || !disp) return null;
|
|
1074
|
-
return disp.beginUiOngoing(id, depsFor(a, leaf.deps()), params);
|
|
1075
|
-
},
|
|
1076
|
-
activate: () => this.activate(),
|
|
1077
|
-
isActive: () => this.isActive(),
|
|
1078
|
-
holdsKeys: () => keyHolder === this,
|
|
1079
|
-
dropKeys: () => {
|
|
1080
|
-
if (keyHolder === this) keyHolder = null;
|
|
1081
|
-
}
|
|
1082
|
-
};
|
|
1083
|
-
};
|
|
1084
|
-
var keyHolder = null;
|
|
1085
|
-
var NODES = /* @__PURE__ */ new WeakMap();
|
|
1086
|
-
function actionsNodeOf(registry) {
|
|
1087
|
-
return registry ? NODES.get(registry) ?? null : null;
|
|
1088
|
-
}
|
|
1089
|
-
function ActionsProvider({ children }) {
|
|
1090
|
-
const depReg = useOptionalDepRegistry();
|
|
1091
|
-
const depRegRef = useLatest(depReg);
|
|
1092
|
-
const node = useMemo(() => new ActionsNode(null, true, () => depRegRef.current), [depRegRef]);
|
|
1093
|
-
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: node.registry, children });
|
|
1094
|
-
}
|
|
1095
|
-
var IS_DEV = (() => {
|
|
1096
|
-
try {
|
|
1097
|
-
return Boolean(import.meta.env?.DEV);
|
|
1098
|
-
} catch {
|
|
1099
|
-
return false;
|
|
1100
|
-
}
|
|
1101
|
-
})();
|
|
1102
|
-
function useActionsRegistry() {
|
|
1103
|
-
return useContext(ActionsContext);
|
|
1342
|
+
function useCommand(id, label, run, options) {
|
|
1343
|
+
const latest = useLatest({ run, options });
|
|
1344
|
+
const keys = useStableByContent(options?.keys, deepEqual);
|
|
1345
|
+
const tags = useStableByContent(options?.tags, deepEqual);
|
|
1346
|
+
const variants = useStableByContent(options?.variants, deepEqual);
|
|
1347
|
+
const hasEnabled = options?.enabled !== void 0;
|
|
1348
|
+
const hasActive = options?.active !== void 0;
|
|
1349
|
+
const icon = options?.icon;
|
|
1350
|
+
const action = useMemo(() => {
|
|
1351
|
+
const read = () => latest.current.options;
|
|
1352
|
+
const a = {
|
|
1353
|
+
id,
|
|
1354
|
+
label,
|
|
1355
|
+
invoker: { timing: "immediate", run: (_deps, params) => latest.current.run(params) }
|
|
1356
|
+
};
|
|
1357
|
+
if (keys !== void 0) a.defaultBinding = bindingOf(keys);
|
|
1358
|
+
if (tags !== void 0) a.tags = tags;
|
|
1359
|
+
if (variants !== void 0) a.variants = variants;
|
|
1360
|
+
if (hasEnabled) a.enabled = () => read()?.enabled?.() ?? true;
|
|
1361
|
+
if (hasActive) a.active = () => read()?.active?.() ?? false;
|
|
1362
|
+
if (icon !== void 0) a.icon = icon;
|
|
1363
|
+
return a;
|
|
1364
|
+
}, [id, label, icon, keys, tags, variants, hasEnabled, hasActive, latest]);
|
|
1365
|
+
useAction(action);
|
|
1104
1366
|
}
|
|
1105
|
-
function
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (!reg) {
|
|
1109
|
-
if (IS_DEV) {
|
|
1110
|
-
console.warn(
|
|
1111
|
-
`useAction("${action.id}"): no <ActionsProvider> is in scope, so the action was not registered and its bindings will never fire.`
|
|
1112
|
-
);
|
|
1113
|
-
}
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
return reg.register(action);
|
|
1117
|
-
}, [reg, action]);
|
|
1367
|
+
function bindingOf(keys) {
|
|
1368
|
+
if (isList(keys)) return [...keys];
|
|
1369
|
+
return "spec" in keys ? [keys] : keys;
|
|
1118
1370
|
}
|
|
1371
|
+
var isList = (keys) => Array.isArray(keys);
|
|
1119
1372
|
function useNodes(descends, ownsTool, parent) {
|
|
1120
1373
|
const parentActions = useActionsRegistry();
|
|
1121
1374
|
const parentDeps = useOptionalDepRegistry();
|
|
@@ -1139,7 +1392,8 @@ function useNodes(descends, ownsTool, parent) {
|
|
|
1139
1392
|
return nodes;
|
|
1140
1393
|
}
|
|
1141
1394
|
function Provide({ nodes, children }) {
|
|
1142
|
-
|
|
1395
|
+
const menuHost = useContext(MenuHostContext) ?? nodes.actions.menuHost();
|
|
1396
|
+
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 }) }) }) });
|
|
1143
1397
|
}
|
|
1144
1398
|
function InputScope({ yoke, children }) {
|
|
1145
1399
|
const nodes = useNodes(false, yoke === void 0, yoke);
|
|
@@ -1177,7 +1431,20 @@ function useOngoingAction(actionId) {
|
|
|
1177
1431
|
}
|
|
1178
1432
|
};
|
|
1179
1433
|
}, [regRef, idRef]);
|
|
1180
|
-
|
|
1434
|
+
const leaving = useRef(null);
|
|
1435
|
+
useEffect(() => {
|
|
1436
|
+
const left = leaving.current;
|
|
1437
|
+
leaving.current = null;
|
|
1438
|
+
if (left !== null && left !== actionId) edit.commit();
|
|
1439
|
+
return () => {
|
|
1440
|
+
leaving.current = actionId;
|
|
1441
|
+
queueMicrotask(() => {
|
|
1442
|
+
if (leaving.current !== actionId) return;
|
|
1443
|
+
leaving.current = null;
|
|
1444
|
+
edit.commit();
|
|
1445
|
+
});
|
|
1446
|
+
};
|
|
1447
|
+
}, [edit, actionId]);
|
|
1181
1448
|
return edit;
|
|
1182
1449
|
}
|
|
1183
1450
|
function samePos(a, b) {
|
|
@@ -1459,6 +1726,6 @@ function useTools(opts) {
|
|
|
1459
1726
|
);
|
|
1460
1727
|
}
|
|
1461
1728
|
|
|
1462
|
-
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 };
|
|
1729
|
+
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 };
|
|
1463
1730
|
//# sourceMappingURL=react.js.map
|
|
1464
1731
|
//# sourceMappingURL=react.js.map
|