@weasel-js/routing 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/{chunk-Q3Y3DPKH.js → chunk-ZGKJWZ4S.js} +31 -10
- package/dist/chunk-ZGKJWZ4S.js.map +1 -0
- package/dist/{index-Bmq4lSHY.d.ts → index-BV53gfyB.d.ts} +115 -42
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/internal--mYnGecm.d.ts +275 -0
- package/dist/internal.d.ts +7 -0
- package/dist/internal.js +3 -0
- package/dist/internal.js.map +1 -0
- package/dist/react.d.ts +8 -272
- package/dist/react.js +121 -130
- package/dist/react.js.map +1 -1
- package/package.json +10 -5
- package/dist/chunk-Q3Y3DPKH.js.map +0 -1
package/dist/react.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createDepRegistry, createDispatcher, scopeBindings, liveScope, isDev, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog,
|
|
2
|
-
import { createContext, useState, useContext, useMemo,
|
|
1
|
+
import { createDepRegistry, createDispatcher, isTool, scopeBindings, liveScope, isDev, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, isEditableTarget, clientToCanvasRect, openPointerSession, LONG_PRESS_MS, publishLiveDispatch, pointerGestureId, itemsFromDataTransfer, itemsFromClipboardData, ScopeNode, pushOwner, validateActionId, validateActionDefaultBinding, depNodeOf, DepNode, buildDepsFromRequires } from './chunk-ZGKJWZ4S.js';
|
|
2
|
+
import { createContext, useState, useContext, useMemo, useEffect, useRef, useCallback, useSyncExternalStore } from 'react';
|
|
3
|
+
import { useLatest, useStableByContent, sameList } from '@weasel-js/react';
|
|
3
4
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
5
|
import { pastDragThreshold } from '@weasel-js/gestures';
|
|
5
6
|
import { resolveCursorTier } from '@weasel-js/cursor';
|
|
@@ -109,11 +110,10 @@ function useOptionalDepRegistry() {
|
|
|
109
110
|
}
|
|
110
111
|
function useDepSource(name, source) {
|
|
111
112
|
const registry = useDepRegistry();
|
|
112
|
-
const sourceRef =
|
|
113
|
-
sourceRef.current = source;
|
|
113
|
+
const sourceRef = useLatest(source);
|
|
114
114
|
useEffect(() => {
|
|
115
115
|
return registry.register(name, () => sourceRef.current());
|
|
116
|
-
}, [name, registry]);
|
|
116
|
+
}, [name, registry, sourceRef]);
|
|
117
117
|
}
|
|
118
118
|
var DROPOVER_CLASS = "weasel-dropover";
|
|
119
119
|
function stylusOf(e) {
|
|
@@ -156,23 +156,21 @@ function computeMultiTouchGeometry(positions) {
|
|
|
156
156
|
return { centroid, spread };
|
|
157
157
|
}
|
|
158
158
|
function useGestureDispatcher(opts) {
|
|
159
|
-
const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, requestRedraw, paintedCursor, getRuleCtx, onDoubleClick, views, channels } = opts;
|
|
159
|
+
const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, requestRedraw, paintedCursor, getRuleCtx, onDoubleClick, views, channels, longPress } = opts;
|
|
160
160
|
const pointerChannel = channels?.pointer !== false;
|
|
161
161
|
const wheelChannel = channels?.wheel !== false;
|
|
162
162
|
const pinchChannel = channels?.pinch !== false;
|
|
163
163
|
const contextMenuChannel = channels?.contextMenu !== false;
|
|
164
164
|
const ingestChannel = channels?.ingest !== false;
|
|
165
|
-
const onDoubleClickRef =
|
|
166
|
-
onDoubleClickRef.current = onDoubleClick;
|
|
165
|
+
const onDoubleClickRef = useLatest(onDoubleClick);
|
|
167
166
|
const activeTool = useActiveToolContext();
|
|
168
167
|
const depRegistry = useDepRegistry();
|
|
169
168
|
const dispatcherRef = useRef(null);
|
|
170
169
|
if (!dispatcherRef.current) {
|
|
171
170
|
dispatcherRef.current = dispatcherOpt ?? createDispatcher();
|
|
172
171
|
}
|
|
173
|
-
const registryRef =
|
|
174
|
-
|
|
175
|
-
const ctxRef = useRef({
|
|
172
|
+
const registryRef = useLatest(actions);
|
|
173
|
+
const ctxRef = useLatest({
|
|
176
174
|
actions,
|
|
177
175
|
depRegistry,
|
|
178
176
|
activeToolId: activeTool.active,
|
|
@@ -180,15 +178,7 @@ function useGestureDispatcher(opts) {
|
|
|
180
178
|
entriesById,
|
|
181
179
|
isMac: IS_MAC
|
|
182
180
|
});
|
|
183
|
-
|
|
184
|
-
actions,
|
|
185
|
-
depRegistry,
|
|
186
|
-
activeToolId: activeTool.active,
|
|
187
|
-
hotkeyStack: activeTool.hotkeyStack,
|
|
188
|
-
entriesById,
|
|
189
|
-
isMac: IS_MAC
|
|
190
|
-
};
|
|
191
|
-
const rootTargetRef = useRef({
|
|
181
|
+
const rootTargetRef = useLatest({
|
|
192
182
|
id: null,
|
|
193
183
|
dispatcher: dispatcherRef.current,
|
|
194
184
|
affordanceAt,
|
|
@@ -196,30 +186,19 @@ function useGestureDispatcher(opts) {
|
|
|
196
186
|
clientToWorld,
|
|
197
187
|
getRuleCtx
|
|
198
188
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
dispatcher: dispatcherRef.current,
|
|
202
|
-
affordanceAt,
|
|
203
|
-
classifyTarget,
|
|
204
|
-
clientToWorld,
|
|
205
|
-
getRuleCtx
|
|
206
|
-
};
|
|
207
|
-
const viewsRef = useRef(views);
|
|
208
|
-
viewsRef.current = views;
|
|
209
|
-
const allDispatchers = () => [
|
|
189
|
+
const viewsRef = useLatest(views);
|
|
190
|
+
const allDispatchers = useCallback(() => [
|
|
210
191
|
rootTargetRef.current.dispatcher,
|
|
211
192
|
...(viewsRef.current?.targets() ?? []).map((t) => t.dispatcher)
|
|
212
|
-
];
|
|
213
|
-
const requestRedrawRef =
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
paintedCursorRef.current = paintedCursor;
|
|
193
|
+
], [rootTargetRef, viewsRef]);
|
|
194
|
+
const requestRedrawRef = useLatest(requestRedraw);
|
|
195
|
+
const paintedCursorRef = useLatest(paintedCursor);
|
|
196
|
+
const longPressRef = useLatest(longPress);
|
|
217
197
|
const lastClickRef = useRef(null);
|
|
218
198
|
const liveToolsRef = useRef(/* @__PURE__ */ new Set());
|
|
219
199
|
const prevActiveRef = useRef(activeTool.active);
|
|
220
|
-
const entriesByIdRef =
|
|
221
|
-
|
|
222
|
-
const fireLifecycle = (id, hook) => {
|
|
200
|
+
const entriesByIdRef = useLatest(entriesById);
|
|
201
|
+
const fireLifecycle = useCallback((id, hook) => {
|
|
223
202
|
const tool = entriesByIdRef.current.get(id);
|
|
224
203
|
if (!tool || !isTool(tool)) return;
|
|
225
204
|
const fn = tool[hook];
|
|
@@ -229,7 +208,7 @@ function useGestureDispatcher(opts) {
|
|
|
229
208
|
} catch (err) {
|
|
230
209
|
console.error(`weasel: tool "${id}".${hook} threw`, err);
|
|
231
210
|
}
|
|
232
|
-
};
|
|
211
|
+
}, [entriesByIdRef]);
|
|
233
212
|
useEffect(() => {
|
|
234
213
|
const prev = liveToolsRef.current;
|
|
235
214
|
const next = new Set(
|
|
@@ -247,7 +226,7 @@ function useGestureDispatcher(opts) {
|
|
|
247
226
|
const live = liveToolsRef.current;
|
|
248
227
|
liveToolsRef.current = /* @__PURE__ */ new Set();
|
|
249
228
|
for (const id of live) fireLifecycle(id, "onDeactivate");
|
|
250
|
-
}, []);
|
|
229
|
+
}, [fireLifecycle]);
|
|
251
230
|
useEffect(() => {
|
|
252
231
|
if (!enabled) return;
|
|
253
232
|
let disposed = false;
|
|
@@ -312,22 +291,27 @@ function useGestureDispatcher(opts) {
|
|
|
312
291
|
};
|
|
313
292
|
let multiTouchStart = null;
|
|
314
293
|
const TAP_THRESHOLD_PX = 8;
|
|
315
|
-
const LONG_PRESS_MS = 500;
|
|
316
294
|
const longPressTimers = /* @__PURE__ */ new Map();
|
|
295
|
+
const clearPending = (pointerId) => {
|
|
296
|
+
const store = longPressRef.current?.state;
|
|
297
|
+
const p = store?.get();
|
|
298
|
+
if (p && (pointerId === void 0 || p.pointerId === pointerId)) store.set(null);
|
|
299
|
+
};
|
|
317
300
|
const cancelLongPress = (pointerId) => {
|
|
318
301
|
const t = longPressTimers.get(pointerId);
|
|
319
302
|
if (t !== void 0) {
|
|
320
303
|
clearTimeout(t);
|
|
321
304
|
longPressTimers.delete(pointerId);
|
|
322
305
|
}
|
|
306
|
+
clearPending(pointerId);
|
|
323
307
|
};
|
|
324
308
|
const cancelAllLongPress = () => {
|
|
325
309
|
for (const t of longPressTimers.values()) clearTimeout(t);
|
|
326
310
|
longPressTimers.clear();
|
|
311
|
+
clearPending();
|
|
327
312
|
};
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
if (!down) return;
|
|
313
|
+
const HAPTIC_MS = 15;
|
|
314
|
+
const longPressEvents = (down) => {
|
|
331
315
|
const shared = {
|
|
332
316
|
target: down.target,
|
|
333
317
|
altKey: down.altKey,
|
|
@@ -338,23 +322,26 @@ function useGestureDispatcher(opts) {
|
|
|
338
322
|
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
339
323
|
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
340
324
|
};
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
325
|
+
const at = { x: down.worldX, y: down.worldY, clientX: down.clientX, clientY: down.clientY };
|
|
326
|
+
return {
|
|
327
|
+
longpress: { kind: "longpress", ...at, ...shared },
|
|
328
|
+
contextmenu: { kind: "contextmenu", ...at, ...shared }
|
|
329
|
+
};
|
|
330
|
+
};
|
|
331
|
+
const longPressWouldFire = (down) => {
|
|
332
|
+
const evs = longPressEvents(down);
|
|
333
|
+
const d = dispatcherNow();
|
|
334
|
+
return d.resolveOnly(evs.longpress, ctxNow()) !== null || d.resolveOnly(evs.contextmenu, ctxNow()) !== null;
|
|
335
|
+
};
|
|
336
|
+
const fireLongPress = (pointerId, pointerType) => {
|
|
337
|
+
const down = held.get(pointerId)?.down;
|
|
338
|
+
clearPending(pointerId);
|
|
339
|
+
if (!down) return;
|
|
340
|
+
const evs = longPressEvents(down);
|
|
341
|
+
let result = dispatch(evs.longpress);
|
|
342
|
+
if (result === "unhandled") result = dispatch(evs.contextmenu);
|
|
343
|
+
if (result === "handled" && longPressRef.current?.haptics !== false && (pointerType === "touch" || pointerType === "pen") && typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
344
|
+
navigator.vibrate(HAPTIC_MS);
|
|
358
345
|
}
|
|
359
346
|
};
|
|
360
347
|
const DOUBLE_CLICK_MAX_MS = 600;
|
|
@@ -567,14 +554,30 @@ function useGestureDispatcher(opts) {
|
|
|
567
554
|
if (held.size < 2) dispatch({ ...ev, stage: "press" });
|
|
568
555
|
if ((e.pointerType === "touch" || e.pointerType === "pen") && held.size === 1) {
|
|
569
556
|
cancelLongPress(e.pointerId);
|
|
557
|
+
const duration = longPressRef.current?.duration ?? LONG_PRESS_MS;
|
|
570
558
|
longPressTimers.set(
|
|
571
559
|
e.pointerId,
|
|
572
560
|
setTimeout(() => {
|
|
573
561
|
longPressTimers.delete(e.pointerId);
|
|
574
562
|
if (disposed) return;
|
|
575
|
-
fireLongPress(e.pointerId);
|
|
576
|
-
},
|
|
563
|
+
fireLongPress(e.pointerId, e.pointerType);
|
|
564
|
+
}, duration)
|
|
577
565
|
);
|
|
566
|
+
const store = longPressRef.current?.state;
|
|
567
|
+
const rec = held.get(e.pointerId);
|
|
568
|
+
if (store && rec) {
|
|
569
|
+
store.set({
|
|
570
|
+
pointerId: e.pointerId,
|
|
571
|
+
pointerType: e.pointerType,
|
|
572
|
+
client: { x: e.clientX, y: e.clientY },
|
|
573
|
+
local: toCanvasLocal({ x: e.clientX, y: e.clientY }),
|
|
574
|
+
world: { x: w.x, y: w.y },
|
|
575
|
+
viewId: target().id,
|
|
576
|
+
startedAt: performance.now(),
|
|
577
|
+
duration,
|
|
578
|
+
armed: longPressWouldFire(rec.down)
|
|
579
|
+
});
|
|
580
|
+
}
|
|
578
581
|
}
|
|
579
582
|
if (held.size >= 2) {
|
|
580
583
|
cancelAllLongPress();
|
|
@@ -1017,7 +1020,25 @@ function useGestureDispatcher(opts) {
|
|
|
1017
1020
|
held.clear();
|
|
1018
1021
|
for (const d of allDispatchers()) d.cancelAll("cancel");
|
|
1019
1022
|
};
|
|
1020
|
-
}, [
|
|
1023
|
+
}, [
|
|
1024
|
+
enabled,
|
|
1025
|
+
keyboard,
|
|
1026
|
+
canvasRef,
|
|
1027
|
+
pointerChannel,
|
|
1028
|
+
wheelChannel,
|
|
1029
|
+
pinchChannel,
|
|
1030
|
+
contextMenuChannel,
|
|
1031
|
+
ingestChannel,
|
|
1032
|
+
allDispatchers,
|
|
1033
|
+
ctxRef,
|
|
1034
|
+
longPressRef,
|
|
1035
|
+
onDoubleClickRef,
|
|
1036
|
+
paintedCursorRef,
|
|
1037
|
+
registryRef,
|
|
1038
|
+
requestRedrawRef,
|
|
1039
|
+
rootTargetRef,
|
|
1040
|
+
viewsRef
|
|
1041
|
+
]);
|
|
1021
1042
|
}
|
|
1022
1043
|
var ActionsContext = createContext(null);
|
|
1023
1044
|
function depsFor(a, r) {
|
|
@@ -1162,9 +1183,8 @@ function actionsNodeOf(registry) {
|
|
|
1162
1183
|
}
|
|
1163
1184
|
function ActionsProvider({ children }) {
|
|
1164
1185
|
const depReg = useOptionalDepRegistry();
|
|
1165
|
-
const depRegRef =
|
|
1166
|
-
depRegRef.current
|
|
1167
|
-
const node = useMemo(() => new ActionsNode(null, true, () => depRegRef.current), []);
|
|
1186
|
+
const depRegRef = useLatest(depReg);
|
|
1187
|
+
const node = useMemo(() => new ActionsNode(null, true, () => depRegRef.current), [depRegRef]);
|
|
1168
1188
|
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: node.registry, children });
|
|
1169
1189
|
}
|
|
1170
1190
|
var IS_DEV = (() => {
|
|
@@ -1228,10 +1248,8 @@ function Yoke({ value, children }) {
|
|
|
1228
1248
|
}
|
|
1229
1249
|
function useOngoingAction(actionId) {
|
|
1230
1250
|
const reg = useActionsRegistry();
|
|
1231
|
-
const regRef =
|
|
1232
|
-
|
|
1233
|
-
const idRef = useRef(actionId);
|
|
1234
|
-
idRef.current = actionId;
|
|
1251
|
+
const regRef = useLatest(reg);
|
|
1252
|
+
const idRef = useLatest(actionId);
|
|
1235
1253
|
const ctrlRef = useRef(null);
|
|
1236
1254
|
const edit = useMemo(() => {
|
|
1237
1255
|
const end = (reason) => {
|
|
@@ -1255,51 +1273,43 @@ function useOngoingAction(actionId) {
|
|
|
1255
1273
|
end("cancel");
|
|
1256
1274
|
}
|
|
1257
1275
|
};
|
|
1258
|
-
}, []);
|
|
1276
|
+
}, [regRef, idRef]);
|
|
1259
1277
|
useEffect(() => () => edit.commit(), [edit, actionId]);
|
|
1260
1278
|
return edit;
|
|
1261
1279
|
}
|
|
1262
1280
|
function useContributions(opts) {
|
|
1263
1281
|
const ctx = useActiveToolContext();
|
|
1264
1282
|
const actionsRegistry = useActionsRegistry();
|
|
1265
|
-
const
|
|
1266
|
-
const isFirstRender =
|
|
1267
|
-
|
|
1268
|
-
if (isFirstRender) {
|
|
1269
|
-
hasInitializedRef.current = true;
|
|
1270
|
-
needsFirstMountSyncRef.current = ctx.active === null && opts.focused !== null;
|
|
1271
|
-
}
|
|
1283
|
+
const firstMountRef = useRef(null);
|
|
1284
|
+
const isFirstRender = firstMountRef.current == null;
|
|
1285
|
+
if (firstMountRef.current == null) firstMountRef.current = { sync: ctx.active === null && opts.focused !== null };
|
|
1272
1286
|
useEffect(() => {
|
|
1273
|
-
|
|
1274
|
-
|
|
1287
|
+
const first = firstMountRef.current;
|
|
1288
|
+
if (!first?.sync) return;
|
|
1289
|
+
first.sync = false;
|
|
1275
1290
|
ctx.setActive(opts.focused);
|
|
1276
1291
|
}, []);
|
|
1277
1292
|
const focused = isFirstRender && ctx.active === null && opts.focused !== null ? opts.focused : ctx.active;
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1280
|
-
const focusedRef = useRef(focused);
|
|
1281
|
-
focusedRef.current = focused;
|
|
1282
|
-
const engagedRef = useRef(ctx.hotkeyStack);
|
|
1283
|
-
engagedRef.current = ctx.hotkeyStack;
|
|
1293
|
+
const entries = useStableByContent(opts.entries, sameList);
|
|
1294
|
+
const engaged = ctx.hotkeyStack;
|
|
1284
1295
|
const setFocused = useCallback((id) => {
|
|
1285
1296
|
ctx.setActive(id);
|
|
1286
1297
|
}, [ctx]);
|
|
1287
1298
|
const snapshot = useCallback(() => {
|
|
1288
|
-
const engaged = engagedRef.current;
|
|
1289
1299
|
const engagedIds = new Set(engaged);
|
|
1290
1300
|
const ordered = [];
|
|
1291
1301
|
for (const id of engaged) {
|
|
1292
|
-
const entry =
|
|
1302
|
+
const entry = entries.find((e) => e.id === id);
|
|
1293
1303
|
if (entry && !ordered.includes(entry)) ordered.push(entry);
|
|
1294
1304
|
}
|
|
1295
|
-
for (const entry of
|
|
1305
|
+
for (const entry of entries) {
|
|
1296
1306
|
if (!ordered.includes(entry)) ordered.push(entry);
|
|
1297
1307
|
}
|
|
1298
1308
|
return {
|
|
1299
1309
|
ordered,
|
|
1300
|
-
state: { focusedId:
|
|
1310
|
+
state: { focusedId: focused, engagedIds }
|
|
1301
1311
|
};
|
|
1302
|
-
}, []);
|
|
1312
|
+
}, [entries, focused, engaged]);
|
|
1303
1313
|
const scopedBindings = useCallback(() => {
|
|
1304
1314
|
const { ordered, state } = snapshot();
|
|
1305
1315
|
return scopeBindings(ordered, state);
|
|
@@ -1320,9 +1330,9 @@ function useContributions(opts) {
|
|
|
1320
1330
|
}
|
|
1321
1331
|
return [...active, ...hotkey, ...ambient];
|
|
1322
1332
|
}, [snapshot]);
|
|
1323
|
-
useOffhandAction(
|
|
1333
|
+
useOffhandAction(entries);
|
|
1324
1334
|
const lastConflictSigRef = useRef(null);
|
|
1325
|
-
const entrySig =
|
|
1335
|
+
const entrySig = entries.map((e) => e.id).join(",");
|
|
1326
1336
|
const modesRegistry = opts.modes;
|
|
1327
1337
|
useEffect(() => {
|
|
1328
1338
|
if (!isDev()) return;
|
|
@@ -1331,7 +1341,7 @@ function useContributions(opts) {
|
|
|
1331
1341
|
lastConflictSigRef.current = { sig: entrySig, modes: modesRegistry };
|
|
1332
1342
|
const registry = [];
|
|
1333
1343
|
const ambient = [];
|
|
1334
|
-
for (const entry of
|
|
1344
|
+
for (const entry of entries) {
|
|
1335
1345
|
if (entry.eligibility?.focus) registry.push(entry);
|
|
1336
1346
|
if (entry.eligibility?.always || entry.eligibility?.claimed) ambient.push(entry);
|
|
1337
1347
|
}
|
|
@@ -1341,18 +1351,10 @@ function useContributions(opts) {
|
|
|
1341
1351
|
actions: actionsRegistry?.list() ?? [],
|
|
1342
1352
|
...modesRegistry ? { modes: modesRegistry.list().map(activeModeOf) } : {}
|
|
1343
1353
|
});
|
|
1344
|
-
}, [entrySig, actionsRegistry, modesRegistry]);
|
|
1354
|
+
}, [entrySig, entries, actionsRegistry, modesRegistry]);
|
|
1345
1355
|
return useMemo(
|
|
1346
|
-
() => ({
|
|
1347
|
-
|
|
1348
|
-
return entriesRef.current;
|
|
1349
|
-
},
|
|
1350
|
-
focused,
|
|
1351
|
-
setFocused,
|
|
1352
|
-
scopedBindings,
|
|
1353
|
-
overlays
|
|
1354
|
-
}),
|
|
1355
|
-
[focused, setFocused, scopedBindings, overlays]
|
|
1356
|
+
() => ({ entries, focused, setFocused, scopedBindings, overlays }),
|
|
1357
|
+
[entries, focused, setFocused, scopedBindings, overlays]
|
|
1356
1358
|
);
|
|
1357
1359
|
}
|
|
1358
1360
|
function useOffhandAction(entries) {
|
|
@@ -1365,8 +1367,6 @@ function useOffhandAction(entries) {
|
|
|
1365
1367
|
return actionsRegistry.register(makeToolOffhandAction(buildToolOffhandBindings(specs)));
|
|
1366
1368
|
}, [actionsRegistry, offhandSig]);
|
|
1367
1369
|
}
|
|
1368
|
-
|
|
1369
|
-
// src/tools/useTools.ts
|
|
1370
1370
|
function declareSlot(tool, slot) {
|
|
1371
1371
|
const hotkey = tool.def?.hotkey;
|
|
1372
1372
|
const eligibility = {
|
|
@@ -1378,22 +1378,16 @@ function declareSlot(tool, slot) {
|
|
|
1378
1378
|
}
|
|
1379
1379
|
var NO_AMBIENT = [];
|
|
1380
1380
|
function useStableSources(registry, ambient) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
ref.current = next;
|
|
1386
|
-
}
|
|
1387
|
-
return ref.current;
|
|
1381
|
+
return useStableByContent(
|
|
1382
|
+
{ registry, ambient: ambient ?? NO_AMBIENT },
|
|
1383
|
+
(a, b) => sameRecord(a.registry, b.registry) && sameList(a.ambient, b.ambient)
|
|
1384
|
+
);
|
|
1388
1385
|
}
|
|
1389
1386
|
function sameRecord(a, b) {
|
|
1390
1387
|
const ak = Object.keys(a);
|
|
1391
1388
|
if (ak.length !== Object.keys(b).length) return false;
|
|
1392
1389
|
return ak.every((k) => k in b && a[k] === b[k]);
|
|
1393
1390
|
}
|
|
1394
|
-
function sameList(a, b) {
|
|
1395
|
-
return a.length === b.length && a.every((t, i) => t === b[i]);
|
|
1396
|
-
}
|
|
1397
1391
|
function sameEligibility(a, b) {
|
|
1398
1392
|
if (!a) return false;
|
|
1399
1393
|
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
@@ -1427,12 +1421,9 @@ function useTools(opts) {
|
|
|
1427
1421
|
...opts.modes ? { modes: opts.modes } : {}
|
|
1428
1422
|
});
|
|
1429
1423
|
const hotkeyEngaged = ctx.hotkeyStack.at(-1) ?? null;
|
|
1430
|
-
const slottedRef =
|
|
1431
|
-
|
|
1432
|
-
const
|
|
1433
|
-
activeRef.current = contributions.focused;
|
|
1434
|
-
const hotkeyRef = useRef(hotkeyEngaged);
|
|
1435
|
-
hotkeyRef.current = hotkeyEngaged;
|
|
1424
|
+
const slottedRef = useLatest(slotted);
|
|
1425
|
+
const activeRef = useLatest(contributions.focused);
|
|
1426
|
+
const hotkeyRef = useLatest(hotkeyEngaged);
|
|
1436
1427
|
const setFocused = contributions.setFocused;
|
|
1437
1428
|
const setActive = useCallback(
|
|
1438
1429
|
(id) => {
|
|
@@ -1442,7 +1433,7 @@ function useTools(opts) {
|
|
|
1442
1433
|
dlog("tools", "active:", activeRef.current, "\u2192", id);
|
|
1443
1434
|
setFocused(id);
|
|
1444
1435
|
},
|
|
1445
|
-
[setFocused]
|
|
1436
|
+
[setFocused, slottedRef, activeRef]
|
|
1446
1437
|
);
|
|
1447
1438
|
const engageHotkey = useCallback(
|
|
1448
1439
|
(id) => {
|
|
@@ -1452,15 +1443,15 @@ function useTools(opts) {
|
|
|
1452
1443
|
dlog("tools", "hotkey engaged:", id);
|
|
1453
1444
|
ctx.pushHotkey(id);
|
|
1454
1445
|
},
|
|
1455
|
-
[ctx]
|
|
1446
|
+
[ctx, slottedRef]
|
|
1456
1447
|
);
|
|
1457
1448
|
const disengageHotkey = useCallback(() => {
|
|
1458
1449
|
if (hotkeyRef.current) dlog("tools", "hotkey disengaged:", hotkeyRef.current);
|
|
1459
1450
|
ctx.popHotkey();
|
|
1460
|
-
}, [ctx]);
|
|
1451
|
+
}, [ctx, hotkeyRef]);
|
|
1461
1452
|
const has = useCallback(
|
|
1462
|
-
(id) => id in
|
|
1463
|
-
[]
|
|
1453
|
+
(id) => id in slotted.registry || slotted.ambient.some((t) => t.id === id),
|
|
1454
|
+
[slotted]
|
|
1464
1455
|
);
|
|
1465
1456
|
const active = contributions.focused;
|
|
1466
1457
|
const getActiveOverlays = contributions.overlays;
|