@weasel-js/routing 1.9.4 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Y5RURZBH.js → chunk-VCEIQ64H.js} +1017 -851
- package/dist/chunk-VCEIQ64H.js.map +1 -0
- package/dist/{index-gArM2tbt.d.ts → index-DkOqUxow.d.ts} +370 -596
- package/dist/index.d.ts +3 -3
- package/dist/index.js +16 -64
- package/dist/index.js.map +1 -1
- package/dist/{internal-B0SsznMG.d.ts → internal-Boc2I3zn.d.ts} +38 -67
- package/dist/internal.d.ts +4 -4
- package/dist/react.d.ts +123 -34
- package/dist/react.js +688 -751
- 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, tuneAction, pushOwner, evaluateEnabled, validateActionId, validateActionDefaultBinding, depNodeOf, DepNode, clientToCanvas, DEV, publishLiveDispatch, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, buildDepsFromRequires, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer } from './chunk-VCEIQ64H.js';
|
|
2
2
|
import { createContext, useState, useContext, useEffect, useRef, useCallback, useMemo, useSyncExternalStore } from 'react';
|
|
3
|
+
import { isApplePlatform, heldPointers, 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 !== null && 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,183 @@ 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 whereOf = ({ affordance, bodyTarget, bodyKind }) => ({
|
|
272
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
273
|
+
...bodyTarget !== void 0 ? { bodyTarget } : {},
|
|
274
|
+
...bodyKind !== void 0 ? { bodyKind } : {}
|
|
275
|
+
});
|
|
276
|
+
var hoverOf = (e) => ({
|
|
277
|
+
clientX: e.clientX,
|
|
278
|
+
clientY: e.clientY,
|
|
279
|
+
altKey: e.altKey,
|
|
280
|
+
ctrlKey: e.ctrlKey,
|
|
281
|
+
metaKey: e.metaKey,
|
|
282
|
+
shiftKey: e.shiftKey
|
|
283
|
+
});
|
|
284
|
+
function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () => void 0) {
|
|
285
|
+
const { canvas, routeAt, toWorld, target, dispatch, dispatcherNow, ctxNow } = core;
|
|
286
|
+
const ownSink = createCursorSink({ element: () => canvas, painted: paintedCursor });
|
|
287
|
+
const override = {
|
|
288
|
+
apply: (spec) => (cursorSink() ?? ownSink).setOverride(spec === null ? null : resolveCursorTier(spec)),
|
|
289
|
+
clear: () => (cursorSink() ?? ownSink).setOverride(null)
|
|
290
|
+
};
|
|
291
|
+
let last = null;
|
|
292
|
+
let facts = null;
|
|
293
|
+
const resolve = () => {
|
|
294
|
+
if (!canvas || !last) return;
|
|
295
|
+
if (pointersHeld() > 0 || dispatcherNow().inFlight().size > 0) {
|
|
296
|
+
override.apply(dispatcherNow().inFlightCursor());
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const h = last;
|
|
300
|
+
routeAt(null, h.clientX, h.clientY);
|
|
301
|
+
const w = toWorld(h.clientX, h.clientY);
|
|
302
|
+
const where = facts?.at === h ? facts.where : underPointer(target(), h, w);
|
|
303
|
+
if (where.affordance?.cursor) {
|
|
304
|
+
override.apply(where.affordance.cursor);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const ctx = ctxNow();
|
|
308
|
+
const d = dispatcherNow();
|
|
309
|
+
const press = { kind: "pointerdown", x: w.x, y: w.y, clientX: h.clientX, clientY: h.clientY, ...where };
|
|
310
|
+
if (DEV) publishLiveDispatch(() => d.explain(press, ctx));
|
|
311
|
+
const click = { kind: "click", x: w.x, y: w.y, pressX: w.x, pressY: w.y, ...where };
|
|
312
|
+
const cursor = d.resolveOnly(press, ctx)?.action.cursor ?? dispatcherNow().resolveOnly(click, ctx)?.action.cursor;
|
|
313
|
+
override.apply(cursor ?? null);
|
|
314
|
+
};
|
|
315
|
+
const refresh = () => {
|
|
316
|
+
resolve();
|
|
317
|
+
const painted = paintedCursor();
|
|
318
|
+
if (!painted?.active() || !canvas || !last) return;
|
|
319
|
+
const local = core.toCanvasLocal(last.clientX, last.clientY);
|
|
320
|
+
painted.setPointer(local.x, local.y);
|
|
321
|
+
};
|
|
322
|
+
const refreshSoon = () => {
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
if (!core.disposed()) refresh();
|
|
325
|
+
}, 0);
|
|
326
|
+
};
|
|
327
|
+
return {
|
|
328
|
+
moved(e) {
|
|
329
|
+
last = hoverOf(e);
|
|
330
|
+
if (pointersHeld() === 0) {
|
|
331
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
332
|
+
const where = underPointer(target(), last, w);
|
|
333
|
+
const from = facts ? { from: whereOf(facts.where) } : {};
|
|
334
|
+
facts = { at: last, where };
|
|
335
|
+
dispatch({ kind: "hover", x: w.x, y: w.y, clientX: e.clientX, clientY: e.clientY, ...where, ...from });
|
|
336
|
+
}
|
|
337
|
+
refresh();
|
|
338
|
+
},
|
|
339
|
+
released(e) {
|
|
340
|
+
last = hoverOf(e);
|
|
341
|
+
refreshSoon();
|
|
342
|
+
},
|
|
343
|
+
onLeave() {
|
|
344
|
+
if (facts) {
|
|
345
|
+
const { at, where } = facts;
|
|
346
|
+
const w = toWorld(at.clientX, at.clientY);
|
|
347
|
+
dispatch({ kind: "hover", off: true, x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, from: whereOf(where) });
|
|
348
|
+
}
|
|
349
|
+
facts = null;
|
|
350
|
+
last = null;
|
|
351
|
+
if (DEV) publishLiveDispatch(null);
|
|
352
|
+
paintedCursor()?.clearPointer();
|
|
353
|
+
override.clear();
|
|
354
|
+
},
|
|
355
|
+
onKey(e) {
|
|
356
|
+
if (!last) return;
|
|
357
|
+
last = { ...last, altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey };
|
|
358
|
+
refreshSoon();
|
|
359
|
+
},
|
|
360
|
+
clear: override.clear
|
|
361
|
+
};
|
|
362
|
+
}
|
|
159
363
|
|
|
160
364
|
// src/interactions/dispatcher/channels/ingest.ts
|
|
161
365
|
var DROPOVER_CLASS = "weasel-dropover";
|
|
162
366
|
function ingestInput(core) {
|
|
163
367
|
const { canvas, routeAt, toWorld, dispatch } = core;
|
|
164
368
|
const onDragOver = (e) => {
|
|
165
|
-
|
|
369
|
+
const dt = e.dataTransfer;
|
|
370
|
+
if (!dt) return;
|
|
371
|
+
routeAt(null, e.clientX, e.clientY);
|
|
372
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
373
|
+
const wanted = core.dispatcherNow().resolveOnly({
|
|
374
|
+
kind: "drop",
|
|
375
|
+
items: itemTypesFromDataTransfer(dt),
|
|
376
|
+
x: w.x,
|
|
377
|
+
y: w.y,
|
|
378
|
+
clientX: e.clientX,
|
|
379
|
+
clientY: e.clientY,
|
|
380
|
+
altKey: e.altKey,
|
|
381
|
+
ctrlKey: e.ctrlKey,
|
|
382
|
+
metaKey: e.metaKey,
|
|
383
|
+
shiftKey: e.shiftKey
|
|
384
|
+
}, core.ctxNow()) !== null;
|
|
385
|
+
canvas?.classList.toggle(DROPOVER_CLASS, wanted);
|
|
386
|
+
if (!wanted) return;
|
|
166
387
|
e.preventDefault();
|
|
167
|
-
|
|
168
|
-
canvas?.classList.add(DROPOVER_CLASS);
|
|
388
|
+
dt.dropEffect = "copy";
|
|
169
389
|
};
|
|
170
390
|
const onDragLeave = () => {
|
|
171
391
|
canvas?.classList.remove(DROPOVER_CLASS);
|
|
@@ -226,24 +446,32 @@ function ingestInput(core) {
|
|
|
226
446
|
}
|
|
227
447
|
|
|
228
448
|
// src/interactions/dispatcher/channels/keyboard.ts
|
|
449
|
+
function typedWithAltGraph(e) {
|
|
450
|
+
if (!e.ctrlKey || !e.altKey || e.key.length !== 1 || !e.getModifierState?.("AltGraph")) return false;
|
|
451
|
+
const base = /^(?:Key|Digit)(.)$/.exec(e.code)?.[1];
|
|
452
|
+
return base === void 0 || base.toLowerCase() !== e.key.toLowerCase();
|
|
453
|
+
}
|
|
229
454
|
function keyboardInput(core) {
|
|
230
455
|
const { dispatch, dispatcherNow } = core;
|
|
231
456
|
const heldKeys = /* @__PURE__ */ new Map();
|
|
232
457
|
const codeOf = (e) => e.code ? { code: e.code } : {};
|
|
233
458
|
const physicalKey = (e) => e.code || e.key.toLowerCase();
|
|
234
459
|
const onKeyDown = (e) => {
|
|
235
|
-
if (e.defaultPrevented || isEditableTarget(e.target) ||
|
|
460
|
+
if (e.defaultPrevented || isEditableTarget(e.target) || focusedControlOwnsKey(e)) return;
|
|
236
461
|
if (!core.holdsKeys()) return;
|
|
237
462
|
if (e.repeat && heldKeys.has(physicalKey(e))) {
|
|
238
463
|
e.preventDefault();
|
|
239
464
|
return;
|
|
240
465
|
}
|
|
466
|
+
if (press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey })) return;
|
|
467
|
+
if (typedWithAltGraph(e)) press(e, { altKey: false, ctrlKey: false });
|
|
468
|
+
};
|
|
469
|
+
const press = (e, mods) => {
|
|
241
470
|
const keyEv = {
|
|
242
471
|
kind: "key",
|
|
243
472
|
key: e.key,
|
|
244
473
|
...codeOf(e),
|
|
245
|
-
|
|
246
|
-
ctrlKey: e.ctrlKey,
|
|
474
|
+
...mods,
|
|
247
475
|
metaKey: e.metaKey,
|
|
248
476
|
shiftKey: e.shiftKey,
|
|
249
477
|
repeat: e.repeat
|
|
@@ -254,8 +482,7 @@ function keyboardInput(core) {
|
|
|
254
482
|
key: e.key,
|
|
255
483
|
...codeOf(e),
|
|
256
484
|
phase: "down",
|
|
257
|
-
|
|
258
|
-
ctrlKey: e.ctrlKey,
|
|
485
|
+
...mods,
|
|
259
486
|
metaKey: e.metaKey,
|
|
260
487
|
shiftKey: e.shiftKey
|
|
261
488
|
};
|
|
@@ -264,9 +491,9 @@ function keyboardInput(core) {
|
|
|
264
491
|
if (heldResult === "handled") {
|
|
265
492
|
heldKeys.set(physicalKey(e), { key: e.key, ...codeOf(e), on: heldOn });
|
|
266
493
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
494
|
+
const handled = keyResult === "handled" || heldResult === "handled";
|
|
495
|
+
if (handled) e.preventDefault();
|
|
496
|
+
return handled;
|
|
270
497
|
};
|
|
271
498
|
const onKeyUp = (e) => {
|
|
272
499
|
const physical = physicalKey(e);
|
|
@@ -305,6 +532,175 @@ function keyboardInput(core) {
|
|
|
305
532
|
return { onKeyDown, onKeyUp, releaseAll };
|
|
306
533
|
}
|
|
307
534
|
|
|
535
|
+
// src/interactions/dispatcher/channels/listeners.ts
|
|
536
|
+
function listenerSet() {
|
|
537
|
+
const removers = [];
|
|
538
|
+
return {
|
|
539
|
+
on(target, type, listener, options) {
|
|
540
|
+
if (!target) return;
|
|
541
|
+
target.addEventListener(type, listener, options);
|
|
542
|
+
removers.push(() => {
|
|
543
|
+
target.removeEventListener(type, listener, options);
|
|
544
|
+
});
|
|
545
|
+
},
|
|
546
|
+
removeAll() {
|
|
547
|
+
for (const remove of removers.splice(0)) remove();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
var sampleOf = (e) => ({
|
|
552
|
+
pointerId: e.pointerId,
|
|
553
|
+
clientX: e.clientX,
|
|
554
|
+
clientY: e.clientY,
|
|
555
|
+
target: e.target,
|
|
556
|
+
altKey: e.altKey,
|
|
557
|
+
ctrlKey: e.ctrlKey,
|
|
558
|
+
metaKey: e.metaKey,
|
|
559
|
+
shiftKey: e.shiftKey,
|
|
560
|
+
...stylusOf(e)
|
|
561
|
+
});
|
|
562
|
+
function holdPublisher(core, longPress) {
|
|
563
|
+
return (pending) => {
|
|
564
|
+
const store = longPress()?.state;
|
|
565
|
+
if (!store) return;
|
|
566
|
+
if (!pending) {
|
|
567
|
+
store.set(null);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const { press } = pending;
|
|
571
|
+
const evs = longPressEvents(press);
|
|
572
|
+
const d = core.dispatcherNow();
|
|
573
|
+
store.set({
|
|
574
|
+
pointerId: pending.pointerId,
|
|
575
|
+
pointerType: pending.pointerType,
|
|
576
|
+
client: { x: press.clientX, y: press.clientY },
|
|
577
|
+
local: core.toCanvasLocal(press.clientX, press.clientY),
|
|
578
|
+
world: { x: press.worldX, y: press.worldY },
|
|
579
|
+
viewId: core.target().id,
|
|
580
|
+
startedAt: pending.startedAt,
|
|
581
|
+
duration: pending.duration,
|
|
582
|
+
armed: d.resolveOnly(evs.longpress, core.ctxNow()) !== null || d.resolveOnly(evs.contextmenu, core.ctxNow()) !== null
|
|
583
|
+
});
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
function holdClock(core) {
|
|
587
|
+
let holdView = null;
|
|
588
|
+
return {
|
|
589
|
+
clock: {
|
|
590
|
+
now: () => performance.now(),
|
|
591
|
+
setTimer: (fn, ms) => {
|
|
592
|
+
const view = core.routedView();
|
|
593
|
+
holdView = { id: view };
|
|
594
|
+
return setTimeout(() => {
|
|
595
|
+
holdView = null;
|
|
596
|
+
core.setRoutedView(view);
|
|
597
|
+
fn();
|
|
598
|
+
}, ms);
|
|
599
|
+
},
|
|
600
|
+
clearTimer: (timer) => {
|
|
601
|
+
holdView = null;
|
|
602
|
+
clearTimeout(timer);
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
routeToPress: () => {
|
|
606
|
+
if (holdView) core.setRoutedView(holdView.id);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function pointerInput(core, opts) {
|
|
611
|
+
const { canvas, routeAt, routeDown, routeRelease, toWorld, target } = core;
|
|
612
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
613
|
+
let claimedDoubleClick = false;
|
|
614
|
+
const hold = holdClock(core);
|
|
615
|
+
const recognizer = createPointerRecognizer({
|
|
616
|
+
sink: {
|
|
617
|
+
dispatch: (ev) => core.dispatch(ev),
|
|
618
|
+
multiTouchInFlight: (fingers) => core.dispatcherNow().inFlight().has(multiTouchGestureId(fingers))
|
|
619
|
+
},
|
|
620
|
+
clock: hold.clock,
|
|
621
|
+
toWorld,
|
|
622
|
+
toSurface: (p) => core.toCanvasLocal(p.x, p.y),
|
|
623
|
+
longPressMs: () => opts.longPress()?.duration,
|
|
624
|
+
clicks: opts.clicks,
|
|
625
|
+
observer: {
|
|
626
|
+
click: (world) => {
|
|
627
|
+
opts.onClick()?.(world);
|
|
628
|
+
},
|
|
629
|
+
doubleClick: (world) => {
|
|
630
|
+
opts.onDoubleClick()?.(world);
|
|
631
|
+
},
|
|
632
|
+
holdChanged: holdPublisher(core, opts.longPress),
|
|
633
|
+
longPress: () => {
|
|
634
|
+
if (opts.longPress()?.haptics === false) return;
|
|
635
|
+
if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
636
|
+
navigator.vibrate(LONG_PRESS_HAPTIC_MS);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
const onMove = (e) => {
|
|
642
|
+
routeAt(e.pointerId, e.clientX, e.clientY);
|
|
643
|
+
recognizer.move(sampleOf(e));
|
|
644
|
+
opts.hover.moved(e);
|
|
645
|
+
};
|
|
646
|
+
const onUp = (e) => {
|
|
647
|
+
routeAt(e.pointerId, e.clientX, e.clientY);
|
|
648
|
+
sessions.delete(e.pointerId);
|
|
649
|
+
const { doubleClick } = recognizer.release(sampleOf(e));
|
|
650
|
+
if (doubleClick) claimedDoubleClick = doubleClick === "handled" || opts.onDoubleClick() !== void 0;
|
|
651
|
+
routeRelease(e.pointerId);
|
|
652
|
+
opts.hover.released(e);
|
|
653
|
+
};
|
|
654
|
+
const onCancel = (pointerId) => {
|
|
655
|
+
if (core.disposed()) return;
|
|
656
|
+
const at = recognizer.positionOf(pointerId);
|
|
657
|
+
if (!at) return;
|
|
658
|
+
sessions.delete(pointerId);
|
|
659
|
+
routeAt(pointerId, at.x, at.y);
|
|
660
|
+
recognizer.cancel(pointerId);
|
|
661
|
+
routeRelease(pointerId);
|
|
662
|
+
};
|
|
663
|
+
return {
|
|
664
|
+
onPointerDown(e) {
|
|
665
|
+
claimedDoubleClick = false;
|
|
666
|
+
if ((e.button ?? 0) !== 0) return;
|
|
667
|
+
if (!canvas) return;
|
|
668
|
+
if (isInControlWithin(e.target, canvas)) return;
|
|
669
|
+
routeDown(e.pointerId, e.clientX, e.clientY);
|
|
670
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
671
|
+
const where = underPointer(target(), e, w);
|
|
672
|
+
sessions.set(e.pointerId, openPointerSession(canvas, e, {
|
|
673
|
+
onMove,
|
|
674
|
+
onEnd: onUp,
|
|
675
|
+
onCancel: () => {
|
|
676
|
+
onCancel(e.pointerId);
|
|
677
|
+
}
|
|
678
|
+
}));
|
|
679
|
+
recognizer.press({ ...sampleOf(e), x: w.x, y: w.y, pointerType: e.pointerType, ...where });
|
|
680
|
+
},
|
|
681
|
+
onHoverMove(e) {
|
|
682
|
+
if (!sessions.has(e.pointerId)) onMove(e);
|
|
683
|
+
},
|
|
684
|
+
onNativeDoubleClick(e) {
|
|
685
|
+
if (!claimedDoubleClick) return;
|
|
686
|
+
claimedDoubleClick = false;
|
|
687
|
+
e.preventDefault();
|
|
688
|
+
},
|
|
689
|
+
takeNativeHold: () => {
|
|
690
|
+
hold.routeToPress();
|
|
691
|
+
return recognizer.takeHold();
|
|
692
|
+
},
|
|
693
|
+
cancelHeld() {
|
|
694
|
+
for (const session of [...sessions.values()]) session.cancel();
|
|
695
|
+
},
|
|
696
|
+
dispose() {
|
|
697
|
+
recognizer.reset();
|
|
698
|
+
for (const session of sessions.values()) session.cancel();
|
|
699
|
+
sessions.clear();
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
308
704
|
// src/interactions/dispatcher/channels/webkitPinch.ts
|
|
309
705
|
function webkitPinchInput(core) {
|
|
310
706
|
const { canvas, routeAt, toWorld, target, dispatch, dispatcherNow, ctxNow } = core;
|
|
@@ -318,27 +714,25 @@ function webkitPinchInput(core) {
|
|
|
318
714
|
};
|
|
319
715
|
const pinchEventOf = (e, scale, rotation) => {
|
|
320
716
|
const g = e;
|
|
321
|
-
const rect = canvas?.getBoundingClientRect();
|
|
717
|
+
const rect = typeof g.clientX === "number" && typeof g.clientY === "number" ? void 0 : canvas?.getBoundingClientRect();
|
|
322
718
|
const cx = typeof g.clientX === "number" ? g.clientX : rect ? rect.left + rect.width / 2 : 0;
|
|
323
719
|
const cy = typeof g.clientY === "number" ? g.clientY : rect ? rect.top + rect.height / 2 : 0;
|
|
324
720
|
routeAt(null, cx, cy);
|
|
325
|
-
const
|
|
721
|
+
const local = core.toCanvasLocal(cx, cy);
|
|
326
722
|
const hitPoint = toWorld(cx, cy);
|
|
327
|
-
const
|
|
328
|
-
const body = target().classifyTarget?.(hitPoint);
|
|
329
|
-
return {
|
|
330
|
-
kind: "pinch",
|
|
723
|
+
const mods = {
|
|
331
724
|
altKey: g.altKey === true,
|
|
332
725
|
ctrlKey: g.ctrlKey === true,
|
|
333
726
|
metaKey: g.metaKey === true,
|
|
334
|
-
shiftKey: g.shiftKey === true
|
|
727
|
+
shiftKey: g.shiftKey === true
|
|
728
|
+
};
|
|
729
|
+
return {
|
|
730
|
+
kind: "pinch",
|
|
335
731
|
scale,
|
|
336
732
|
rotation,
|
|
337
|
-
clientX:
|
|
338
|
-
clientY:
|
|
339
|
-
...
|
|
340
|
-
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
341
|
-
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
733
|
+
clientX: local.x,
|
|
734
|
+
clientY: local.y,
|
|
735
|
+
...underPointer(target(), mods, hitPoint)
|
|
342
736
|
};
|
|
343
737
|
};
|
|
344
738
|
const onGestureStart = (e) => {
|
|
@@ -372,7 +766,7 @@ function webkitPinchInput(core) {
|
|
|
372
766
|
|
|
373
767
|
// src/interactions/dispatcher/channels/wheel.ts
|
|
374
768
|
function wheelInput(core, pinchLive) {
|
|
375
|
-
const {
|
|
769
|
+
const { routeAt, toWorld, target, dispatch } = core;
|
|
376
770
|
const onWheel = (e) => {
|
|
377
771
|
core.activate();
|
|
378
772
|
if (pinchLive() && e.ctrlKey) {
|
|
@@ -381,24 +775,15 @@ function wheelInput(core, pinchLive) {
|
|
|
381
775
|
return;
|
|
382
776
|
}
|
|
383
777
|
routeAt(null, e.clientX, e.clientY);
|
|
384
|
-
const
|
|
385
|
-
const [localX, localY] = rect ? clientToCanvasRect(rect, e.clientX, e.clientY) : [e.clientX, e.clientY];
|
|
778
|
+
const local = core.toCanvasLocal(e.clientX, e.clientY);
|
|
386
779
|
const hitPoint = toWorld(e.clientX, e.clientY);
|
|
387
|
-
const affordance = target().affordanceAt?.(hitPoint) ?? void 0;
|
|
388
|
-
const body = target().classifyTarget?.(hitPoint);
|
|
389
780
|
const ev = {
|
|
390
781
|
kind: "wheel",
|
|
391
|
-
altKey: e.altKey,
|
|
392
|
-
ctrlKey: e.ctrlKey,
|
|
393
|
-
metaKey: e.metaKey,
|
|
394
|
-
shiftKey: e.shiftKey,
|
|
395
782
|
deltaX: e.deltaX,
|
|
396
783
|
deltaY: e.deltaY,
|
|
397
|
-
clientX:
|
|
398
|
-
clientY:
|
|
399
|
-
...
|
|
400
|
-
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
401
|
-
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
784
|
+
clientX: local.x,
|
|
785
|
+
clientY: local.y,
|
|
786
|
+
...underPointer(target(), e, hitPoint)
|
|
402
787
|
};
|
|
403
788
|
const result = dispatch(ev);
|
|
404
789
|
if (result === "handled") {
|
|
@@ -409,34 +794,91 @@ function wheelInput(core, pinchLive) {
|
|
|
409
794
|
return onWheel;
|
|
410
795
|
}
|
|
411
796
|
|
|
412
|
-
// src/interactions/dispatcher/
|
|
413
|
-
function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
797
|
+
// src/interactions/dispatcher/attachInput.ts
|
|
798
|
+
function attachInput(canvas, src, channels) {
|
|
799
|
+
let disposed = false;
|
|
800
|
+
const redraw = dispatcherWatch(src.requestRedraw);
|
|
801
|
+
redraw.sync(src.dispatchers());
|
|
802
|
+
const core = createDispatchCore({
|
|
803
|
+
canvas,
|
|
804
|
+
root: src.root,
|
|
805
|
+
views: src.views,
|
|
806
|
+
context: src.context,
|
|
807
|
+
actions: src.actions,
|
|
808
|
+
uses: () => {
|
|
809
|
+
redraw.sync(src.dispatchers());
|
|
810
|
+
},
|
|
811
|
+
disposed: () => disposed
|
|
812
|
+
});
|
|
813
|
+
const keys = keyboardInput(core);
|
|
814
|
+
const pinch = webkitPinchInput(core);
|
|
815
|
+
const ingest = ingestInput(core);
|
|
816
|
+
const held = canvas ? heldPointers(canvas) : null;
|
|
817
|
+
const hover = hoverCursorInput(core, src.paintedCursor, () => held?.count() ?? 0, src.cursorSink);
|
|
818
|
+
const pointer = pointerInput(core, {
|
|
819
|
+
longPress: src.longPress,
|
|
820
|
+
onClick: src.onClick,
|
|
821
|
+
onDoubleClick: src.onDoubleClick,
|
|
822
|
+
clicks: src.clicks,
|
|
823
|
+
hover
|
|
824
|
+
});
|
|
825
|
+
const onWindowBlur = () => {
|
|
826
|
+
pinch.end();
|
|
827
|
+
pointer.cancelHeld();
|
|
828
|
+
keys.releaseAll();
|
|
418
829
|
};
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return { centroid: { x: 0, y: 0 }, spread: 0 };
|
|
830
|
+
const listeners = listenerSet();
|
|
831
|
+
const keyCapture = channels.keyboard === "first";
|
|
832
|
+
if (channels.keyboard) {
|
|
833
|
+
listeners.on(window, "keydown", keys.onKeyDown, keyCapture);
|
|
834
|
+
listeners.on(window, "keyup", keys.onKeyUp, keyCapture);
|
|
425
835
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
836
|
+
listeners.on(window, "blur", onWindowBlur);
|
|
837
|
+
listeners.on(canvas, "pointerdown", () => {
|
|
838
|
+
src.actions().activate();
|
|
839
|
+
});
|
|
840
|
+
if (channels.pointer) {
|
|
841
|
+
listeners.on(window, "keydown", hover.onKey);
|
|
842
|
+
listeners.on(window, "keyup", hover.onKey);
|
|
843
|
+
listeners.on(canvas, "pointerleave", hover.onLeave);
|
|
844
|
+
listeners.on(canvas, "pointerdown", pointer.onPointerDown);
|
|
845
|
+
listeners.on(canvas, "pointermove", pointer.onHoverMove);
|
|
846
|
+
listeners.on(canvas, "dblclick", pointer.onNativeDoubleClick);
|
|
847
|
+
}
|
|
848
|
+
if (channels.wheel) {
|
|
849
|
+
listeners.on(canvas, "wheel", wheelInput(core, pinch.live), { passive: false, capture: true });
|
|
850
|
+
}
|
|
851
|
+
if (channels.pinch) {
|
|
852
|
+
listeners.on(canvas, "gesturestart", pinch.onGestureStart);
|
|
853
|
+
listeners.on(canvas, "gesturechange", pinch.onGestureChange);
|
|
854
|
+
listeners.on(canvas, "gestureend", pinch.onGestureEnd);
|
|
855
|
+
}
|
|
856
|
+
if (channels.contextMenu) {
|
|
857
|
+
listeners.on(canvas, "contextmenu", contextMenuInput(core, pointer.takeNativeHold));
|
|
858
|
+
}
|
|
859
|
+
if (channels.ingest) {
|
|
860
|
+
listeners.on(canvas, "dragenter", ingest.onDragOver);
|
|
861
|
+
listeners.on(canvas, "dragover", ingest.onDragOver);
|
|
862
|
+
listeners.on(canvas, "dragleave", ingest.onDragLeave);
|
|
863
|
+
listeners.on(canvas, "drop", ingest.onDrop);
|
|
864
|
+
listeners.on(window, "paste", ingest.onPaste);
|
|
431
865
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
866
|
+
return () => {
|
|
867
|
+
listeners.removeAll();
|
|
868
|
+
canvas?.classList.remove(DROPOVER_CLASS);
|
|
869
|
+
src.actions().dropKeys();
|
|
870
|
+
hover.clear();
|
|
871
|
+
disposed = true;
|
|
872
|
+
pointer.dispose();
|
|
873
|
+
held?.release();
|
|
874
|
+
redraw.stop();
|
|
875
|
+
for (const d of src.dispatchers()) d.cancelAll("cancel");
|
|
876
|
+
};
|
|
437
877
|
}
|
|
878
|
+
|
|
879
|
+
// src/interactions/dispatcher/useGestureDispatcher.tsx
|
|
438
880
|
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;
|
|
881
|
+
const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, requestRedraw, paintedCursor, cursorSink, getRuleCtx, onClick, onDoubleClick, views, channels, longPress, claimKeys = false } = opts;
|
|
440
882
|
const pointerChannel = channels?.pointer !== false;
|
|
441
883
|
const wheelChannel = channels?.wheel !== false;
|
|
442
884
|
const pinchChannel = channels?.pinch !== false;
|
|
@@ -446,6 +888,7 @@ function useGestureDispatcher(opts) {
|
|
|
446
888
|
const onDoubleClickRef = useLatest(onDoubleClick);
|
|
447
889
|
const activeTool = useActiveToolContext();
|
|
448
890
|
const depRegistry = useDepRegistry();
|
|
891
|
+
const [isMac] = useState(isApplePlatform);
|
|
449
892
|
const dispatcherRef = useRef(null);
|
|
450
893
|
if (!dispatcherRef.current) {
|
|
451
894
|
dispatcherRef.current = dispatcherOpt ?? createDispatcher();
|
|
@@ -457,7 +900,7 @@ function useGestureDispatcher(opts) {
|
|
|
457
900
|
activeToolId: activeTool.active,
|
|
458
901
|
hotkeyStack: activeTool.hotkeyStack,
|
|
459
902
|
entriesById,
|
|
460
|
-
isMac
|
|
903
|
+
isMac
|
|
461
904
|
});
|
|
462
905
|
const rootTargetRef = useLatest({
|
|
463
906
|
id: null,
|
|
@@ -473,676 +916,46 @@ function useGestureDispatcher(opts) {
|
|
|
473
916
|
...(viewsRef.current?.targets() ?? []).map((t) => t.dispatcher)
|
|
474
917
|
], [rootTargetRef, viewsRef]);
|
|
475
918
|
const requestRedrawRef = useLatest(requestRedraw);
|
|
476
|
-
const
|
|
919
|
+
const cursorRef = useLatest({ painted: paintedCursor, sink: cursorSink });
|
|
477
920
|
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) {
|
|
921
|
+
const clicksRef = useRef({ last: null });
|
|
922
|
+
useToolLifecycle({
|
|
923
|
+
entriesById,
|
|
924
|
+
active: activeTool.active,
|
|
925
|
+
hotkeyStack: activeTool.hotkeyStack,
|
|
926
|
+
// The incoming tool must not inherit a live gesture.
|
|
927
|
+
onSwitch: () => {
|
|
509
928
|
for (const d of allDispatchers()) d.cancelAll("cancel");
|
|
510
929
|
}
|
|
511
|
-
prevActiveRef.current = activeTool.active;
|
|
512
|
-
for (const id of next) if (!prev.has(id)) fireLifecycle(id, "onActivate");
|
|
513
930
|
});
|
|
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
931
|
useEffect(() => {
|
|
520
932
|
if (enabled && claimKeys) registryRef.current.activate();
|
|
521
933
|
}, [enabled, claimKeys, registryRef]);
|
|
522
934
|
useEffect(() => {
|
|
523
935
|
if (!enabled) return;
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
const canvas = canvasRef.current;
|
|
549
|
-
const toWorld = (cx, cy) => {
|
|
550
|
-
const fn = target().clientToWorld;
|
|
551
|
-
return fn ? fn(cx, cy) : { x: cx, y: cy };
|
|
552
|
-
};
|
|
553
|
-
const dispatch = (ev, on) => {
|
|
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 = {
|
|
562
|
-
canvas,
|
|
563
|
-
target,
|
|
564
|
-
routeAt,
|
|
565
|
-
routedView: () => routedId,
|
|
566
|
-
setRoutedView: (id) => {
|
|
567
|
-
routedId = id;
|
|
936
|
+
return attachInput(canvasRef.current, {
|
|
937
|
+
root: () => rootTargetRef.current,
|
|
938
|
+
views: () => viewsRef.current,
|
|
939
|
+
context: () => ctxRef.current,
|
|
940
|
+
actions: () => registryRef.current,
|
|
941
|
+
dispatchers: allDispatchers,
|
|
942
|
+
requestRedraw: () => {
|
|
943
|
+
requestRedrawRef.current?.();
|
|
568
944
|
},
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const { onDragOver, onDragLeave, onDrop, onPaste } = ingestInput(core);
|
|
584
|
-
const held = /* @__PURE__ */ new Map();
|
|
585
|
-
const toCanvasLocal = (p) => {
|
|
586
|
-
const rect = canvas?.getBoundingClientRect();
|
|
587
|
-
if (!rect) return p;
|
|
588
|
-
const [x, y] = clientToCanvasRect(rect, p.x, p.y);
|
|
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;
|
|
655
|
-
const onWindowBlur = () => {
|
|
656
|
-
pinch.end();
|
|
657
|
-
for (const p of [...held.values()]) p.session.cancel();
|
|
658
|
-
keys.releaseAll();
|
|
659
|
-
};
|
|
660
|
-
let claimedDoubleClick = false;
|
|
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
|
-
};
|
|
1085
|
-
const keyCapture = keyboard === "first";
|
|
1086
|
-
if (keyboard) {
|
|
1087
|
-
window.addEventListener("keydown", onKeyDown, keyCapture);
|
|
1088
|
-
window.addEventListener("keyup", onKeyUp, keyCapture);
|
|
1089
|
-
}
|
|
1090
|
-
window.addEventListener("blur", onWindowBlur);
|
|
1091
|
-
canvas?.addEventListener("pointerdown", onPressActivate);
|
|
1092
|
-
window.addEventListener("keydown", scheduleHoverCursorRefresh);
|
|
1093
|
-
window.addEventListener("keyup", scheduleHoverCursorRefresh);
|
|
1094
|
-
if (pointerChannel) {
|
|
1095
|
-
canvas?.addEventListener("pointerleave", onHoverPointerLeave);
|
|
1096
|
-
canvas?.addEventListener("pointerdown", onPointerDown);
|
|
1097
|
-
canvas?.addEventListener("pointermove", onHoverMove);
|
|
1098
|
-
canvas?.addEventListener("dblclick", onNativeDoubleClick);
|
|
1099
|
-
}
|
|
1100
|
-
if (wheelChannel) canvas?.addEventListener("wheel", onWheel, { passive: false, capture: true });
|
|
1101
|
-
if (pinchChannel) {
|
|
1102
|
-
canvas?.addEventListener("gesturestart", onGestureStart);
|
|
1103
|
-
canvas?.addEventListener("gesturechange", onGestureChange);
|
|
1104
|
-
canvas?.addEventListener("gestureend", onGestureEnd);
|
|
1105
|
-
}
|
|
1106
|
-
if (contextMenuChannel) canvas?.addEventListener("contextmenu", onContextMenu);
|
|
1107
|
-
if (ingestChannel) {
|
|
1108
|
-
canvas?.addEventListener("dragenter", onDragOver);
|
|
1109
|
-
canvas?.addEventListener("dragover", onDragOver);
|
|
1110
|
-
canvas?.addEventListener("dragleave", onDragLeave);
|
|
1111
|
-
canvas?.addEventListener("drop", onDrop);
|
|
1112
|
-
window.addEventListener("paste", onPaste);
|
|
1113
|
-
}
|
|
1114
|
-
return () => {
|
|
1115
|
-
if (keyboard) {
|
|
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);
|
|
1137
|
-
canvas?.classList.remove(DROPOVER_CLASS);
|
|
1138
|
-
registryRef.current.dropKeys();
|
|
1139
|
-
clearHoverCursor();
|
|
1140
|
-
cancelAllLongPress();
|
|
1141
|
-
disposed = true;
|
|
1142
|
-
for (const p of held.values()) p.session.cancel();
|
|
1143
|
-
held.clear();
|
|
1144
|
-
for (const d of allDispatchers()) d.cancelAll("cancel");
|
|
1145
|
-
};
|
|
945
|
+
paintedCursor: () => cursorRef.current.painted?.(),
|
|
946
|
+
cursorSink: () => cursorRef.current.sink?.(),
|
|
947
|
+
longPress: () => longPressRef.current,
|
|
948
|
+
onClick: () => onClickRef.current,
|
|
949
|
+
onDoubleClick: () => onDoubleClickRef.current,
|
|
950
|
+
clicks: clicksRef.current
|
|
951
|
+
}, {
|
|
952
|
+
keyboard,
|
|
953
|
+
pointer: pointerChannel,
|
|
954
|
+
wheel: wheelChannel,
|
|
955
|
+
pinch: pinchChannel,
|
|
956
|
+
contextMenu: contextMenuChannel,
|
|
957
|
+
ingest: ingestChannel
|
|
958
|
+
});
|
|
1146
959
|
}, [
|
|
1147
960
|
enabled,
|
|
1148
961
|
keyboard,
|
|
@@ -1157,7 +970,7 @@ function useGestureDispatcher(opts) {
|
|
|
1157
970
|
longPressRef,
|
|
1158
971
|
onClickRef,
|
|
1159
972
|
onDoubleClickRef,
|
|
1160
|
-
|
|
973
|
+
cursorRef,
|
|
1161
974
|
registryRef,
|
|
1162
975
|
requestRedrawRef,
|
|
1163
976
|
rootTargetRef,
|
|
@@ -1186,6 +999,7 @@ var ActionsNode = class extends ScopeNode {
|
|
|
1186
999
|
contextDeps;
|
|
1187
1000
|
actions = /* @__PURE__ */ new Map();
|
|
1188
1001
|
muted = /* @__PURE__ */ new Map();
|
|
1002
|
+
tunings = /* @__PURE__ */ new Map();
|
|
1189
1003
|
dispatchers = [];
|
|
1190
1004
|
cache = null;
|
|
1191
1005
|
/** Called on `activate`, so a scope's dep and tool nodes move with it. */
|
|
@@ -1194,10 +1008,19 @@ var ActionsNode = class extends ScopeNode {
|
|
|
1194
1008
|
for (const n of this.chain()) {
|
|
1195
1009
|
if (n.muted.has(id)) return void 0;
|
|
1196
1010
|
const a = n.actions.get(id)?.at(-1);
|
|
1197
|
-
if (a) return a;
|
|
1011
|
+
if (a) return this.tuned(a);
|
|
1198
1012
|
}
|
|
1199
1013
|
return void 0;
|
|
1200
1014
|
}
|
|
1015
|
+
/** `a` under every tuning for its id from the root down to here. */
|
|
1016
|
+
tuned(a) {
|
|
1017
|
+
const tunings = [];
|
|
1018
|
+
for (const n of this.chain()) {
|
|
1019
|
+
const stack = n.tunings.get(a.id);
|
|
1020
|
+
if (stack) tunings.unshift(...stack);
|
|
1021
|
+
}
|
|
1022
|
+
return tunings.length ? tuneAction(a, tunings) : a;
|
|
1023
|
+
}
|
|
1201
1024
|
snapshot() {
|
|
1202
1025
|
const leaf = this.start();
|
|
1203
1026
|
const cached = this.cache;
|
|
@@ -1274,24 +1097,35 @@ var ActionsNode = class extends ScopeNode {
|
|
|
1274
1097
|
this.changed();
|
|
1275
1098
|
};
|
|
1276
1099
|
},
|
|
1100
|
+
tune: (id, tuning) => {
|
|
1101
|
+
let stack = this.tunings.get(id);
|
|
1102
|
+
if (!stack) this.tunings.set(id, stack = []);
|
|
1103
|
+
const release = pushOwner(stack, tuning);
|
|
1104
|
+
this.changed();
|
|
1105
|
+
let released = false;
|
|
1106
|
+
return () => {
|
|
1107
|
+
if (released) return;
|
|
1108
|
+
released = true;
|
|
1109
|
+
release();
|
|
1110
|
+
if (stack.length === 0) this.tunings.delete(id);
|
|
1111
|
+
this.changed();
|
|
1112
|
+
};
|
|
1113
|
+
},
|
|
1277
1114
|
list: () => this.snapshot(),
|
|
1278
1115
|
trigger: (id, params) => {
|
|
1279
1116
|
const leaf = this.start();
|
|
1280
1117
|
const a = leaf.resolve(id);
|
|
1281
1118
|
if (!a?.invoker || a.invoker.timing !== "immediate") return false;
|
|
1119
|
+
const deps = depsFor(a, leaf.deps());
|
|
1120
|
+
if (!evaluateEnabled(a, deps).enabled) return false;
|
|
1282
1121
|
try {
|
|
1283
|
-
a.invoker.run(
|
|
1122
|
+
a.invoker.run(deps, params);
|
|
1284
1123
|
} catch (err) {
|
|
1285
1124
|
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
1286
1125
|
}
|
|
1287
1126
|
return true;
|
|
1288
1127
|
},
|
|
1289
|
-
subscribe: (listener) =>
|
|
1290
|
-
this.tree.listeners.add(listener);
|
|
1291
|
-
return () => {
|
|
1292
|
-
this.tree.listeners.delete(listener);
|
|
1293
|
-
};
|
|
1294
|
-
},
|
|
1128
|
+
subscribe: (listener) => this.tree.listeners.subscribe(listener),
|
|
1295
1129
|
setDispatcher: (d) => pushOwner(this.dispatchers, d),
|
|
1296
1130
|
begin: (id, params) => {
|
|
1297
1131
|
const leaf = this.start();
|
|
@@ -1404,9 +1238,112 @@ function useOngoingAction(actionId) {
|
|
|
1404
1238
|
}
|
|
1405
1239
|
};
|
|
1406
1240
|
}, [regRef, idRef]);
|
|
1407
|
-
|
|
1241
|
+
const leaving = useRef(null);
|
|
1242
|
+
useEffect(() => {
|
|
1243
|
+
const left = leaving.current;
|
|
1244
|
+
leaving.current = null;
|
|
1245
|
+
if (left !== null && left !== actionId) edit.commit();
|
|
1246
|
+
return () => {
|
|
1247
|
+
leaving.current = actionId;
|
|
1248
|
+
queueMicrotask(() => {
|
|
1249
|
+
if (leaving.current !== actionId) return;
|
|
1250
|
+
leaving.current = null;
|
|
1251
|
+
edit.commit();
|
|
1252
|
+
});
|
|
1253
|
+
};
|
|
1254
|
+
}, [edit, actionId]);
|
|
1408
1255
|
return edit;
|
|
1409
1256
|
}
|
|
1257
|
+
function samePos(a, b) {
|
|
1258
|
+
if (a === null || b === null) return a === b;
|
|
1259
|
+
return a.worldX === b.worldX && a.worldY === b.worldY && a.viewId === b.viewId;
|
|
1260
|
+
}
|
|
1261
|
+
function createPointerStore() {
|
|
1262
|
+
let current = null;
|
|
1263
|
+
let version = 0;
|
|
1264
|
+
const changed = createEmitter();
|
|
1265
|
+
const get = () => current;
|
|
1266
|
+
return {
|
|
1267
|
+
get,
|
|
1268
|
+
set(next) {
|
|
1269
|
+
if (samePos(current, next)) return;
|
|
1270
|
+
current = next;
|
|
1271
|
+
version++;
|
|
1272
|
+
changed.emit();
|
|
1273
|
+
},
|
|
1274
|
+
subscribe: changed.subscribe,
|
|
1275
|
+
getVersion: () => version,
|
|
1276
|
+
getDropPoint: get
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
var PointerContext = createContext(null);
|
|
1280
|
+
function PointerContextProvider({ children, store }) {
|
|
1281
|
+
const [own] = useState(createPointerStore);
|
|
1282
|
+
return /* @__PURE__ */ jsx(PointerContext.Provider, { value: store ?? own, children });
|
|
1283
|
+
}
|
|
1284
|
+
function usePointerContext() {
|
|
1285
|
+
return useContext(PointerContext);
|
|
1286
|
+
}
|
|
1287
|
+
var noSubscribe = () => () => {
|
|
1288
|
+
};
|
|
1289
|
+
var noPointer = () => null;
|
|
1290
|
+
function usePointerPosition() {
|
|
1291
|
+
const store = usePointerContext();
|
|
1292
|
+
return useSyncExternalStore(
|
|
1293
|
+
store ? store.subscribe : noSubscribe,
|
|
1294
|
+
store ? store.get : noPointer,
|
|
1295
|
+
store ? store.get : noPointer
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
var SelectionContext = createContext(null);
|
|
1299
|
+
function SelectionContextProvider({ children }) {
|
|
1300
|
+
const [state, setState] = useState({ selection: [], kinds: void 0 });
|
|
1301
|
+
const lastSerializedRef = useRef("");
|
|
1302
|
+
const publishSelection = useCallback((ids, kinds) => {
|
|
1303
|
+
const next = JSON.stringify([ids, kinds]);
|
|
1304
|
+
if (next === lastSerializedRef.current) return;
|
|
1305
|
+
lastSerializedRef.current = next;
|
|
1306
|
+
setState({ selection: ids, kinds });
|
|
1307
|
+
}, []);
|
|
1308
|
+
const value = useMemo(
|
|
1309
|
+
() => ({
|
|
1310
|
+
selection: state.selection,
|
|
1311
|
+
...state.kinds !== void 0 ? { kinds: state.kinds } : {},
|
|
1312
|
+
publishSelection
|
|
1313
|
+
}),
|
|
1314
|
+
[state, publishSelection]
|
|
1315
|
+
);
|
|
1316
|
+
return /* @__PURE__ */ jsx(SelectionContext.Provider, { value, children });
|
|
1317
|
+
}
|
|
1318
|
+
function useSelectionContext() {
|
|
1319
|
+
return useContext(SelectionContext);
|
|
1320
|
+
}
|
|
1321
|
+
function SelectionContextProviderIfRoot({
|
|
1322
|
+
children
|
|
1323
|
+
}) {
|
|
1324
|
+
const parent = useContext(SelectionContext);
|
|
1325
|
+
if (parent !== null) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
1326
|
+
return /* @__PURE__ */ jsx(SelectionContextProvider, { children });
|
|
1327
|
+
}
|
|
1328
|
+
function usePublishSelection(ids, kinds) {
|
|
1329
|
+
const ctx = useContext(SelectionContext);
|
|
1330
|
+
const idsRef = useLatest(ids);
|
|
1331
|
+
const kindsRef = useLatest(kinds);
|
|
1332
|
+
const serialized = JSON.stringify([ids, kinds]);
|
|
1333
|
+
const publish = ctx?.publishSelection;
|
|
1334
|
+
useEffect(() => {
|
|
1335
|
+
if (!publish) return;
|
|
1336
|
+
publish(idsRef.current, kindsRef.current);
|
|
1337
|
+
}, [publish, serialized, idsRef, kindsRef]);
|
|
1338
|
+
const ctxRef = useLatest(ctx);
|
|
1339
|
+
const serializedRef = useLatest(serialized);
|
|
1340
|
+
useEffect(() => () => {
|
|
1341
|
+
const slot = ctxRef.current;
|
|
1342
|
+
if (slot && JSON.stringify([slot.selection, slot.kinds]) === serializedRef.current) {
|
|
1343
|
+
slot.publishSelection([]);
|
|
1344
|
+
}
|
|
1345
|
+
}, [ctxRef, serializedRef]);
|
|
1346
|
+
}
|
|
1410
1347
|
var NO_MODES = () => () => {
|
|
1411
1348
|
};
|
|
1412
1349
|
function useContributions(opts) {
|
|
@@ -1596,6 +1533,6 @@ function useTools(opts) {
|
|
|
1596
1533
|
);
|
|
1597
1534
|
}
|
|
1598
1535
|
|
|
1599
|
-
export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, InputScope, Yoke, useAction, useActionsRegistry, useActiveToolContext, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useOffhandAction, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, useTools, useYoke };
|
|
1536
|
+
export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, InputScope, PointerContextProvider, SelectionContextProvider, SelectionContextProviderIfRoot, Yoke, createPointerStore, useAction, useActionsRegistry, useActiveToolContext, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useOffhandAction, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
|
|
1600
1537
|
//# sourceMappingURL=react.js.map
|
|
1601
1538
|
//# sourceMappingURL=react.js.map
|