@weasel-js/routing 1.9.4 → 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/dist/{chunk-Y5RURZBH.js → chunk-4DR6AJMN.js} +979 -860
- package/dist/chunk-4DR6AJMN.js.map +1 -0
- package/dist/{index-gArM2tbt.d.ts → index-Bm2QEV09.d.ts} +286 -592
- package/dist/index.d.ts +3 -3
- package/dist/index.js +14 -64
- package/dist/index.js.map +1 -1
- package/dist/{internal-B0SsznMG.d.ts → internal-D0rDmi7t.d.ts} +38 -67
- package/dist/internal.d.ts +3 -3
- package/dist/react.d.ts +119 -34
- package/dist/react.js +590 -727
- package/dist/react.js.map +1 -1
- package/package.json +9 -8
- package/dist/chunk-Y5RURZBH.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,8 +111,93 @@ function useDepSource(name, source) {
|
|
|
115
111
|
return registry.register(name, () => sourceRef.current());
|
|
116
112
|
}, [name, registry, sourceRef]);
|
|
117
113
|
}
|
|
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
|
+
}
|
|
118
146
|
|
|
119
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
|
+
});
|
|
164
|
+
return {
|
|
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
|
|
199
|
+
};
|
|
200
|
+
}
|
|
120
201
|
function withViewDeps(base, overlay) {
|
|
121
202
|
return {
|
|
122
203
|
register: (name, source) => base().register(name, source),
|
|
@@ -128,44 +209,171 @@ function withViewDeps(base, overlay) {
|
|
|
128
209
|
};
|
|
129
210
|
}
|
|
130
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
|
+
|
|
131
250
|
// src/interactions/dispatcher/channels/contextMenu.ts
|
|
132
|
-
function contextMenuInput(core) {
|
|
251
|
+
function contextMenuInput(core, takeNativeHold) {
|
|
133
252
|
const { routeAt, toWorld, target, dispatch } = core;
|
|
134
253
|
const onContextMenu = (e) => {
|
|
135
254
|
e.preventDefault();
|
|
255
|
+
if (takeNativeHold()) return;
|
|
136
256
|
routeAt(null, e.clientX, e.clientY);
|
|
137
257
|
const w = toWorld(e.clientX, e.clientY);
|
|
138
|
-
const affordance = target().affordanceAt?.(w) ?? void 0;
|
|
139
|
-
const menuBody = target().classifyTarget?.(w);
|
|
140
258
|
const ev = {
|
|
141
259
|
kind: "contextmenu",
|
|
142
260
|
target: e.target,
|
|
143
|
-
altKey: e.altKey,
|
|
144
|
-
ctrlKey: e.ctrlKey,
|
|
145
|
-
metaKey: e.metaKey,
|
|
146
|
-
shiftKey: e.shiftKey,
|
|
147
261
|
x: w.x,
|
|
148
262
|
y: w.y,
|
|
149
263
|
clientX: e.clientX,
|
|
150
264
|
clientY: e.clientY,
|
|
151
|
-
...
|
|
152
|
-
...menuBody?.body !== void 0 ? { bodyTarget: menuBody.body } : {},
|
|
153
|
-
...menuBody?.kind !== void 0 ? { bodyKind: menuBody.kind } : {}
|
|
265
|
+
...underPointer(target(), e, w)
|
|
154
266
|
};
|
|
155
267
|
dispatch(ev);
|
|
156
268
|
};
|
|
157
269
|
return onContextMenu;
|
|
158
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
|
+
}
|
|
159
351
|
|
|
160
352
|
// src/interactions/dispatcher/channels/ingest.ts
|
|
161
353
|
var DROPOVER_CLASS = "weasel-dropover";
|
|
162
354
|
function ingestInput(core) {
|
|
163
355
|
const { canvas, routeAt, toWorld, dispatch } = core;
|
|
164
356
|
const onDragOver = (e) => {
|
|
165
|
-
|
|
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;
|
|
166
375
|
e.preventDefault();
|
|
167
|
-
|
|
168
|
-
canvas?.classList.add(DROPOVER_CLASS);
|
|
376
|
+
dt.dropEffect = "copy";
|
|
169
377
|
};
|
|
170
378
|
const onDragLeave = () => {
|
|
171
379
|
canvas?.classList.remove(DROPOVER_CLASS);
|
|
@@ -226,24 +434,32 @@ function ingestInput(core) {
|
|
|
226
434
|
}
|
|
227
435
|
|
|
228
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
|
+
}
|
|
229
442
|
function keyboardInput(core) {
|
|
230
443
|
const { dispatch, dispatcherNow } = core;
|
|
231
444
|
const heldKeys = /* @__PURE__ */ new Map();
|
|
232
445
|
const codeOf = (e) => e.code ? { code: e.code } : {};
|
|
233
446
|
const physicalKey = (e) => e.code || e.key.toLowerCase();
|
|
234
447
|
const onKeyDown = (e) => {
|
|
235
|
-
if (e.defaultPrevented || isEditableTarget(e.target) ||
|
|
448
|
+
if (e.defaultPrevented || isEditableTarget(e.target) || focusedControlOwnsKey(e)) return;
|
|
236
449
|
if (!core.holdsKeys()) return;
|
|
237
450
|
if (e.repeat && heldKeys.has(physicalKey(e))) {
|
|
238
451
|
e.preventDefault();
|
|
239
452
|
return;
|
|
240
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) => {
|
|
241
458
|
const keyEv = {
|
|
242
459
|
kind: "key",
|
|
243
460
|
key: e.key,
|
|
244
461
|
...codeOf(e),
|
|
245
|
-
|
|
246
|
-
ctrlKey: e.ctrlKey,
|
|
462
|
+
...mods,
|
|
247
463
|
metaKey: e.metaKey,
|
|
248
464
|
shiftKey: e.shiftKey,
|
|
249
465
|
repeat: e.repeat
|
|
@@ -254,8 +470,7 @@ function keyboardInput(core) {
|
|
|
254
470
|
key: e.key,
|
|
255
471
|
...codeOf(e),
|
|
256
472
|
phase: "down",
|
|
257
|
-
|
|
258
|
-
ctrlKey: e.ctrlKey,
|
|
473
|
+
...mods,
|
|
259
474
|
metaKey: e.metaKey,
|
|
260
475
|
shiftKey: e.shiftKey
|
|
261
476
|
};
|
|
@@ -264,9 +479,9 @@ function keyboardInput(core) {
|
|
|
264
479
|
if (heldResult === "handled") {
|
|
265
480
|
heldKeys.set(physicalKey(e), { key: e.key, ...codeOf(e), on: heldOn });
|
|
266
481
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
482
|
+
const handled = keyResult === "handled" || heldResult === "handled";
|
|
483
|
+
if (handled) e.preventDefault();
|
|
484
|
+
return handled;
|
|
270
485
|
};
|
|
271
486
|
const onKeyUp = (e) => {
|
|
272
487
|
const physical = physicalKey(e);
|
|
@@ -305,6 +520,176 @@ function keyboardInput(core) {
|
|
|
305
520
|
return { onKeyDown, onKeyUp, releaseAll };
|
|
306
521
|
}
|
|
307
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
|
+
|
|
308
693
|
// src/interactions/dispatcher/channels/webkitPinch.ts
|
|
309
694
|
function webkitPinchInput(core) {
|
|
310
695
|
const { canvas, routeAt, toWorld, target, dispatch, dispatcherNow, ctxNow } = core;
|
|
@@ -318,27 +703,25 @@ function webkitPinchInput(core) {
|
|
|
318
703
|
};
|
|
319
704
|
const pinchEventOf = (e, scale, rotation) => {
|
|
320
705
|
const g = e;
|
|
321
|
-
const rect = canvas?.getBoundingClientRect();
|
|
706
|
+
const rect = typeof g.clientX === "number" && typeof g.clientY === "number" ? void 0 : canvas?.getBoundingClientRect();
|
|
322
707
|
const cx = typeof g.clientX === "number" ? g.clientX : rect ? rect.left + rect.width / 2 : 0;
|
|
323
708
|
const cy = typeof g.clientY === "number" ? g.clientY : rect ? rect.top + rect.height / 2 : 0;
|
|
324
709
|
routeAt(null, cx, cy);
|
|
325
|
-
const
|
|
710
|
+
const local = core.toCanvasLocal(cx, cy);
|
|
326
711
|
const hitPoint = toWorld(cx, cy);
|
|
327
|
-
const
|
|
328
|
-
const body = target().classifyTarget?.(hitPoint);
|
|
329
|
-
return {
|
|
330
|
-
kind: "pinch",
|
|
712
|
+
const mods = {
|
|
331
713
|
altKey: g.altKey === true,
|
|
332
714
|
ctrlKey: g.ctrlKey === true,
|
|
333
715
|
metaKey: g.metaKey === true,
|
|
334
|
-
shiftKey: g.shiftKey === true
|
|
716
|
+
shiftKey: g.shiftKey === true
|
|
717
|
+
};
|
|
718
|
+
return {
|
|
719
|
+
kind: "pinch",
|
|
335
720
|
scale,
|
|
336
721
|
rotation,
|
|
337
|
-
clientX:
|
|
338
|
-
clientY:
|
|
339
|
-
...
|
|
340
|
-
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
341
|
-
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
722
|
+
clientX: local.x,
|
|
723
|
+
clientY: local.y,
|
|
724
|
+
...underPointer(target(), mods, hitPoint)
|
|
342
725
|
};
|
|
343
726
|
};
|
|
344
727
|
const onGestureStart = (e) => {
|
|
@@ -372,7 +755,7 @@ function webkitPinchInput(core) {
|
|
|
372
755
|
|
|
373
756
|
// src/interactions/dispatcher/channels/wheel.ts
|
|
374
757
|
function wheelInput(core, pinchLive) {
|
|
375
|
-
const {
|
|
758
|
+
const { routeAt, toWorld, target, dispatch } = core;
|
|
376
759
|
const onWheel = (e) => {
|
|
377
760
|
core.activate();
|
|
378
761
|
if (pinchLive() && e.ctrlKey) {
|
|
@@ -381,24 +764,15 @@ function wheelInput(core, pinchLive) {
|
|
|
381
764
|
return;
|
|
382
765
|
}
|
|
383
766
|
routeAt(null, e.clientX, e.clientY);
|
|
384
|
-
const
|
|
385
|
-
const [localX, localY] = rect ? clientToCanvasRect(rect, e.clientX, e.clientY) : [e.clientX, e.clientY];
|
|
767
|
+
const local = core.toCanvasLocal(e.clientX, e.clientY);
|
|
386
768
|
const hitPoint = toWorld(e.clientX, e.clientY);
|
|
387
|
-
const affordance = target().affordanceAt?.(hitPoint) ?? void 0;
|
|
388
|
-
const body = target().classifyTarget?.(hitPoint);
|
|
389
769
|
const ev = {
|
|
390
770
|
kind: "wheel",
|
|
391
|
-
altKey: e.altKey,
|
|
392
|
-
ctrlKey: e.ctrlKey,
|
|
393
|
-
metaKey: e.metaKey,
|
|
394
|
-
shiftKey: e.shiftKey,
|
|
395
771
|
deltaX: e.deltaX,
|
|
396
772
|
deltaY: e.deltaY,
|
|
397
|
-
clientX:
|
|
398
|
-
clientY:
|
|
399
|
-
...
|
|
400
|
-
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
401
|
-
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
773
|
+
clientX: local.x,
|
|
774
|
+
clientY: local.y,
|
|
775
|
+
...underPointer(target(), e, hitPoint)
|
|
402
776
|
};
|
|
403
777
|
const result = dispatch(ev);
|
|
404
778
|
if (result === "handled") {
|
|
@@ -410,33 +784,8 @@ function wheelInput(core, pinchLive) {
|
|
|
410
784
|
}
|
|
411
785
|
|
|
412
786
|
// src/interactions/dispatcher/useGestureDispatcher.tsx
|
|
413
|
-
function stylusOf(e) {
|
|
414
|
-
return {
|
|
415
|
-
...typeof e.pressure === "number" ? { pressure: e.pressure } : {},
|
|
416
|
-
...typeof e.tiltX === "number" ? { tiltX: e.tiltX } : {},
|
|
417
|
-
...typeof e.tiltY === "number" ? { tiltY: e.tiltY } : {}
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
var IS_MAC = isApplePlatform();
|
|
421
|
-
function computeMultiTouchGeometry(positions) {
|
|
422
|
-
const pts = [...positions];
|
|
423
|
-
if (pts.length < 2) {
|
|
424
|
-
return { centroid: { x: 0, y: 0 }, spread: 0 };
|
|
425
|
-
}
|
|
426
|
-
let sumX = 0;
|
|
427
|
-
let sumY = 0;
|
|
428
|
-
for (const p of pts) {
|
|
429
|
-
sumX += p.x;
|
|
430
|
-
sumY += p.y;
|
|
431
|
-
}
|
|
432
|
-
const centroid = { x: sumX / pts.length, y: sumY / pts.length };
|
|
433
|
-
const dx = pts[1].x - pts[0].x;
|
|
434
|
-
const dy = pts[1].y - pts[0].y;
|
|
435
|
-
const spread = Math.sqrt(dx * dx + dy * dy);
|
|
436
|
-
return { centroid, spread };
|
|
437
|
-
}
|
|
438
787
|
function useGestureDispatcher(opts) {
|
|
439
|
-
const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, requestRedraw, paintedCursor, getRuleCtx, onClick, onDoubleClick, views, channels, longPress, claimKeys = false } = 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;
|
|
440
789
|
const pointerChannel = channels?.pointer !== false;
|
|
441
790
|
const wheelChannel = channels?.wheel !== false;
|
|
442
791
|
const pinchChannel = channels?.pinch !== false;
|
|
@@ -446,6 +795,7 @@ function useGestureDispatcher(opts) {
|
|
|
446
795
|
const onDoubleClickRef = useLatest(onDoubleClick);
|
|
447
796
|
const activeTool = useActiveToolContext();
|
|
448
797
|
const depRegistry = useDepRegistry();
|
|
798
|
+
const [isMac] = useState(isApplePlatform);
|
|
449
799
|
const dispatcherRef = useRef(null);
|
|
450
800
|
if (!dispatcherRef.current) {
|
|
451
801
|
dispatcherRef.current = dispatcherOpt ?? createDispatcher();
|
|
@@ -457,7 +807,7 @@ function useGestureDispatcher(opts) {
|
|
|
457
807
|
activeToolId: activeTool.active,
|
|
458
808
|
hotkeyStack: activeTool.hotkeyStack,
|
|
459
809
|
entriesById,
|
|
460
|
-
isMac
|
|
810
|
+
isMac
|
|
461
811
|
});
|
|
462
812
|
const rootTargetRef = useLatest({
|
|
463
813
|
id: null,
|
|
@@ -473,674 +823,100 @@ function useGestureDispatcher(opts) {
|
|
|
473
823
|
...(viewsRef.current?.targets() ?? []).map((t) => t.dispatcher)
|
|
474
824
|
], [rootTargetRef, viewsRef]);
|
|
475
825
|
const requestRedrawRef = useLatest(requestRedraw);
|
|
476
|
-
const
|
|
826
|
+
const cursorRef = useLatest({ painted: paintedCursor, sink: cursorSink });
|
|
477
827
|
const longPressRef = useLatest(longPress);
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const tool = entriesByIdRef.current.get(id);
|
|
486
|
-
if (!tool || !isTool(tool)) {
|
|
487
|
-
scratches.delete(id);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
if (hook === "onActivate" || !scratches.has(id)) scratches.set(id, tool.initScratch?.());
|
|
491
|
-
const scratch = scratches.get(id);
|
|
492
|
-
if (hook === "onDeactivate") scratches.delete(id);
|
|
493
|
-
const fn = tool[hook];
|
|
494
|
-
if (!fn) return;
|
|
495
|
-
try {
|
|
496
|
-
fn({ scratch });
|
|
497
|
-
} catch (err) {
|
|
498
|
-
console.error(`weasel: tool "${id}".${hook} threw`, err);
|
|
499
|
-
}
|
|
500
|
-
}, [entriesByIdRef]);
|
|
501
|
-
useEffect(() => {
|
|
502
|
-
const prev = liveToolsRef.current;
|
|
503
|
-
const next = new Set(
|
|
504
|
-
activeTool.active === null ? activeTool.hotkeyStack : [activeTool.active, ...activeTool.hotkeyStack]
|
|
505
|
-
);
|
|
506
|
-
liveToolsRef.current = next;
|
|
507
|
-
for (const id of prev) if (!next.has(id)) fireLifecycle(id, "onDeactivate");
|
|
508
|
-
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: () => {
|
|
509
835
|
for (const d of allDispatchers()) d.cancelAll("cancel");
|
|
510
836
|
}
|
|
511
|
-
prevActiveRef.current = activeTool.active;
|
|
512
|
-
for (const id of next) if (!prev.has(id)) fireLifecycle(id, "onActivate");
|
|
513
837
|
});
|
|
514
|
-
useEffect(() => () => {
|
|
515
|
-
const live = liveToolsRef.current;
|
|
516
|
-
liveToolsRef.current = /* @__PURE__ */ new Set();
|
|
517
|
-
for (const id of live) fireLifecycle(id, "onDeactivate");
|
|
518
|
-
}, [fireLifecycle]);
|
|
519
838
|
useEffect(() => {
|
|
520
839
|
if (enabled && claimKeys) registryRef.current.activate();
|
|
521
840
|
}, [enabled, claimKeys, registryRef]);
|
|
522
841
|
useEffect(() => {
|
|
523
842
|
if (!enabled) return;
|
|
524
843
|
let disposed = false;
|
|
525
|
-
let routedId = null;
|
|
526
|
-
const target = () => {
|
|
527
|
-
if (routedId === null) return rootTargetRef.current;
|
|
528
|
-
return viewsRef.current?.targets().find((t) => t.id === routedId) ?? rootTargetRef.current;
|
|
529
|
-
};
|
|
530
|
-
const routeAt = (pointerId, clientX, clientY) => {
|
|
531
|
-
routedId = viewsRef.current?.resolver.at(pointerId, clientX, clientY).id ?? null;
|
|
532
|
-
};
|
|
533
|
-
const routeDown = (pointerId, clientX, clientY) => {
|
|
534
|
-
routedId = viewsRef.current?.resolver.begin(pointerId, clientX, clientY).id ?? null;
|
|
535
|
-
};
|
|
536
|
-
const routeRelease = (pointerId) => {
|
|
537
|
-
viewsRef.current?.resolver.end(pointerId);
|
|
538
|
-
};
|
|
539
|
-
const dispatcherNow = () => target().dispatcher;
|
|
540
|
-
const routedDeps = withViewDeps(() => ctxRef.current.depRegistry, () => target().deps?.());
|
|
541
|
-
const routedRuleCtx = () => target().getRuleCtx?.();
|
|
542
|
-
const ctxNow = () => ({
|
|
543
|
-
...ctxRef.current,
|
|
544
|
-
depRegistry: routedDeps,
|
|
545
|
-
getRuleCtx: routedRuleCtx,
|
|
546
|
-
viewId: target().id
|
|
547
|
-
});
|
|
548
844
|
const canvas = canvasRef.current;
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const
|
|
554
|
-
const d = on ?? dispatcherNow();
|
|
555
|
-
const before = d.inFlight().size;
|
|
556
|
-
const result = d.handleInput(ev, ctxNow());
|
|
557
|
-
const after = d.inFlight().size;
|
|
558
|
-
if (before > 0 || after > 0) requestRedrawRef.current?.();
|
|
559
|
-
return result;
|
|
560
|
-
};
|
|
561
|
-
const core = {
|
|
845
|
+
const redraw = dispatcherWatch(() => {
|
|
846
|
+
requestRedrawRef.current?.();
|
|
847
|
+
});
|
|
848
|
+
redraw.sync(allDispatchers());
|
|
849
|
+
const core = createDispatchCore({
|
|
562
850
|
canvas,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
851
|
+
root: () => rootTargetRef.current,
|
|
852
|
+
views: () => viewsRef.current,
|
|
853
|
+
context: () => ctxRef.current,
|
|
854
|
+
actions: () => registryRef.current,
|
|
855
|
+
uses: () => {
|
|
856
|
+
redraw.sync(allDispatchers());
|
|
568
857
|
},
|
|
569
|
-
dispatcherNow,
|
|
570
|
-
ctxNow,
|
|
571
|
-
toWorld,
|
|
572
|
-
dispatch,
|
|
573
|
-
holdsKeys: () => canvas ? registryRef.current.holdsKeys() : registryRef.current.isActive(),
|
|
574
|
-
activate: () => registryRef.current.activate(),
|
|
575
858
|
disposed: () => disposed
|
|
576
|
-
};
|
|
859
|
+
});
|
|
577
860
|
const keys = keyboardInput(core);
|
|
578
|
-
const { onKeyDown, onKeyUp } = keys;
|
|
579
861
|
const pinch = webkitPinchInput(core);
|
|
580
|
-
const
|
|
581
|
-
const
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return { x, y };
|
|
590
|
-
};
|
|
591
|
-
const heldGeometry = () => {
|
|
592
|
-
const { centroid, spread } = computeMultiTouchGeometry(
|
|
593
|
-
[...held.values()].map((p) => p.pos)
|
|
594
|
-
);
|
|
595
|
-
return { centroid: toCanvasLocal(centroid), spread };
|
|
596
|
-
};
|
|
597
|
-
let multiTouchStart = null;
|
|
598
|
-
const TAP_THRESHOLD_PX = 8;
|
|
599
|
-
const longPressTimers = /* @__PURE__ */ new Map();
|
|
600
|
-
const clearPending = (pointerId) => {
|
|
601
|
-
const store = longPressRef.current?.state;
|
|
602
|
-
const p = store?.get();
|
|
603
|
-
if (p && (pointerId === void 0 || p.pointerId === pointerId)) store.set(null);
|
|
604
|
-
};
|
|
605
|
-
const cancelLongPress = (pointerId) => {
|
|
606
|
-
const t = longPressTimers.get(pointerId);
|
|
607
|
-
if (t !== void 0) {
|
|
608
|
-
clearTimeout(t);
|
|
609
|
-
longPressTimers.delete(pointerId);
|
|
610
|
-
}
|
|
611
|
-
clearPending(pointerId);
|
|
612
|
-
};
|
|
613
|
-
const cancelAllLongPress = () => {
|
|
614
|
-
for (const t of longPressTimers.values()) clearTimeout(t);
|
|
615
|
-
longPressTimers.clear();
|
|
616
|
-
clearPending();
|
|
617
|
-
};
|
|
618
|
-
const HAPTIC_MS = 15;
|
|
619
|
-
const longPressEvents = (down) => {
|
|
620
|
-
const shared = {
|
|
621
|
-
target: down.target,
|
|
622
|
-
altKey: down.altKey,
|
|
623
|
-
ctrlKey: down.ctrlKey,
|
|
624
|
-
metaKey: down.metaKey,
|
|
625
|
-
shiftKey: down.shiftKey,
|
|
626
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
627
|
-
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
628
|
-
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
629
|
-
};
|
|
630
|
-
const at = { x: down.worldX, y: down.worldY, clientX: down.clientX, clientY: down.clientY };
|
|
631
|
-
return {
|
|
632
|
-
longpress: { kind: "longpress", ...at, ...shared },
|
|
633
|
-
contextmenu: { kind: "contextmenu", ...at, ...shared }
|
|
634
|
-
};
|
|
635
|
-
};
|
|
636
|
-
const longPressWouldFire = (down) => {
|
|
637
|
-
const evs = longPressEvents(down);
|
|
638
|
-
const d = dispatcherNow();
|
|
639
|
-
return d.resolveOnly(evs.longpress, ctxNow()) !== null || d.resolveOnly(evs.contextmenu, ctxNow()) !== null;
|
|
640
|
-
};
|
|
641
|
-
const fireLongPress = (pointerId, pointerType) => {
|
|
642
|
-
const rec = held.get(pointerId);
|
|
643
|
-
clearPending(pointerId);
|
|
644
|
-
if (!rec) return;
|
|
645
|
-
const evs = longPressEvents(rec.down);
|
|
646
|
-
let result = dispatch(evs.longpress);
|
|
647
|
-
if (result === "unhandled") result = dispatch(evs.contextmenu);
|
|
648
|
-
if (result === "handled") rec.buffered = null;
|
|
649
|
-
if (result === "handled" && longPressRef.current?.haptics !== false && (pointerType === "touch" || pointerType === "pen") && typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
650
|
-
navigator.vibrate(HAPTIC_MS);
|
|
651
|
-
}
|
|
652
|
-
};
|
|
653
|
-
const DOUBLE_CLICK_MAX_MS = 600;
|
|
654
|
-
const DOUBLE_CLICK_MAX_PX = 8;
|
|
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
|
+
});
|
|
655
871
|
const onWindowBlur = () => {
|
|
656
872
|
pinch.end();
|
|
657
|
-
|
|
873
|
+
pointer.cancelHeld();
|
|
658
874
|
keys.releaseAll();
|
|
659
875
|
};
|
|
660
|
-
|
|
661
|
-
const onNativeDoubleClick = (e) => {
|
|
662
|
-
if (!claimedDoubleClick) return;
|
|
663
|
-
claimedDoubleClick = false;
|
|
664
|
-
e.preventDefault();
|
|
665
|
-
};
|
|
666
|
-
const onPointerDown = (e) => {
|
|
667
|
-
claimedDoubleClick = false;
|
|
668
|
-
if ((e.button ?? 0) !== 0) return;
|
|
669
|
-
if (!canvas) return;
|
|
670
|
-
if (isInControlWithin(e.target, canvas)) return;
|
|
671
|
-
routeDown(e.pointerId, e.clientX, e.clientY);
|
|
672
|
-
const w = toWorld(e.clientX, e.clientY);
|
|
673
|
-
const affordance = target().affordanceAt?.(w) ?? void 0;
|
|
674
|
-
const body = target().classifyTarget?.(w);
|
|
675
|
-
const bodyTarget = body?.body;
|
|
676
|
-
const bodyKind = body?.kind;
|
|
677
|
-
const ev = {
|
|
678
|
-
kind: "pointerdown",
|
|
679
|
-
target: e.target,
|
|
680
|
-
pointerId: e.pointerId,
|
|
681
|
-
x: w.x,
|
|
682
|
-
y: w.y,
|
|
683
|
-
clientX: e.clientX,
|
|
684
|
-
clientY: e.clientY,
|
|
685
|
-
altKey: e.altKey,
|
|
686
|
-
ctrlKey: e.ctrlKey,
|
|
687
|
-
metaKey: e.metaKey,
|
|
688
|
-
shiftKey: e.shiftKey,
|
|
689
|
-
...stylusOf(e),
|
|
690
|
-
...affordance !== void 0 ? { affordance } : {},
|
|
691
|
-
...bodyTarget !== void 0 ? { bodyTarget } : {},
|
|
692
|
-
...bodyKind !== void 0 ? { bodyKind } : {}
|
|
693
|
-
};
|
|
694
|
-
held.set(e.pointerId, {
|
|
695
|
-
session: openPointerSession(canvas, e, {
|
|
696
|
-
onMove: onPointerMove,
|
|
697
|
-
onEnd: onPointerUp,
|
|
698
|
-
onCancel: () => {
|
|
699
|
-
onPointerCancel(e.pointerId);
|
|
700
|
-
}
|
|
701
|
-
}),
|
|
702
|
-
pos: { x: e.clientX, y: e.clientY },
|
|
703
|
-
// Deferred until the first past-threshold pointermove: a bare
|
|
704
|
-
// press-and-release should fire `click`, not start a drag action.
|
|
705
|
-
buffered: { ev, clientX: e.clientX, clientY: e.clientY },
|
|
706
|
-
down: {
|
|
707
|
-
target: e.target,
|
|
708
|
-
clientX: e.clientX,
|
|
709
|
-
clientY: e.clientY,
|
|
710
|
-
worldX: w.x,
|
|
711
|
-
worldY: w.y,
|
|
712
|
-
affordance,
|
|
713
|
-
bodyTarget,
|
|
714
|
-
bodyKind,
|
|
715
|
-
altKey: e.altKey,
|
|
716
|
-
ctrlKey: e.ctrlKey,
|
|
717
|
-
metaKey: e.metaKey,
|
|
718
|
-
shiftKey: e.shiftKey
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
if (held.size < 2) dispatch({ ...ev, stage: "press" });
|
|
722
|
-
if ((e.pointerType === "touch" || e.pointerType === "pen") && held.size === 1) {
|
|
723
|
-
cancelLongPress(e.pointerId);
|
|
724
|
-
const duration = longPressRef.current?.duration ?? LONG_PRESS_MS;
|
|
725
|
-
longPressTimers.set(
|
|
726
|
-
e.pointerId,
|
|
727
|
-
setTimeout(() => {
|
|
728
|
-
longPressTimers.delete(e.pointerId);
|
|
729
|
-
if (disposed) return;
|
|
730
|
-
fireLongPress(e.pointerId, e.pointerType);
|
|
731
|
-
}, duration)
|
|
732
|
-
);
|
|
733
|
-
const store = longPressRef.current?.state;
|
|
734
|
-
const rec = held.get(e.pointerId);
|
|
735
|
-
if (store && rec) {
|
|
736
|
-
store.set({
|
|
737
|
-
pointerId: e.pointerId,
|
|
738
|
-
pointerType: e.pointerType,
|
|
739
|
-
client: { x: e.clientX, y: e.clientY },
|
|
740
|
-
local: toCanvasLocal({ x: e.clientX, y: e.clientY }),
|
|
741
|
-
world: { x: w.x, y: w.y },
|
|
742
|
-
viewId: target().id,
|
|
743
|
-
startedAt: performance.now(),
|
|
744
|
-
duration,
|
|
745
|
-
armed: longPressWouldFire(rec.down)
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
if (held.size >= 2) {
|
|
750
|
-
cancelAllLongPress();
|
|
751
|
-
for (const p of held.values()) p.buffered = null;
|
|
752
|
-
const { centroid, spread } = heldGeometry();
|
|
753
|
-
if (!multiTouchStart) {
|
|
754
|
-
multiTouchStart = {
|
|
755
|
-
fingers: held.size,
|
|
756
|
-
centroid: { ...centroid },
|
|
757
|
-
at: /* @__PURE__ */ new Map(),
|
|
758
|
-
moved: false,
|
|
759
|
-
startedAt: performance.now()
|
|
760
|
-
};
|
|
761
|
-
} else {
|
|
762
|
-
multiTouchStart.fingers = held.size;
|
|
763
|
-
}
|
|
764
|
-
for (const [id, p] of held) {
|
|
765
|
-
if (!multiTouchStart.at.has(id)) multiTouchStart.at.set(id, { ...p.pos });
|
|
766
|
-
}
|
|
767
|
-
const mt = {
|
|
768
|
-
kind: "multitouch",
|
|
769
|
-
fingers: held.size,
|
|
770
|
-
altKey: e.altKey,
|
|
771
|
-
ctrlKey: e.ctrlKey,
|
|
772
|
-
metaKey: e.metaKey,
|
|
773
|
-
shiftKey: e.shiftKey,
|
|
774
|
-
centroid,
|
|
775
|
-
spread
|
|
776
|
-
};
|
|
777
|
-
dispatch(mt);
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
const endMultiTouch = (end, fingers, mods) => {
|
|
781
|
-
dispatch({
|
|
782
|
-
kind: "multitouch",
|
|
783
|
-
fingers,
|
|
784
|
-
end,
|
|
785
|
-
altKey: mods.altKey,
|
|
786
|
-
ctrlKey: mods.ctrlKey,
|
|
787
|
-
metaKey: mods.metaKey,
|
|
788
|
-
shiftKey: mods.shiftKey
|
|
789
|
-
});
|
|
790
|
-
};
|
|
791
|
-
const onPointerMove = (e) => {
|
|
792
|
-
routeAt(e.pointerId, e.clientX, e.clientY);
|
|
793
|
-
const rec = held.get(e.pointerId);
|
|
794
|
-
if (rec) rec.pos = { x: e.clientX, y: e.clientY };
|
|
795
|
-
const joinedAt = multiTouchStart?.at.get(e.pointerId);
|
|
796
|
-
if (multiTouchStart && joinedAt && Math.hypot(e.clientX - joinedAt.x, e.clientY - joinedAt.y) > TAP_THRESHOLD_PX) {
|
|
797
|
-
multiTouchStart.moved = true;
|
|
798
|
-
}
|
|
799
|
-
const buffered = rec?.buffered;
|
|
800
|
-
if (rec && buffered) {
|
|
801
|
-
if (pastDragThreshold(buffered, e)) {
|
|
802
|
-
rec.buffered = null;
|
|
803
|
-
cancelLongPress(e.pointerId);
|
|
804
|
-
dispatch(buffered.ev);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
const mtGestureId = `multitouch-${held.size}`;
|
|
808
|
-
if (held.size >= 2 && dispatcherNow().inFlight().has(mtGestureId)) {
|
|
809
|
-
const { centroid, spread } = heldGeometry();
|
|
810
|
-
const mtEv = {
|
|
811
|
-
kind: "multitouch",
|
|
812
|
-
fingers: held.size,
|
|
813
|
-
altKey: e.altKey,
|
|
814
|
-
ctrlKey: e.ctrlKey,
|
|
815
|
-
metaKey: e.metaKey,
|
|
816
|
-
shiftKey: e.shiftKey,
|
|
817
|
-
centroid,
|
|
818
|
-
spread
|
|
819
|
-
};
|
|
820
|
-
dispatch(mtEv);
|
|
821
|
-
}
|
|
822
|
-
const w = toWorld(e.clientX, e.clientY);
|
|
823
|
-
const ev = {
|
|
824
|
-
kind: "pointermove",
|
|
825
|
-
pointerId: e.pointerId,
|
|
826
|
-
x: w.x,
|
|
827
|
-
y: w.y,
|
|
828
|
-
clientX: e.clientX,
|
|
829
|
-
clientY: e.clientY,
|
|
830
|
-
altKey: e.altKey,
|
|
831
|
-
ctrlKey: e.ctrlKey,
|
|
832
|
-
metaKey: e.metaKey,
|
|
833
|
-
shiftKey: e.shiftKey,
|
|
834
|
-
...stylusOf(e)
|
|
835
|
-
};
|
|
836
|
-
dispatch(ev);
|
|
837
|
-
lastHover = {
|
|
838
|
-
clientX: e.clientX,
|
|
839
|
-
clientY: e.clientY,
|
|
840
|
-
altKey: e.altKey,
|
|
841
|
-
ctrlKey: e.ctrlKey,
|
|
842
|
-
metaKey: e.metaKey,
|
|
843
|
-
shiftKey: e.shiftKey
|
|
844
|
-
};
|
|
845
|
-
refreshHoverCursor();
|
|
846
|
-
};
|
|
847
|
-
let lastHover = null;
|
|
848
|
-
let cursorOverridden = false;
|
|
849
|
-
const clearHoverCursor = () => {
|
|
850
|
-
paintedCursorRef.current?.()?.setOverride(null);
|
|
851
|
-
if (cursorOverridden && canvas) {
|
|
852
|
-
canvas.style.cursor = "";
|
|
853
|
-
cursorOverridden = false;
|
|
854
|
-
}
|
|
855
|
-
};
|
|
856
|
-
const applyHoverCursor = (spec) => {
|
|
857
|
-
const tier = spec === null ? null : resolveCursorTier(spec);
|
|
858
|
-
if (!tier || !canvas) {
|
|
859
|
-
clearHoverCursor();
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
const cursor = tier.kind === "css" ? tier.css : "none";
|
|
863
|
-
if (!cursor) {
|
|
864
|
-
clearHoverCursor();
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
paintedCursorRef.current?.()?.setOverride(tier.kind === "painted" ? tier : "none");
|
|
868
|
-
canvas.style.cursor = cursor;
|
|
869
|
-
cursorOverridden = true;
|
|
870
|
-
};
|
|
871
|
-
function pushPaintedPointer() {
|
|
872
|
-
const painted = paintedCursorRef.current?.();
|
|
873
|
-
if (!painted?.active() || !canvas || !lastHover) return;
|
|
874
|
-
const r = canvas.getBoundingClientRect();
|
|
875
|
-
painted.setPointer(lastHover.clientX - r.left, lastHover.clientY - r.top);
|
|
876
|
-
}
|
|
877
|
-
function refreshHoverCursor() {
|
|
878
|
-
resolveHoverCursor();
|
|
879
|
-
pushPaintedPointer();
|
|
880
|
-
}
|
|
881
|
-
function resolveHoverCursor() {
|
|
882
|
-
if (!canvas || !lastHover) return;
|
|
883
|
-
if (held.size > 0 || dispatcherNow().inFlight().size > 0) {
|
|
884
|
-
applyHoverCursor(dispatcherNow().inFlightCursor());
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
const h = lastHover;
|
|
888
|
-
routeAt(null, h.clientX, h.clientY);
|
|
889
|
-
const w = toWorld(h.clientX, h.clientY);
|
|
890
|
-
const affordance = target().affordanceAt?.(w) ?? void 0;
|
|
891
|
-
if (affordance?.cursor) {
|
|
892
|
-
applyHoverCursor(affordance.cursor);
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
const hoverBody = target().classifyTarget?.(w);
|
|
896
|
-
const bodyTarget = hoverBody?.body;
|
|
897
|
-
const bodyKind = hoverBody?.kind;
|
|
898
|
-
const where = {
|
|
899
|
-
altKey: h.altKey,
|
|
900
|
-
ctrlKey: h.ctrlKey,
|
|
901
|
-
metaKey: h.metaKey,
|
|
902
|
-
shiftKey: h.shiftKey,
|
|
903
|
-
...affordance !== void 0 ? { affordance } : {},
|
|
904
|
-
...bodyTarget !== void 0 ? { bodyTarget } : {},
|
|
905
|
-
...bodyKind !== void 0 ? { bodyKind } : {}
|
|
906
|
-
};
|
|
907
|
-
const ctx = ctxNow();
|
|
908
|
-
const press = {
|
|
909
|
-
kind: "pointerdown",
|
|
910
|
-
x: w.x,
|
|
911
|
-
y: w.y,
|
|
912
|
-
clientX: h.clientX,
|
|
913
|
-
clientY: h.clientY,
|
|
914
|
-
...where
|
|
915
|
-
};
|
|
916
|
-
const d = dispatcherNow();
|
|
917
|
-
publishLiveDispatch(() => d.explain(press, ctx));
|
|
918
|
-
const dragCursor = d.resolveOnly(press, ctx)?.action.cursor;
|
|
919
|
-
const cursor = dragCursor ?? dispatcherNow().resolveOnly(
|
|
920
|
-
{ kind: "click", x: w.x, y: w.y, pressX: w.x, pressY: w.y, ...where },
|
|
921
|
-
ctx
|
|
922
|
-
)?.action.cursor;
|
|
923
|
-
applyHoverCursor(cursor ?? null);
|
|
924
|
-
}
|
|
925
|
-
const scheduleHoverCursorRefresh = (e) => {
|
|
926
|
-
if (!lastHover) return;
|
|
927
|
-
lastHover = {
|
|
928
|
-
...lastHover,
|
|
929
|
-
altKey: e.altKey,
|
|
930
|
-
ctrlKey: e.ctrlKey,
|
|
931
|
-
metaKey: e.metaKey,
|
|
932
|
-
shiftKey: e.shiftKey
|
|
933
|
-
};
|
|
934
|
-
setTimeout(() => {
|
|
935
|
-
if (!disposed) refreshHoverCursor();
|
|
936
|
-
}, 0);
|
|
937
|
-
};
|
|
938
|
-
const onHoverMove = (e) => {
|
|
939
|
-
if (held.has(e.pointerId)) return;
|
|
940
|
-
onPointerMove(e);
|
|
941
|
-
};
|
|
942
|
-
const onHoverPointerLeave = () => {
|
|
943
|
-
lastHover = null;
|
|
944
|
-
publishLiveDispatch(null);
|
|
945
|
-
paintedCursorRef.current?.()?.clearPointer();
|
|
946
|
-
clearHoverCursor();
|
|
947
|
-
};
|
|
948
|
-
const onPointerUp = (e) => {
|
|
949
|
-
routeAt(e.pointerId, e.clientX, e.clientY);
|
|
950
|
-
cancelLongPress(e.pointerId);
|
|
951
|
-
const prevSize = held.size;
|
|
952
|
-
const rec = held.get(e.pointerId);
|
|
953
|
-
held.delete(e.pointerId);
|
|
954
|
-
const staysUnderThreshold = rec?.buffered != null;
|
|
955
|
-
if (prevSize >= 2) endMultiTouch("commit", prevSize, e);
|
|
956
|
-
if (prevSize >= 2 && held.size < 2) {
|
|
957
|
-
const start = multiTouchStart;
|
|
958
|
-
if (start) {
|
|
959
|
-
const centroid = toCanvasLocal(
|
|
960
|
-
computeMultiTouchGeometry([
|
|
961
|
-
...[...held.values()].map((p) => p.pos),
|
|
962
|
-
{ x: e.clientX, y: e.clientY }
|
|
963
|
-
]).centroid
|
|
964
|
-
);
|
|
965
|
-
const dx = centroid.x - start.centroid.x;
|
|
966
|
-
const dy = centroid.y - start.centroid.y;
|
|
967
|
-
const brief = performance.now() - start.startedAt < (longPressRef.current?.duration ?? LONG_PRESS_MS);
|
|
968
|
-
if (brief && !start.moved && Math.hypot(dx, dy) <= TAP_THRESHOLD_PX) {
|
|
969
|
-
const tap = {
|
|
970
|
-
kind: "multitouchtap",
|
|
971
|
-
fingers: start.fingers,
|
|
972
|
-
altKey: e.altKey,
|
|
973
|
-
ctrlKey: e.ctrlKey,
|
|
974
|
-
metaKey: e.metaKey,
|
|
975
|
-
shiftKey: e.shiftKey
|
|
976
|
-
};
|
|
977
|
-
dispatch(tap);
|
|
978
|
-
}
|
|
979
|
-
multiTouchStart = null;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
const movedDuringDrag = rec ? rec.down.clientX !== e.clientX || rec.down.clientY !== e.clientY : true;
|
|
983
|
-
const hadDragInFlight = dispatcherNow().inFlight().has(pointerGestureId(e.pointerId)) && movedDuringDrag;
|
|
984
|
-
const wUp = toWorld(e.clientX, e.clientY);
|
|
985
|
-
const ev = {
|
|
986
|
-
kind: "pointerup",
|
|
987
|
-
pointerId: e.pointerId,
|
|
988
|
-
x: wUp.x,
|
|
989
|
-
y: wUp.y,
|
|
990
|
-
clientX: e.clientX,
|
|
991
|
-
clientY: e.clientY,
|
|
992
|
-
altKey: e.altKey,
|
|
993
|
-
ctrlKey: e.ctrlKey,
|
|
994
|
-
metaKey: e.metaKey,
|
|
995
|
-
shiftKey: e.shiftKey
|
|
996
|
-
};
|
|
997
|
-
dispatch(ev);
|
|
998
|
-
const down = rec?.down;
|
|
999
|
-
if (!hadDragInFlight && staysUnderThreshold && down) {
|
|
1000
|
-
const wClick = toWorld(e.clientX, e.clientY);
|
|
1001
|
-
const clickEv = {
|
|
1002
|
-
kind: "click",
|
|
1003
|
-
target: e.target,
|
|
1004
|
-
altKey: e.altKey,
|
|
1005
|
-
ctrlKey: e.ctrlKey,
|
|
1006
|
-
metaKey: e.metaKey,
|
|
1007
|
-
shiftKey: e.shiftKey,
|
|
1008
|
-
x: wClick.x,
|
|
1009
|
-
y: wClick.y,
|
|
1010
|
-
clientX: e.clientX,
|
|
1011
|
-
clientY: e.clientY,
|
|
1012
|
-
pressX: down.worldX,
|
|
1013
|
-
pressY: down.worldY,
|
|
1014
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
1015
|
-
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
1016
|
-
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
1017
|
-
};
|
|
1018
|
-
dispatch(clickEv);
|
|
1019
|
-
onClickRef.current?.({ x: wClick.x, y: wClick.y });
|
|
1020
|
-
const now = Date.now();
|
|
1021
|
-
const prev = lastClickRef.current;
|
|
1022
|
-
const dx = e.clientX - (prev?.clientX ?? 0);
|
|
1023
|
-
const dy = e.clientY - (prev?.clientY ?? 0);
|
|
1024
|
-
if (prev && now - prev.t < DOUBLE_CLICK_MAX_MS && Math.hypot(dx, dy) < DOUBLE_CLICK_MAX_PX) {
|
|
1025
|
-
const dblEv = {
|
|
1026
|
-
kind: "doubleclick",
|
|
1027
|
-
target: e.target,
|
|
1028
|
-
altKey: e.altKey,
|
|
1029
|
-
ctrlKey: e.ctrlKey,
|
|
1030
|
-
metaKey: e.metaKey,
|
|
1031
|
-
shiftKey: e.shiftKey,
|
|
1032
|
-
x: wClick.x,
|
|
1033
|
-
y: wClick.y,
|
|
1034
|
-
clientX: e.clientX,
|
|
1035
|
-
clientY: e.clientY,
|
|
1036
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
1037
|
-
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
1038
|
-
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
1039
|
-
};
|
|
1040
|
-
onDoubleClickRef.current?.({ x: wClick.x, y: wClick.y });
|
|
1041
|
-
const handled = dispatch(dblEv) === "handled";
|
|
1042
|
-
claimedDoubleClick = handled || onDoubleClickRef.current !== void 0;
|
|
1043
|
-
lastClickRef.current = null;
|
|
1044
|
-
} else {
|
|
1045
|
-
lastClickRef.current = { t: now, clientX: e.clientX, clientY: e.clientY };
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
routeRelease(e.pointerId);
|
|
1049
|
-
lastHover = {
|
|
1050
|
-
clientX: e.clientX,
|
|
1051
|
-
clientY: e.clientY,
|
|
1052
|
-
altKey: e.altKey,
|
|
1053
|
-
ctrlKey: e.ctrlKey,
|
|
1054
|
-
metaKey: e.metaKey,
|
|
1055
|
-
shiftKey: e.shiftKey
|
|
1056
|
-
};
|
|
1057
|
-
setTimeout(() => {
|
|
1058
|
-
if (!disposed) refreshHoverCursor();
|
|
1059
|
-
}, 0);
|
|
1060
|
-
};
|
|
1061
|
-
const onPointerCancel = (pointerId) => {
|
|
1062
|
-
if (disposed) return;
|
|
1063
|
-
const rec = held.get(pointerId);
|
|
1064
|
-
if (!rec) return;
|
|
1065
|
-
const prevSize = held.size;
|
|
1066
|
-
held.delete(pointerId);
|
|
1067
|
-
routeAt(pointerId, rec.pos.x, rec.pos.y);
|
|
1068
|
-
cancelLongPress(pointerId);
|
|
1069
|
-
if (prevSize >= 2) endMultiTouch("cancel", prevSize, rec.down);
|
|
1070
|
-
multiTouchStart = null;
|
|
1071
|
-
const ev = {
|
|
1072
|
-
kind: "pointercancel",
|
|
1073
|
-
pointerId,
|
|
1074
|
-
altKey: rec.down.altKey,
|
|
1075
|
-
ctrlKey: rec.down.ctrlKey,
|
|
1076
|
-
metaKey: rec.down.metaKey,
|
|
1077
|
-
shiftKey: rec.down.shiftKey
|
|
1078
|
-
};
|
|
1079
|
-
dispatch(ev);
|
|
1080
|
-
routeRelease(pointerId);
|
|
1081
|
-
};
|
|
1082
|
-
const onPressActivate = () => {
|
|
1083
|
-
registryRef.current.activate();
|
|
1084
|
-
};
|
|
876
|
+
const listeners = listenerSet();
|
|
1085
877
|
const keyCapture = keyboard === "first";
|
|
1086
878
|
if (keyboard) {
|
|
1087
|
-
|
|
1088
|
-
|
|
879
|
+
listeners.on(window, "keydown", keys.onKeyDown, keyCapture);
|
|
880
|
+
listeners.on(window, "keyup", keys.onKeyUp, keyCapture);
|
|
1089
881
|
}
|
|
1090
|
-
|
|
1091
|
-
canvas
|
|
1092
|
-
|
|
1093
|
-
|
|
882
|
+
listeners.on(window, "blur", onWindowBlur);
|
|
883
|
+
listeners.on(canvas, "pointerdown", () => {
|
|
884
|
+
registryRef.current.activate();
|
|
885
|
+
});
|
|
1094
886
|
if (pointerChannel) {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
canvas
|
|
1098
|
-
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 });
|
|
1099
896
|
}
|
|
1100
|
-
if (wheelChannel) canvas?.addEventListener("wheel", onWheel, { passive: false, capture: true });
|
|
1101
897
|
if (pinchChannel) {
|
|
1102
|
-
canvas
|
|
1103
|
-
canvas
|
|
1104
|
-
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));
|
|
1105
904
|
}
|
|
1106
|
-
if (contextMenuChannel) canvas?.addEventListener("contextmenu", onContextMenu);
|
|
1107
905
|
if (ingestChannel) {
|
|
1108
|
-
canvas
|
|
1109
|
-
canvas
|
|
1110
|
-
canvas
|
|
1111
|
-
canvas
|
|
1112
|
-
|
|
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);
|
|
1113
911
|
}
|
|
1114
912
|
return () => {
|
|
1115
|
-
|
|
1116
|
-
window.removeEventListener("keydown", onKeyDown, keyCapture);
|
|
1117
|
-
window.removeEventListener("keyup", onKeyUp, keyCapture);
|
|
1118
|
-
}
|
|
1119
|
-
window.removeEventListener("blur", onWindowBlur);
|
|
1120
|
-
canvas?.removeEventListener("pointerdown", onPressActivate);
|
|
1121
|
-
window.removeEventListener("keydown", scheduleHoverCursorRefresh);
|
|
1122
|
-
window.removeEventListener("keyup", scheduleHoverCursorRefresh);
|
|
1123
|
-
canvas?.removeEventListener("pointerleave", onHoverPointerLeave);
|
|
1124
|
-
canvas?.removeEventListener("wheel", onWheel, { capture: true });
|
|
1125
|
-
canvas?.removeEventListener("gesturestart", onGestureStart);
|
|
1126
|
-
canvas?.removeEventListener("gesturechange", onGestureChange);
|
|
1127
|
-
canvas?.removeEventListener("gestureend", onGestureEnd);
|
|
1128
|
-
canvas?.removeEventListener("pointerdown", onPointerDown);
|
|
1129
|
-
canvas?.removeEventListener("pointermove", onHoverMove);
|
|
1130
|
-
canvas?.removeEventListener("dblclick", onNativeDoubleClick);
|
|
1131
|
-
canvas?.removeEventListener("contextmenu", onContextMenu);
|
|
1132
|
-
canvas?.removeEventListener("dragenter", onDragOver);
|
|
1133
|
-
canvas?.removeEventListener("dragover", onDragOver);
|
|
1134
|
-
canvas?.removeEventListener("dragleave", onDragLeave);
|
|
1135
|
-
canvas?.removeEventListener("drop", onDrop);
|
|
1136
|
-
window.removeEventListener("paste", onPaste);
|
|
913
|
+
listeners.removeAll();
|
|
1137
914
|
canvas?.classList.remove(DROPOVER_CLASS);
|
|
1138
915
|
registryRef.current.dropKeys();
|
|
1139
|
-
|
|
1140
|
-
cancelAllLongPress();
|
|
916
|
+
hover.clear();
|
|
1141
917
|
disposed = true;
|
|
1142
|
-
|
|
1143
|
-
|
|
918
|
+
pointer.dispose();
|
|
919
|
+
redraw.stop();
|
|
1144
920
|
for (const d of allDispatchers()) d.cancelAll("cancel");
|
|
1145
921
|
};
|
|
1146
922
|
}, [
|
|
@@ -1157,7 +933,7 @@ function useGestureDispatcher(opts) {
|
|
|
1157
933
|
longPressRef,
|
|
1158
934
|
onClickRef,
|
|
1159
935
|
onDoubleClickRef,
|
|
1160
|
-
|
|
936
|
+
cursorRef,
|
|
1161
937
|
registryRef,
|
|
1162
938
|
requestRedrawRef,
|
|
1163
939
|
rootTargetRef,
|
|
@@ -1279,19 +1055,16 @@ var ActionsNode = class extends ScopeNode {
|
|
|
1279
1055
|
const leaf = this.start();
|
|
1280
1056
|
const a = leaf.resolve(id);
|
|
1281
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;
|
|
1282
1060
|
try {
|
|
1283
|
-
a.invoker.run(
|
|
1061
|
+
a.invoker.run(deps, params);
|
|
1284
1062
|
} catch (err) {
|
|
1285
1063
|
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
1286
1064
|
}
|
|
1287
1065
|
return true;
|
|
1288
1066
|
},
|
|
1289
|
-
subscribe: (listener) =>
|
|
1290
|
-
this.tree.listeners.add(listener);
|
|
1291
|
-
return () => {
|
|
1292
|
-
this.tree.listeners.delete(listener);
|
|
1293
|
-
};
|
|
1294
|
-
},
|
|
1067
|
+
subscribe: (listener) => this.tree.listeners.subscribe(listener),
|
|
1295
1068
|
setDispatcher: (d) => pushOwner(this.dispatchers, d),
|
|
1296
1069
|
begin: (id, params) => {
|
|
1297
1070
|
const leaf = this.start();
|
|
@@ -1407,6 +1180,96 @@ function useOngoingAction(actionId) {
|
|
|
1407
1180
|
useEffect(() => () => edit.commit(), [edit, actionId]);
|
|
1408
1181
|
return edit;
|
|
1409
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
|
+
}
|
|
1410
1273
|
var NO_MODES = () => () => {
|
|
1411
1274
|
};
|
|
1412
1275
|
function useContributions(opts) {
|
|
@@ -1596,6 +1459,6 @@ function useTools(opts) {
|
|
|
1596
1459
|
);
|
|
1597
1460
|
}
|
|
1598
1461
|
|
|
1599
|
-
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 };
|
|
1600
1463
|
//# sourceMappingURL=react.js.map
|
|
1601
1464
|
//# sourceMappingURL=react.js.map
|