@behindthemusictree/genre-tree-view 6.4.0 → 6.5.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/index.cjs +181 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +181 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -803,6 +803,18 @@ function computeFitScale(contentWidth, contentHeight, viewportWidth, viewportHei
|
|
|
803
803
|
);
|
|
804
804
|
}
|
|
805
805
|
|
|
806
|
+
// src/info-panel-geometry.ts
|
|
807
|
+
function resolveInfoPanelSide() {
|
|
808
|
+
return "left";
|
|
809
|
+
}
|
|
810
|
+
function resolveInfoPanelObscuredArea(element, viewport, panelWidth) {
|
|
811
|
+
if (!element || !viewport) return null;
|
|
812
|
+
if (viewport.clientWidth > 0 && viewport.clientWidth <= INFO_PANEL_SHEET_MAX_WIDTH) {
|
|
813
|
+
return { side: "bottom", height: viewport.clientHeight * INFO_PANEL_SHEET_PEEK_FRACTION };
|
|
814
|
+
}
|
|
815
|
+
return { width: panelWidth, side: resolveInfoPanelSide() };
|
|
816
|
+
}
|
|
817
|
+
|
|
806
818
|
// src/use-pan-zoom.ts
|
|
807
819
|
function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
808
820
|
const [panX, setPanX] = (0, import_react.useState)(0);
|
|
@@ -813,6 +825,17 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
813
825
|
const settleTimerRef = (0, import_react.useRef)(null);
|
|
814
826
|
const stageAnimationRef = (0, import_react.useRef)(null);
|
|
815
827
|
const defaultViewportRef = (0, import_react.useRef)(options.defaultViewport);
|
|
828
|
+
const operationRef = (0, import_react.useRef)(0);
|
|
829
|
+
const restoredSelectionRef = (0, import_react.useRef)(void 0);
|
|
830
|
+
const restoredRevisionRef = (0, import_react.useRef)(void 0);
|
|
831
|
+
const restoredFrameRef = (0, import_react.useRef)(void 0);
|
|
832
|
+
const pendingRestoreRef = (0, import_react.useRef)(null);
|
|
833
|
+
const measuredFrameRef = (0, import_react.useRef)(void 0);
|
|
834
|
+
const fittedFrameRef = (0, import_react.useRef)(null);
|
|
835
|
+
const frameElementsRef = (0, import_react.useRef)([]);
|
|
836
|
+
const retryFrameRef = (0, import_react.useRef)(() => {
|
|
837
|
+
});
|
|
838
|
+
const pendingCenterRef = (0, import_react.useRef)(null);
|
|
816
839
|
const centerGuardUntilRef = (0, import_react.useRef)(0);
|
|
817
840
|
const onViewportChangeRef = (0, import_react.useRef)(options.onViewportChange);
|
|
818
841
|
(0, import_react.useEffect)(() => {
|
|
@@ -886,6 +909,7 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
886
909
|
}, [viewportRef]);
|
|
887
910
|
const zoomAtPoint = (0, import_react.useCallback)(
|
|
888
911
|
(computeNewScale, clientX, clientY) => {
|
|
912
|
+
operationRef.current += 1;
|
|
889
913
|
const viewport = viewportRef.current;
|
|
890
914
|
if (!viewport) {
|
|
891
915
|
return;
|
|
@@ -969,19 +993,26 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
969
993
|
const viewport = viewportRef.current;
|
|
970
994
|
if (!viewport) return;
|
|
971
995
|
const observer = new ResizeObserver((entries) => {
|
|
972
|
-
const entry = entries[0];
|
|
973
|
-
if (!entry)
|
|
996
|
+
const entry = entries?.[0];
|
|
997
|
+
if (!entry) {
|
|
998
|
+
retryFrameRef.current();
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
974
1001
|
const { width, height } = entry.contentRect;
|
|
975
1002
|
const previous = viewportSizeRef.current;
|
|
976
1003
|
viewportSizeRef.current = { width, height };
|
|
977
|
-
if (!previous)
|
|
1004
|
+
if (!previous || previous.width <= 0 || previous.height <= 0) {
|
|
1005
|
+
retryFrameRef.current();
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
978
1008
|
const dx = (width - previous.width) / 2;
|
|
979
1009
|
const dy = (height - previous.height) / 2;
|
|
980
1010
|
if (dx === 0 && dy === 0) return;
|
|
981
1011
|
const view = viewRef.current;
|
|
982
1012
|
view.x = clampPanAxis(view.x + dx, view.k, "x");
|
|
983
1013
|
view.y = clampPanAxis(view.y + dy, view.k, "y");
|
|
984
|
-
commitView();
|
|
1014
|
+
commitView(false);
|
|
1015
|
+
retryFrameRef.current();
|
|
985
1016
|
});
|
|
986
1017
|
observer.observe(viewport);
|
|
987
1018
|
return () => observer.disconnect();
|
|
@@ -992,6 +1023,7 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
992
1023
|
return;
|
|
993
1024
|
}
|
|
994
1025
|
const handleWheel = (event) => {
|
|
1026
|
+
operationRef.current += 1;
|
|
995
1027
|
if (event.target.closest(".gtv-info-panel") !== null) {
|
|
996
1028
|
return;
|
|
997
1029
|
}
|
|
@@ -1030,6 +1062,7 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1030
1062
|
}, [viewportRef, wheelZoom]);
|
|
1031
1063
|
const zoomByButton = (0, import_react.useCallback)(
|
|
1032
1064
|
(direction) => {
|
|
1065
|
+
operationRef.current += 1;
|
|
1033
1066
|
const viewport = viewportRef.current;
|
|
1034
1067
|
if (!viewport) return;
|
|
1035
1068
|
const rect = viewport.getBoundingClientRect();
|
|
@@ -1044,13 +1077,14 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1044
1077
|
);
|
|
1045
1078
|
const fitToFrame = (0, import_react.useCallback)(
|
|
1046
1079
|
(elements, { silent = false } = {}) => {
|
|
1080
|
+
if (!silent) operationRef.current += 1;
|
|
1081
|
+
frameElementsRef.current = elements;
|
|
1047
1082
|
const viewport = viewportRef.current;
|
|
1048
1083
|
const present = elements.filter((el) => Boolean(el));
|
|
1049
|
-
if (!viewport || present.length === 0) return;
|
|
1084
|
+
if (!viewport || present.length === 0) return false;
|
|
1050
1085
|
const viewportRect = viewport.getBoundingClientRect();
|
|
1051
|
-
if (viewportRect.width <= 0 || viewportRect.height <= 0) return;
|
|
1052
|
-
|
|
1053
|
-
if (viewportRect.width <= ZOOM_FIT_PADDING * 2 || viewportRect.height <= ZOOM_FIT_PADDING * 2) return;
|
|
1086
|
+
if (viewportRect.width <= 0 || viewportRect.height <= 0) return false;
|
|
1087
|
+
if (viewportRect.width <= ZOOM_FIT_PADDING * 2 || viewportRect.height <= ZOOM_FIT_PADDING * 2) return false;
|
|
1054
1088
|
const rects = present.map((el) => el.getBoundingClientRect());
|
|
1055
1089
|
const contentLeft = Math.min(...rects.map((r) => r.left));
|
|
1056
1090
|
const contentTop = Math.min(...rects.map((r) => r.top));
|
|
@@ -1059,7 +1093,7 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1059
1093
|
const view = viewRef.current;
|
|
1060
1094
|
const contentWidth = (contentRight - contentLeft) / view.k;
|
|
1061
1095
|
const contentHeight = (contentBottom - contentTop) / view.k;
|
|
1062
|
-
if (contentWidth <= 0 || contentHeight <= 0) return;
|
|
1096
|
+
if (contentWidth <= 0 || contentHeight <= 0) return false;
|
|
1063
1097
|
const contentOriginX = (contentLeft - viewportRect.left - view.x) / view.k;
|
|
1064
1098
|
const contentOriginY = (contentTop - viewportRect.top - view.y) / view.k;
|
|
1065
1099
|
contentBoundsRef.current = {
|
|
@@ -1078,6 +1112,41 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1078
1112
|
const newMinScale = Math.min(ZOOM_MIN_SCALE, fitScale);
|
|
1079
1113
|
minScaleRef.current = newMinScale;
|
|
1080
1114
|
setMinScale(newMinScale);
|
|
1115
|
+
measuredFrameRef.current = options.frameKey;
|
|
1116
|
+
const pending = pendingRestoreRef.current;
|
|
1117
|
+
if (pending && pending.operation !== operationRef.current) {
|
|
1118
|
+
pendingRestoreRef.current = null;
|
|
1119
|
+
pendingCenterRef.current = null;
|
|
1120
|
+
fittedFrameRef.current = { frame: options.frameKey };
|
|
1121
|
+
return true;
|
|
1122
|
+
}
|
|
1123
|
+
if (pending && pending.frame === options.frameKey && pending.operation === operationRef.current) {
|
|
1124
|
+
pendingRestoreRef.current = null;
|
|
1125
|
+
restoredRevisionRef.current = pending.revision;
|
|
1126
|
+
restoredFrameRef.current = pending.frame;
|
|
1127
|
+
restoredSelectionRef.current = options.selectedNodeId;
|
|
1128
|
+
defaultViewportRef.current = void 0;
|
|
1129
|
+
viewRef.current = viewportToView({ ...pending.viewport, zoom: clampZoomScale(pending.viewport.zoom, newMinScale) }, viewportRect.width, viewportRect.height);
|
|
1130
|
+
fittedFrameRef.current = { frame: options.frameKey };
|
|
1131
|
+
pendingCenterRef.current = null;
|
|
1132
|
+
commitView(false);
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
if (silent && fittedFrameRef.current && fittedFrameRef.current.frame === options.frameKey) {
|
|
1136
|
+
const center2 = pendingCenterRef.current;
|
|
1137
|
+
pendingCenterRef.current = null;
|
|
1138
|
+
center2?.();
|
|
1139
|
+
return true;
|
|
1140
|
+
}
|
|
1141
|
+
fittedFrameRef.current = { frame: options.frameKey };
|
|
1142
|
+
const center = pendingCenterRef.current;
|
|
1143
|
+
pendingCenterRef.current = null;
|
|
1144
|
+
if (silent && center && !defaultViewportRef.current) {
|
|
1145
|
+
center();
|
|
1146
|
+
return true;
|
|
1147
|
+
}
|
|
1148
|
+
zoomAnimationRef.current = null;
|
|
1149
|
+
centerAnimationRef.current = null;
|
|
1081
1150
|
const seed = defaultViewportRef.current;
|
|
1082
1151
|
if (seed) {
|
|
1083
1152
|
defaultViewportRef.current = void 0;
|
|
@@ -1091,14 +1160,15 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1091
1160
|
view.y = seeded.y;
|
|
1092
1161
|
centerGuardUntilRef.current = performance.now() + DEFAULT_VIEWPORT_CENTER_GUARD_MS;
|
|
1093
1162
|
commitView(false);
|
|
1094
|
-
return;
|
|
1163
|
+
return true;
|
|
1095
1164
|
}
|
|
1096
1165
|
view.k = fitScale;
|
|
1097
1166
|
view.x = viewportRect.width / 2 - (contentOriginX + contentWidth / 2) * fitScale;
|
|
1098
1167
|
view.y = viewportRect.height / 2 - (contentOriginY + contentHeight / 2) * fitScale;
|
|
1099
1168
|
commitView(!silent);
|
|
1169
|
+
return true;
|
|
1100
1170
|
},
|
|
1101
|
-
[viewportRef, commitView]
|
|
1171
|
+
[viewportRef, commitView, options.frameKey, options.selectedNodeId]
|
|
1102
1172
|
);
|
|
1103
1173
|
const centerAnimationRef = (0, import_react.useRef)(null);
|
|
1104
1174
|
const centerAnimationFrameIdRef = (0, import_react.useRef)(null);
|
|
@@ -1132,7 +1202,19 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1132
1202
|
stepCenterAnimationRef.current = stepCenterAnimation;
|
|
1133
1203
|
}, [stepCenterAnimation]);
|
|
1134
1204
|
const centerOnElement = (0, import_react.useCallback)(
|
|
1135
|
-
(element, targetScale, obscured) => {
|
|
1205
|
+
(element, targetScale, obscured, automatic = false) => {
|
|
1206
|
+
if (automatic && restoredRevisionRef.current !== void 0 && restoredFrameRef.current === options.frameKey && restoredSelectionRef.current === options.selectedNodeId) return;
|
|
1207
|
+
if (!automatic) operationRef.current += 1;
|
|
1208
|
+
const viewportSize = viewportRef.current?.getBoundingClientRect();
|
|
1209
|
+
if (automatic && (measuredFrameRef.current !== options.frameKey || fittedFrameRef.current === null || !viewportSize?.width || !viewportSize.height)) {
|
|
1210
|
+
const operation = operationRef.current;
|
|
1211
|
+
pendingCenterRef.current = () => {
|
|
1212
|
+
if (operation !== operationRef.current) return;
|
|
1213
|
+
const currentElement = element?.id ? viewportRef.current?.querySelector(`#${CSS.escape(element.id)}`) : element;
|
|
1214
|
+
centerLatestRef.current(currentElement, targetScale, obscured ? resolveInfoPanelObscuredArea(currentElement, viewportRef.current, INFO_PANEL_WIDTH) : obscured, true);
|
|
1215
|
+
};
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1136
1218
|
const viewport = viewportRef.current;
|
|
1137
1219
|
if (!viewport || !element || performance.now() < centerGuardUntilRef.current) return;
|
|
1138
1220
|
const viewportRect = viewport.getBoundingClientRect();
|
|
@@ -1165,8 +1247,17 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1165
1247
|
centerAnimationFrameIdRef.current = requestAnimationFrame(() => stepCenterAnimationRef.current());
|
|
1166
1248
|
}
|
|
1167
1249
|
},
|
|
1168
|
-
[viewportRef]
|
|
1250
|
+
[viewportRef, options.frameKey, options.selectedNodeId]
|
|
1169
1251
|
);
|
|
1252
|
+
const centerLatestRef = (0, import_react.useRef)(centerOnElement);
|
|
1253
|
+
(0, import_react.useEffect)(() => {
|
|
1254
|
+
centerLatestRef.current = centerOnElement;
|
|
1255
|
+
}, [centerOnElement]);
|
|
1256
|
+
(0, import_react.useEffect)(() => {
|
|
1257
|
+
retryFrameRef.current = () => {
|
|
1258
|
+
fitToFrame(frameElementsRef.current, { silent: true });
|
|
1259
|
+
};
|
|
1260
|
+
}, [fitToFrame]);
|
|
1170
1261
|
const lastPointRef = (0, import_react.useRef)({ x: 0, y: 0 });
|
|
1171
1262
|
const minScaleRef = (0, import_react.useRef)(minScale);
|
|
1172
1263
|
const suppressedPointersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
@@ -1241,6 +1332,7 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1241
1332
|
}, [handlePointerUp]);
|
|
1242
1333
|
const handlePointerDown = (0, import_react.useCallback)((event) => {
|
|
1243
1334
|
if (event.button !== 0) return;
|
|
1335
|
+
operationRef.current += 1;
|
|
1244
1336
|
const isInteractive = event.target.closest("g.node, foreignObject, .gtv-zoom-controls, .gtv-wheel-chip, .gtv-info-panel") !== null;
|
|
1245
1337
|
const isPageScrollTouch = wheelZoom === "modifier" && event.pointerType === "touch";
|
|
1246
1338
|
const pointers = activePointersRef.current;
|
|
@@ -1259,6 +1351,42 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1259
1351
|
window.addEventListener("pointercancel", stablePointerUp);
|
|
1260
1352
|
}
|
|
1261
1353
|
}, [stablePointerMove, stablePointerUp, wheelZoom]);
|
|
1354
|
+
const restore = options.viewportRestore;
|
|
1355
|
+
const restoreRevision = restore?.revision;
|
|
1356
|
+
const restoreX = restore?.viewport.x;
|
|
1357
|
+
const restoreY = restore?.viewport.y;
|
|
1358
|
+
const restoreZoom = restore?.viewport.zoom;
|
|
1359
|
+
(0, import_react.useEffect)(() => {
|
|
1360
|
+
if (restoreRevision === restoredRevisionRef.current || restoreRevision === void 0 || restoreX === void 0 || restoreY === void 0 || restoreZoom === void 0) return;
|
|
1361
|
+
if (!Number.isFinite(restoreX) || !Number.isFinite(restoreY) || !Number.isFinite(restoreZoom) || restoreZoom <= 0) return;
|
|
1362
|
+
const viewportElement = viewportRef.current;
|
|
1363
|
+
if (!viewportElement) return;
|
|
1364
|
+
restoredRevisionRef.current = restoreRevision;
|
|
1365
|
+
const operation = ++operationRef.current;
|
|
1366
|
+
zoomAnimationRef.current = null;
|
|
1367
|
+
centerAnimationRef.current = null;
|
|
1368
|
+
if (animationFrameIdRef.current !== null) cancelAnimationFrame(animationFrameIdRef.current);
|
|
1369
|
+
if (centerAnimationFrameIdRef.current !== null) cancelAnimationFrame(centerAnimationFrameIdRef.current);
|
|
1370
|
+
if (frameIdRef.current !== null) cancelAnimationFrame(frameIdRef.current);
|
|
1371
|
+
if (settleTimerRef.current !== null) clearTimeout(settleTimerRef.current);
|
|
1372
|
+
animationFrameIdRef.current = null;
|
|
1373
|
+
centerAnimationFrameIdRef.current = null;
|
|
1374
|
+
frameIdRef.current = null;
|
|
1375
|
+
settleTimerRef.current = null;
|
|
1376
|
+
activePointersRef.current.clear();
|
|
1377
|
+
suppressedPointersRef.current.clear();
|
|
1378
|
+
pinchStartRef.current = null;
|
|
1379
|
+
window.removeEventListener("pointermove", stablePointerMove);
|
|
1380
|
+
window.removeEventListener("pointerup", stablePointerUp);
|
|
1381
|
+
window.removeEventListener("pointercancel", stablePointerUp);
|
|
1382
|
+
pendingRestoreRef.current = {
|
|
1383
|
+
viewport: { x: restoreX, y: restoreY, zoom: restoreZoom },
|
|
1384
|
+
revision: restoreRevision,
|
|
1385
|
+
frame: options.frameKey,
|
|
1386
|
+
operation
|
|
1387
|
+
};
|
|
1388
|
+
if (measuredFrameRef.current === options.frameKey) retryFrameRef.current();
|
|
1389
|
+
}, [restoreRevision, restoreX, restoreY, restoreZoom, options.frameKey, options.selectedNodeId, viewportRef, commitView, stablePointerMove, stablePointerUp]);
|
|
1262
1390
|
(0, import_react.useEffect)(() => {
|
|
1263
1391
|
const pointers = activePointersRef.current;
|
|
1264
1392
|
const suppressed = suppressedPointersRef.current;
|
|
@@ -1293,20 +1421,6 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1293
1421
|
|
|
1294
1422
|
// src/use-node-info-panel.ts
|
|
1295
1423
|
var import_react2 = require("react");
|
|
1296
|
-
|
|
1297
|
-
// src/info-panel-geometry.ts
|
|
1298
|
-
function resolveInfoPanelSide() {
|
|
1299
|
-
return "left";
|
|
1300
|
-
}
|
|
1301
|
-
function resolveInfoPanelObscuredArea(element, viewport, panelWidth) {
|
|
1302
|
-
if (!element || !viewport) return null;
|
|
1303
|
-
if (viewport.clientWidth > 0 && viewport.clientWidth <= INFO_PANEL_SHEET_MAX_WIDTH) {
|
|
1304
|
-
return { side: "bottom", height: viewport.clientHeight * INFO_PANEL_SHEET_PEEK_FRACTION };
|
|
1305
|
-
}
|
|
1306
|
-
return { width: panelWidth, side: resolveInfoPanelSide() };
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// src/use-node-info-panel.ts
|
|
1310
1424
|
function useNodeInfoPanel({
|
|
1311
1425
|
selectedNodeId,
|
|
1312
1426
|
onSelectedNodeChange
|
|
@@ -1315,7 +1429,7 @@ function useNodeInfoPanel({
|
|
|
1315
1429
|
const [prevSelectedNodeId, setPrevSelectedNodeId] = (0, import_react2.useState)(selectedNodeId);
|
|
1316
1430
|
if (selectedNodeId !== prevSelectedNodeId) {
|
|
1317
1431
|
setPrevSelectedNodeId(selectedNodeId);
|
|
1318
|
-
if (prevSelectedNodeId
|
|
1432
|
+
if (prevSelectedNodeId !== void 0) setPanel(null);
|
|
1319
1433
|
}
|
|
1320
1434
|
const shownNodeIdRef = (0, import_react2.useRef)(null);
|
|
1321
1435
|
const onSelectedNodeChangeRef = (0, import_react2.useRef)(onSelectedNodeChange);
|
|
@@ -1325,20 +1439,32 @@ function useNodeInfoPanel({
|
|
|
1325
1439
|
});
|
|
1326
1440
|
const showNodeInfo = (0, import_react2.useCallback)(
|
|
1327
1441
|
(node, element, viewport, { notify = true } = {}) => {
|
|
1442
|
+
if (notify && selectedNodeId !== void 0) {
|
|
1443
|
+
if (node.id !== selectedNodeId) onSelectedNodeChangeRef.current?.(node);
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1328
1446
|
if (!element || !viewport) return;
|
|
1329
1447
|
setPanel({ node, side: resolveInfoPanelSide() });
|
|
1330
1448
|
const changed = node.id !== shownNodeIdRef.current;
|
|
1331
1449
|
shownNodeIdRef.current = node.id;
|
|
1332
1450
|
if (changed && notify) onSelectedNodeChangeRef.current?.(node);
|
|
1333
1451
|
},
|
|
1334
|
-
[]
|
|
1452
|
+
[selectedNodeId]
|
|
1335
1453
|
);
|
|
1336
1454
|
const closeNodeInfo = (0, import_react2.useCallback)(() => {
|
|
1455
|
+
if (selectedNodeId !== void 0) {
|
|
1456
|
+
if (selectedNodeId !== null) onSelectedNodeChangeRef.current?.(null);
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1337
1459
|
setPanel(null);
|
|
1338
1460
|
shownNodeIdRef.current = null;
|
|
1339
1461
|
onSelectedNodeChangeRef.current?.(null);
|
|
1340
|
-
}, []);
|
|
1341
|
-
return {
|
|
1462
|
+
}, [selectedNodeId]);
|
|
1463
|
+
return {
|
|
1464
|
+
panel: selectedNodeId !== void 0 && panel?.node.id !== selectedNodeId ? null : panel,
|
|
1465
|
+
showNodeInfo,
|
|
1466
|
+
closeNodeInfo
|
|
1467
|
+
};
|
|
1342
1468
|
}
|
|
1343
1469
|
|
|
1344
1470
|
// src/use-tree-index.ts
|
|
@@ -1924,6 +2050,7 @@ function GenreTree({
|
|
|
1924
2050
|
overlayPanel,
|
|
1925
2051
|
wheelZoom,
|
|
1926
2052
|
defaultViewport,
|
|
2053
|
+
viewportRestore,
|
|
1927
2054
|
onViewportChange,
|
|
1928
2055
|
controlsOrientation = "vertical",
|
|
1929
2056
|
showZoomControls = "auto",
|
|
@@ -1934,7 +2061,8 @@ function GenreTree({
|
|
|
1934
2061
|
const svgRef = (0, import_react8.useRef)(null);
|
|
1935
2062
|
const viewportRef = (0, import_react8.useRef)(null);
|
|
1936
2063
|
const stageRef = (0, import_react8.useRef)(null);
|
|
1937
|
-
const
|
|
2064
|
+
const viewportFrame = (0, import_react8.useMemo)(() => JSON.stringify([orientation, nodes.map(({ id, parentId, side }) => [id, parentId, side])]), [nodes, orientation]);
|
|
2065
|
+
const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, viewportRestore, selectedNodeId: selectedNodeIdProp, onViewportChange, frameKey: viewportFrame });
|
|
1938
2066
|
const { panel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId: selectedNodeIdProp, onSelectedNodeChange });
|
|
1939
2067
|
const itemCountRange = (0, import_react8.useMemo)(
|
|
1940
2068
|
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: nodeCountForSizing ?? nodes.length }),
|
|
@@ -2073,7 +2201,8 @@ function GenreTree({
|
|
|
2073
2201
|
centerOnElementRef.current(
|
|
2074
2202
|
element,
|
|
2075
2203
|
ZOOM_FOCUS_SCALE,
|
|
2076
|
-
resolveInfoPanelObscuredArea(element, viewportRef.current, INFO_PANEL_WIDTH)
|
|
2204
|
+
resolveInfoPanelObscuredArea(element, viewportRef.current, INFO_PANEL_WIDTH),
|
|
2205
|
+
true
|
|
2077
2206
|
);
|
|
2078
2207
|
showNodeInfoRef.current(targetNode, element, viewportRef.current, { notify: false });
|
|
2079
2208
|
}, [interactive, selectedNodeIdProp, index, panel]);
|
|
@@ -2085,12 +2214,10 @@ function GenreTree({
|
|
|
2085
2214
|
}
|
|
2086
2215
|
};
|
|
2087
2216
|
}, []);
|
|
2088
|
-
const hasInitialFitRef = (0, import_react8.useRef)(false);
|
|
2089
2217
|
(0, import_react8.useEffect)(() => {
|
|
2090
|
-
if (
|
|
2218
|
+
if (!interactive) return;
|
|
2091
2219
|
const elements = queryTreeContentElements(svgRef.current);
|
|
2092
2220
|
if (elements.length === 0) return;
|
|
2093
|
-
hasInitialFitRef.current = true;
|
|
2094
2221
|
panZoom.fitToFrame(elements, { silent: true });
|
|
2095
2222
|
});
|
|
2096
2223
|
const svg = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
@@ -2345,6 +2472,7 @@ function WheelCore({
|
|
|
2345
2472
|
overlayPanel,
|
|
2346
2473
|
wheelZoom,
|
|
2347
2474
|
defaultViewport,
|
|
2475
|
+
viewportRestore,
|
|
2348
2476
|
onViewportChange,
|
|
2349
2477
|
controlsOrientation = "vertical",
|
|
2350
2478
|
showZoomControls = "auto",
|
|
@@ -2386,7 +2514,8 @@ function WheelCore({
|
|
|
2386
2514
|
);
|
|
2387
2515
|
const viewportRef = (0, import_react9.useRef)(null);
|
|
2388
2516
|
const stageRef = (0, import_react9.useRef)(null);
|
|
2389
|
-
const
|
|
2517
|
+
const viewportFrame = (0, import_react9.useMemo)(() => JSON.stringify(nodes.map(({ id, parentId, side }) => [id, parentId, side])), [nodes]);
|
|
2518
|
+
const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, viewportRestore, selectedNodeId, onViewportChange, frameKey: viewportFrame });
|
|
2390
2519
|
const { panel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId, onSelectedNodeChange });
|
|
2391
2520
|
const wheelCircleRef = (0, import_react9.useRef)(null);
|
|
2392
2521
|
const treeAnchorRef = (0, import_react9.useRef)(null);
|
|
@@ -2400,15 +2529,12 @@ function WheelCore({
|
|
|
2400
2529
|
(0, import_react9.useEffect)(() => {
|
|
2401
2530
|
if (effectiveRootId) onRootSelectRef.current?.(effectiveRootId);
|
|
2402
2531
|
}, [effectiveRootId]);
|
|
2403
|
-
const hasInitialFitRef = (0, import_react9.useRef)(false);
|
|
2404
2532
|
(0, import_react9.useEffect)(() => {
|
|
2405
|
-
if (hasInitialFitRef.current) return;
|
|
2406
2533
|
const elements = [
|
|
2407
2534
|
wheelCircleRef.current,
|
|
2408
2535
|
...queryTreeContentElements(treeAnchorRef.current)
|
|
2409
2536
|
];
|
|
2410
2537
|
if (!elements.some(Boolean)) return;
|
|
2411
|
-
hasInitialFitRef.current = true;
|
|
2412
2538
|
panZoom.fitToFrame(elements, { silent: true });
|
|
2413
2539
|
});
|
|
2414
2540
|
const selectedGroup = groups.find((group) => group.root.id === effectiveRootId) ?? null;
|
|
@@ -2468,7 +2594,8 @@ function WheelCore({
|
|
|
2468
2594
|
panZoom.centerOnElement(
|
|
2469
2595
|
element,
|
|
2470
2596
|
ZOOM_FOCUS_SCALE,
|
|
2471
|
-
resolveInfoPanelObscuredArea(element, viewportRef.current, INFO_PANEL_WIDTH)
|
|
2597
|
+
resolveInfoPanelObscuredArea(element, viewportRef.current, INFO_PANEL_WIDTH),
|
|
2598
|
+
true
|
|
2472
2599
|
);
|
|
2473
2600
|
showNodeInfo(targetNode, element, viewportRef.current, { notify: false });
|
|
2474
2601
|
}, [selectedNodeId, effectiveRootId, nodes, panel, showNodeInfo]);
|
|
@@ -3461,6 +3588,7 @@ function WheelRadialCore({
|
|
|
3461
3588
|
overlayPanel,
|
|
3462
3589
|
wheelZoom,
|
|
3463
3590
|
defaultViewport,
|
|
3591
|
+
viewportRestore,
|
|
3464
3592
|
onViewportChange,
|
|
3465
3593
|
controlsOrientation = "vertical",
|
|
3466
3594
|
showZoomControls = "auto",
|
|
@@ -3477,7 +3605,8 @@ function WheelRadialCore({
|
|
|
3477
3605
|
);
|
|
3478
3606
|
const viewportRef = (0, import_react10.useRef)(null);
|
|
3479
3607
|
const stageRef = (0, import_react10.useRef)(null);
|
|
3480
|
-
const
|
|
3608
|
+
const viewportFrame = (0, import_react10.useMemo)(() => JSON.stringify(nodes.map(({ id, parentId, side }) => [id, parentId, side])), [nodes]);
|
|
3609
|
+
const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, viewportRestore, selectedNodeId, onViewportChange, frameKey: viewportFrame });
|
|
3481
3610
|
const { panel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId, onSelectedNodeChange });
|
|
3482
3611
|
const centerOnElementRef = (0, import_react10.useRef)(panZoom.centerOnElement);
|
|
3483
3612
|
(0, import_react10.useEffect)(() => {
|
|
@@ -3696,12 +3825,9 @@ function WheelRadialCore({
|
|
|
3696
3825
|
wheelItemCountRange,
|
|
3697
3826
|
panel?.node.id
|
|
3698
3827
|
]);
|
|
3699
|
-
const hasInitialFitRef = (0, import_react10.useRef)(false);
|
|
3700
3828
|
(0, import_react10.useEffect)(() => {
|
|
3701
|
-
if (hasInitialFitRef.current) return;
|
|
3702
3829
|
const elements = [wheelCircleRef.current, coreSvgRef.current];
|
|
3703
3830
|
if (!elements.some(Boolean)) return;
|
|
3704
|
-
hasInitialFitRef.current = true;
|
|
3705
3831
|
panZoom.fitToFrame(elements, { silent: true });
|
|
3706
3832
|
});
|
|
3707
3833
|
const handleChipClick = (rootId) => {
|
|
@@ -3717,7 +3843,8 @@ function WheelRadialCore({
|
|
|
3717
3843
|
panZoom.centerOnElement(
|
|
3718
3844
|
element,
|
|
3719
3845
|
ZOOM_FOCUS_SCALE,
|
|
3720
|
-
resolveInfoPanelObscuredArea(element, viewportRef.current, INFO_PANEL_WIDTH)
|
|
3846
|
+
resolveInfoPanelObscuredArea(element, viewportRef.current, INFO_PANEL_WIDTH),
|
|
3847
|
+
true
|
|
3721
3848
|
);
|
|
3722
3849
|
showNodeInfo(targetNode, element, viewportRef.current, { notify: false });
|
|
3723
3850
|
}, [selectedNodeId, index, panel, showNodeInfo]);
|
|
@@ -4147,6 +4274,7 @@ function WheelRadialPopCoreCore({
|
|
|
4147
4274
|
overlayPanel,
|
|
4148
4275
|
wheelZoom,
|
|
4149
4276
|
defaultViewport,
|
|
4277
|
+
viewportRestore,
|
|
4150
4278
|
onViewportChange,
|
|
4151
4279
|
controlsOrientation = "vertical",
|
|
4152
4280
|
showZoomControls = "auto",
|
|
@@ -4226,7 +4354,8 @@ function WheelRadialPopCoreCore({
|
|
|
4226
4354
|
const viewportRef = (0, import_react13.useRef)(null);
|
|
4227
4355
|
const stageRef = (0, import_react13.useRef)(null);
|
|
4228
4356
|
const dotRadiusMemoryRef = (0, import_react13.useRef)(/* @__PURE__ */ new Map());
|
|
4229
|
-
const
|
|
4357
|
+
const viewportFrame = (0, import_react13.useMemo)(() => JSON.stringify([focusNodeId, nodes.map(({ id, parentId, side }) => [id, parentId, side])]), [nodes, focusNodeId]);
|
|
4358
|
+
const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, viewportRestore, selectedNodeId, onViewportChange, frameKey: viewportFrame });
|
|
4230
4359
|
const { panel: openPanel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId, onSelectedNodeChange });
|
|
4231
4360
|
const isInFrame = (id) => id === focusNodeId || centerSubtreeNodeIds.has(id) || ringRootById.has(id);
|
|
4232
4361
|
const panel = openPanel && isInFrame(openPanel.node.id) ? openPanel : null;
|
|
@@ -4830,18 +4959,12 @@ function WheelRadialPopCoreCore({
|
|
|
4830
4959
|
index,
|
|
4831
4960
|
regionLabel
|
|
4832
4961
|
]);
|
|
4833
|
-
const hasInitialFitRef = (0, import_react13.useRef)(false);
|
|
4834
4962
|
(0, import_react13.useEffect)(() => {
|
|
4835
|
-
hasInitialFitRef.current = false;
|
|
4836
|
-
}, [focusNodeId]);
|
|
4837
|
-
(0, import_react13.useEffect)(() => {
|
|
4838
|
-
if (hasInitialFitRef.current) return;
|
|
4839
4963
|
const elements = [
|
|
4840
4964
|
wheelCircleRef.current,
|
|
4841
4965
|
...queryTreeContentElements(popSvgRef.current)
|
|
4842
4966
|
];
|
|
4843
4967
|
if (!elements.some(Boolean)) return;
|
|
4844
|
-
hasInitialFitRef.current = true;
|
|
4845
4968
|
panZoom.fitToFrame(elements, { silent: true });
|
|
4846
4969
|
});
|
|
4847
4970
|
const handleChipClick = (rootId) => {
|
|
@@ -4863,7 +4986,8 @@ function WheelRadialPopCoreCore({
|
|
|
4863
4986
|
panZoom.centerOnElement(
|
|
4864
4987
|
element,
|
|
4865
4988
|
ZOOM_FOCUS_SCALE,
|
|
4866
|
-
resolveInfoPanelObscuredArea(element, viewportRef.current, INFO_PANEL_WIDTH)
|
|
4989
|
+
resolveInfoPanelObscuredArea(element, viewportRef.current, INFO_PANEL_WIDTH),
|
|
4990
|
+
true
|
|
4867
4991
|
);
|
|
4868
4992
|
showNodeInfo(targetNode, element, viewportRef.current, { notify: false });
|
|
4869
4993
|
}, [selectedNodeId, index, openPanel, showNodeInfo, centerSubtreeNodeIds, ringRootById]);
|
|
@@ -5361,7 +5485,7 @@ function GenreTreeOutline(props) {
|
|
|
5361
5485
|
return;
|
|
5362
5486
|
}
|
|
5363
5487
|
const changed = store.get().selectedId !== node.id;
|
|
5364
|
-
store.set({ selectedId: node.id });
|
|
5488
|
+
if (latestProps.current.selectedNodeId === void 0) store.set({ selectedId: node.id });
|
|
5365
5489
|
if (changed) latestProps.current.onSelectedNodeChange?.(node);
|
|
5366
5490
|
onNodeClick?.(node, event.nativeEvent);
|
|
5367
5491
|
},
|
|
@@ -5393,7 +5517,7 @@ function GenreTreeOutline(props) {
|
|
|
5393
5517
|
}
|
|
5394
5518
|
const changed = store.get().selectedId !== nodeId;
|
|
5395
5519
|
if (changed) pendingScrollIdRef.current = nodeId;
|
|
5396
|
-
store.set({ openIds, selectedId: nodeId });
|
|
5520
|
+
store.set({ openIds, ...notify && latestProps.current.selectedNodeId !== void 0 ? {} : { selectedId: nodeId } });
|
|
5397
5521
|
if (changed && notify) latestProps.current.onSelectedNodeChange?.(index.byId.get(nodeId));
|
|
5398
5522
|
};
|
|
5399
5523
|
const selectNodeRef = (0, import_react14.useRef)(selectNode);
|
|
@@ -5434,7 +5558,7 @@ function GenreTreeOutline(props) {
|
|
|
5434
5558
|
ancestorNodes: index.ancestorsOf(selectedNode.id).map((n) => ({ node: n, ...model.styleOf(n) })),
|
|
5435
5559
|
side: "left",
|
|
5436
5560
|
onClose: () => {
|
|
5437
|
-
store.set({ selectedId: null });
|
|
5561
|
+
if (latestProps.current.selectedNodeId === void 0) store.set({ selectedId: null });
|
|
5438
5562
|
latestProps.current.onSelectedNodeChange?.(null);
|
|
5439
5563
|
},
|
|
5440
5564
|
onSelectNode: (id) => selectNodeRef.current(id),
|