@behindthemusictree/genre-tree-view 6.2.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +269 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +276 -131
- package/dist/index.js.map +1 -1
- package/dist/styles.css +104 -47
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -131,6 +131,9 @@ var MIN_NODE_WIDTH = 180;
|
|
|
131
131
|
var MAX_NODE_WIDTH = 700;
|
|
132
132
|
var MIN_NODE_HEIGHT = 35;
|
|
133
133
|
var MAX_NODE_HEIGHT = 120;
|
|
134
|
+
var NODE_SIZE_REFERENCE_COUNT = 100;
|
|
135
|
+
var HIGH_DENSITY_NODE_MAX_FLOOR_SCALE = 1.5;
|
|
136
|
+
var LOW_DENSITY_DOT_MAX_RADIUS = 70;
|
|
134
137
|
var TOOLBAR_MENU_X_GAP = 4;
|
|
135
138
|
var ACTIONS_OVERLAY_WIDTH = MENU_WIDTH + TOOLBAR_MENU_X_GAP;
|
|
136
139
|
var ACTIONS_OVERLAY_HEIGHT = RECT_BASE_DIMENSIONS.HEIGHT * 7;
|
|
@@ -145,23 +148,47 @@ var HORIZONTAL_SEPARATION_BETWEEN_NODES = (NODE_DIMENSIONS.WIDTH + HORIZONTAL_SE
|
|
|
145
148
|
var VERTICAL_SEPARATION_BETWEEN_NODES = NODE_DIMENSIONS.HEIGHT + VERTICAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
146
149
|
var VERTICAL_ORIENTATION_DEPTH_SEPARATION = VERTICAL_SEPARATION_BETWEEN_NODES * 2 * DEPTH_AXIS_SPACING_FACTOR;
|
|
147
150
|
var SIBLING_SEPARATION_BETWEEN_NODES = MAX_NODE_WIDTH + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
148
|
-
function
|
|
149
|
-
|
|
151
|
+
function getDensityAdjustedMax(baseMax, min, nodeCount) {
|
|
152
|
+
const safeNodeCount = Number.isFinite(nodeCount) ? Math.max(1, nodeCount) : 1;
|
|
153
|
+
const availableRangeScale = Math.min(1, NODE_SIZE_REFERENCE_COUNT / safeNodeCount);
|
|
154
|
+
const denseTreeFloor = min * HIGH_DENSITY_NODE_MAX_FLOOR_SCALE;
|
|
155
|
+
return denseTreeFloor + (baseMax - denseTreeFloor) * availableRangeScale;
|
|
156
|
+
}
|
|
157
|
+
function getDensityAdjustedDotMax(nodeCount) {
|
|
158
|
+
const safeNodeCount = Number.isFinite(nodeCount) ? Math.max(1, nodeCount) : 1;
|
|
159
|
+
const inverseDensityRadius = NODE_DOT_MAX_RADIUS * NODE_SIZE_REFERENCE_COUNT / safeNodeCount;
|
|
160
|
+
const denseTreeFloor = NODE_DOT_MIN_RADIUS * HIGH_DENSITY_NODE_MAX_FLOOR_SCALE;
|
|
161
|
+
return Math.max(denseTreeFloor, Math.min(LOW_DENSITY_DOT_MAX_RADIUS, inverseDensityRadius));
|
|
162
|
+
}
|
|
163
|
+
function getItemCountRange(nodes, options = {}) {
|
|
150
164
|
const counts = nodes.map((node) => node.itemCount);
|
|
151
|
-
|
|
165
|
+
const validScaleMax = options.itemCountScaleMax !== void 0 && Number.isFinite(options.itemCountScaleMax) && options.itemCountScaleMax >= 0;
|
|
166
|
+
const nodeCount = options.nodeCountForSizing ?? nodes.length;
|
|
167
|
+
const observedMin = counts.length === 0 ? 0 : Math.min(...counts);
|
|
168
|
+
const observedMax = counts.length === 0 ? 0 : Math.max(...counts);
|
|
169
|
+
const includeDensityCaps = options.nodeCountForSizing !== void 0 || validScaleMax;
|
|
170
|
+
return {
|
|
171
|
+
min: validScaleMax ? 0 : observedMin,
|
|
172
|
+
max: validScaleMax ? options.itemCountScaleMax : observedMax,
|
|
173
|
+
...includeDensityCaps && {
|
|
174
|
+
maxNodeWidth: getDensityAdjustedMax(MAX_NODE_WIDTH, MIN_NODE_WIDTH, nodeCount),
|
|
175
|
+
maxNodeHeight: getDensityAdjustedMax(MAX_NODE_HEIGHT, MIN_NODE_HEIGHT, nodeCount),
|
|
176
|
+
maxDotRadius: getDensityAdjustedDotMax(nodeCount)
|
|
177
|
+
}
|
|
178
|
+
};
|
|
152
179
|
}
|
|
153
180
|
function logarithmicPosition(itemCount, range) {
|
|
154
181
|
const { min, max } = range;
|
|
155
182
|
const logMin = Math.log1p(Math.max(min, 0));
|
|
156
183
|
const logMax = Math.log1p(Math.max(max, 0));
|
|
157
184
|
const logValue = Math.log1p(Math.max(itemCount, 0));
|
|
158
|
-
return logMax > logMin ? (logValue - logMin) / (logMax - logMin) : 0;
|
|
185
|
+
return logMax > logMin ? Math.max(0, Math.min(1, (logValue - logMin) / (logMax - logMin))) : 0;
|
|
159
186
|
}
|
|
160
187
|
function calculateNodeDimensions(itemCount, range) {
|
|
161
188
|
const t = logarithmicPosition(itemCount, range);
|
|
162
189
|
return {
|
|
163
|
-
WIDTH: Math.round(MIN_NODE_WIDTH + t * (MAX_NODE_WIDTH - MIN_NODE_WIDTH)),
|
|
164
|
-
HEIGHT: Math.round(MIN_NODE_HEIGHT + t * (MAX_NODE_HEIGHT - MIN_NODE_HEIGHT))
|
|
190
|
+
WIDTH: Math.round(MIN_NODE_WIDTH + t * ((range.maxNodeWidth ?? MAX_NODE_WIDTH) - MIN_NODE_WIDTH)),
|
|
191
|
+
HEIGHT: Math.round(MIN_NODE_HEIGHT + t * ((range.maxNodeHeight ?? MAX_NODE_HEIGHT) - MIN_NODE_HEIGHT))
|
|
165
192
|
};
|
|
166
193
|
}
|
|
167
194
|
var NODE_MIN_BORDER_WIDTH = 1;
|
|
@@ -206,7 +233,7 @@ var NODE_DOT_LABEL_GAP = 6;
|
|
|
206
233
|
var NODE_LAYOUT_SLOT_RADIUS = 8;
|
|
207
234
|
function calculateNodeDotRadius(itemCount, range) {
|
|
208
235
|
const minArea = NODE_DOT_MIN_RADIUS ** 2;
|
|
209
|
-
const maxArea = NODE_DOT_MAX_RADIUS ** 2;
|
|
236
|
+
const maxArea = (range.maxDotRadius ?? NODE_DOT_MAX_RADIUS) ** 2;
|
|
210
237
|
return Math.sqrt(minArea + logarithmicPosition(itemCount, range) * (maxArea - minArea));
|
|
211
238
|
}
|
|
212
239
|
var WHEEL_RADIUS = 260;
|
|
@@ -241,19 +268,22 @@ var PAN_MIN_VISIBLE_PX = 80;
|
|
|
241
268
|
var PAN_ZOOM_SETTLE_MS = 100;
|
|
242
269
|
var DEFAULT_VIEWPORT_CENTER_GUARD_MS = 1e3;
|
|
243
270
|
var INFO_PANEL_WIDTH = 280;
|
|
271
|
+
var INFO_PANEL_SHEET_MAX_WIDTH = 767;
|
|
272
|
+
var INFO_PANEL_SHEET_PEEK_FRACTION = 0.4;
|
|
273
|
+
var INFO_PANEL_SHEET_DRAG_THRESHOLD_PX = 32;
|
|
244
274
|
|
|
245
275
|
// src/NodeHelper.tsx
|
|
246
276
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
247
277
|
function buildTreeHierarchyStructure(d3Lib, nodes) {
|
|
248
278
|
return d3Lib.stratify().id((d) => d.id).parentId((d) => d.parentId)(nodes);
|
|
249
279
|
}
|
|
250
|
-
function calculateLocalRootDimensions(d3Lib, nodes) {
|
|
280
|
+
function calculateLocalRootDimensions(d3Lib, nodes, options = {}) {
|
|
251
281
|
const root = buildTreeHierarchyStructure(d3Lib, nodes);
|
|
252
|
-
const range = getItemCountRange(root.descendants().map((d) => d.data));
|
|
282
|
+
const range = getItemCountRange(root.descendants().map((d) => d.data), options);
|
|
253
283
|
return calculateNodeDimensions(root.data.itemCount, range);
|
|
254
284
|
}
|
|
255
|
-
function calculateRootAnchorClearance(d3Lib, nodes, rootItemCount, rootItemCountRange, axis) {
|
|
256
|
-
const localExtent = calculateLocalRootDimensions(d3Lib, nodes)[axis];
|
|
285
|
+
function calculateRootAnchorClearance(d3Lib, nodes, rootItemCount, rootItemCountRange, axis, options = {}) {
|
|
286
|
+
const localExtent = calculateLocalRootDimensions(d3Lib, nodes, options)[axis];
|
|
257
287
|
const displayExtent = calculateNodeDimensions(rootItemCount, rootItemCountRange)[axis];
|
|
258
288
|
return (localExtent + displayExtent) / 2;
|
|
259
289
|
}
|
|
@@ -337,13 +367,31 @@ function appendPaths(d3Lib, svg, treeData, itemCountRange, orientation = "horizo
|
|
|
337
367
|
}
|
|
338
368
|
|
|
339
369
|
// src/tree-renderer.ts
|
|
340
|
-
function
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
const
|
|
370
|
+
function getLayoutSpacings(itemCountRange, depthSpacingScale = 1) {
|
|
371
|
+
const maxWidth = itemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH;
|
|
372
|
+
const maxHeight = itemCountRange.maxNodeHeight ?? MAX_NODE_HEIGHT;
|
|
373
|
+
const verticalSeparation = maxHeight + VERTICAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
374
|
+
return {
|
|
375
|
+
horizontalDepth: (maxWidth + ACTIONS_OVERLAY_WIDTH + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES) * DEPTH_AXIS_SPACING_FACTOR * depthSpacingScale,
|
|
376
|
+
verticalBreadth: verticalSeparation,
|
|
377
|
+
verticalDepth: verticalSeparation * 2 * DEPTH_AXIS_SPACING_FACTOR * depthSpacingScale,
|
|
378
|
+
verticalSibling: maxWidth + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function calculateSvgDimensions(d3Lib, treeData, orientation = "horizontal", hideRoot = false, depthSpacingScale = 1, suppliedItemCountRange) {
|
|
382
|
+
const treeNodes = treeData.descendants();
|
|
383
|
+
const nodes = treeNodes;
|
|
384
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
385
|
+
treeNodes.map((d) => d.data)
|
|
386
|
+
);
|
|
387
|
+
const maxNodeDimensions = {
|
|
388
|
+
WIDTH: itemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH,
|
|
389
|
+
HEIGHT: itemCountRange.maxNodeHeight ?? MAX_NODE_HEIGHT
|
|
390
|
+
};
|
|
344
391
|
const maximumLevel = d3Lib.max(nodes, (d) => d.depth);
|
|
345
|
-
const
|
|
346
|
-
const
|
|
392
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
393
|
+
const depthSeparation = layoutSpacings.verticalDepth;
|
|
394
|
+
const depthSeparationHorizontal = layoutSpacings.horizontalDepth;
|
|
347
395
|
if (isVerticalOrientation(orientation)) {
|
|
348
396
|
const svgHeight2 = hideRoot ? maximumLevel * depthSeparation : maximumLevel * depthSeparation + maxNodeDimensions.HEIGHT + ACTIONS_OVERLAY_HEIGHT / 2;
|
|
349
397
|
const rootWidth = calculateNodeDimensions(
|
|
@@ -405,10 +453,15 @@ function calculateSvgDimensions(d3Lib, treeData, orientation = "horizontal", hid
|
|
|
405
453
|
const svgWidth = maximumLevel * depthSeparationHorizontal + maxNodeDimensions.WIDTH + ACTIONS_OVERLAY_WIDTH;
|
|
406
454
|
return { svgWidth, svgHeight, highestVerticalCoordinate, rootDepthOffset: 0 };
|
|
407
455
|
}
|
|
408
|
-
function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation = "horizontal", rootDepthOffset = 0, svgWidth = 0, depthSpacingScale = 1) {
|
|
456
|
+
function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation = "horizontal", rootDepthOffset = 0, svgWidth = 0, depthSpacingScale = 1, suppliedItemCountRange) {
|
|
457
|
+
const treeNodes = treeData.descendants();
|
|
458
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
459
|
+
treeNodes.map((d) => d.data)
|
|
460
|
+
);
|
|
461
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
409
462
|
if (isVerticalOrientation(orientation)) {
|
|
410
463
|
const maximumLevel = d3Lib.max(treeData.descendants(), (d) => d.depth);
|
|
411
|
-
const depthSeparation =
|
|
464
|
+
const depthSeparation = layoutSpacings.verticalDepth;
|
|
412
465
|
treeData.each(function(d) {
|
|
413
466
|
d.x = d.x + highestVerticalCoordinate;
|
|
414
467
|
d.y = depthAxisSign(orientation) === -1 ? (maximumLevel - d.depth) * depthSeparation : d.depth * depthSeparation + rootDepthOffset;
|
|
@@ -430,24 +483,30 @@ function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation
|
|
|
430
483
|
});
|
|
431
484
|
return treeData;
|
|
432
485
|
}
|
|
433
|
-
function createTreeLayout(d3Lib, root, orientation = "horizontal", depthSpacingScale = 1) {
|
|
486
|
+
function createTreeLayout(d3Lib, root, orientation = "horizontal", depthSpacingScale = 1, suppliedItemCountRange) {
|
|
487
|
+
const nodes = root.descendants();
|
|
488
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
489
|
+
nodes.map((d) => d.data)
|
|
490
|
+
);
|
|
491
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
434
492
|
const nodeSize = isVerticalOrientation(orientation) ? [
|
|
435
|
-
|
|
436
|
-
|
|
493
|
+
layoutSpacings.verticalSibling,
|
|
494
|
+
layoutSpacings.verticalDepth
|
|
437
495
|
] : [
|
|
438
|
-
|
|
439
|
-
|
|
496
|
+
layoutSpacings.verticalBreadth,
|
|
497
|
+
layoutSpacings.horizontalDepth
|
|
440
498
|
];
|
|
441
499
|
const treeLayout = d3Lib.tree().nodeSize(nodeSize).separation(() => 1);
|
|
442
500
|
return treeLayout(root);
|
|
443
501
|
}
|
|
444
|
-
function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNodeId, reparentForbiddenIds, rootColor, callbacks, orientation = "horizontal", hideRoot = false, selectedNodeId = null) {
|
|
502
|
+
function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNodeId, reparentForbiddenIds, rootColor, callbacks, orientation = "horizontal", hideRoot = false, selectedNodeId = null, suppliedItemCountRange) {
|
|
445
503
|
const { onReparentTargetSelect, onNodeClick } = callbacks;
|
|
446
504
|
if (!svgRef.current) {
|
|
447
505
|
throw new Error("SVG reference is null");
|
|
448
506
|
}
|
|
449
|
-
const
|
|
450
|
-
|
|
507
|
+
const treeNodes = treeData.descendants();
|
|
508
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
509
|
+
treeNodes.map((d) => d.data)
|
|
451
510
|
);
|
|
452
511
|
const svg = d3Lib.select(svgRef.current).append("svg").attr("width", svgWidth).attr("height", svgHeight).style("overflow", "visible").append("g");
|
|
453
512
|
const safeRootId = treeData.data.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
@@ -595,7 +654,7 @@ function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNod
|
|
|
595
654
|
}
|
|
596
655
|
|
|
597
656
|
// src/FloatingControls.tsx
|
|
598
|
-
import {
|
|
657
|
+
import { ZoomControls } from "@behindthemusictree/ui";
|
|
599
658
|
|
|
600
659
|
// src/BusyIndicator.tsx
|
|
601
660
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -608,7 +667,7 @@ function BusyIndicator() {
|
|
|
608
667
|
|
|
609
668
|
// src/FloatingControls.tsx
|
|
610
669
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
611
|
-
function FloatingControls({ panZoom, onFit, orientation, leading, busy }) {
|
|
670
|
+
function FloatingControls({ panZoom, onFit, orientation, zoomControls, leading, busy }) {
|
|
612
671
|
return /* @__PURE__ */ jsxs3(
|
|
613
672
|
"div",
|
|
614
673
|
{
|
|
@@ -619,31 +678,18 @@ function FloatingControls({ panZoom, onFit, orientation, leading, busy }) {
|
|
|
619
678
|
children: [
|
|
620
679
|
busy && /* @__PURE__ */ jsx3(BusyIndicator, {}),
|
|
621
680
|
leading && /* @__PURE__ */ jsx3("div", { className: "gtv-floating-controls-leading", children: leading }),
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
/* @__PURE__ */ jsx3(
|
|
635
|
-
"button",
|
|
636
|
-
{
|
|
637
|
-
type: "button",
|
|
638
|
-
className: ["gtv-zoom-btn", !panZoom.canZoomOut && "gtv-zoom-btn--disabled"].filter(Boolean).join(" "),
|
|
639
|
-
disabled: !panZoom.canZoomOut,
|
|
640
|
-
onClick: panZoom.zoomOut,
|
|
641
|
-
"aria-label": "Zoom out",
|
|
642
|
-
children: /* @__PURE__ */ jsx3(MdZoomOut, { className: "gtv-icon", size: 18 })
|
|
643
|
-
}
|
|
644
|
-
),
|
|
645
|
-
/* @__PURE__ */ jsx3("button", { type: "button", className: "gtv-zoom-btn", onClick: onFit, "aria-label": "Fit to frame", children: /* @__PURE__ */ jsx3(MdFitScreen, { className: "gtv-icon", size: 18 }) })
|
|
646
|
-
] })
|
|
681
|
+
zoomControls !== "never" && /* @__PURE__ */ jsx3(
|
|
682
|
+
ZoomControls,
|
|
683
|
+
{
|
|
684
|
+
onZoomIn: panZoom.zoomIn,
|
|
685
|
+
onZoomOut: panZoom.zoomOut,
|
|
686
|
+
onFit,
|
|
687
|
+
canZoomIn: panZoom.canZoomIn,
|
|
688
|
+
canZoomOut: panZoom.canZoomOut,
|
|
689
|
+
orientation,
|
|
690
|
+
className: zoomControls === "auto" ? "gtv-zoom-controls gtv-zoom-controls--auto" : "gtv-zoom-controls"
|
|
691
|
+
}
|
|
692
|
+
)
|
|
647
693
|
]
|
|
648
694
|
}
|
|
649
695
|
);
|
|
@@ -1047,9 +1093,10 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1047
1093
|
const newScale = clampZoomScale(Math.max(targetScale, currentScale), minScaleRef.current);
|
|
1048
1094
|
const centerX = (rect.left + rect.width / 2 - viewportRect.left - view.x) / currentScale;
|
|
1049
1095
|
const centerY = (rect.top + rect.height / 2 - viewportRect.top - view.y) / currentScale;
|
|
1050
|
-
const availableCenterX =
|
|
1096
|
+
const availableCenterX = obscured?.side === "left" ? obscured.width + (viewportRect.width - obscured.width) / 2 : obscured?.side === "right" ? (viewportRect.width - obscured.width) / 2 : viewportRect.width / 2;
|
|
1097
|
+
const availableCenterY = obscured?.side === "bottom" ? (viewportRect.height - obscured.height) / 2 : viewportRect.height / 2;
|
|
1051
1098
|
const targetPanX = availableCenterX - centerX * newScale;
|
|
1052
|
-
const targetPanY =
|
|
1099
|
+
const targetPanY = availableCenterY - centerY * newScale;
|
|
1053
1100
|
zoomAnimationRef.current = null;
|
|
1054
1101
|
if (animationFrameIdRef.current !== null) {
|
|
1055
1102
|
cancelAnimationFrame(animationFrameIdRef.current);
|
|
@@ -1203,6 +1250,9 @@ function resolveInfoPanelSide() {
|
|
|
1203
1250
|
}
|
|
1204
1251
|
function resolveInfoPanelObscuredArea(element, viewport, panelWidth) {
|
|
1205
1252
|
if (!element || !viewport) return null;
|
|
1253
|
+
if (viewport.clientWidth > 0 && viewport.clientWidth <= INFO_PANEL_SHEET_MAX_WIDTH) {
|
|
1254
|
+
return { side: "bottom", height: viewport.clientHeight * INFO_PANEL_SHEET_PEEK_FRACTION };
|
|
1255
|
+
}
|
|
1206
1256
|
return { width: panelWidth, side: resolveInfoPanelSide() };
|
|
1207
1257
|
}
|
|
1208
1258
|
|
|
@@ -1284,8 +1334,8 @@ function useTreeIndex(nodes) {
|
|
|
1284
1334
|
}
|
|
1285
1335
|
|
|
1286
1336
|
// src/InfoPanel.tsx
|
|
1287
|
-
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
1288
|
-
import { MdArrowBack, MdClose, MdZoomIn
|
|
1337
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState5 } from "react";
|
|
1338
|
+
import { MdArrowBack, MdClose, MdZoomIn } from "react-icons/md";
|
|
1289
1339
|
|
|
1290
1340
|
// src/InfoPanelExpandButton.tsx
|
|
1291
1341
|
import { MdCloseFullscreen, MdOpenInFull } from "react-icons/md";
|
|
@@ -1524,6 +1574,27 @@ function InfoPanel({
|
|
|
1524
1574
|
hideExpandButton = false
|
|
1525
1575
|
}) {
|
|
1526
1576
|
const panelRef = useRef3(null);
|
|
1577
|
+
const [sheetFull, setSheetFull] = useState5(false);
|
|
1578
|
+
const grabberDragRef = useRef3(null);
|
|
1579
|
+
const onGrabberPointerDown = (event) => {
|
|
1580
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1581
|
+
grabberDragRef.current = { startY: event.clientY, dragged: false };
|
|
1582
|
+
};
|
|
1583
|
+
const onGrabberPointerUp = (event) => {
|
|
1584
|
+
const drag = grabberDragRef.current;
|
|
1585
|
+
if (!drag) return;
|
|
1586
|
+
const dy = event.clientY - drag.startY;
|
|
1587
|
+
if (Math.abs(dy) < INFO_PANEL_SHEET_DRAG_THRESHOLD_PX) return;
|
|
1588
|
+
drag.dragged = true;
|
|
1589
|
+
if (dy < 0) setSheetFull(true);
|
|
1590
|
+
else if (sheetFull) setSheetFull(false);
|
|
1591
|
+
else if (!hideClose) onClose();
|
|
1592
|
+
};
|
|
1593
|
+
const onGrabberClick = () => {
|
|
1594
|
+
const dragged = grabberDragRef.current?.dragged ?? false;
|
|
1595
|
+
grabberDragRef.current = null;
|
|
1596
|
+
if (!dragged) setSheetFull((full) => !full);
|
|
1597
|
+
};
|
|
1527
1598
|
useEffect3(() => {
|
|
1528
1599
|
panelRef.current?.toggleAttribute("inert", collapsed);
|
|
1529
1600
|
}, [collapsed]);
|
|
@@ -1626,9 +1697,21 @@ function InfoPanel({
|
|
|
1626
1697
|
return /* @__PURE__ */ jsxs6(
|
|
1627
1698
|
"div",
|
|
1628
1699
|
{
|
|
1629
|
-
className: `gtv-info-panel gtv-info-panel--${side}${expanded ? " gtv-info-panel--expanded" : ""}${expanded && !hasAside ? " gtv-info-panel--expanded-solo" : ""}${collapsed ? " gtv-info-panel--collapsed" : ""}`,
|
|
1700
|
+
className: `gtv-info-panel gtv-info-panel--sheet${sheetFull ? " gtv-info-panel--sheet-full" : ""} gtv-info-panel--${side}${expanded ? " gtv-info-panel--expanded" : ""}${expanded && !hasAside ? " gtv-info-panel--expanded-solo" : ""}${collapsed ? " gtv-info-panel--collapsed" : ""}`,
|
|
1630
1701
|
ref: panelRef,
|
|
1631
1702
|
children: [
|
|
1703
|
+
/* @__PURE__ */ jsx7(
|
|
1704
|
+
"button",
|
|
1705
|
+
{
|
|
1706
|
+
type: "button",
|
|
1707
|
+
className: "gtv-info-panel-grabber",
|
|
1708
|
+
"aria-label": sheetFull ? "Lower panel" : "Raise panel",
|
|
1709
|
+
"aria-expanded": sheetFull,
|
|
1710
|
+
onPointerDown: onGrabberPointerDown,
|
|
1711
|
+
onPointerUp: onGrabberPointerUp,
|
|
1712
|
+
onClick: onGrabberClick
|
|
1713
|
+
}
|
|
1714
|
+
),
|
|
1632
1715
|
/* @__PURE__ */ jsxs6(
|
|
1633
1716
|
"div",
|
|
1634
1717
|
{
|
|
@@ -1667,7 +1750,7 @@ function InfoPanel({
|
|
|
1667
1750
|
onClick: onZoomIn,
|
|
1668
1751
|
"aria-label": "Zoom in",
|
|
1669
1752
|
title: "Zoom in",
|
|
1670
|
-
children: /* @__PURE__ */ jsx7(
|
|
1753
|
+
children: /* @__PURE__ */ jsx7(MdZoomIn, { className: "gtv-icon", size: 16 })
|
|
1671
1754
|
}
|
|
1672
1755
|
),
|
|
1673
1756
|
onExpandedChange && !hideExpandButton && /* @__PURE__ */ jsx7(InfoPanelExpandButton, { expanded, onExpandedChange, style: { color: textColor } }),
|
|
@@ -1780,6 +1863,8 @@ function GenreTree({
|
|
|
1780
1863
|
hideInfoPanelClose = false,
|
|
1781
1864
|
hideInfoPanelTitle = false,
|
|
1782
1865
|
itemCountLabel,
|
|
1866
|
+
itemCountScaleMax,
|
|
1867
|
+
nodeCountForSizing,
|
|
1783
1868
|
readOnly,
|
|
1784
1869
|
renderDangerZone,
|
|
1785
1870
|
expanded,
|
|
@@ -1791,6 +1876,7 @@ function GenreTree({
|
|
|
1791
1876
|
defaultViewport,
|
|
1792
1877
|
onViewportChange,
|
|
1793
1878
|
controlsOrientation = "vertical",
|
|
1879
|
+
showZoomControls = "auto",
|
|
1794
1880
|
floatingControlsLeading,
|
|
1795
1881
|
busy
|
|
1796
1882
|
}) {
|
|
@@ -1800,6 +1886,10 @@ function GenreTree({
|
|
|
1800
1886
|
const stageRef = useRef5(null);
|
|
1801
1887
|
const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, onViewportChange });
|
|
1802
1888
|
const { panel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId: selectedNodeIdProp, onSelectedNodeChange });
|
|
1889
|
+
const itemCountRange = useMemo2(
|
|
1890
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: nodeCountForSizing ?? nodes.length }),
|
|
1891
|
+
[nodes, itemCountScaleMax, nodeCountForSizing]
|
|
1892
|
+
);
|
|
1803
1893
|
const showNodeInfoRef = useRef5(showNodeInfo);
|
|
1804
1894
|
useEffect5(() => {
|
|
1805
1895
|
showNodeInfoRef.current = showNodeInfo;
|
|
@@ -1814,7 +1904,8 @@ function GenreTree({
|
|
|
1814
1904
|
d3,
|
|
1815
1905
|
root,
|
|
1816
1906
|
orientation,
|
|
1817
|
-
depthSpacingScale
|
|
1907
|
+
depthSpacingScale,
|
|
1908
|
+
itemCountRange
|
|
1818
1909
|
);
|
|
1819
1910
|
const {
|
|
1820
1911
|
svgWidth: width,
|
|
@@ -1826,7 +1917,8 @@ function GenreTree({
|
|
|
1826
1917
|
originalTreeData,
|
|
1827
1918
|
orientation,
|
|
1828
1919
|
hideRoot,
|
|
1829
|
-
depthSpacingScale
|
|
1920
|
+
depthSpacingScale,
|
|
1921
|
+
itemCountRange
|
|
1830
1922
|
);
|
|
1831
1923
|
const reshapedTreeData = setupTreeLayout(
|
|
1832
1924
|
d3,
|
|
@@ -1835,7 +1927,8 @@ function GenreTree({
|
|
|
1835
1927
|
orientation,
|
|
1836
1928
|
rootDepthOffset,
|
|
1837
1929
|
width,
|
|
1838
|
-
depthSpacingScale
|
|
1930
|
+
depthSpacingScale,
|
|
1931
|
+
itemCountRange
|
|
1839
1932
|
);
|
|
1840
1933
|
return {
|
|
1841
1934
|
treeData: reshapedTreeData,
|
|
@@ -1843,7 +1936,7 @@ function GenreTree({
|
|
|
1843
1936
|
svgWidth: width,
|
|
1844
1937
|
svgHeight: height
|
|
1845
1938
|
};
|
|
1846
|
-
}, [nodes, rootColor, orientation, hideRoot, depthSpacingScale]);
|
|
1939
|
+
}, [nodes, rootColor, orientation, hideRoot, depthSpacingScale, itemCountRange]);
|
|
1847
1940
|
const reparentForbiddenIds = useMemo2(() => {
|
|
1848
1941
|
if (!reparentingNodeId) return [];
|
|
1849
1942
|
const reparentingHierarchyNode = treeData.descendants().find((d) => d.data.id === reparentingNodeId);
|
|
@@ -1894,7 +1987,8 @@ function GenreTree({
|
|
|
1894
1987
|
},
|
|
1895
1988
|
orientation,
|
|
1896
1989
|
hideRoot,
|
|
1897
|
-
effectiveSelectedNodeId
|
|
1990
|
+
effectiveSelectedNodeId,
|
|
1991
|
+
itemCountRange
|
|
1898
1992
|
);
|
|
1899
1993
|
}, [
|
|
1900
1994
|
treeData,
|
|
@@ -1915,7 +2009,8 @@ function GenreTree({
|
|
|
1915
2009
|
additionalActions,
|
|
1916
2010
|
orientation,
|
|
1917
2011
|
hideRoot,
|
|
1918
|
-
effectiveSelectedNodeId
|
|
2012
|
+
effectiveSelectedNodeId,
|
|
2013
|
+
itemCountRange
|
|
1919
2014
|
]);
|
|
1920
2015
|
useEffect5(() => {
|
|
1921
2016
|
if (!interactive || !selectedNodeIdProp) return;
|
|
@@ -2076,6 +2171,7 @@ function GenreTree({
|
|
|
2076
2171
|
{
|
|
2077
2172
|
panZoom,
|
|
2078
2173
|
orientation: controlsOrientation,
|
|
2174
|
+
zoomControls: showZoomControls,
|
|
2079
2175
|
leading: floatingControlsLeading,
|
|
2080
2176
|
busy,
|
|
2081
2177
|
onFit: () => panZoom.fitToFrame(queryTreeContentElements(svgRef.current))
|
|
@@ -2087,7 +2183,7 @@ function GenreTree({
|
|
|
2087
2183
|
}
|
|
2088
2184
|
|
|
2089
2185
|
// src/GenreTreeWheelBase.tsx
|
|
2090
|
-
import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as
|
|
2186
|
+
import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState6 } from "react";
|
|
2091
2187
|
import * as d32 from "d3";
|
|
2092
2188
|
|
|
2093
2189
|
// src/root-grouping.ts
|
|
@@ -2188,6 +2284,8 @@ function WheelCore({
|
|
|
2188
2284
|
hideInfoPanelClose = false,
|
|
2189
2285
|
hideInfoPanelTitle = false,
|
|
2190
2286
|
itemCountLabel,
|
|
2287
|
+
itemCountScaleMax,
|
|
2288
|
+
nodeCountForSizing,
|
|
2191
2289
|
readOnly,
|
|
2192
2290
|
renderDangerZone,
|
|
2193
2291
|
expanded,
|
|
@@ -2199,10 +2297,16 @@ function WheelCore({
|
|
|
2199
2297
|
defaultViewport,
|
|
2200
2298
|
onViewportChange,
|
|
2201
2299
|
controlsOrientation = "vertical",
|
|
2300
|
+
showZoomControls = "auto",
|
|
2202
2301
|
floatingControlsLeading,
|
|
2203
2302
|
busy,
|
|
2204
2303
|
selectedNodeId
|
|
2205
2304
|
}) {
|
|
2305
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
2306
|
+
const wheelSizeRange = useMemo3(
|
|
2307
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
2308
|
+
[nodes, itemCountScaleMax, fullNodeCountForSizing]
|
|
2309
|
+
);
|
|
2206
2310
|
const treeOrientation = direction === "left" ? "horizontal-anchored" : "vertical";
|
|
2207
2311
|
const landingAngle = direction === "left" ? 90 : 0;
|
|
2208
2312
|
const chipHalfExtentVar = direction === "left" ? "--gtv-wheel-chip-half-width" : "--gtv-wheel-chip-half-height";
|
|
@@ -2212,18 +2316,18 @@ function WheelCore({
|
|
|
2212
2316
|
return getGenreTreeColor(nodes.find((n) => n.id === rootId)?.wikidataId);
|
|
2213
2317
|
};
|
|
2214
2318
|
const wheelRadius = useMemo3(
|
|
2215
|
-
() => calculateWheelRadius(groups.length, MAX_NODE_WIDTH, WHEEL_RADIUS),
|
|
2216
|
-
[groups.length]
|
|
2319
|
+
() => calculateWheelRadius(groups.length, wheelSizeRange.maxNodeWidth ?? MAX_NODE_WIDTH, WHEEL_RADIUS),
|
|
2320
|
+
[groups.length, wheelSizeRange.maxNodeWidth]
|
|
2217
2321
|
);
|
|
2218
|
-
const wheelViewportHeight = wheelRadius * 2 + MAX_NODE_HEIGHT / 2;
|
|
2322
|
+
const wheelViewportHeight = wheelRadius * 2 + (wheelSizeRange.maxNodeHeight ?? MAX_NODE_HEIGHT) / 2;
|
|
2219
2323
|
const rootItemCountById = useMemo3(
|
|
2220
2324
|
() => new Map(groups.map((group) => [group.root.id, group.root.itemCount])),
|
|
2221
2325
|
[groups]
|
|
2222
2326
|
);
|
|
2223
|
-
const [selectedRootId, setSelectedRootId] =
|
|
2327
|
+
const [selectedRootId, setSelectedRootId] = useState6(
|
|
2224
2328
|
groups[0]?.root.id ?? null
|
|
2225
2329
|
);
|
|
2226
|
-
const [rotationDeg, setRotationDeg] =
|
|
2330
|
+
const [rotationDeg, setRotationDeg] = useState6(
|
|
2227
2331
|
() => groups.length > 0 ? computeRotationForSelection(
|
|
2228
2332
|
0,
|
|
2229
2333
|
getChipAngle(0, groups.length),
|
|
@@ -2262,16 +2366,18 @@ function WheelCore({
|
|
|
2262
2366
|
() => getItemCountRange(
|
|
2263
2367
|
groups.map((group) => ({
|
|
2264
2368
|
itemCount: rootItemCountById.get(group.root.id)
|
|
2265
|
-
}))
|
|
2369
|
+
})),
|
|
2370
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
2266
2371
|
),
|
|
2267
|
-
[groups, rootItemCountById]
|
|
2372
|
+
[groups, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
2268
2373
|
);
|
|
2269
2374
|
const rootChipHalfExtent = selectedGroup ? calculateRootAnchorClearance(
|
|
2270
2375
|
d32,
|
|
2271
2376
|
selectedGroup.nodes,
|
|
2272
2377
|
rootItemCountById.get(selectedGroup.root.id),
|
|
2273
2378
|
rootItemCountRange,
|
|
2274
|
-
direction === "left" ? "WIDTH" : "HEIGHT"
|
|
2379
|
+
direction === "left" ? "WIDTH" : "HEIGHT",
|
|
2380
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
2275
2381
|
) : 0;
|
|
2276
2382
|
const sectorFillGradient = useMemo3(
|
|
2277
2383
|
() => buildWheelSectorGradient(
|
|
@@ -2289,7 +2395,7 @@ function WheelCore({
|
|
|
2289
2395
|
);
|
|
2290
2396
|
}
|
|
2291
2397
|
};
|
|
2292
|
-
const [appliedSelectedNodeId, setAppliedSelectedNodeId] =
|
|
2398
|
+
const [appliedSelectedNodeId, setAppliedSelectedNodeId] = useState6(selectedNodeId);
|
|
2293
2399
|
if (selectedNodeId !== appliedSelectedNodeId) {
|
|
2294
2400
|
setAppliedSelectedNodeId(selectedNodeId);
|
|
2295
2401
|
const owningGroupIndex = selectedNodeId ? groups.findIndex(
|
|
@@ -2361,6 +2467,8 @@ function WheelCore({
|
|
|
2361
2467
|
{
|
|
2362
2468
|
nodes: selectedGroup.nodes,
|
|
2363
2469
|
itemCountLabel,
|
|
2470
|
+
itemCountScaleMax,
|
|
2471
|
+
nodeCountForSizing: fullNodeCountForSizing,
|
|
2364
2472
|
orientation: treeOrientation,
|
|
2365
2473
|
hideRoot: true,
|
|
2366
2474
|
interactive: false,
|
|
@@ -2572,6 +2680,7 @@ function WheelCore({
|
|
|
2572
2680
|
{
|
|
2573
2681
|
panZoom,
|
|
2574
2682
|
orientation: controlsOrientation,
|
|
2683
|
+
zoomControls: showZoomControls,
|
|
2575
2684
|
leading: floatingControlsLeading,
|
|
2576
2685
|
busy,
|
|
2577
2686
|
onFit: () => panZoom.fitToFrame([
|
|
@@ -2598,24 +2707,38 @@ function GenreTreeWheelRight(props) {
|
|
|
2598
2707
|
}
|
|
2599
2708
|
|
|
2600
2709
|
// src/GenreTreeWheelRadialBase.tsx
|
|
2601
|
-
import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef7, useState as
|
|
2710
|
+
import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef7, useState as useState7 } from "react";
|
|
2602
2711
|
import * as d33 from "d3";
|
|
2603
2712
|
|
|
2604
2713
|
// src/defer-click.ts
|
|
2605
2714
|
function createDeferredClick() {
|
|
2606
2715
|
let timeoutId = null;
|
|
2716
|
+
let pendingKey = void 0;
|
|
2717
|
+
let doubledAt = -Infinity;
|
|
2607
2718
|
const cancel = () => {
|
|
2608
2719
|
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
2609
2720
|
timeoutId = null;
|
|
2610
2721
|
};
|
|
2611
|
-
const schedule = (action) => {
|
|
2722
|
+
const schedule = (action, onDouble, key) => {
|
|
2723
|
+
if (onDouble && timeoutId !== null && key === pendingKey) {
|
|
2724
|
+
cancel();
|
|
2725
|
+
doubledAt = Date.now();
|
|
2726
|
+
onDouble();
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2612
2729
|
cancel();
|
|
2730
|
+
pendingKey = key;
|
|
2613
2731
|
timeoutId = setTimeout(() => {
|
|
2614
2732
|
timeoutId = null;
|
|
2615
2733
|
action();
|
|
2616
2734
|
}, DOUBLE_CLICK_DELAY_MS);
|
|
2617
2735
|
};
|
|
2618
|
-
|
|
2736
|
+
const doubleClick = (onDouble) => {
|
|
2737
|
+
cancel();
|
|
2738
|
+
if (Date.now() - doubledAt < DOUBLE_CLICK_DELAY_MS) return;
|
|
2739
|
+
onDouble();
|
|
2740
|
+
};
|
|
2741
|
+
return { schedule, cancel, doubleClick };
|
|
2619
2742
|
}
|
|
2620
2743
|
|
|
2621
2744
|
// src/radial-wheel-geometry.ts
|
|
@@ -2805,13 +2928,15 @@ function calculatePopSubtreeRadialExtent(hierarchy, coreRootCircleRadius = 0) {
|
|
|
2805
2928
|
POP_TREE_DEPTH_RADIAL_SPACING
|
|
2806
2929
|
) + MAX_NODE_WIDTH / 2 + POP_SUBTREE_OUTER_MARGIN;
|
|
2807
2930
|
}
|
|
2808
|
-
function computePopRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, coreRootCircleRadius, wedgeSpanDegrees = POP_WEDGE_SPAN_DEGREES, depthSpacing = POP_TREE_DEPTH_RADIAL_SPACING) {
|
|
2931
|
+
function computePopRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, coreRootCircleRadius, wedgeSpanDegrees = POP_WEDGE_SPAN_DEGREES, depthSpacing = POP_TREE_DEPTH_RADIAL_SPACING, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
2809
2932
|
return layoutWedge(
|
|
2810
2933
|
d3Lib,
|
|
2811
2934
|
hierarchy,
|
|
2812
2935
|
wedgeCenterAngleDegrees,
|
|
2813
2936
|
wedgeSpanDegrees,
|
|
2814
|
-
popRadiusOfDepth(coreRootCircleRadius, depthSpacing)
|
|
2937
|
+
popRadiusOfDepth(coreRootCircleRadius, depthSpacing),
|
|
2938
|
+
"center",
|
|
2939
|
+
dotRadius
|
|
2815
2940
|
);
|
|
2816
2941
|
}
|
|
2817
2942
|
var MIN_DOT_GAP = 5.6;
|
|
@@ -2821,15 +2946,15 @@ function popRadiusOfDepth(coreRootCircleRadius, depthSpacing) {
|
|
|
2821
2946
|
function coreRadiusOfDepth(coreRootCircleRadius, depthSpacing) {
|
|
2822
2947
|
return (depth) => getRadialDepthRadius(depth + 1, coreRootCircleRadius, depthSpacing);
|
|
2823
2948
|
}
|
|
2824
|
-
function measureWedge(_d3Lib, hierarchy, radiusOfDepth) {
|
|
2825
|
-
const
|
|
2949
|
+
function measureWedge(_d3Lib, hierarchy, radiusOfDepth, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
2950
|
+
const slotRadius = () => dotRadius;
|
|
2826
2951
|
const offsetFromParent = /* @__PURE__ */ new Map();
|
|
2827
2952
|
const shiftLevel = (level, by) => ({
|
|
2828
2953
|
left: { ...level.left, x: level.left.x + by },
|
|
2829
2954
|
right: { ...level.right, x: level.right.x + by }
|
|
2830
2955
|
});
|
|
2831
2956
|
const pack = (node) => {
|
|
2832
|
-
const own = { x: 0, dotRadius:
|
|
2957
|
+
const own = { x: 0, dotRadius: slotRadius() };
|
|
2833
2958
|
if (!node.children) return [{ left: own, right: own }];
|
|
2834
2959
|
const below = [];
|
|
2835
2960
|
const childOffsets = [];
|
|
@@ -2861,7 +2986,7 @@ function measureWedge(_d3Lib, hierarchy, radiusOfDepth) {
|
|
|
2861
2986
|
let min = Infinity;
|
|
2862
2987
|
let max = -Infinity;
|
|
2863
2988
|
hierarchy.each((d) => {
|
|
2864
|
-
const inset = (
|
|
2989
|
+
const inset = (slotRadius() + MIN_DOT_GAP / 2) / radiusOfDepth(d.depth);
|
|
2865
2990
|
min = Math.min(min, d.x - inset);
|
|
2866
2991
|
max = Math.max(max, d.x + inset);
|
|
2867
2992
|
});
|
|
@@ -2881,8 +3006,8 @@ function splitAtBranching(hierarchy) {
|
|
|
2881
3006
|
const children = branching.children;
|
|
2882
3007
|
return children && children.length > 1 ? { chain, branching, children } : null;
|
|
2883
3008
|
}
|
|
2884
|
-
function layoutWedge(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, radiusOfDepth, slack = "center") {
|
|
2885
|
-
const neededRad = measureWedge(d3Lib, hierarchy, radiusOfDepth);
|
|
3009
|
+
function layoutWedge(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, radiusOfDepth, slack = "center", dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
3010
|
+
const neededRad = measureWedge(d3Lib, hierarchy, radiusOfDepth, dotRadius);
|
|
2886
3011
|
const wedgeSpanRad = wedgeSpanDegrees * Math.PI / 180;
|
|
2887
3012
|
const scale = Math.min(MAX_WEDGE_STRETCH, wedgeSpanRad / neededRad);
|
|
2888
3013
|
const wedgeCenterRad = wedgeCenterAngleDegrees * Math.PI / 180;
|
|
@@ -3111,12 +3236,12 @@ function renderPopSubtree(d3Lib, svg, hierarchy, rootColor, reparentingNodeId, r
|
|
|
3111
3236
|
group.on("click", (event) => {
|
|
3112
3237
|
if (reparentingNodeId) return;
|
|
3113
3238
|
const element = event.currentTarget;
|
|
3114
|
-
deferredClick.schedule(
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
onNodeDoubleClick?.(d.data);
|
|
3239
|
+
deferredClick.schedule(
|
|
3240
|
+
() => onNodeClick?.(d.data, event, element),
|
|
3241
|
+
() => onNodeDoubleClick?.(d.data)
|
|
3242
|
+
);
|
|
3119
3243
|
});
|
|
3244
|
+
group.on("dblclick", () => deferredClick.doubleClick(() => onNodeDoubleClick?.(d.data)));
|
|
3120
3245
|
});
|
|
3121
3246
|
}
|
|
3122
3247
|
function hideCollidingLabels(d3Lib, container, itemCountRange, zoomScale) {
|
|
@@ -3193,14 +3318,15 @@ function buildCoreHierarchy(d3Lib, coreNodes) {
|
|
|
3193
3318
|
});
|
|
3194
3319
|
return hierarchy;
|
|
3195
3320
|
}
|
|
3196
|
-
function computeCoreRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, coreRootCircleRadius, depthSpacing) {
|
|
3321
|
+
function computeCoreRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, coreRootCircleRadius, depthSpacing, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
3197
3322
|
return layoutWedge(
|
|
3198
3323
|
d3Lib,
|
|
3199
3324
|
hierarchy,
|
|
3200
3325
|
wedgeCenterAngleDegrees,
|
|
3201
3326
|
wedgeSpanDegrees,
|
|
3202
3327
|
coreRadiusOfDepth(coreRootCircleRadius, depthSpacing),
|
|
3203
|
-
"between-children"
|
|
3328
|
+
"between-children",
|
|
3329
|
+
dotRadius
|
|
3204
3330
|
);
|
|
3205
3331
|
}
|
|
3206
3332
|
function calculateCoreSubtreeRadialExtent(hierarchy, depthSpacing, coreRootCircleRadius = 0) {
|
|
@@ -3274,6 +3400,8 @@ function WheelRadialCore({
|
|
|
3274
3400
|
hideInfoPanelClose = false,
|
|
3275
3401
|
hideInfoPanelTitle = false,
|
|
3276
3402
|
itemCountLabel,
|
|
3403
|
+
itemCountScaleMax,
|
|
3404
|
+
nodeCountForSizing,
|
|
3277
3405
|
readOnly,
|
|
3278
3406
|
renderDangerZone,
|
|
3279
3407
|
expanded,
|
|
@@ -3285,14 +3413,16 @@ function WheelRadialCore({
|
|
|
3285
3413
|
defaultViewport,
|
|
3286
3414
|
onViewportChange,
|
|
3287
3415
|
controlsOrientation = "vertical",
|
|
3416
|
+
showZoomControls = "auto",
|
|
3288
3417
|
floatingControlsLeading,
|
|
3289
3418
|
busy,
|
|
3290
3419
|
selectedNodeId
|
|
3291
3420
|
}) {
|
|
3421
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
3292
3422
|
const index = useTreeIndex(nodes);
|
|
3293
3423
|
const groups = useMemo4(() => groupNodesByRoot(nodes), [nodes]);
|
|
3294
3424
|
const rootColorOf = (node) => getGenreTreeColor(index.byId.get(index.rootIdById.get(node.id) ?? node.id)?.wikidataId);
|
|
3295
|
-
const [topRootId, setTopRootId] =
|
|
3425
|
+
const [topRootId, setTopRootId] = useState7(
|
|
3296
3426
|
groups[0]?.root.id ?? null
|
|
3297
3427
|
);
|
|
3298
3428
|
const viewportRef = useRef7(null);
|
|
@@ -3322,7 +3452,7 @@ function WheelRadialCore({
|
|
|
3322
3452
|
() => computeRadialLayout(rootWeights, topIndex, LANDING_ANGLE),
|
|
3323
3453
|
[rootWeights, topIndex]
|
|
3324
3454
|
);
|
|
3325
|
-
const [angleMemo, setAngleMemo] =
|
|
3455
|
+
const [angleMemo, setAngleMemo] = useState7(() => ({
|
|
3326
3456
|
layout,
|
|
3327
3457
|
angles: computeContinuousAngles(groups, layout, /* @__PURE__ */ new Map())
|
|
3328
3458
|
}));
|
|
@@ -3341,11 +3471,15 @@ function WheelRadialCore({
|
|
|
3341
3471
|
() => getItemCountRange(
|
|
3342
3472
|
groups.map((group) => ({
|
|
3343
3473
|
itemCount: rootItemCountById.get(group.root.id)
|
|
3344
|
-
}))
|
|
3474
|
+
})),
|
|
3475
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
3345
3476
|
),
|
|
3346
|
-
[groups, rootItemCountById]
|
|
3477
|
+
[groups, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
3478
|
+
);
|
|
3479
|
+
const wheelItemCountRange = useMemo4(
|
|
3480
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
3481
|
+
[nodes, itemCountScaleMax, fullNodeCountForSizing]
|
|
3347
3482
|
);
|
|
3348
|
-
const wheelItemCountRange = useMemo4(() => getItemCountRange(nodes), [nodes]);
|
|
3349
3483
|
const sectorSpanByRootId = useMemo4(() => {
|
|
3350
3484
|
const map = /* @__PURE__ */ new Map();
|
|
3351
3485
|
if (groups.length <= 1) return map;
|
|
@@ -3395,10 +3529,10 @@ function WheelRadialCore({
|
|
|
3395
3529
|
const chipClearanceFloor = useMemo4(
|
|
3396
3530
|
() => calculateWheelRadiusForAngles(
|
|
3397
3531
|
layout.map((slot) => slot.angle),
|
|
3398
|
-
MAX_NODE_WIDTH,
|
|
3532
|
+
wheelItemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH,
|
|
3399
3533
|
WHEEL_RADIUS
|
|
3400
3534
|
),
|
|
3401
|
-
[layout]
|
|
3535
|
+
[layout, wheelItemCountRange.maxNodeWidth]
|
|
3402
3536
|
);
|
|
3403
3537
|
const maxCoreExtentDelta = useMemo4(() => {
|
|
3404
3538
|
let extent = 0;
|
|
@@ -3768,6 +3902,7 @@ function WheelRadialCore({
|
|
|
3768
3902
|
{
|
|
3769
3903
|
panZoom,
|
|
3770
3904
|
orientation: controlsOrientation,
|
|
3905
|
+
zoomControls: showZoomControls,
|
|
3771
3906
|
leading: floatingControlsLeading,
|
|
3772
3907
|
busy,
|
|
3773
3908
|
onFit: () => panZoom.fitToFrame([wheelCircleRef.current, coreSvgRef.current])
|
|
@@ -3785,13 +3920,13 @@ function GenreTreeWheelRadial(props) {
|
|
|
3785
3920
|
}
|
|
3786
3921
|
|
|
3787
3922
|
// src/GenreTreeWheelRadialPopCoreBase.tsx
|
|
3788
|
-
import { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo5, useRef as useRef8, useState as
|
|
3923
|
+
import { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo5, useRef as useRef8, useState as useState10 } from "react";
|
|
3789
3924
|
import * as d34 from "d3";
|
|
3790
3925
|
|
|
3791
3926
|
// src/use-focus-history.ts
|
|
3792
|
-
import { useState as
|
|
3927
|
+
import { useState as useState8 } from "react";
|
|
3793
3928
|
function useFocusHistory(focusNodeId, isFocusable) {
|
|
3794
|
-
const [state, setState] =
|
|
3929
|
+
const [state, setState] = useState8({ seen: focusNodeId, stack: [], pending: void 0 });
|
|
3795
3930
|
if (state.seen !== focusNodeId) {
|
|
3796
3931
|
const popTo = state.pending === focusNodeId ? state.stack.lastIndexOf(focusNodeId) : -1;
|
|
3797
3932
|
setState({
|
|
@@ -3813,13 +3948,13 @@ function useFocusHistory(focusNodeId, isFocusable) {
|
|
|
3813
3948
|
}
|
|
3814
3949
|
|
|
3815
3950
|
// src/WheelBreadcrumb.tsx
|
|
3816
|
-
import { Fragment as Fragment3, useState as
|
|
3951
|
+
import { Fragment as Fragment3, useState as useState9 } from "react";
|
|
3817
3952
|
import { MdHome } from "react-icons/md";
|
|
3818
3953
|
import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3819
3954
|
var MAX_VISIBLE_ANCESTORS = 3;
|
|
3820
3955
|
var ANCESTORS_KEPT_WHEN_COLLAPSED = 2;
|
|
3821
3956
|
function WheelBreadcrumb({ ancestors, currentName, onHome, onAncestor }) {
|
|
3822
|
-
const [expanded, setExpanded] =
|
|
3957
|
+
const [expanded, setExpanded] = useState9(false);
|
|
3823
3958
|
const collapsed = !expanded && ancestors.length > MAX_VISIBLE_ANCESTORS;
|
|
3824
3959
|
const visibleAncestors = collapsed ? ancestors.slice(-ANCESTORS_KEPT_WHEN_COLLAPSED) : ancestors;
|
|
3825
3960
|
const separator = /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u203A" });
|
|
@@ -3951,6 +4086,8 @@ function WheelRadialPopCoreCore({
|
|
|
3951
4086
|
hideInfoPanelClose = false,
|
|
3952
4087
|
hideInfoPanelTitle = false,
|
|
3953
4088
|
itemCountLabel,
|
|
4089
|
+
itemCountScaleMax,
|
|
4090
|
+
nodeCountForSizing,
|
|
3954
4091
|
readOnly,
|
|
3955
4092
|
renderDangerZone,
|
|
3956
4093
|
expanded,
|
|
@@ -3962,6 +4099,7 @@ function WheelRadialPopCoreCore({
|
|
|
3962
4099
|
defaultViewport,
|
|
3963
4100
|
onViewportChange,
|
|
3964
4101
|
controlsOrientation = "vertical",
|
|
4102
|
+
showZoomControls = "auto",
|
|
3965
4103
|
floatingControlsLeading,
|
|
3966
4104
|
busy,
|
|
3967
4105
|
allowWheelRotation = true,
|
|
@@ -3969,6 +4107,7 @@ function WheelRadialPopCoreCore({
|
|
|
3969
4107
|
focusNodeId: requestedFocusNodeId = null,
|
|
3970
4108
|
onFocusChange
|
|
3971
4109
|
}) {
|
|
4110
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
3972
4111
|
const index = useTreeIndex(nodes);
|
|
3973
4112
|
const focusNodeId = useMemo5(
|
|
3974
4113
|
() => requestedFocusNodeId !== null && isZoomableNode(nodes, requestedFocusNodeId) ? requestedFocusNodeId : null,
|
|
@@ -4022,13 +4161,13 @@ function WheelRadialPopCoreCore({
|
|
|
4022
4161
|
(node) => getGenreNodeStyle(index, node.id),
|
|
4023
4162
|
[index]
|
|
4024
4163
|
);
|
|
4025
|
-
const [topRootId, setTopRootId] =
|
|
4164
|
+
const [topRootId, setTopRootId] = useState10(
|
|
4026
4165
|
groups[0]?.root.id ?? null
|
|
4027
4166
|
);
|
|
4028
|
-
const [rotationTopRootId, setRotationTopRootId] =
|
|
4167
|
+
const [rotationTopRootId, setRotationTopRootId] = useState10(
|
|
4029
4168
|
groups[0]?.root.id ?? null
|
|
4030
4169
|
);
|
|
4031
|
-
const [renderedFocusNodeId, setRenderedFocusNodeId] =
|
|
4170
|
+
const [renderedFocusNodeId, setRenderedFocusNodeId] = useState10(focusNodeId);
|
|
4032
4171
|
if (renderedFocusNodeId !== focusNodeId) {
|
|
4033
4172
|
setRenderedFocusNodeId(focusNodeId);
|
|
4034
4173
|
setTopRootId(groups[0]?.root.id ?? null);
|
|
@@ -4091,7 +4230,7 @@ function WheelRadialPopCoreCore({
|
|
|
4091
4230
|
const popSvgRef = useRef8(null);
|
|
4092
4231
|
const centerChipRef = useRef8(null);
|
|
4093
4232
|
const regionLabelRef = useRef8(null);
|
|
4094
|
-
const [regionLabel, setRegionLabel] =
|
|
4233
|
+
const [regionLabel, setRegionLabel] = useState10(null);
|
|
4095
4234
|
const effectiveTopRootId = groups.some((group) => group.root.id === topRootId) ? topRootId : groups[0]?.root.id ?? null;
|
|
4096
4235
|
const effectiveRotationTopRootId = groups.some(
|
|
4097
4236
|
(group) => group.root.id === rotationTopRootId
|
|
@@ -4108,8 +4247,8 @@ function WheelRadialPopCoreCore({
|
|
|
4108
4247
|
() => getItemCountRange([
|
|
4109
4248
|
...nodes,
|
|
4110
4249
|
...[...rootItemCountById.values()].map((itemCount) => ({ itemCount }))
|
|
4111
|
-
]),
|
|
4112
|
-
[nodes, rootItemCountById]
|
|
4250
|
+
], { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
4251
|
+
[nodes, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
4113
4252
|
);
|
|
4114
4253
|
const isCenterMainstream = isMainstreamPopLineage(index, centerNode.id);
|
|
4115
4254
|
const centerNodeColor = isCenterMainstream ? "#ffffff" : getGenreNodeStyle(index, centerNode.id).fill;
|
|
@@ -4143,8 +4282,8 @@ function WheelRadialPopCoreCore({
|
|
|
4143
4282
|
centerSubtreeHierarchy,
|
|
4144
4283
|
0,
|
|
4145
4284
|
POP_TREE_DEPTH_RADIAL_SPACING
|
|
4146
|
-
) : NODE_LAYOUT_SLOT_RADIUS,
|
|
4147
|
-
[centerSubtreeHierarchy]
|
|
4285
|
+
) : wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS,
|
|
4286
|
+
[centerSubtreeHierarchy, wheelItemCountRange.maxDotRadius]
|
|
4148
4287
|
);
|
|
4149
4288
|
const maxCoreExtentDelta = useMemo5(() => {
|
|
4150
4289
|
let extent = 0;
|
|
@@ -4166,7 +4305,8 @@ function WheelRadialPopCoreCore({
|
|
|
4166
4305
|
(radius) => new Map(
|
|
4167
4306
|
groups.map((group) => {
|
|
4168
4307
|
const { pop, core } = branchHierarchiesByRootId.get(group.root.id);
|
|
4169
|
-
const
|
|
4308
|
+
const dotRadius = wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS;
|
|
4309
|
+
const chipWidth = 2 * dotRadius;
|
|
4170
4310
|
return [
|
|
4171
4311
|
group.root.id,
|
|
4172
4312
|
{
|
|
@@ -4174,14 +4314,16 @@ function WheelRadialPopCoreCore({
|
|
|
4174
4314
|
(hierarchy) => measureWedge(
|
|
4175
4315
|
d34,
|
|
4176
4316
|
hierarchy,
|
|
4177
|
-
popRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING)
|
|
4317
|
+
popRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING),
|
|
4318
|
+
dotRadius
|
|
4178
4319
|
)
|
|
4179
4320
|
),
|
|
4180
4321
|
core: core.map(
|
|
4181
4322
|
(hierarchy) => measureWedge(
|
|
4182
4323
|
d34,
|
|
4183
4324
|
hierarchy,
|
|
4184
|
-
coreRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING)
|
|
4325
|
+
coreRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING),
|
|
4326
|
+
dotRadius
|
|
4185
4327
|
)
|
|
4186
4328
|
),
|
|
4187
4329
|
chip: 2 * Math.asin(Math.min(1, (chipWidth / 2 + CHIP_GAP) / radius))
|
|
@@ -4189,7 +4331,7 @@ function WheelRadialPopCoreCore({
|
|
|
4189
4331
|
];
|
|
4190
4332
|
})
|
|
4191
4333
|
),
|
|
4192
|
-
[groups, branchHierarchiesByRootId]
|
|
4334
|
+
[groups, branchHierarchiesByRootId, wheelItemCountRange.maxDotRadius]
|
|
4193
4335
|
);
|
|
4194
4336
|
const coreRootCircleRadius = useMemo5(
|
|
4195
4337
|
() => solveRingRadius(
|
|
@@ -4199,11 +4341,11 @@ function WheelRadialPopCoreCore({
|
|
|
4199
4341
|
),
|
|
4200
4342
|
Math.max(
|
|
4201
4343
|
WHEEL_POP_CORE_RADIUS,
|
|
4202
|
-
mainstreamCircleRadius + NODE_DOT_MAX_RADIUS + POP_SUBTREE_OUTER_MARGIN,
|
|
4344
|
+
mainstreamCircleRadius + (wheelItemCountRange.maxDotRadius ?? NODE_DOT_MAX_RADIUS) + POP_SUBTREE_OUTER_MARGIN,
|
|
4203
4345
|
popReachRequiredRadius
|
|
4204
4346
|
)
|
|
4205
4347
|
),
|
|
4206
|
-
[measureBranchNeedsAt, mainstreamCircleRadius, popReachRequiredRadius]
|
|
4348
|
+
[measureBranchNeedsAt, mainstreamCircleRadius, popReachRequiredRadius, wheelItemCountRange.maxDotRadius]
|
|
4207
4349
|
);
|
|
4208
4350
|
const branchNeedsByRootId = useMemo5(
|
|
4209
4351
|
() => measureBranchNeedsAt(coreRootCircleRadius),
|
|
@@ -4220,7 +4362,7 @@ function WheelRadialPopCoreCore({
|
|
|
4220
4362
|
() => computeRadialLayout(rootWeights, topIndex, LANDING_ANGLE2),
|
|
4221
4363
|
[rootWeights, topIndex]
|
|
4222
4364
|
);
|
|
4223
|
-
const [angleMemo, setAngleMemo] =
|
|
4365
|
+
const [angleMemo, setAngleMemo] = useState10(() => ({
|
|
4224
4366
|
layout,
|
|
4225
4367
|
angles: computeContinuousAngles2(groups, layout, /* @__PURE__ */ new Map())
|
|
4226
4368
|
}));
|
|
@@ -4307,7 +4449,8 @@ function WheelRadialPopCoreCore({
|
|
|
4307
4449
|
angle,
|
|
4308
4450
|
coreRootCircleRadius,
|
|
4309
4451
|
span,
|
|
4310
|
-
POP_TREE_DEPTH_RADIAL_SPACING
|
|
4452
|
+
POP_TREE_DEPTH_RADIAL_SPACING,
|
|
4453
|
+
wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS
|
|
4311
4454
|
);
|
|
4312
4455
|
const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
|
|
4313
4456
|
const branchGroup = sectorGroup.append("g").attr("class", "gtv-wheel-pop-branch").attr("data-gtv-branch-index", branchIndex);
|
|
@@ -4373,7 +4516,8 @@ function WheelRadialPopCoreCore({
|
|
|
4373
4516
|
angle,
|
|
4374
4517
|
span,
|
|
4375
4518
|
coreRootCircleRadius,
|
|
4376
|
-
POP_TREE_DEPTH_RADIAL_SPACING
|
|
4519
|
+
POP_TREE_DEPTH_RADIAL_SPACING,
|
|
4520
|
+
wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS
|
|
4377
4521
|
);
|
|
4378
4522
|
const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
|
|
4379
4523
|
const angleOf = (d) => {
|
|
@@ -4537,7 +4681,7 @@ function WheelRadialPopCoreCore({
|
|
|
4537
4681
|
originGroup.selectAll("g.gtv-root-label").select("text").style("pointer-events", "all").style("cursor", "pointer").on("click", (_event, d) => {
|
|
4538
4682
|
const chip = d.id === centerNode.id ? centerChipRef.current : viewportRef.current?.querySelector(`#group-${CSS.escape(d.id)}`);
|
|
4539
4683
|
chip?.click();
|
|
4540
|
-
}).on("dblclick", (_event, d) => focusOnNodeRef.current(d.id));
|
|
4684
|
+
}).on("dblclick", (_event, d) => chipClickRef.current.doubleClick(() => focusOnNodeRef.current(d.id)));
|
|
4541
4685
|
}, [
|
|
4542
4686
|
centerNode,
|
|
4543
4687
|
centerHasPopDisc,
|
|
@@ -4696,7 +4840,7 @@ function WheelRadialPopCoreCore({
|
|
|
4696
4840
|
)
|
|
4697
4841
|
};
|
|
4698
4842
|
});
|
|
4699
|
-
}, [groups, continuousAngleByRootId, sectorSpanByRootId]);
|
|
4843
|
+
}, [groups, continuousAngleByRootId, sectorSpanByRootId, index]);
|
|
4700
4844
|
return /* @__PURE__ */ jsxs12(
|
|
4701
4845
|
"div",
|
|
4702
4846
|
{
|
|
@@ -4806,9 +4950,9 @@ function WheelRadialPopCoreCore({
|
|
|
4806
4950
|
showNodeInfo(centerNode, chip, viewportRef.current);
|
|
4807
4951
|
onNodeClick?.(centerNode, nativeEvent);
|
|
4808
4952
|
}
|
|
4809
|
-
});
|
|
4953
|
+
}, () => focusOnNode(centerNode.id), centerNode.id);
|
|
4810
4954
|
},
|
|
4811
|
-
onDoubleClick: () => focusOnNode(centerNode.id),
|
|
4955
|
+
onDoubleClick: () => chipClickRef.current.doubleClick(() => focusOnNode(centerNode.id)),
|
|
4812
4956
|
"aria-label": centerNode.name
|
|
4813
4957
|
}
|
|
4814
4958
|
) }) }),
|
|
@@ -4868,9 +5012,9 @@ function WheelRadialPopCoreCore({
|
|
|
4868
5012
|
showNodeInfo(root, chip, viewportRef.current);
|
|
4869
5013
|
onNodeClick?.(root, nativeEvent);
|
|
4870
5014
|
}
|
|
4871
|
-
});
|
|
5015
|
+
}, () => focusOnNode(group.root.id), group.root.id);
|
|
4872
5016
|
},
|
|
4873
|
-
onDoubleClick: () => focusOnNode(group.root.id)
|
|
5017
|
+
onDoubleClick: () => chipClickRef.current.doubleClick(() => focusOnNode(group.root.id))
|
|
4874
5018
|
}
|
|
4875
5019
|
) })
|
|
4876
5020
|
},
|
|
@@ -4965,6 +5109,7 @@ function WheelRadialPopCoreCore({
|
|
|
4965
5109
|
{
|
|
4966
5110
|
panZoom,
|
|
4967
5111
|
orientation: controlsOrientation,
|
|
5112
|
+
zoomControls: showZoomControls,
|
|
4968
5113
|
leading: floatingControlsLeading,
|
|
4969
5114
|
busy,
|
|
4970
5115
|
onFit: () => panZoom.fitToFrame([
|
|
@@ -4991,7 +5136,7 @@ import {
|
|
|
4991
5136
|
useEffect as useEffect9,
|
|
4992
5137
|
useMemo as useMemo6,
|
|
4993
5138
|
useRef as useRef9,
|
|
4994
|
-
useState as
|
|
5139
|
+
useState as useState11,
|
|
4995
5140
|
useSyncExternalStore
|
|
4996
5141
|
} from "react";
|
|
4997
5142
|
import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
@@ -5149,7 +5294,7 @@ function GenreTreeOutline(props) {
|
|
|
5149
5294
|
}
|
|
5150
5295
|
return ids;
|
|
5151
5296
|
}, [reparentingNodeId, index]);
|
|
5152
|
-
const [store] =
|
|
5297
|
+
const [store] = useState11(
|
|
5153
5298
|
() => createOutlineStore({
|
|
5154
5299
|
selectedId: null,
|
|
5155
5300
|
openIds: /* @__PURE__ */ new Set(),
|