@weasel-js/routing 1.9.3 → 1.10.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/README.md +6 -5
- package/dist/{chunk-LOP2JTTA.js → chunk-4DR6AJMN.js} +1093 -922
- package/dist/chunk-4DR6AJMN.js.map +1 -0
- package/dist/{index-CrbDNXT_.d.ts → index-Bm2QEV09.d.ts} +576 -706
- package/dist/index.d.ts +5 -4
- package/dist/index.js +27 -74
- package/dist/index.js.map +1 -1
- package/dist/{internal-B1B6gfOS.d.ts → internal-D0rDmi7t.d.ts} +46 -78
- package/dist/internal.d.ts +5 -4
- package/dist/react.d.ts +140 -98
- package/dist/react.js +870 -896
- package/dist/react.js.map +1 -1
- package/package.json +9 -7
- package/dist/chunk-LOP2JTTA.js.map +0 -1
package/dist/react.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createDepRegistry, createDispatcher,
|
|
1
|
+
import { createDepRegistry, createDispatcher, assembleEntries, liveScope, isDev, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, ScopeNode, isTool, DEV, publishLiveDispatch, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, pushOwner, evaluateEnabled, validateActionId, validateActionDefaultBinding, depNodeOf, DepNode, clientToCanvas, buildDepsFromRequires, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer } from './chunk-4DR6AJMN.js';
|
|
2
2
|
import { createContext, useState, useContext, useEffect, useRef, useCallback, useMemo, useSyncExternalStore } from 'react';
|
|
3
|
+
import { isApplePlatform, createPointerRecognizer, isInControlWithin, longPressEvents, stylusOf } from '@weasel-js/gestures';
|
|
3
4
|
import { useLatest, useStableByContent, sameList } from '@weasel-js/react';
|
|
4
5
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { createCursorSink, resolveCursorTier } from '@weasel-js/cursor';
|
|
7
|
+
import { createEmitter } from '@weasel-js/registry';
|
|
7
8
|
import { activeModeOf } from '@weasel-js/modes';
|
|
8
9
|
|
|
9
10
|
var ToolNode = class extends ScopeNode {
|
|
@@ -48,12 +49,7 @@ var ToolNode = class extends ScopeNode {
|
|
|
48
49
|
this.value = { from, state, out };
|
|
49
50
|
return out;
|
|
50
51
|
}
|
|
51
|
-
subscribe = (listener) =>
|
|
52
|
-
this.tree.listeners.add(listener);
|
|
53
|
-
return () => {
|
|
54
|
-
this.tree.listeners.delete(listener);
|
|
55
|
-
};
|
|
56
|
-
};
|
|
52
|
+
subscribe = (listener) => this.tree.listeners.subscribe(listener);
|
|
57
53
|
};
|
|
58
54
|
var ActiveToolContext = createContext(null);
|
|
59
55
|
var ActiveToolNodeContext = ActiveToolContext;
|
|
@@ -115,19 +111,93 @@ function useDepSource(name, source) {
|
|
|
115
111
|
return registry.register(name, () => sourceRef.current());
|
|
116
112
|
}, [name, registry, sourceRef]);
|
|
117
113
|
}
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
function useToolLifecycle(opts) {
|
|
115
|
+
const { active, hotkeyStack } = opts;
|
|
116
|
+
const liveRef = useRef(/* @__PURE__ */ new Set());
|
|
117
|
+
const prevActiveRef = useRef(active);
|
|
118
|
+
const entriesRef = useLatest(opts.entriesById);
|
|
119
|
+
const onSwitchRef = useLatest(opts.onSwitch);
|
|
120
|
+
const fire = useCallback((id, hook) => {
|
|
121
|
+
const tool = entriesRef.current.get(id);
|
|
122
|
+
if (!tool || !isTool(tool)) return;
|
|
123
|
+
const fn = tool[hook];
|
|
124
|
+
if (!fn) return;
|
|
125
|
+
try {
|
|
126
|
+
fn();
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error(`weasel: tool "${id}".${hook} threw`, err);
|
|
129
|
+
}
|
|
130
|
+
}, [entriesRef]);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const prev = liveRef.current;
|
|
133
|
+
const next = new Set(active === null ? hotkeyStack : [active, ...hotkeyStack]);
|
|
134
|
+
liveRef.current = next;
|
|
135
|
+
for (const id of prev) if (!next.has(id)) fire(id, "onDeactivate");
|
|
136
|
+
if (prevActiveRef.current !== active) onSwitchRef.current?.();
|
|
137
|
+
prevActiveRef.current = active;
|
|
138
|
+
for (const id of next) if (!prev.has(id)) fire(id, "onActivate");
|
|
139
|
+
});
|
|
140
|
+
useEffect(() => () => {
|
|
141
|
+
const live = liveRef.current;
|
|
142
|
+
liveRef.current = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const id of live) fire(id, "onDeactivate");
|
|
144
|
+
}, [fire]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/interactions/dispatcher/dispatchCore.ts
|
|
148
|
+
function createDispatchCore(src) {
|
|
149
|
+
const { canvas } = src;
|
|
150
|
+
let routedId = null;
|
|
151
|
+
const target = () => {
|
|
152
|
+
if (routedId === null) return src.root();
|
|
153
|
+
return src.views()?.targets().find((t) => t.id === routedId) ?? src.root();
|
|
154
|
+
};
|
|
155
|
+
const dispatcherNow = () => target().dispatcher;
|
|
156
|
+
const routedDeps = withViewDeps(() => src.context().depRegistry, () => target().deps?.());
|
|
157
|
+
const routedRuleCtx = () => target().getRuleCtx?.();
|
|
158
|
+
const ctxNow = () => ({
|
|
159
|
+
...src.context(),
|
|
160
|
+
depRegistry: routedDeps,
|
|
161
|
+
getRuleCtx: routedRuleCtx,
|
|
162
|
+
viewId: target().id
|
|
163
|
+
});
|
|
120
164
|
return {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
165
|
+
canvas,
|
|
166
|
+
target,
|
|
167
|
+
routeAt: (pointerId, clientX, clientY) => {
|
|
168
|
+
routedId = src.views()?.resolver.at(pointerId, clientX, clientY).id ?? null;
|
|
169
|
+
},
|
|
170
|
+
routeDown: (pointerId, clientX, clientY) => {
|
|
171
|
+
routedId = src.views()?.resolver.begin(pointerId, clientX, clientY).id ?? null;
|
|
172
|
+
},
|
|
173
|
+
routeRelease: (pointerId) => {
|
|
174
|
+
src.views()?.resolver.end(pointerId);
|
|
175
|
+
},
|
|
176
|
+
routedView: () => routedId,
|
|
177
|
+
setRoutedView: (id) => {
|
|
178
|
+
routedId = id;
|
|
179
|
+
},
|
|
180
|
+
dispatcherNow,
|
|
181
|
+
ctxNow,
|
|
182
|
+
toWorld: (cx, cy) => {
|
|
183
|
+
const fn = target().clientToWorld;
|
|
184
|
+
return fn ? fn(cx, cy) : { x: cx, y: cy };
|
|
185
|
+
},
|
|
186
|
+
toCanvasLocal: (cx, cy) => {
|
|
187
|
+
if (!canvas) return { x: cx, y: cy };
|
|
188
|
+
const [x, y] = clientToCanvas(canvas, cx, cy);
|
|
189
|
+
return { x, y };
|
|
190
|
+
},
|
|
191
|
+
dispatch: (ev, on) => {
|
|
192
|
+
const d = on ?? dispatcherNow();
|
|
193
|
+
src.uses(d);
|
|
194
|
+
return d.handleInput(ev, ctxNow());
|
|
195
|
+
},
|
|
196
|
+
holdsKeys: () => canvas ? src.actions().holdsKeys() : src.actions().isActive(),
|
|
197
|
+
activate: () => src.actions().activate(),
|
|
198
|
+
disposed: src.disposed
|
|
124
199
|
};
|
|
125
200
|
}
|
|
126
|
-
var IS_MAC = typeof navigator !== "undefined" && /mac/i.test(
|
|
127
|
-
// `||`, not `??`: jsdom (and some embedders) report an empty-string
|
|
128
|
-
// platform, which is not nullish and would swallow the UA fallback.
|
|
129
|
-
navigator.platform || navigator.userAgent
|
|
130
|
-
);
|
|
131
201
|
function withViewDeps(base, overlay) {
|
|
132
202
|
return {
|
|
133
203
|
register: (name, source) => base().register(name, source),
|
|
@@ -138,25 +208,584 @@ function withViewDeps(base, overlay) {
|
|
|
138
208
|
}
|
|
139
209
|
};
|
|
140
210
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
211
|
+
|
|
212
|
+
// src/interactions/dispatcher/dispatcherWatch.ts
|
|
213
|
+
function dispatcherWatch(onChange) {
|
|
214
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
215
|
+
return {
|
|
216
|
+
sync(live) {
|
|
217
|
+
for (const d of live) {
|
|
218
|
+
if (!subscriptions.has(d)) subscriptions.set(d, d.subscribe(onChange));
|
|
219
|
+
}
|
|
220
|
+
if (subscriptions.size === live.length) return;
|
|
221
|
+
const keep = new Set(live);
|
|
222
|
+
for (const [d, unsubscribe] of subscriptions) {
|
|
223
|
+
if (keep.has(d)) continue;
|
|
224
|
+
unsubscribe();
|
|
225
|
+
subscriptions.delete(d);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
stop() {
|
|
229
|
+
for (const unsubscribe of subscriptions.values()) unsubscribe();
|
|
230
|
+
subscriptions.clear();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/interactions/dispatcher/underPointer.ts
|
|
236
|
+
function underPointer(view, mods, w) {
|
|
237
|
+
const affordance = view.affordanceAt?.(w) ?? void 0;
|
|
238
|
+
const body = view.classifyTarget?.(w);
|
|
239
|
+
return {
|
|
240
|
+
altKey: mods.altKey,
|
|
241
|
+
ctrlKey: mods.ctrlKey,
|
|
242
|
+
metaKey: mods.metaKey,
|
|
243
|
+
shiftKey: mods.shiftKey,
|
|
244
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
245
|
+
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
246
|
+
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/interactions/dispatcher/channels/contextMenu.ts
|
|
251
|
+
function contextMenuInput(core, takeNativeHold) {
|
|
252
|
+
const { routeAt, toWorld, target, dispatch } = core;
|
|
253
|
+
const onContextMenu = (e) => {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
if (takeNativeHold()) return;
|
|
256
|
+
routeAt(null, e.clientX, e.clientY);
|
|
257
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
258
|
+
const ev = {
|
|
259
|
+
kind: "contextmenu",
|
|
260
|
+
target: e.target,
|
|
261
|
+
x: w.x,
|
|
262
|
+
y: w.y,
|
|
263
|
+
clientX: e.clientX,
|
|
264
|
+
clientY: e.clientY,
|
|
265
|
+
...underPointer(target(), e, w)
|
|
266
|
+
};
|
|
267
|
+
dispatch(ev);
|
|
268
|
+
};
|
|
269
|
+
return onContextMenu;
|
|
270
|
+
}
|
|
271
|
+
var hoverOf = (e) => ({
|
|
272
|
+
clientX: e.clientX,
|
|
273
|
+
clientY: e.clientY,
|
|
274
|
+
altKey: e.altKey,
|
|
275
|
+
ctrlKey: e.ctrlKey,
|
|
276
|
+
metaKey: e.metaKey,
|
|
277
|
+
shiftKey: e.shiftKey
|
|
278
|
+
});
|
|
279
|
+
function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () => void 0) {
|
|
280
|
+
const { canvas, routeAt, toWorld, target, dispatch, dispatcherNow, ctxNow } = core;
|
|
281
|
+
const ownSink = createCursorSink({ element: () => canvas, painted: paintedCursor });
|
|
282
|
+
const override = {
|
|
283
|
+
apply: (spec) => (cursorSink() ?? ownSink).setOverride(spec === null ? null : resolveCursorTier(spec)),
|
|
284
|
+
clear: () => (cursorSink() ?? ownSink).setOverride(null)
|
|
285
|
+
};
|
|
286
|
+
let last = null;
|
|
287
|
+
let facts = null;
|
|
288
|
+
const resolve = () => {
|
|
289
|
+
if (!canvas || !last) return;
|
|
290
|
+
if (pointersHeld() > 0 || dispatcherNow().inFlight().size > 0) {
|
|
291
|
+
override.apply(dispatcherNow().inFlightCursor());
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const h = last;
|
|
295
|
+
routeAt(null, h.clientX, h.clientY);
|
|
296
|
+
const w = toWorld(h.clientX, h.clientY);
|
|
297
|
+
const where = facts?.at === h ? facts.where : underPointer(target(), h, w);
|
|
298
|
+
if (where.affordance?.cursor) {
|
|
299
|
+
override.apply(where.affordance.cursor);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const ctx = ctxNow();
|
|
303
|
+
const d = dispatcherNow();
|
|
304
|
+
const press = { kind: "pointerdown", x: w.x, y: w.y, clientX: h.clientX, clientY: h.clientY, ...where };
|
|
305
|
+
if (DEV) publishLiveDispatch(() => d.explain(press, ctx));
|
|
306
|
+
const click = { kind: "click", x: w.x, y: w.y, pressX: w.x, pressY: w.y, ...where };
|
|
307
|
+
const cursor = d.resolveOnly(press, ctx)?.action.cursor ?? dispatcherNow().resolveOnly(click, ctx)?.action.cursor;
|
|
308
|
+
override.apply(cursor ?? null);
|
|
309
|
+
};
|
|
310
|
+
const refresh = () => {
|
|
311
|
+
resolve();
|
|
312
|
+
const painted = paintedCursor();
|
|
313
|
+
if (!painted?.active() || !canvas || !last) return;
|
|
314
|
+
const local = core.toCanvasLocal(last.clientX, last.clientY);
|
|
315
|
+
painted.setPointer(local.x, local.y);
|
|
316
|
+
};
|
|
317
|
+
const refreshSoon = () => {
|
|
318
|
+
setTimeout(() => {
|
|
319
|
+
if (!core.disposed()) refresh();
|
|
320
|
+
}, 0);
|
|
321
|
+
};
|
|
322
|
+
return {
|
|
323
|
+
moved(e) {
|
|
324
|
+
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
|
+
}
|
|
331
|
+
refresh();
|
|
332
|
+
},
|
|
333
|
+
released(e) {
|
|
334
|
+
last = hoverOf(e);
|
|
335
|
+
refreshSoon();
|
|
336
|
+
},
|
|
337
|
+
onLeave() {
|
|
338
|
+
last = null;
|
|
339
|
+
if (DEV) publishLiveDispatch(null);
|
|
340
|
+
paintedCursor()?.clearPointer();
|
|
341
|
+
override.clear();
|
|
342
|
+
},
|
|
343
|
+
onKey(e) {
|
|
344
|
+
if (!last) return;
|
|
345
|
+
last = { ...last, altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey };
|
|
346
|
+
refreshSoon();
|
|
347
|
+
},
|
|
348
|
+
clear: override.clear
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/interactions/dispatcher/channels/ingest.ts
|
|
353
|
+
var DROPOVER_CLASS = "weasel-dropover";
|
|
354
|
+
function ingestInput(core) {
|
|
355
|
+
const { canvas, routeAt, toWorld, dispatch } = core;
|
|
356
|
+
const onDragOver = (e) => {
|
|
357
|
+
const dt = e.dataTransfer;
|
|
358
|
+
if (!dt) return;
|
|
359
|
+
routeAt(null, e.clientX, e.clientY);
|
|
360
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
361
|
+
const wanted = core.dispatcherNow().resolveOnly({
|
|
362
|
+
kind: "drop",
|
|
363
|
+
items: itemTypesFromDataTransfer(dt),
|
|
364
|
+
x: w.x,
|
|
365
|
+
y: w.y,
|
|
366
|
+
clientX: e.clientX,
|
|
367
|
+
clientY: e.clientY,
|
|
368
|
+
altKey: e.altKey,
|
|
369
|
+
ctrlKey: e.ctrlKey,
|
|
370
|
+
metaKey: e.metaKey,
|
|
371
|
+
shiftKey: e.shiftKey
|
|
372
|
+
}, core.ctxNow()) !== null;
|
|
373
|
+
canvas?.classList.toggle(DROPOVER_CLASS, wanted);
|
|
374
|
+
if (!wanted) return;
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
dt.dropEffect = "copy";
|
|
377
|
+
};
|
|
378
|
+
const onDragLeave = () => {
|
|
379
|
+
canvas?.classList.remove(DROPOVER_CLASS);
|
|
380
|
+
};
|
|
381
|
+
const onDrop = (e) => {
|
|
382
|
+
canvas?.classList.remove(DROPOVER_CLASS);
|
|
383
|
+
const dt = e.dataTransfer;
|
|
384
|
+
if (!dt) return;
|
|
385
|
+
e.preventDefault();
|
|
386
|
+
routeAt(null, e.clientX, e.clientY);
|
|
387
|
+
const dropView = core.routedView();
|
|
388
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
389
|
+
const base = {
|
|
390
|
+
altKey: e.altKey,
|
|
391
|
+
ctrlKey: e.ctrlKey,
|
|
392
|
+
metaKey: e.metaKey,
|
|
393
|
+
shiftKey: e.shiftKey
|
|
394
|
+
};
|
|
395
|
+
void itemsFromDataTransfer(dt).then((items) => {
|
|
396
|
+
if (core.disposed()) return;
|
|
397
|
+
if (items.length === 0) return;
|
|
398
|
+
const routedNow = core.routedView();
|
|
399
|
+
core.setRoutedView(dropView);
|
|
400
|
+
try {
|
|
401
|
+
dispatch({
|
|
402
|
+
kind: "drop",
|
|
403
|
+
items,
|
|
404
|
+
x: w.x,
|
|
405
|
+
y: w.y,
|
|
406
|
+
clientX: e.clientX,
|
|
407
|
+
clientY: e.clientY,
|
|
408
|
+
...base
|
|
409
|
+
});
|
|
410
|
+
} finally {
|
|
411
|
+
core.setRoutedView(routedNow);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
const onPaste = (e) => {
|
|
416
|
+
if (e.defaultPrevented || isEditableTarget(e.target)) return;
|
|
417
|
+
if (!core.holdsKeys()) return;
|
|
418
|
+
const cd = e.clipboardData;
|
|
419
|
+
if (!cd) return;
|
|
420
|
+
const items = itemsFromClipboardData(cd);
|
|
421
|
+
if (items.length === 0) return;
|
|
422
|
+
const result = dispatch({
|
|
423
|
+
kind: "paste",
|
|
424
|
+
items,
|
|
425
|
+
// ClipboardEvent carries no modifier state.
|
|
426
|
+
altKey: false,
|
|
427
|
+
ctrlKey: false,
|
|
428
|
+
metaKey: false,
|
|
429
|
+
shiftKey: false
|
|
430
|
+
});
|
|
431
|
+
if (result === "handled") e.preventDefault();
|
|
432
|
+
};
|
|
433
|
+
return { onDragOver, onDragLeave, onDrop, onPaste };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/interactions/dispatcher/channels/keyboard.ts
|
|
437
|
+
function typedWithAltGraph(e) {
|
|
438
|
+
if (!e.ctrlKey || !e.altKey || e.key.length !== 1 || !e.getModifierState?.("AltGraph")) return false;
|
|
439
|
+
const base = /^(?:Key|Digit)(.)$/.exec(e.code)?.[1];
|
|
440
|
+
return base === void 0 || base.toLowerCase() !== e.key.toLowerCase();
|
|
441
|
+
}
|
|
442
|
+
function keyboardInput(core) {
|
|
443
|
+
const { dispatch, dispatcherNow } = core;
|
|
444
|
+
const heldKeys = /* @__PURE__ */ new Map();
|
|
445
|
+
const codeOf = (e) => e.code ? { code: e.code } : {};
|
|
446
|
+
const physicalKey = (e) => e.code || e.key.toLowerCase();
|
|
447
|
+
const onKeyDown = (e) => {
|
|
448
|
+
if (e.defaultPrevented || isEditableTarget(e.target) || focusedControlOwnsKey(e)) return;
|
|
449
|
+
if (!core.holdsKeys()) return;
|
|
450
|
+
if (e.repeat && heldKeys.has(physicalKey(e))) {
|
|
451
|
+
e.preventDefault();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey })) return;
|
|
455
|
+
if (typedWithAltGraph(e)) press(e, { altKey: false, ctrlKey: false });
|
|
456
|
+
};
|
|
457
|
+
const press = (e, mods) => {
|
|
458
|
+
const keyEv = {
|
|
459
|
+
kind: "key",
|
|
460
|
+
key: e.key,
|
|
461
|
+
...codeOf(e),
|
|
462
|
+
...mods,
|
|
463
|
+
metaKey: e.metaKey,
|
|
464
|
+
shiftKey: e.shiftKey,
|
|
465
|
+
repeat: e.repeat
|
|
466
|
+
};
|
|
467
|
+
const keyResult = dispatch(keyEv);
|
|
468
|
+
const heldEv = {
|
|
469
|
+
kind: "key-held",
|
|
470
|
+
key: e.key,
|
|
471
|
+
...codeOf(e),
|
|
472
|
+
phase: "down",
|
|
473
|
+
...mods,
|
|
474
|
+
metaKey: e.metaKey,
|
|
475
|
+
shiftKey: e.shiftKey
|
|
476
|
+
};
|
|
477
|
+
const heldOn = dispatcherNow();
|
|
478
|
+
const heldResult = dispatch(heldEv, heldOn);
|
|
479
|
+
if (heldResult === "handled") {
|
|
480
|
+
heldKeys.set(physicalKey(e), { key: e.key, ...codeOf(e), on: heldOn });
|
|
481
|
+
}
|
|
482
|
+
const handled = keyResult === "handled" || heldResult === "handled";
|
|
483
|
+
if (handled) e.preventDefault();
|
|
484
|
+
return handled;
|
|
485
|
+
};
|
|
486
|
+
const onKeyUp = (e) => {
|
|
487
|
+
const physical = physicalKey(e);
|
|
488
|
+
const down = heldKeys.get(physical);
|
|
489
|
+
if (down === void 0) return;
|
|
490
|
+
const ev = {
|
|
491
|
+
kind: "key-held",
|
|
492
|
+
key: down.key,
|
|
493
|
+
...down.code !== void 0 ? { code: down.code } : {},
|
|
494
|
+
phase: "up",
|
|
495
|
+
altKey: e.altKey,
|
|
496
|
+
ctrlKey: e.ctrlKey,
|
|
497
|
+
metaKey: e.metaKey,
|
|
498
|
+
shiftKey: e.shiftKey
|
|
499
|
+
};
|
|
500
|
+
heldKeys.delete(physical);
|
|
501
|
+
dispatch(ev, down.on);
|
|
502
|
+
};
|
|
503
|
+
const releaseAll = () => {
|
|
504
|
+
const releasing = [...heldKeys.values()];
|
|
505
|
+
heldKeys.clear();
|
|
506
|
+
for (const { key, code, on } of releasing) {
|
|
507
|
+
const ev = {
|
|
508
|
+
kind: "key-held",
|
|
509
|
+
key,
|
|
510
|
+
...code !== void 0 ? { code } : {},
|
|
511
|
+
phase: "up",
|
|
512
|
+
altKey: false,
|
|
513
|
+
ctrlKey: false,
|
|
514
|
+
metaKey: false,
|
|
515
|
+
shiftKey: false
|
|
516
|
+
};
|
|
517
|
+
dispatch(ev, on);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
return { onKeyDown, onKeyUp, releaseAll };
|
|
157
521
|
}
|
|
522
|
+
|
|
523
|
+
// src/interactions/dispatcher/channels/listeners.ts
|
|
524
|
+
function listenerSet() {
|
|
525
|
+
const removers = [];
|
|
526
|
+
return {
|
|
527
|
+
on(target, type, listener, options) {
|
|
528
|
+
if (!target) return;
|
|
529
|
+
target.addEventListener(type, listener, options);
|
|
530
|
+
removers.push(() => {
|
|
531
|
+
target.removeEventListener(type, listener, options);
|
|
532
|
+
});
|
|
533
|
+
},
|
|
534
|
+
removeAll() {
|
|
535
|
+
for (const remove of removers.splice(0)) remove();
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
var sampleOf = (e) => ({
|
|
540
|
+
pointerId: e.pointerId,
|
|
541
|
+
clientX: e.clientX,
|
|
542
|
+
clientY: e.clientY,
|
|
543
|
+
target: e.target,
|
|
544
|
+
altKey: e.altKey,
|
|
545
|
+
ctrlKey: e.ctrlKey,
|
|
546
|
+
metaKey: e.metaKey,
|
|
547
|
+
shiftKey: e.shiftKey,
|
|
548
|
+
...stylusOf(e)
|
|
549
|
+
});
|
|
550
|
+
function holdPublisher(core, longPress) {
|
|
551
|
+
return (pending) => {
|
|
552
|
+
const store = longPress()?.state;
|
|
553
|
+
if (!store) return;
|
|
554
|
+
if (!pending) {
|
|
555
|
+
store.set(null);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const { press } = pending;
|
|
559
|
+
const evs = longPressEvents(press);
|
|
560
|
+
const d = core.dispatcherNow();
|
|
561
|
+
store.set({
|
|
562
|
+
pointerId: pending.pointerId,
|
|
563
|
+
pointerType: pending.pointerType,
|
|
564
|
+
client: { x: press.clientX, y: press.clientY },
|
|
565
|
+
local: core.toCanvasLocal(press.clientX, press.clientY),
|
|
566
|
+
world: { x: press.worldX, y: press.worldY },
|
|
567
|
+
viewId: core.target().id,
|
|
568
|
+
startedAt: pending.startedAt,
|
|
569
|
+
duration: pending.duration,
|
|
570
|
+
armed: d.resolveOnly(evs.longpress, core.ctxNow()) !== null || d.resolveOnly(evs.contextmenu, core.ctxNow()) !== null
|
|
571
|
+
});
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function holdClock(core) {
|
|
575
|
+
let holdView = null;
|
|
576
|
+
return {
|
|
577
|
+
clock: {
|
|
578
|
+
now: () => performance.now(),
|
|
579
|
+
setTimer: (fn, ms) => {
|
|
580
|
+
const view = core.routedView();
|
|
581
|
+
holdView = { id: view };
|
|
582
|
+
return setTimeout(() => {
|
|
583
|
+
holdView = null;
|
|
584
|
+
core.setRoutedView(view);
|
|
585
|
+
fn();
|
|
586
|
+
}, ms);
|
|
587
|
+
},
|
|
588
|
+
clearTimer: (timer) => {
|
|
589
|
+
holdView = null;
|
|
590
|
+
clearTimeout(timer);
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
routeToPress: () => {
|
|
594
|
+
if (holdView) core.setRoutedView(holdView.id);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
function pointerInput(core, opts) {
|
|
599
|
+
const { canvas, routeAt, routeDown, routeRelease, toWorld, target } = core;
|
|
600
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
601
|
+
let claimedDoubleClick = false;
|
|
602
|
+
const hold = holdClock(core);
|
|
603
|
+
const recognizer = createPointerRecognizer({
|
|
604
|
+
sink: {
|
|
605
|
+
dispatch: (ev) => core.dispatch(ev),
|
|
606
|
+
multiTouchInFlight: (fingers) => core.dispatcherNow().inFlight().has(multiTouchGestureId(fingers))
|
|
607
|
+
},
|
|
608
|
+
clock: hold.clock,
|
|
609
|
+
toWorld,
|
|
610
|
+
toSurface: (p) => core.toCanvasLocal(p.x, p.y),
|
|
611
|
+
longPressMs: () => opts.longPress()?.duration,
|
|
612
|
+
clicks: opts.clicks,
|
|
613
|
+
observer: {
|
|
614
|
+
click: (world) => {
|
|
615
|
+
opts.onClick()?.(world);
|
|
616
|
+
},
|
|
617
|
+
doubleClick: (world) => {
|
|
618
|
+
opts.onDoubleClick()?.(world);
|
|
619
|
+
},
|
|
620
|
+
holdChanged: holdPublisher(core, opts.longPress),
|
|
621
|
+
longPress: () => {
|
|
622
|
+
if (opts.longPress()?.haptics === false) return;
|
|
623
|
+
if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
624
|
+
navigator.vibrate(LONG_PRESS_HAPTIC_MS);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
const onMove = (e) => {
|
|
630
|
+
routeAt(e.pointerId, e.clientX, e.clientY);
|
|
631
|
+
recognizer.move(sampleOf(e));
|
|
632
|
+
opts.hover.moved(e);
|
|
633
|
+
};
|
|
634
|
+
const onUp = (e) => {
|
|
635
|
+
routeAt(e.pointerId, e.clientX, e.clientY);
|
|
636
|
+
sessions.delete(e.pointerId);
|
|
637
|
+
const { doubleClick } = recognizer.release(sampleOf(e));
|
|
638
|
+
if (doubleClick) claimedDoubleClick = doubleClick === "handled" || opts.onDoubleClick() !== void 0;
|
|
639
|
+
routeRelease(e.pointerId);
|
|
640
|
+
opts.hover.released(e);
|
|
641
|
+
};
|
|
642
|
+
const onCancel = (pointerId) => {
|
|
643
|
+
if (core.disposed()) return;
|
|
644
|
+
const at = recognizer.positionOf(pointerId);
|
|
645
|
+
if (!at) return;
|
|
646
|
+
sessions.delete(pointerId);
|
|
647
|
+
routeAt(pointerId, at.x, at.y);
|
|
648
|
+
recognizer.cancel(pointerId);
|
|
649
|
+
routeRelease(pointerId);
|
|
650
|
+
};
|
|
651
|
+
return {
|
|
652
|
+
onPointerDown(e) {
|
|
653
|
+
claimedDoubleClick = false;
|
|
654
|
+
if ((e.button ?? 0) !== 0) return;
|
|
655
|
+
if (!canvas) return;
|
|
656
|
+
if (isInControlWithin(e.target, canvas)) return;
|
|
657
|
+
routeDown(e.pointerId, e.clientX, e.clientY);
|
|
658
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
659
|
+
const where = underPointer(target(), e, w);
|
|
660
|
+
sessions.set(e.pointerId, openPointerSession(canvas, e, {
|
|
661
|
+
onMove,
|
|
662
|
+
onEnd: onUp,
|
|
663
|
+
onCancel: () => {
|
|
664
|
+
onCancel(e.pointerId);
|
|
665
|
+
}
|
|
666
|
+
}));
|
|
667
|
+
recognizer.press({ ...sampleOf(e), x: w.x, y: w.y, pointerType: e.pointerType, ...where });
|
|
668
|
+
},
|
|
669
|
+
onHoverMove(e) {
|
|
670
|
+
if (!sessions.has(e.pointerId)) onMove(e);
|
|
671
|
+
},
|
|
672
|
+
onNativeDoubleClick(e) {
|
|
673
|
+
if (!claimedDoubleClick) return;
|
|
674
|
+
claimedDoubleClick = false;
|
|
675
|
+
e.preventDefault();
|
|
676
|
+
},
|
|
677
|
+
takeNativeHold: () => {
|
|
678
|
+
hold.routeToPress();
|
|
679
|
+
return recognizer.takeHold();
|
|
680
|
+
},
|
|
681
|
+
heldCount: () => recognizer.heldCount(),
|
|
682
|
+
cancelHeld() {
|
|
683
|
+
for (const session of [...sessions.values()]) session.cancel();
|
|
684
|
+
},
|
|
685
|
+
dispose() {
|
|
686
|
+
recognizer.reset();
|
|
687
|
+
for (const session of sessions.values()) session.cancel();
|
|
688
|
+
sessions.clear();
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/interactions/dispatcher/channels/webkitPinch.ts
|
|
694
|
+
function webkitPinchInput(core) {
|
|
695
|
+
const { canvas, routeAt, toWorld, target, dispatch, dispatcherNow, ctxNow } = core;
|
|
696
|
+
let pinchLive = false;
|
|
697
|
+
let pinchScale = 1;
|
|
698
|
+
let pinchRotation = 0;
|
|
699
|
+
const endPinch = () => {
|
|
700
|
+
pinchLive = false;
|
|
701
|
+
pinchScale = 1;
|
|
702
|
+
pinchRotation = 0;
|
|
703
|
+
};
|
|
704
|
+
const pinchEventOf = (e, scale, rotation) => {
|
|
705
|
+
const g = e;
|
|
706
|
+
const rect = typeof g.clientX === "number" && typeof g.clientY === "number" ? void 0 : canvas?.getBoundingClientRect();
|
|
707
|
+
const cx = typeof g.clientX === "number" ? g.clientX : rect ? rect.left + rect.width / 2 : 0;
|
|
708
|
+
const cy = typeof g.clientY === "number" ? g.clientY : rect ? rect.top + rect.height / 2 : 0;
|
|
709
|
+
routeAt(null, cx, cy);
|
|
710
|
+
const local = core.toCanvasLocal(cx, cy);
|
|
711
|
+
const hitPoint = toWorld(cx, cy);
|
|
712
|
+
const mods = {
|
|
713
|
+
altKey: g.altKey === true,
|
|
714
|
+
ctrlKey: g.ctrlKey === true,
|
|
715
|
+
metaKey: g.metaKey === true,
|
|
716
|
+
shiftKey: g.shiftKey === true
|
|
717
|
+
};
|
|
718
|
+
return {
|
|
719
|
+
kind: "pinch",
|
|
720
|
+
scale,
|
|
721
|
+
rotation,
|
|
722
|
+
clientX: local.x,
|
|
723
|
+
clientY: local.y,
|
|
724
|
+
...underPointer(target(), mods, hitPoint)
|
|
725
|
+
};
|
|
726
|
+
};
|
|
727
|
+
const onGestureStart = (e) => {
|
|
728
|
+
endPinch();
|
|
729
|
+
const probe = pinchEventOf(e, 1, 0);
|
|
730
|
+
if (dispatcherNow().resolveOnly(probe, ctxNow()) === null) return;
|
|
731
|
+
pinchLive = true;
|
|
732
|
+
e.preventDefault();
|
|
733
|
+
};
|
|
734
|
+
const onGestureChange = (e) => {
|
|
735
|
+
const g = e;
|
|
736
|
+
const scale = typeof g.scale === "number" && g.scale > 0 ? g.scale : pinchScale;
|
|
737
|
+
const rotation = typeof g.rotation === "number" ? g.rotation : pinchRotation;
|
|
738
|
+
const step = scale / pinchScale;
|
|
739
|
+
const turn = rotation - pinchRotation;
|
|
740
|
+
pinchScale = scale;
|
|
741
|
+
pinchRotation = rotation;
|
|
742
|
+
if (dispatch(pinchEventOf(e, step, turn)) === "handled") {
|
|
743
|
+
pinchLive = true;
|
|
744
|
+
e.preventDefault();
|
|
745
|
+
} else if (pinchLive) {
|
|
746
|
+
e.preventDefault();
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
const onGestureEnd = (e) => {
|
|
750
|
+
if (pinchLive) e.preventDefault();
|
|
751
|
+
endPinch();
|
|
752
|
+
};
|
|
753
|
+
return { onGestureStart, onGestureChange, onGestureEnd, live: () => pinchLive, end: endPinch };
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/interactions/dispatcher/channels/wheel.ts
|
|
757
|
+
function wheelInput(core, pinchLive) {
|
|
758
|
+
const { routeAt, toWorld, target, dispatch } = core;
|
|
759
|
+
const onWheel = (e) => {
|
|
760
|
+
core.activate();
|
|
761
|
+
if (pinchLive() && e.ctrlKey) {
|
|
762
|
+
e.preventDefault();
|
|
763
|
+
e.stopPropagation();
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
routeAt(null, e.clientX, e.clientY);
|
|
767
|
+
const local = core.toCanvasLocal(e.clientX, e.clientY);
|
|
768
|
+
const hitPoint = toWorld(e.clientX, e.clientY);
|
|
769
|
+
const ev = {
|
|
770
|
+
kind: "wheel",
|
|
771
|
+
deltaX: e.deltaX,
|
|
772
|
+
deltaY: e.deltaY,
|
|
773
|
+
clientX: local.x,
|
|
774
|
+
clientY: local.y,
|
|
775
|
+
...underPointer(target(), e, hitPoint)
|
|
776
|
+
};
|
|
777
|
+
const result = dispatch(ev);
|
|
778
|
+
if (result === "handled") {
|
|
779
|
+
e.preventDefault();
|
|
780
|
+
e.stopImmediatePropagation();
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
return onWheel;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/interactions/dispatcher/useGestureDispatcher.tsx
|
|
158
787
|
function useGestureDispatcher(opts) {
|
|
159
|
-
const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, requestRedraw, paintedCursor, getRuleCtx, onClick, onDoubleClick, views, channels, longPress } = 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;
|
|
160
789
|
const pointerChannel = channels?.pointer !== false;
|
|
161
790
|
const wheelChannel = channels?.wheel !== false;
|
|
162
791
|
const pinchChannel = channels?.pinch !== false;
|
|
@@ -166,6 +795,7 @@ function useGestureDispatcher(opts) {
|
|
|
166
795
|
const onDoubleClickRef = useLatest(onDoubleClick);
|
|
167
796
|
const activeTool = useActiveToolContext();
|
|
168
797
|
const depRegistry = useDepRegistry();
|
|
798
|
+
const [isMac] = useState(isApplePlatform);
|
|
169
799
|
const dispatcherRef = useRef(null);
|
|
170
800
|
if (!dispatcherRef.current) {
|
|
171
801
|
dispatcherRef.current = dispatcherOpt ?? createDispatcher();
|
|
@@ -177,7 +807,7 @@ function useGestureDispatcher(opts) {
|
|
|
177
807
|
activeToolId: activeTool.active,
|
|
178
808
|
hotkeyStack: activeTool.hotkeyStack,
|
|
179
809
|
entriesById,
|
|
180
|
-
isMac
|
|
810
|
+
isMac
|
|
181
811
|
});
|
|
182
812
|
const rootTargetRef = useLatest({
|
|
183
813
|
id: null,
|
|
@@ -193,844 +823,100 @@ function useGestureDispatcher(opts) {
|
|
|
193
823
|
...(viewsRef.current?.targets() ?? []).map((t) => t.dispatcher)
|
|
194
824
|
], [rootTargetRef, viewsRef]);
|
|
195
825
|
const requestRedrawRef = useLatest(requestRedraw);
|
|
196
|
-
const
|
|
826
|
+
const cursorRef = useLatest({ painted: paintedCursor, sink: cursorSink });
|
|
197
827
|
const longPressRef = useLatest(longPress);
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const fn = tool[hook];
|
|
206
|
-
if (!fn) return;
|
|
207
|
-
try {
|
|
208
|
-
fn({ scratch: tool.initScratch?.() });
|
|
209
|
-
} catch (err) {
|
|
210
|
-
console.error(`weasel: tool "${id}".${hook} threw`, err);
|
|
211
|
-
}
|
|
212
|
-
}, [entriesByIdRef]);
|
|
213
|
-
useEffect(() => {
|
|
214
|
-
const prev = liveToolsRef.current;
|
|
215
|
-
const next = new Set(
|
|
216
|
-
activeTool.active === null ? activeTool.hotkeyStack : [activeTool.active, ...activeTool.hotkeyStack]
|
|
217
|
-
);
|
|
218
|
-
liveToolsRef.current = next;
|
|
219
|
-
for (const id of prev) if (!next.has(id)) fireLifecycle(id, "onDeactivate");
|
|
220
|
-
if (prevActiveRef.current !== activeTool.active) {
|
|
828
|
+
const clicksRef = useRef({ last: null });
|
|
829
|
+
useToolLifecycle({
|
|
830
|
+
entriesById,
|
|
831
|
+
active: activeTool.active,
|
|
832
|
+
hotkeyStack: activeTool.hotkeyStack,
|
|
833
|
+
// The incoming tool must not inherit a live gesture.
|
|
834
|
+
onSwitch: () => {
|
|
221
835
|
for (const d of allDispatchers()) d.cancelAll("cancel");
|
|
222
836
|
}
|
|
223
|
-
prevActiveRef.current = activeTool.active;
|
|
224
|
-
for (const id of next) if (!prev.has(id)) fireLifecycle(id, "onActivate");
|
|
225
837
|
});
|
|
226
|
-
useEffect(() =>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
for (const id of live) fireLifecycle(id, "onDeactivate");
|
|
230
|
-
}, [fireLifecycle]);
|
|
838
|
+
useEffect(() => {
|
|
839
|
+
if (enabled && claimKeys) registryRef.current.activate();
|
|
840
|
+
}, [enabled, claimKeys, registryRef]);
|
|
231
841
|
useEffect(() => {
|
|
232
842
|
if (!enabled) return;
|
|
233
843
|
let disposed = false;
|
|
234
|
-
let routedId = null;
|
|
235
|
-
const target = () => {
|
|
236
|
-
if (routedId === null) return rootTargetRef.current;
|
|
237
|
-
return viewsRef.current?.targets().find((t) => t.id === routedId) ?? rootTargetRef.current;
|
|
238
|
-
};
|
|
239
|
-
const routeAt = (pointerId, clientX, clientY) => {
|
|
240
|
-
routedId = viewsRef.current?.resolver.at(pointerId, clientX, clientY).id ?? null;
|
|
241
|
-
};
|
|
242
|
-
const routeDown = (pointerId, clientX, clientY) => {
|
|
243
|
-
routedId = viewsRef.current?.resolver.begin(pointerId, clientX, clientY).id ?? null;
|
|
244
|
-
};
|
|
245
|
-
const routeRelease = (pointerId) => {
|
|
246
|
-
viewsRef.current?.resolver.end(pointerId);
|
|
247
|
-
};
|
|
248
|
-
const dispatcherNow = () => target().dispatcher;
|
|
249
|
-
const routedDeps = withViewDeps(() => ctxRef.current.depRegistry, () => target().deps?.());
|
|
250
|
-
const routedRuleCtx = () => target().getRuleCtx?.();
|
|
251
|
-
const ctxNow = () => ({
|
|
252
|
-
...ctxRef.current,
|
|
253
|
-
depRegistry: routedDeps,
|
|
254
|
-
getRuleCtx: routedRuleCtx,
|
|
255
|
-
viewId: target().id
|
|
256
|
-
});
|
|
257
844
|
const canvas = canvasRef.current;
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
};
|
|
285
|
-
let pinchLive = false;
|
|
286
|
-
let pinchScale = 1;
|
|
287
|
-
let pinchRotation = 0;
|
|
288
|
-
const endPinch = () => {
|
|
289
|
-
pinchLive = false;
|
|
290
|
-
pinchScale = 1;
|
|
291
|
-
pinchRotation = 0;
|
|
292
|
-
};
|
|
293
|
-
let multiTouchStart = null;
|
|
294
|
-
const TAP_THRESHOLD_PX = 8;
|
|
295
|
-
const longPressTimers = /* @__PURE__ */ new Map();
|
|
296
|
-
const clearPending = (pointerId) => {
|
|
297
|
-
const store = longPressRef.current?.state;
|
|
298
|
-
const p = store?.get();
|
|
299
|
-
if (p && (pointerId === void 0 || p.pointerId === pointerId)) store.set(null);
|
|
300
|
-
};
|
|
301
|
-
const cancelLongPress = (pointerId) => {
|
|
302
|
-
const t = longPressTimers.get(pointerId);
|
|
303
|
-
if (t !== void 0) {
|
|
304
|
-
clearTimeout(t);
|
|
305
|
-
longPressTimers.delete(pointerId);
|
|
306
|
-
}
|
|
307
|
-
clearPending(pointerId);
|
|
308
|
-
};
|
|
309
|
-
const cancelAllLongPress = () => {
|
|
310
|
-
for (const t of longPressTimers.values()) clearTimeout(t);
|
|
311
|
-
longPressTimers.clear();
|
|
312
|
-
clearPending();
|
|
313
|
-
};
|
|
314
|
-
const HAPTIC_MS = 15;
|
|
315
|
-
const longPressEvents = (down) => {
|
|
316
|
-
const shared = {
|
|
317
|
-
target: down.target,
|
|
318
|
-
altKey: down.altKey,
|
|
319
|
-
ctrlKey: down.ctrlKey,
|
|
320
|
-
metaKey: down.metaKey,
|
|
321
|
-
shiftKey: down.shiftKey,
|
|
322
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
323
|
-
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
324
|
-
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
325
|
-
};
|
|
326
|
-
const at = { x: down.worldX, y: down.worldY, clientX: down.clientX, clientY: down.clientY };
|
|
327
|
-
return {
|
|
328
|
-
longpress: { kind: "longpress", ...at, ...shared },
|
|
329
|
-
contextmenu: { kind: "contextmenu", ...at, ...shared }
|
|
330
|
-
};
|
|
331
|
-
};
|
|
332
|
-
const longPressWouldFire = (down) => {
|
|
333
|
-
const evs = longPressEvents(down);
|
|
334
|
-
const d = dispatcherNow();
|
|
335
|
-
return d.resolveOnly(evs.longpress, ctxNow()) !== null || d.resolveOnly(evs.contextmenu, ctxNow()) !== null;
|
|
336
|
-
};
|
|
337
|
-
const fireLongPress = (pointerId, pointerType) => {
|
|
338
|
-
const down = held.get(pointerId)?.down;
|
|
339
|
-
clearPending(pointerId);
|
|
340
|
-
if (!down) return;
|
|
341
|
-
const evs = longPressEvents(down);
|
|
342
|
-
let result = dispatch(evs.longpress);
|
|
343
|
-
if (result === "unhandled") result = dispatch(evs.contextmenu);
|
|
344
|
-
if (result === "handled" && longPressRef.current?.haptics !== false && (pointerType === "touch" || pointerType === "pen") && typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
345
|
-
navigator.vibrate(HAPTIC_MS);
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
const DOUBLE_CLICK_MAX_MS = 600;
|
|
349
|
-
const DOUBLE_CLICK_MAX_PX = 8;
|
|
350
|
-
const onKeyDown = (e) => {
|
|
351
|
-
if (e.defaultPrevented || isEditableTarget(e.target) || activatesFocusedControl(e)) return;
|
|
352
|
-
if (!registryRef.current.isActive()) return;
|
|
353
|
-
if (e.repeat && heldKeys.has(physicalKey(e))) {
|
|
354
|
-
e.preventDefault();
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
const keyEv = {
|
|
358
|
-
kind: "key",
|
|
359
|
-
key: e.key,
|
|
360
|
-
altKey: e.altKey,
|
|
361
|
-
ctrlKey: e.ctrlKey,
|
|
362
|
-
metaKey: e.metaKey,
|
|
363
|
-
shiftKey: e.shiftKey,
|
|
364
|
-
repeat: e.repeat
|
|
365
|
-
};
|
|
366
|
-
const keyResult = dispatch(keyEv);
|
|
367
|
-
const heldEv = {
|
|
368
|
-
kind: "key-held",
|
|
369
|
-
key: e.key,
|
|
370
|
-
phase: "down",
|
|
371
|
-
altKey: e.altKey,
|
|
372
|
-
ctrlKey: e.ctrlKey,
|
|
373
|
-
metaKey: e.metaKey,
|
|
374
|
-
shiftKey: e.shiftKey
|
|
375
|
-
};
|
|
376
|
-
const heldResult = dispatch(heldEv);
|
|
377
|
-
if (heldResult === "handled") {
|
|
378
|
-
heldKeys.set(physicalKey(e), e.key);
|
|
379
|
-
}
|
|
380
|
-
if (keyResult === "handled" || heldResult === "handled") {
|
|
381
|
-
e.preventDefault();
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
const onKeyUp = (e) => {
|
|
385
|
-
const physical = physicalKey(e);
|
|
386
|
-
const downKey = heldKeys.get(physical);
|
|
387
|
-
if (downKey === void 0) return;
|
|
388
|
-
const ev = {
|
|
389
|
-
kind: "key-held",
|
|
390
|
-
key: downKey,
|
|
391
|
-
phase: "up",
|
|
392
|
-
altKey: e.altKey,
|
|
393
|
-
ctrlKey: e.ctrlKey,
|
|
394
|
-
metaKey: e.metaKey,
|
|
395
|
-
shiftKey: e.shiftKey
|
|
396
|
-
};
|
|
397
|
-
dispatch(ev);
|
|
398
|
-
heldKeys.delete(physical);
|
|
399
|
-
};
|
|
845
|
+
const redraw = dispatcherWatch(() => {
|
|
846
|
+
requestRedrawRef.current?.();
|
|
847
|
+
});
|
|
848
|
+
redraw.sync(allDispatchers());
|
|
849
|
+
const core = createDispatchCore({
|
|
850
|
+
canvas,
|
|
851
|
+
root: () => rootTargetRef.current,
|
|
852
|
+
views: () => viewsRef.current,
|
|
853
|
+
context: () => ctxRef.current,
|
|
854
|
+
actions: () => registryRef.current,
|
|
855
|
+
uses: () => {
|
|
856
|
+
redraw.sync(allDispatchers());
|
|
857
|
+
},
|
|
858
|
+
disposed: () => disposed
|
|
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, {
|
|
865
|
+
longPress: () => longPressRef.current,
|
|
866
|
+
onClick: () => onClickRef.current,
|
|
867
|
+
onDoubleClick: () => onDoubleClickRef.current,
|
|
868
|
+
clicks: clicksRef.current,
|
|
869
|
+
hover
|
|
870
|
+
});
|
|
400
871
|
const onWindowBlur = () => {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const ev = {
|
|
405
|
-
kind: "key-held",
|
|
406
|
-
key,
|
|
407
|
-
phase: "up",
|
|
408
|
-
altKey: false,
|
|
409
|
-
ctrlKey: false,
|
|
410
|
-
metaKey: false,
|
|
411
|
-
shiftKey: false
|
|
412
|
-
};
|
|
413
|
-
dispatch(ev);
|
|
414
|
-
}
|
|
415
|
-
heldKeys.clear();
|
|
416
|
-
};
|
|
417
|
-
const onWheel = (e) => {
|
|
418
|
-
registryRef.current.activate();
|
|
419
|
-
if (pinchLive && e.ctrlKey) {
|
|
420
|
-
e.preventDefault();
|
|
421
|
-
e.stopPropagation();
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
routeAt(null, e.clientX, e.clientY);
|
|
425
|
-
const rect = canvas?.getBoundingClientRect();
|
|
426
|
-
const [localX, localY] = rect ? clientToCanvasRect(rect, e.clientX, e.clientY) : [e.clientX, e.clientY];
|
|
427
|
-
const hitPoint = toWorld(e.clientX, e.clientY);
|
|
428
|
-
const affordance = target().affordanceAt?.(hitPoint) ?? void 0;
|
|
429
|
-
const body = target().classifyTarget?.(hitPoint);
|
|
430
|
-
const ev = {
|
|
431
|
-
kind: "wheel",
|
|
432
|
-
altKey: e.altKey,
|
|
433
|
-
ctrlKey: e.ctrlKey,
|
|
434
|
-
metaKey: e.metaKey,
|
|
435
|
-
shiftKey: e.shiftKey,
|
|
436
|
-
deltaX: e.deltaX,
|
|
437
|
-
deltaY: e.deltaY,
|
|
438
|
-
clientX: localX,
|
|
439
|
-
clientY: localY,
|
|
440
|
-
...affordance !== void 0 ? { affordance } : {},
|
|
441
|
-
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
442
|
-
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
443
|
-
};
|
|
444
|
-
const result = dispatch(ev);
|
|
445
|
-
if (result === "handled") {
|
|
446
|
-
e.preventDefault();
|
|
447
|
-
e.stopImmediatePropagation();
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
const pinchEventOf = (e, scale, rotation) => {
|
|
451
|
-
const g = e;
|
|
452
|
-
const rect = canvas?.getBoundingClientRect();
|
|
453
|
-
const cx = typeof g.clientX === "number" ? g.clientX : rect ? rect.left + rect.width / 2 : 0;
|
|
454
|
-
const cy = typeof g.clientY === "number" ? g.clientY : rect ? rect.top + rect.height / 2 : 0;
|
|
455
|
-
routeAt(null, cx, cy);
|
|
456
|
-
const [localX, localY] = rect ? clientToCanvasRect(rect, cx, cy) : [cx, cy];
|
|
457
|
-
const hitPoint = toWorld(cx, cy);
|
|
458
|
-
const affordance = target().affordanceAt?.(hitPoint) ?? void 0;
|
|
459
|
-
const body = target().classifyTarget?.(hitPoint);
|
|
460
|
-
return {
|
|
461
|
-
kind: "pinch",
|
|
462
|
-
altKey: g.altKey === true,
|
|
463
|
-
ctrlKey: g.ctrlKey === true,
|
|
464
|
-
metaKey: g.metaKey === true,
|
|
465
|
-
shiftKey: g.shiftKey === true,
|
|
466
|
-
scale,
|
|
467
|
-
rotation,
|
|
468
|
-
clientX: localX,
|
|
469
|
-
clientY: localY,
|
|
470
|
-
...affordance !== void 0 ? { affordance } : {},
|
|
471
|
-
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
472
|
-
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
473
|
-
};
|
|
474
|
-
};
|
|
475
|
-
const onGestureStart = (e) => {
|
|
476
|
-
endPinch();
|
|
477
|
-
const probe = pinchEventOf(e, 1, 0);
|
|
478
|
-
if (dispatcherNow().resolveOnly(probe, ctxNow()) === null) return;
|
|
479
|
-
pinchLive = true;
|
|
480
|
-
e.preventDefault();
|
|
481
|
-
};
|
|
482
|
-
const onGestureChange = (e) => {
|
|
483
|
-
const g = e;
|
|
484
|
-
const scale = typeof g.scale === "number" && g.scale > 0 ? g.scale : pinchScale;
|
|
485
|
-
const rotation = typeof g.rotation === "number" ? g.rotation : pinchRotation;
|
|
486
|
-
const step = scale / pinchScale;
|
|
487
|
-
const turn = rotation - pinchRotation;
|
|
488
|
-
pinchScale = scale;
|
|
489
|
-
pinchRotation = rotation;
|
|
490
|
-
if (dispatch(pinchEventOf(e, step, turn)) === "handled") {
|
|
491
|
-
pinchLive = true;
|
|
492
|
-
e.preventDefault();
|
|
493
|
-
} else if (pinchLive) {
|
|
494
|
-
e.preventDefault();
|
|
495
|
-
}
|
|
496
|
-
};
|
|
497
|
-
const onGestureEnd = (e) => {
|
|
498
|
-
if (pinchLive) e.preventDefault();
|
|
499
|
-
endPinch();
|
|
500
|
-
};
|
|
501
|
-
let claimedDoubleClick = false;
|
|
502
|
-
const onNativeDoubleClick = (e) => {
|
|
503
|
-
if (!claimedDoubleClick) return;
|
|
504
|
-
claimedDoubleClick = false;
|
|
505
|
-
e.preventDefault();
|
|
506
|
-
};
|
|
507
|
-
const onPointerDown = (e) => {
|
|
508
|
-
claimedDoubleClick = false;
|
|
509
|
-
registryRef.current.activate();
|
|
510
|
-
if ((e.button ?? 0) !== 0) return;
|
|
511
|
-
if (!canvas) return;
|
|
512
|
-
if (isInControlWithin(e.target, canvas)) return;
|
|
513
|
-
routeDown(e.pointerId, e.clientX, e.clientY);
|
|
514
|
-
const w = toWorld(e.clientX, e.clientY);
|
|
515
|
-
const affordance = target().affordanceAt?.(w) ?? void 0;
|
|
516
|
-
const body = target().classifyTarget?.(w);
|
|
517
|
-
const bodyTarget = body?.body;
|
|
518
|
-
const bodyKind = body?.kind;
|
|
519
|
-
const ev = {
|
|
520
|
-
kind: "pointerdown",
|
|
521
|
-
target: e.target,
|
|
522
|
-
pointerId: e.pointerId,
|
|
523
|
-
x: w.x,
|
|
524
|
-
y: w.y,
|
|
525
|
-
clientX: e.clientX,
|
|
526
|
-
clientY: e.clientY,
|
|
527
|
-
altKey: e.altKey,
|
|
528
|
-
ctrlKey: e.ctrlKey,
|
|
529
|
-
metaKey: e.metaKey,
|
|
530
|
-
shiftKey: e.shiftKey,
|
|
531
|
-
...stylusOf(e),
|
|
532
|
-
...affordance !== void 0 ? { affordance } : {},
|
|
533
|
-
...bodyTarget !== void 0 ? { bodyTarget } : {},
|
|
534
|
-
...bodyKind !== void 0 ? { bodyKind } : {}
|
|
535
|
-
};
|
|
536
|
-
held.set(e.pointerId, {
|
|
537
|
-
session: openPointerSession(canvas, e, {
|
|
538
|
-
onMove: onPointerMove,
|
|
539
|
-
onEnd: onPointerUp,
|
|
540
|
-
onCancel: () => {
|
|
541
|
-
onPointerCancel(e.pointerId);
|
|
542
|
-
}
|
|
543
|
-
}),
|
|
544
|
-
pos: { x: e.clientX, y: e.clientY },
|
|
545
|
-
// Deferred until the first past-threshold pointermove: a bare
|
|
546
|
-
// press-and-release should fire `click`, not start a drag action.
|
|
547
|
-
buffered: { ev, clientX: e.clientX, clientY: e.clientY },
|
|
548
|
-
down: {
|
|
549
|
-
target: e.target,
|
|
550
|
-
clientX: e.clientX,
|
|
551
|
-
clientY: e.clientY,
|
|
552
|
-
worldX: w.x,
|
|
553
|
-
worldY: w.y,
|
|
554
|
-
affordance,
|
|
555
|
-
bodyTarget,
|
|
556
|
-
bodyKind,
|
|
557
|
-
altKey: e.altKey,
|
|
558
|
-
ctrlKey: e.ctrlKey,
|
|
559
|
-
metaKey: e.metaKey,
|
|
560
|
-
shiftKey: e.shiftKey
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
if (held.size < 2) dispatch({ ...ev, stage: "press" });
|
|
564
|
-
if ((e.pointerType === "touch" || e.pointerType === "pen") && held.size === 1) {
|
|
565
|
-
cancelLongPress(e.pointerId);
|
|
566
|
-
const duration = longPressRef.current?.duration ?? LONG_PRESS_MS;
|
|
567
|
-
longPressTimers.set(
|
|
568
|
-
e.pointerId,
|
|
569
|
-
setTimeout(() => {
|
|
570
|
-
longPressTimers.delete(e.pointerId);
|
|
571
|
-
if (disposed) return;
|
|
572
|
-
fireLongPress(e.pointerId, e.pointerType);
|
|
573
|
-
}, duration)
|
|
574
|
-
);
|
|
575
|
-
const store = longPressRef.current?.state;
|
|
576
|
-
const rec = held.get(e.pointerId);
|
|
577
|
-
if (store && rec) {
|
|
578
|
-
store.set({
|
|
579
|
-
pointerId: e.pointerId,
|
|
580
|
-
pointerType: e.pointerType,
|
|
581
|
-
client: { x: e.clientX, y: e.clientY },
|
|
582
|
-
local: toCanvasLocal({ x: e.clientX, y: e.clientY }),
|
|
583
|
-
world: { x: w.x, y: w.y },
|
|
584
|
-
viewId: target().id,
|
|
585
|
-
startedAt: performance.now(),
|
|
586
|
-
duration,
|
|
587
|
-
armed: longPressWouldFire(rec.down)
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
if (held.size >= 2) {
|
|
592
|
-
cancelAllLongPress();
|
|
593
|
-
for (const p of held.values()) p.buffered = null;
|
|
594
|
-
const { centroid, spread } = heldGeometry();
|
|
595
|
-
if (!multiTouchStart) {
|
|
596
|
-
multiTouchStart = { fingers: held.size, centroid: { ...centroid } };
|
|
597
|
-
} else {
|
|
598
|
-
multiTouchStart.fingers = held.size;
|
|
599
|
-
}
|
|
600
|
-
const mt = {
|
|
601
|
-
kind: "multitouch",
|
|
602
|
-
fingers: held.size,
|
|
603
|
-
altKey: e.altKey,
|
|
604
|
-
ctrlKey: e.ctrlKey,
|
|
605
|
-
metaKey: e.metaKey,
|
|
606
|
-
shiftKey: e.shiftKey,
|
|
607
|
-
centroid,
|
|
608
|
-
spread
|
|
609
|
-
};
|
|
610
|
-
dispatch(mt);
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
const onPointerMove = (e) => {
|
|
614
|
-
routeAt(e.pointerId, e.clientX, e.clientY);
|
|
615
|
-
const rec = held.get(e.pointerId);
|
|
616
|
-
if (rec) rec.pos = { x: e.clientX, y: e.clientY };
|
|
617
|
-
const buffered = rec?.buffered;
|
|
618
|
-
if (rec && buffered) {
|
|
619
|
-
if (pastDragThreshold(buffered, e)) {
|
|
620
|
-
rec.buffered = null;
|
|
621
|
-
cancelLongPress(e.pointerId);
|
|
622
|
-
dispatch(buffered.ev);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
const mtGestureId = `multitouch-${held.size}`;
|
|
626
|
-
if (held.size >= 2 && dispatcherNow().inFlight().has(mtGestureId)) {
|
|
627
|
-
const { centroid, spread } = heldGeometry();
|
|
628
|
-
const mtEv = {
|
|
629
|
-
kind: "multitouch",
|
|
630
|
-
fingers: held.size,
|
|
631
|
-
altKey: e.altKey,
|
|
632
|
-
ctrlKey: e.ctrlKey,
|
|
633
|
-
metaKey: e.metaKey,
|
|
634
|
-
shiftKey: e.shiftKey,
|
|
635
|
-
centroid,
|
|
636
|
-
spread
|
|
637
|
-
};
|
|
638
|
-
dispatch(mtEv);
|
|
639
|
-
}
|
|
640
|
-
const w = toWorld(e.clientX, e.clientY);
|
|
641
|
-
const ev = {
|
|
642
|
-
kind: "pointermove",
|
|
643
|
-
pointerId: e.pointerId,
|
|
644
|
-
x: w.x,
|
|
645
|
-
y: w.y,
|
|
646
|
-
clientX: e.clientX,
|
|
647
|
-
clientY: e.clientY,
|
|
648
|
-
altKey: e.altKey,
|
|
649
|
-
ctrlKey: e.ctrlKey,
|
|
650
|
-
metaKey: e.metaKey,
|
|
651
|
-
shiftKey: e.shiftKey,
|
|
652
|
-
...stylusOf(e)
|
|
653
|
-
};
|
|
654
|
-
dispatch(ev);
|
|
655
|
-
lastHover = {
|
|
656
|
-
clientX: e.clientX,
|
|
657
|
-
clientY: e.clientY,
|
|
658
|
-
altKey: e.altKey,
|
|
659
|
-
ctrlKey: e.ctrlKey,
|
|
660
|
-
metaKey: e.metaKey,
|
|
661
|
-
shiftKey: e.shiftKey
|
|
662
|
-
};
|
|
663
|
-
refreshHoverCursor();
|
|
664
|
-
};
|
|
665
|
-
let lastHover = null;
|
|
666
|
-
let cursorOverridden = false;
|
|
667
|
-
const clearHoverCursor = () => {
|
|
668
|
-
paintedCursorRef.current?.()?.setOverride(null);
|
|
669
|
-
if (cursorOverridden && canvas) {
|
|
670
|
-
canvas.style.cursor = "";
|
|
671
|
-
cursorOverridden = false;
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
const applyHoverCursor = (spec) => {
|
|
675
|
-
const tier = spec === null ? null : resolveCursorTier(spec);
|
|
676
|
-
if (!tier || !canvas) {
|
|
677
|
-
clearHoverCursor();
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const painted = tier.kind === "painted" ? tier : null;
|
|
681
|
-
paintedCursorRef.current?.()?.setOverride(painted);
|
|
682
|
-
const cursor = tier.kind === "css" ? tier.css : "none";
|
|
683
|
-
if (!cursor) {
|
|
684
|
-
clearHoverCursor();
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
canvas.style.cursor = cursor;
|
|
688
|
-
cursorOverridden = true;
|
|
689
|
-
};
|
|
690
|
-
function pushPaintedPointer() {
|
|
691
|
-
const painted = paintedCursorRef.current?.();
|
|
692
|
-
if (!painted?.active() || !canvas || !lastHover) return;
|
|
693
|
-
const r = canvas.getBoundingClientRect();
|
|
694
|
-
painted.setPointer(lastHover.clientX - r.left, lastHover.clientY - r.top);
|
|
695
|
-
}
|
|
696
|
-
function refreshHoverCursor() {
|
|
697
|
-
resolveHoverCursor();
|
|
698
|
-
pushPaintedPointer();
|
|
699
|
-
}
|
|
700
|
-
function resolveHoverCursor() {
|
|
701
|
-
if (!canvas || !lastHover) return;
|
|
702
|
-
if (held.size > 0 || dispatcherNow().inFlight().size > 0) {
|
|
703
|
-
applyHoverCursor(dispatcherNow().inFlightCursor());
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
const h = lastHover;
|
|
707
|
-
routeAt(null, h.clientX, h.clientY);
|
|
708
|
-
const w = toWorld(h.clientX, h.clientY);
|
|
709
|
-
const affordance = target().affordanceAt?.(w) ?? void 0;
|
|
710
|
-
if (affordance?.cursor) {
|
|
711
|
-
applyHoverCursor(affordance.cursor);
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
const hoverBody = target().classifyTarget?.(w);
|
|
715
|
-
const bodyTarget = hoverBody?.body;
|
|
716
|
-
const bodyKind = hoverBody?.kind;
|
|
717
|
-
const where = {
|
|
718
|
-
altKey: h.altKey,
|
|
719
|
-
ctrlKey: h.ctrlKey,
|
|
720
|
-
metaKey: h.metaKey,
|
|
721
|
-
shiftKey: h.shiftKey,
|
|
722
|
-
...affordance !== void 0 ? { affordance } : {},
|
|
723
|
-
...bodyTarget !== void 0 ? { bodyTarget } : {},
|
|
724
|
-
...bodyKind !== void 0 ? { bodyKind } : {}
|
|
725
|
-
};
|
|
726
|
-
const ctx = ctxNow();
|
|
727
|
-
const press = {
|
|
728
|
-
kind: "pointerdown",
|
|
729
|
-
x: w.x,
|
|
730
|
-
y: w.y,
|
|
731
|
-
clientX: h.clientX,
|
|
732
|
-
clientY: h.clientY,
|
|
733
|
-
...where
|
|
734
|
-
};
|
|
735
|
-
const d = dispatcherNow();
|
|
736
|
-
publishLiveDispatch(() => d.explain(press, ctx));
|
|
737
|
-
const dragCursor = d.resolveOnly(press, ctx)?.action.cursor;
|
|
738
|
-
const cursor = dragCursor ?? dispatcherNow().resolveOnly(
|
|
739
|
-
{ kind: "click", x: w.x, y: w.y, pressX: w.x, pressY: w.y, ...where },
|
|
740
|
-
ctx
|
|
741
|
-
)?.action.cursor;
|
|
742
|
-
applyHoverCursor(cursor ?? null);
|
|
743
|
-
}
|
|
744
|
-
const scheduleHoverCursorRefresh = (e) => {
|
|
745
|
-
if (!lastHover) return;
|
|
746
|
-
lastHover = {
|
|
747
|
-
...lastHover,
|
|
748
|
-
altKey: e.altKey,
|
|
749
|
-
ctrlKey: e.ctrlKey,
|
|
750
|
-
metaKey: e.metaKey,
|
|
751
|
-
shiftKey: e.shiftKey
|
|
752
|
-
};
|
|
753
|
-
setTimeout(() => {
|
|
754
|
-
if (!disposed) refreshHoverCursor();
|
|
755
|
-
}, 0);
|
|
756
|
-
};
|
|
757
|
-
const onHoverMove = (e) => {
|
|
758
|
-
if (held.has(e.pointerId)) return;
|
|
759
|
-
onPointerMove(e);
|
|
760
|
-
};
|
|
761
|
-
const onHoverPointerLeave = () => {
|
|
762
|
-
lastHover = null;
|
|
763
|
-
publishLiveDispatch(null);
|
|
764
|
-
paintedCursorRef.current?.()?.clearPointer();
|
|
765
|
-
clearHoverCursor();
|
|
766
|
-
};
|
|
767
|
-
const onPointerUp = (e) => {
|
|
768
|
-
routeAt(e.pointerId, e.clientX, e.clientY);
|
|
769
|
-
cancelLongPress(e.pointerId);
|
|
770
|
-
const prevSize = held.size;
|
|
771
|
-
const rec = held.get(e.pointerId);
|
|
772
|
-
held.delete(e.pointerId);
|
|
773
|
-
const staysUnderThreshold = rec?.buffered != null;
|
|
774
|
-
if (prevSize >= 2 && held.size < 2) {
|
|
775
|
-
const mtGestureId = `multitouch-${prevSize}`;
|
|
776
|
-
if (dispatcherNow().inFlight().has(mtGestureId)) {
|
|
777
|
-
dispatcherNow().cancelAll("commit");
|
|
778
|
-
}
|
|
779
|
-
const start = multiTouchStart;
|
|
780
|
-
if (start) {
|
|
781
|
-
const centroid = toCanvasLocal(
|
|
782
|
-
computeMultiTouchGeometry([
|
|
783
|
-
...[...held.values()].map((p) => p.pos),
|
|
784
|
-
{ x: e.clientX, y: e.clientY }
|
|
785
|
-
]).centroid
|
|
786
|
-
);
|
|
787
|
-
const dx = centroid.x - start.centroid.x;
|
|
788
|
-
const dy = centroid.y - start.centroid.y;
|
|
789
|
-
if (Math.hypot(dx, dy) <= TAP_THRESHOLD_PX) {
|
|
790
|
-
const tap = {
|
|
791
|
-
kind: "multitouchtap",
|
|
792
|
-
fingers: start.fingers,
|
|
793
|
-
altKey: e.altKey,
|
|
794
|
-
ctrlKey: e.ctrlKey,
|
|
795
|
-
metaKey: e.metaKey,
|
|
796
|
-
shiftKey: e.shiftKey
|
|
797
|
-
};
|
|
798
|
-
dispatch(tap);
|
|
799
|
-
}
|
|
800
|
-
multiTouchStart = null;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
const movedDuringDrag = rec ? rec.down.clientX !== e.clientX || rec.down.clientY !== e.clientY : true;
|
|
804
|
-
const hadDragInFlight = dispatcherNow().inFlight().has(pointerGestureId(e.pointerId)) && movedDuringDrag;
|
|
805
|
-
const wUp = toWorld(e.clientX, e.clientY);
|
|
806
|
-
const ev = {
|
|
807
|
-
kind: "pointerup",
|
|
808
|
-
pointerId: e.pointerId,
|
|
809
|
-
x: wUp.x,
|
|
810
|
-
y: wUp.y,
|
|
811
|
-
clientX: e.clientX,
|
|
812
|
-
clientY: e.clientY,
|
|
813
|
-
altKey: e.altKey,
|
|
814
|
-
ctrlKey: e.ctrlKey,
|
|
815
|
-
metaKey: e.metaKey,
|
|
816
|
-
shiftKey: e.shiftKey
|
|
817
|
-
};
|
|
818
|
-
dispatch(ev);
|
|
819
|
-
const down = rec?.down;
|
|
820
|
-
if (!hadDragInFlight && staysUnderThreshold && down) {
|
|
821
|
-
const wClick = toWorld(e.clientX, e.clientY);
|
|
822
|
-
const clickEv = {
|
|
823
|
-
kind: "click",
|
|
824
|
-
target: e.target,
|
|
825
|
-
altKey: e.altKey,
|
|
826
|
-
ctrlKey: e.ctrlKey,
|
|
827
|
-
metaKey: e.metaKey,
|
|
828
|
-
shiftKey: e.shiftKey,
|
|
829
|
-
x: wClick.x,
|
|
830
|
-
y: wClick.y,
|
|
831
|
-
clientX: e.clientX,
|
|
832
|
-
clientY: e.clientY,
|
|
833
|
-
pressX: down.worldX,
|
|
834
|
-
pressY: down.worldY,
|
|
835
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
836
|
-
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
837
|
-
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
838
|
-
};
|
|
839
|
-
dispatch(clickEv);
|
|
840
|
-
onClickRef.current?.({ x: wClick.x, y: wClick.y });
|
|
841
|
-
const now = Date.now();
|
|
842
|
-
const prev = lastClickRef.current;
|
|
843
|
-
const dx = e.clientX - (prev?.clientX ?? 0);
|
|
844
|
-
const dy = e.clientY - (prev?.clientY ?? 0);
|
|
845
|
-
if (prev && now - prev.t < DOUBLE_CLICK_MAX_MS && Math.hypot(dx, dy) < DOUBLE_CLICK_MAX_PX) {
|
|
846
|
-
const dblEv = {
|
|
847
|
-
kind: "doubleclick",
|
|
848
|
-
target: e.target,
|
|
849
|
-
altKey: e.altKey,
|
|
850
|
-
ctrlKey: e.ctrlKey,
|
|
851
|
-
metaKey: e.metaKey,
|
|
852
|
-
shiftKey: e.shiftKey,
|
|
853
|
-
x: wClick.x,
|
|
854
|
-
y: wClick.y,
|
|
855
|
-
clientX: e.clientX,
|
|
856
|
-
clientY: e.clientY,
|
|
857
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
858
|
-
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
859
|
-
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
860
|
-
};
|
|
861
|
-
onDoubleClickRef.current?.({ x: wClick.x, y: wClick.y });
|
|
862
|
-
const handled = dispatch(dblEv) === "handled";
|
|
863
|
-
claimedDoubleClick = handled || onDoubleClickRef.current !== void 0;
|
|
864
|
-
lastClickRef.current = null;
|
|
865
|
-
} else {
|
|
866
|
-
lastClickRef.current = { t: now, clientX: e.clientX, clientY: e.clientY };
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
routeRelease(e.pointerId);
|
|
870
|
-
lastHover = {
|
|
871
|
-
clientX: e.clientX,
|
|
872
|
-
clientY: e.clientY,
|
|
873
|
-
altKey: e.altKey,
|
|
874
|
-
ctrlKey: e.ctrlKey,
|
|
875
|
-
metaKey: e.metaKey,
|
|
876
|
-
shiftKey: e.shiftKey
|
|
877
|
-
};
|
|
878
|
-
setTimeout(() => {
|
|
879
|
-
if (!disposed) refreshHoverCursor();
|
|
880
|
-
}, 0);
|
|
881
|
-
};
|
|
882
|
-
const onPointerCancel = (pointerId) => {
|
|
883
|
-
if (disposed) return;
|
|
884
|
-
const rec = held.get(pointerId);
|
|
885
|
-
if (!rec) return;
|
|
886
|
-
held.delete(pointerId);
|
|
887
|
-
routeAt(pointerId, rec.pos.x, rec.pos.y);
|
|
888
|
-
cancelLongPress(pointerId);
|
|
889
|
-
multiTouchStart = null;
|
|
890
|
-
const ev = {
|
|
891
|
-
kind: "pointercancel",
|
|
892
|
-
pointerId,
|
|
893
|
-
altKey: rec.down.altKey,
|
|
894
|
-
ctrlKey: rec.down.ctrlKey,
|
|
895
|
-
metaKey: rec.down.metaKey,
|
|
896
|
-
shiftKey: rec.down.shiftKey
|
|
897
|
-
};
|
|
898
|
-
dispatch(ev);
|
|
899
|
-
routeRelease(pointerId);
|
|
900
|
-
};
|
|
901
|
-
const onContextMenu = (e) => {
|
|
902
|
-
e.preventDefault();
|
|
903
|
-
routeAt(null, e.clientX, e.clientY);
|
|
904
|
-
const w = toWorld(e.clientX, e.clientY);
|
|
905
|
-
const affordance = target().affordanceAt?.(w) ?? void 0;
|
|
906
|
-
const menuBody = target().classifyTarget?.(w);
|
|
907
|
-
const ev = {
|
|
908
|
-
kind: "contextmenu",
|
|
909
|
-
target: e.target,
|
|
910
|
-
altKey: e.altKey,
|
|
911
|
-
ctrlKey: e.ctrlKey,
|
|
912
|
-
metaKey: e.metaKey,
|
|
913
|
-
shiftKey: e.shiftKey,
|
|
914
|
-
x: w.x,
|
|
915
|
-
y: w.y,
|
|
916
|
-
clientX: e.clientX,
|
|
917
|
-
clientY: e.clientY,
|
|
918
|
-
...affordance !== void 0 ? { affordance } : {},
|
|
919
|
-
...menuBody?.body !== void 0 ? { bodyTarget: menuBody.body } : {},
|
|
920
|
-
...menuBody?.kind !== void 0 ? { bodyKind: menuBody.kind } : {}
|
|
921
|
-
};
|
|
922
|
-
dispatch(ev);
|
|
923
|
-
};
|
|
924
|
-
const onDragOver = (e) => {
|
|
925
|
-
if (!e.dataTransfer) return;
|
|
926
|
-
e.preventDefault();
|
|
927
|
-
e.dataTransfer.dropEffect = "copy";
|
|
928
|
-
canvas?.classList.add(DROPOVER_CLASS);
|
|
929
|
-
};
|
|
930
|
-
const onDragLeave = () => {
|
|
931
|
-
canvas?.classList.remove(DROPOVER_CLASS);
|
|
932
|
-
};
|
|
933
|
-
const onDrop = (e) => {
|
|
934
|
-
canvas?.classList.remove(DROPOVER_CLASS);
|
|
935
|
-
const dt = e.dataTransfer;
|
|
936
|
-
if (!dt) return;
|
|
937
|
-
e.preventDefault();
|
|
938
|
-
routeAt(null, e.clientX, e.clientY);
|
|
939
|
-
const dropOn = target().dispatcher;
|
|
940
|
-
const w = toWorld(e.clientX, e.clientY);
|
|
941
|
-
const base = {
|
|
942
|
-
altKey: e.altKey,
|
|
943
|
-
ctrlKey: e.ctrlKey,
|
|
944
|
-
metaKey: e.metaKey,
|
|
945
|
-
shiftKey: e.shiftKey
|
|
946
|
-
};
|
|
947
|
-
void itemsFromDataTransfer(dt).then((items) => {
|
|
948
|
-
if (disposed) return;
|
|
949
|
-
if (items.length === 0) return;
|
|
950
|
-
dispatch({
|
|
951
|
-
kind: "drop",
|
|
952
|
-
items,
|
|
953
|
-
x: w.x,
|
|
954
|
-
y: w.y,
|
|
955
|
-
clientX: e.clientX,
|
|
956
|
-
clientY: e.clientY,
|
|
957
|
-
...base
|
|
958
|
-
}, dropOn);
|
|
959
|
-
});
|
|
960
|
-
};
|
|
961
|
-
const onPaste = (e) => {
|
|
962
|
-
if (isEditableTarget(e.target)) return;
|
|
963
|
-
const cd = e.clipboardData;
|
|
964
|
-
if (!cd) return;
|
|
965
|
-
const items = itemsFromClipboardData(cd);
|
|
966
|
-
if (items.length === 0) return;
|
|
967
|
-
const result = dispatch({
|
|
968
|
-
kind: "paste",
|
|
969
|
-
items,
|
|
970
|
-
// ClipboardEvent carries no modifier state.
|
|
971
|
-
altKey: false,
|
|
972
|
-
ctrlKey: false,
|
|
973
|
-
metaKey: false,
|
|
974
|
-
shiftKey: false
|
|
975
|
-
});
|
|
976
|
-
if (result === "handled") e.preventDefault();
|
|
872
|
+
pinch.end();
|
|
873
|
+
pointer.cancelHeld();
|
|
874
|
+
keys.releaseAll();
|
|
977
875
|
};
|
|
876
|
+
const listeners = listenerSet();
|
|
978
877
|
const keyCapture = keyboard === "first";
|
|
979
878
|
if (keyboard) {
|
|
980
|
-
|
|
981
|
-
|
|
879
|
+
listeners.on(window, "keydown", keys.onKeyDown, keyCapture);
|
|
880
|
+
listeners.on(window, "keyup", keys.onKeyUp, keyCapture);
|
|
982
881
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
882
|
+
listeners.on(window, "blur", onWindowBlur);
|
|
883
|
+
listeners.on(canvas, "pointerdown", () => {
|
|
884
|
+
registryRef.current.activate();
|
|
885
|
+
});
|
|
986
886
|
if (pointerChannel) {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
canvas
|
|
990
|
-
canvas
|
|
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 });
|
|
991
896
|
}
|
|
992
|
-
if (wheelChannel) canvas?.addEventListener("wheel", onWheel, { passive: false, capture: true });
|
|
993
897
|
if (pinchChannel) {
|
|
994
|
-
canvas
|
|
995
|
-
canvas
|
|
996
|
-
canvas
|
|
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));
|
|
997
904
|
}
|
|
998
|
-
if (contextMenuChannel) canvas?.addEventListener("contextmenu", onContextMenu);
|
|
999
905
|
if (ingestChannel) {
|
|
1000
|
-
canvas
|
|
1001
|
-
canvas
|
|
1002
|
-
canvas
|
|
1003
|
-
canvas
|
|
1004
|
-
|
|
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);
|
|
1005
911
|
}
|
|
1006
912
|
return () => {
|
|
1007
|
-
|
|
1008
|
-
window.removeEventListener("keydown", onKeyDown, keyCapture);
|
|
1009
|
-
window.removeEventListener("keyup", onKeyUp, keyCapture);
|
|
1010
|
-
}
|
|
1011
|
-
window.removeEventListener("blur", onWindowBlur);
|
|
1012
|
-
window.removeEventListener("keydown", scheduleHoverCursorRefresh);
|
|
1013
|
-
window.removeEventListener("keyup", scheduleHoverCursorRefresh);
|
|
1014
|
-
canvas?.removeEventListener("pointerleave", onHoverPointerLeave);
|
|
1015
|
-
canvas?.removeEventListener("wheel", onWheel, { capture: true });
|
|
1016
|
-
canvas?.removeEventListener("gesturestart", onGestureStart);
|
|
1017
|
-
canvas?.removeEventListener("gesturechange", onGestureChange);
|
|
1018
|
-
canvas?.removeEventListener("gestureend", onGestureEnd);
|
|
1019
|
-
canvas?.removeEventListener("pointerdown", onPointerDown);
|
|
1020
|
-
canvas?.removeEventListener("pointermove", onHoverMove);
|
|
1021
|
-
canvas?.removeEventListener("dblclick", onNativeDoubleClick);
|
|
1022
|
-
canvas?.removeEventListener("contextmenu", onContextMenu);
|
|
1023
|
-
canvas?.removeEventListener("dragenter", onDragOver);
|
|
1024
|
-
canvas?.removeEventListener("dragover", onDragOver);
|
|
1025
|
-
canvas?.removeEventListener("dragleave", onDragLeave);
|
|
1026
|
-
canvas?.removeEventListener("drop", onDrop);
|
|
1027
|
-
window.removeEventListener("paste", onPaste);
|
|
913
|
+
listeners.removeAll();
|
|
1028
914
|
canvas?.classList.remove(DROPOVER_CLASS);
|
|
1029
|
-
|
|
1030
|
-
|
|
915
|
+
registryRef.current.dropKeys();
|
|
916
|
+
hover.clear();
|
|
1031
917
|
disposed = true;
|
|
1032
|
-
|
|
1033
|
-
|
|
918
|
+
pointer.dispose();
|
|
919
|
+
redraw.stop();
|
|
1034
920
|
for (const d of allDispatchers()) d.cancelAll("cancel");
|
|
1035
921
|
};
|
|
1036
922
|
}, [
|
|
@@ -1047,7 +933,7 @@ function useGestureDispatcher(opts) {
|
|
|
1047
933
|
longPressRef,
|
|
1048
934
|
onClickRef,
|
|
1049
935
|
onDoubleClickRef,
|
|
1050
|
-
|
|
936
|
+
cursorRef,
|
|
1051
937
|
registryRef,
|
|
1052
938
|
requestRedrawRef,
|
|
1053
939
|
rootTargetRef,
|
|
@@ -1118,6 +1004,7 @@ var ActionsNode = class extends ScopeNode {
|
|
|
1118
1004
|
return null;
|
|
1119
1005
|
}
|
|
1120
1006
|
activate() {
|
|
1007
|
+
keyHolder = this;
|
|
1121
1008
|
super.activate();
|
|
1122
1009
|
for (const f of this.onActivate) f();
|
|
1123
1010
|
}
|
|
@@ -1138,7 +1025,11 @@ var ActionsNode = class extends ScopeNode {
|
|
|
1138
1025
|
const i = cur.lastIndexOf(action);
|
|
1139
1026
|
if (i === -1) return;
|
|
1140
1027
|
cur.splice(i, 1);
|
|
1141
|
-
if (cur.length === 0)
|
|
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
|
+
}
|
|
1142
1033
|
this.changed();
|
|
1143
1034
|
};
|
|
1144
1035
|
},
|
|
@@ -1163,22 +1054,17 @@ var ActionsNode = class extends ScopeNode {
|
|
|
1163
1054
|
trigger: (id, params) => {
|
|
1164
1055
|
const leaf = this.start();
|
|
1165
1056
|
const a = leaf.resolve(id);
|
|
1166
|
-
if (!a) return false;
|
|
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;
|
|
1167
1060
|
try {
|
|
1168
|
-
|
|
1169
|
-
a.invoker.run(depsFor(a, leaf.deps()), params);
|
|
1170
|
-
}
|
|
1061
|
+
a.invoker.run(deps, params);
|
|
1171
1062
|
} catch (err) {
|
|
1172
1063
|
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
1173
1064
|
}
|
|
1174
1065
|
return true;
|
|
1175
1066
|
},
|
|
1176
|
-
subscribe: (listener) =>
|
|
1177
|
-
this.tree.listeners.add(listener);
|
|
1178
|
-
return () => {
|
|
1179
|
-
this.tree.listeners.delete(listener);
|
|
1180
|
-
};
|
|
1181
|
-
},
|
|
1067
|
+
subscribe: (listener) => this.tree.listeners.subscribe(listener),
|
|
1182
1068
|
setDispatcher: (d) => pushOwner(this.dispatchers, d),
|
|
1183
1069
|
begin: (id, params) => {
|
|
1184
1070
|
const leaf = this.start();
|
|
@@ -1188,9 +1074,14 @@ var ActionsNode = class extends ScopeNode {
|
|
|
1188
1074
|
return disp.beginUiOngoing(id, depsFor(a, leaf.deps()), params);
|
|
1189
1075
|
},
|
|
1190
1076
|
activate: () => this.activate(),
|
|
1191
|
-
isActive: () => this.isActive()
|
|
1077
|
+
isActive: () => this.isActive(),
|
|
1078
|
+
holdsKeys: () => keyHolder === this,
|
|
1079
|
+
dropKeys: () => {
|
|
1080
|
+
if (keyHolder === this) keyHolder = null;
|
|
1081
|
+
}
|
|
1192
1082
|
};
|
|
1193
1083
|
};
|
|
1084
|
+
var keyHolder = null;
|
|
1194
1085
|
var NODES = /* @__PURE__ */ new WeakMap();
|
|
1195
1086
|
function actionsNodeOf(registry) {
|
|
1196
1087
|
return registry ? NODES.get(registry) ?? null : null;
|
|
@@ -1271,16 +1162,14 @@ function useOngoingAction(actionId) {
|
|
|
1271
1162
|
ctrlRef.current = null;
|
|
1272
1163
|
ctrl?.end(reason);
|
|
1273
1164
|
};
|
|
1165
|
+
const drive = (params) => {
|
|
1166
|
+
if (ctrlRef.current?.active) ctrlRef.current.update(params);
|
|
1167
|
+
else ctrlRef.current = regRef.current?.begin(idRef.current, params) ?? null;
|
|
1168
|
+
};
|
|
1274
1169
|
return {
|
|
1275
|
-
input
|
|
1276
|
-
if (ctrlRef.current) ctrlRef.current.update(params);
|
|
1277
|
-
else ctrlRef.current = regRef.current?.begin(idRef.current, params) ?? null;
|
|
1278
|
-
},
|
|
1170
|
+
input: drive,
|
|
1279
1171
|
commit(params) {
|
|
1280
|
-
if (params !== void 0)
|
|
1281
|
-
if (ctrlRef.current) ctrlRef.current.update(params);
|
|
1282
|
-
else ctrlRef.current = regRef.current?.begin(idRef.current, params) ?? null;
|
|
1283
|
-
}
|
|
1172
|
+
if (params !== void 0) drive(params);
|
|
1284
1173
|
end("commit");
|
|
1285
1174
|
},
|
|
1286
1175
|
cancel() {
|
|
@@ -1291,6 +1180,98 @@ function useOngoingAction(actionId) {
|
|
|
1291
1180
|
useEffect(() => () => edit.commit(), [edit, actionId]);
|
|
1292
1181
|
return edit;
|
|
1293
1182
|
}
|
|
1183
|
+
function samePos(a, b) {
|
|
1184
|
+
if (a === null || b === null) return a === b;
|
|
1185
|
+
return a.worldX === b.worldX && a.worldY === b.worldY && a.viewId === b.viewId;
|
|
1186
|
+
}
|
|
1187
|
+
function createPointerStore() {
|
|
1188
|
+
let current = null;
|
|
1189
|
+
let version = 0;
|
|
1190
|
+
const changed = createEmitter();
|
|
1191
|
+
const get = () => current;
|
|
1192
|
+
return {
|
|
1193
|
+
get,
|
|
1194
|
+
set(next) {
|
|
1195
|
+
if (samePos(current, next)) return;
|
|
1196
|
+
current = next;
|
|
1197
|
+
version++;
|
|
1198
|
+
changed.emit();
|
|
1199
|
+
},
|
|
1200
|
+
subscribe: changed.subscribe,
|
|
1201
|
+
getVersion: () => version,
|
|
1202
|
+
getDropPoint: get
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
var PointerContext = createContext(null);
|
|
1206
|
+
function PointerContextProvider({ children, store }) {
|
|
1207
|
+
const [own] = useState(createPointerStore);
|
|
1208
|
+
return /* @__PURE__ */ jsx(PointerContext.Provider, { value: store ?? own, children });
|
|
1209
|
+
}
|
|
1210
|
+
function usePointerContext() {
|
|
1211
|
+
return useContext(PointerContext);
|
|
1212
|
+
}
|
|
1213
|
+
var noSubscribe = () => () => {
|
|
1214
|
+
};
|
|
1215
|
+
var noPointer = () => null;
|
|
1216
|
+
function usePointerPosition() {
|
|
1217
|
+
const store = usePointerContext();
|
|
1218
|
+
return useSyncExternalStore(
|
|
1219
|
+
store ? store.subscribe : noSubscribe,
|
|
1220
|
+
store ? store.get : noPointer,
|
|
1221
|
+
store ? store.get : noPointer
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
var SelectionContext = createContext(null);
|
|
1225
|
+
function SelectionContextProvider({ children }) {
|
|
1226
|
+
const [state, setState] = useState({ selection: [], kinds: void 0 });
|
|
1227
|
+
const lastSerializedRef = useRef("");
|
|
1228
|
+
const publishSelection = useCallback((ids, kinds) => {
|
|
1229
|
+
const next = JSON.stringify([ids, kinds]);
|
|
1230
|
+
if (next === lastSerializedRef.current) return;
|
|
1231
|
+
lastSerializedRef.current = next;
|
|
1232
|
+
setState({ selection: ids, kinds });
|
|
1233
|
+
}, []);
|
|
1234
|
+
const value = useMemo(
|
|
1235
|
+
() => ({
|
|
1236
|
+
selection: state.selection,
|
|
1237
|
+
...state.kinds !== void 0 ? { kinds: state.kinds } : {},
|
|
1238
|
+
publishSelection
|
|
1239
|
+
}),
|
|
1240
|
+
[state, publishSelection]
|
|
1241
|
+
);
|
|
1242
|
+
return /* @__PURE__ */ jsx(SelectionContext.Provider, { value, children });
|
|
1243
|
+
}
|
|
1244
|
+
function useSelectionContext() {
|
|
1245
|
+
return useContext(SelectionContext);
|
|
1246
|
+
}
|
|
1247
|
+
function SelectionContextProviderIfRoot({
|
|
1248
|
+
children
|
|
1249
|
+
}) {
|
|
1250
|
+
const parent = useContext(SelectionContext);
|
|
1251
|
+
if (parent !== null) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
1252
|
+
return /* @__PURE__ */ jsx(SelectionContextProvider, { children });
|
|
1253
|
+
}
|
|
1254
|
+
function usePublishSelection(ids, kinds) {
|
|
1255
|
+
const ctx = useContext(SelectionContext);
|
|
1256
|
+
const idsRef = useLatest(ids);
|
|
1257
|
+
const kindsRef = useLatest(kinds);
|
|
1258
|
+
const serialized = JSON.stringify([ids, kinds]);
|
|
1259
|
+
const publish = ctx?.publishSelection;
|
|
1260
|
+
useEffect(() => {
|
|
1261
|
+
if (!publish) return;
|
|
1262
|
+
publish(idsRef.current, kindsRef.current);
|
|
1263
|
+
}, [publish, serialized, idsRef, kindsRef]);
|
|
1264
|
+
const ctxRef = useLatest(ctx);
|
|
1265
|
+
const serializedRef = useLatest(serialized);
|
|
1266
|
+
useEffect(() => () => {
|
|
1267
|
+
const slot = ctxRef.current;
|
|
1268
|
+
if (slot && JSON.stringify([slot.selection, slot.kinds]) === serializedRef.current) {
|
|
1269
|
+
slot.publishSelection([]);
|
|
1270
|
+
}
|
|
1271
|
+
}, [ctxRef, serializedRef]);
|
|
1272
|
+
}
|
|
1273
|
+
var NO_MODES = () => () => {
|
|
1274
|
+
};
|
|
1294
1275
|
function useContributions(opts) {
|
|
1295
1276
|
const ctx = useActiveToolContext();
|
|
1296
1277
|
const actionsRegistry = useActionsRegistry();
|
|
@@ -1309,25 +1290,18 @@ function useContributions(opts) {
|
|
|
1309
1290
|
const setFocused = useCallback((id) => {
|
|
1310
1291
|
ctx.setActive(id);
|
|
1311
1292
|
}, [ctx]);
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
state: { focusedId: focused, engagedIds }
|
|
1325
|
-
};
|
|
1326
|
-
}, [entries, focused, engaged]);
|
|
1327
|
-
const scopedBindings = useCallback(() => {
|
|
1328
|
-
const { ordered, state } = snapshot();
|
|
1329
|
-
return scopeBindings(ordered, state);
|
|
1330
|
-
}, [snapshot]);
|
|
1293
|
+
const modes = opts.modes;
|
|
1294
|
+
const modeVersion = useSyncExternalStore(
|
|
1295
|
+
modes ? modes.subscribe : NO_MODES,
|
|
1296
|
+
() => modes?.getVersion() ?? 0
|
|
1297
|
+
);
|
|
1298
|
+
const snapshot = useCallback(() => assembleEntries(entries, {
|
|
1299
|
+
focusedId: focused,
|
|
1300
|
+
hotkeyStack: engaged,
|
|
1301
|
+
...modes ? { allowedCapabilities: activeModeOf(modes.current()).allowedCapabilities } : {}
|
|
1302
|
+
// The version is what changes; `modes` is one object for the registry's life.
|
|
1303
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1304
|
+
}), [entries, focused, engaged, modes, modeVersion]);
|
|
1331
1305
|
const overlays = useCallback((position = "top") => {
|
|
1332
1306
|
const { ordered, state } = snapshot();
|
|
1333
1307
|
const active = [];
|
|
@@ -1367,8 +1341,8 @@ function useContributions(opts) {
|
|
|
1367
1341
|
});
|
|
1368
1342
|
}, [entrySig, entries, actionsRegistry, modesRegistry]);
|
|
1369
1343
|
return useMemo(
|
|
1370
|
-
() => ({ entries, focused, setFocused,
|
|
1371
|
-
[entries, focused, setFocused,
|
|
1344
|
+
() => ({ entries, focused, setFocused, overlays }),
|
|
1345
|
+
[entries, focused, setFocused, overlays]
|
|
1372
1346
|
);
|
|
1373
1347
|
}
|
|
1374
1348
|
function useOffhandAction(entries) {
|
|
@@ -1485,6 +1459,6 @@ function useTools(opts) {
|
|
|
1485
1459
|
);
|
|
1486
1460
|
}
|
|
1487
1461
|
|
|
1488
|
-
export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, InputScope, Yoke, useAction, useActionsRegistry, useActiveToolContext, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useOffhandAction, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, useTools, useYoke };
|
|
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 };
|
|
1489
1463
|
//# sourceMappingURL=react.js.map
|
|
1490
1464
|
//# sourceMappingURL=react.js.map
|