@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.cjs
CHANGED
|
@@ -181,6 +181,9 @@ var MIN_NODE_WIDTH = 180;
|
|
|
181
181
|
var MAX_NODE_WIDTH = 700;
|
|
182
182
|
var MIN_NODE_HEIGHT = 35;
|
|
183
183
|
var MAX_NODE_HEIGHT = 120;
|
|
184
|
+
var NODE_SIZE_REFERENCE_COUNT = 100;
|
|
185
|
+
var HIGH_DENSITY_NODE_MAX_FLOOR_SCALE = 1.5;
|
|
186
|
+
var LOW_DENSITY_DOT_MAX_RADIUS = 70;
|
|
184
187
|
var TOOLBAR_MENU_X_GAP = 4;
|
|
185
188
|
var ACTIONS_OVERLAY_WIDTH = MENU_WIDTH + TOOLBAR_MENU_X_GAP;
|
|
186
189
|
var ACTIONS_OVERLAY_HEIGHT = RECT_BASE_DIMENSIONS.HEIGHT * 7;
|
|
@@ -195,23 +198,47 @@ var HORIZONTAL_SEPARATION_BETWEEN_NODES = (NODE_DIMENSIONS.WIDTH + HORIZONTAL_SE
|
|
|
195
198
|
var VERTICAL_SEPARATION_BETWEEN_NODES = NODE_DIMENSIONS.HEIGHT + VERTICAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
196
199
|
var VERTICAL_ORIENTATION_DEPTH_SEPARATION = VERTICAL_SEPARATION_BETWEEN_NODES * 2 * DEPTH_AXIS_SPACING_FACTOR;
|
|
197
200
|
var SIBLING_SEPARATION_BETWEEN_NODES = MAX_NODE_WIDTH + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
198
|
-
function
|
|
199
|
-
|
|
201
|
+
function getDensityAdjustedMax(baseMax, min, nodeCount) {
|
|
202
|
+
const safeNodeCount = Number.isFinite(nodeCount) ? Math.max(1, nodeCount) : 1;
|
|
203
|
+
const availableRangeScale = Math.min(1, NODE_SIZE_REFERENCE_COUNT / safeNodeCount);
|
|
204
|
+
const denseTreeFloor = min * HIGH_DENSITY_NODE_MAX_FLOOR_SCALE;
|
|
205
|
+
return denseTreeFloor + (baseMax - denseTreeFloor) * availableRangeScale;
|
|
206
|
+
}
|
|
207
|
+
function getDensityAdjustedDotMax(nodeCount) {
|
|
208
|
+
const safeNodeCount = Number.isFinite(nodeCount) ? Math.max(1, nodeCount) : 1;
|
|
209
|
+
const inverseDensityRadius = NODE_DOT_MAX_RADIUS * NODE_SIZE_REFERENCE_COUNT / safeNodeCount;
|
|
210
|
+
const denseTreeFloor = NODE_DOT_MIN_RADIUS * HIGH_DENSITY_NODE_MAX_FLOOR_SCALE;
|
|
211
|
+
return Math.max(denseTreeFloor, Math.min(LOW_DENSITY_DOT_MAX_RADIUS, inverseDensityRadius));
|
|
212
|
+
}
|
|
213
|
+
function getItemCountRange(nodes, options = {}) {
|
|
200
214
|
const counts = nodes.map((node) => node.itemCount);
|
|
201
|
-
|
|
215
|
+
const validScaleMax = options.itemCountScaleMax !== void 0 && Number.isFinite(options.itemCountScaleMax) && options.itemCountScaleMax >= 0;
|
|
216
|
+
const nodeCount = options.nodeCountForSizing ?? nodes.length;
|
|
217
|
+
const observedMin = counts.length === 0 ? 0 : Math.min(...counts);
|
|
218
|
+
const observedMax = counts.length === 0 ? 0 : Math.max(...counts);
|
|
219
|
+
const includeDensityCaps = options.nodeCountForSizing !== void 0 || validScaleMax;
|
|
220
|
+
return {
|
|
221
|
+
min: validScaleMax ? 0 : observedMin,
|
|
222
|
+
max: validScaleMax ? options.itemCountScaleMax : observedMax,
|
|
223
|
+
...includeDensityCaps && {
|
|
224
|
+
maxNodeWidth: getDensityAdjustedMax(MAX_NODE_WIDTH, MIN_NODE_WIDTH, nodeCount),
|
|
225
|
+
maxNodeHeight: getDensityAdjustedMax(MAX_NODE_HEIGHT, MIN_NODE_HEIGHT, nodeCount),
|
|
226
|
+
maxDotRadius: getDensityAdjustedDotMax(nodeCount)
|
|
227
|
+
}
|
|
228
|
+
};
|
|
202
229
|
}
|
|
203
230
|
function logarithmicPosition(itemCount, range) {
|
|
204
231
|
const { min, max } = range;
|
|
205
232
|
const logMin = Math.log1p(Math.max(min, 0));
|
|
206
233
|
const logMax = Math.log1p(Math.max(max, 0));
|
|
207
234
|
const logValue = Math.log1p(Math.max(itemCount, 0));
|
|
208
|
-
return logMax > logMin ? (logValue - logMin) / (logMax - logMin) : 0;
|
|
235
|
+
return logMax > logMin ? Math.max(0, Math.min(1, (logValue - logMin) / (logMax - logMin))) : 0;
|
|
209
236
|
}
|
|
210
237
|
function calculateNodeDimensions(itemCount, range) {
|
|
211
238
|
const t = logarithmicPosition(itemCount, range);
|
|
212
239
|
return {
|
|
213
|
-
WIDTH: Math.round(MIN_NODE_WIDTH + t * (MAX_NODE_WIDTH - MIN_NODE_WIDTH)),
|
|
214
|
-
HEIGHT: Math.round(MIN_NODE_HEIGHT + t * (MAX_NODE_HEIGHT - MIN_NODE_HEIGHT))
|
|
240
|
+
WIDTH: Math.round(MIN_NODE_WIDTH + t * ((range.maxNodeWidth ?? MAX_NODE_WIDTH) - MIN_NODE_WIDTH)),
|
|
241
|
+
HEIGHT: Math.round(MIN_NODE_HEIGHT + t * ((range.maxNodeHeight ?? MAX_NODE_HEIGHT) - MIN_NODE_HEIGHT))
|
|
215
242
|
};
|
|
216
243
|
}
|
|
217
244
|
var NODE_MIN_BORDER_WIDTH = 1;
|
|
@@ -256,7 +283,7 @@ var NODE_DOT_LABEL_GAP = 6;
|
|
|
256
283
|
var NODE_LAYOUT_SLOT_RADIUS = 8;
|
|
257
284
|
function calculateNodeDotRadius(itemCount, range) {
|
|
258
285
|
const minArea = NODE_DOT_MIN_RADIUS ** 2;
|
|
259
|
-
const maxArea = NODE_DOT_MAX_RADIUS ** 2;
|
|
286
|
+
const maxArea = (range.maxDotRadius ?? NODE_DOT_MAX_RADIUS) ** 2;
|
|
260
287
|
return Math.sqrt(minArea + logarithmicPosition(itemCount, range) * (maxArea - minArea));
|
|
261
288
|
}
|
|
262
289
|
var WHEEL_RADIUS = 260;
|
|
@@ -291,19 +318,22 @@ var PAN_MIN_VISIBLE_PX = 80;
|
|
|
291
318
|
var PAN_ZOOM_SETTLE_MS = 100;
|
|
292
319
|
var DEFAULT_VIEWPORT_CENTER_GUARD_MS = 1e3;
|
|
293
320
|
var INFO_PANEL_WIDTH = 280;
|
|
321
|
+
var INFO_PANEL_SHEET_MAX_WIDTH = 767;
|
|
322
|
+
var INFO_PANEL_SHEET_PEEK_FRACTION = 0.4;
|
|
323
|
+
var INFO_PANEL_SHEET_DRAG_THRESHOLD_PX = 32;
|
|
294
324
|
|
|
295
325
|
// src/NodeHelper.tsx
|
|
296
326
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
297
327
|
function buildTreeHierarchyStructure(d3Lib, nodes) {
|
|
298
328
|
return d3Lib.stratify().id((d) => d.id).parentId((d) => d.parentId)(nodes);
|
|
299
329
|
}
|
|
300
|
-
function calculateLocalRootDimensions(d3Lib, nodes) {
|
|
330
|
+
function calculateLocalRootDimensions(d3Lib, nodes, options = {}) {
|
|
301
331
|
const root = buildTreeHierarchyStructure(d3Lib, nodes);
|
|
302
|
-
const range = getItemCountRange(root.descendants().map((d) => d.data));
|
|
332
|
+
const range = getItemCountRange(root.descendants().map((d) => d.data), options);
|
|
303
333
|
return calculateNodeDimensions(root.data.itemCount, range);
|
|
304
334
|
}
|
|
305
|
-
function calculateRootAnchorClearance(d3Lib, nodes, rootItemCount, rootItemCountRange, axis) {
|
|
306
|
-
const localExtent = calculateLocalRootDimensions(d3Lib, nodes)[axis];
|
|
335
|
+
function calculateRootAnchorClearance(d3Lib, nodes, rootItemCount, rootItemCountRange, axis, options = {}) {
|
|
336
|
+
const localExtent = calculateLocalRootDimensions(d3Lib, nodes, options)[axis];
|
|
307
337
|
const displayExtent = calculateNodeDimensions(rootItemCount, rootItemCountRange)[axis];
|
|
308
338
|
return (localExtent + displayExtent) / 2;
|
|
309
339
|
}
|
|
@@ -387,13 +417,31 @@ function appendPaths(d3Lib, svg, treeData, itemCountRange, orientation = "horizo
|
|
|
387
417
|
}
|
|
388
418
|
|
|
389
419
|
// src/tree-renderer.ts
|
|
390
|
-
function
|
|
391
|
-
const
|
|
392
|
-
const
|
|
393
|
-
const
|
|
420
|
+
function getLayoutSpacings(itemCountRange, depthSpacingScale = 1) {
|
|
421
|
+
const maxWidth = itemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH;
|
|
422
|
+
const maxHeight = itemCountRange.maxNodeHeight ?? MAX_NODE_HEIGHT;
|
|
423
|
+
const verticalSeparation = maxHeight + VERTICAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
424
|
+
return {
|
|
425
|
+
horizontalDepth: (maxWidth + ACTIONS_OVERLAY_WIDTH + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES) * DEPTH_AXIS_SPACING_FACTOR * depthSpacingScale,
|
|
426
|
+
verticalBreadth: verticalSeparation,
|
|
427
|
+
verticalDepth: verticalSeparation * 2 * DEPTH_AXIS_SPACING_FACTOR * depthSpacingScale,
|
|
428
|
+
verticalSibling: maxWidth + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function calculateSvgDimensions(d3Lib, treeData, orientation = "horizontal", hideRoot = false, depthSpacingScale = 1, suppliedItemCountRange) {
|
|
432
|
+
const treeNodes = treeData.descendants();
|
|
433
|
+
const nodes = treeNodes;
|
|
434
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
435
|
+
treeNodes.map((d) => d.data)
|
|
436
|
+
);
|
|
437
|
+
const maxNodeDimensions = {
|
|
438
|
+
WIDTH: itemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH,
|
|
439
|
+
HEIGHT: itemCountRange.maxNodeHeight ?? MAX_NODE_HEIGHT
|
|
440
|
+
};
|
|
394
441
|
const maximumLevel = d3Lib.max(nodes, (d) => d.depth);
|
|
395
|
-
const
|
|
396
|
-
const
|
|
442
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
443
|
+
const depthSeparation = layoutSpacings.verticalDepth;
|
|
444
|
+
const depthSeparationHorizontal = layoutSpacings.horizontalDepth;
|
|
397
445
|
if (isVerticalOrientation(orientation)) {
|
|
398
446
|
const svgHeight2 = hideRoot ? maximumLevel * depthSeparation : maximumLevel * depthSeparation + maxNodeDimensions.HEIGHT + ACTIONS_OVERLAY_HEIGHT / 2;
|
|
399
447
|
const rootWidth = calculateNodeDimensions(
|
|
@@ -455,10 +503,15 @@ function calculateSvgDimensions(d3Lib, treeData, orientation = "horizontal", hid
|
|
|
455
503
|
const svgWidth = maximumLevel * depthSeparationHorizontal + maxNodeDimensions.WIDTH + ACTIONS_OVERLAY_WIDTH;
|
|
456
504
|
return { svgWidth, svgHeight, highestVerticalCoordinate, rootDepthOffset: 0 };
|
|
457
505
|
}
|
|
458
|
-
function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation = "horizontal", rootDepthOffset = 0, svgWidth = 0, depthSpacingScale = 1) {
|
|
506
|
+
function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation = "horizontal", rootDepthOffset = 0, svgWidth = 0, depthSpacingScale = 1, suppliedItemCountRange) {
|
|
507
|
+
const treeNodes = treeData.descendants();
|
|
508
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
509
|
+
treeNodes.map((d) => d.data)
|
|
510
|
+
);
|
|
511
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
459
512
|
if (isVerticalOrientation(orientation)) {
|
|
460
513
|
const maximumLevel = d3Lib.max(treeData.descendants(), (d) => d.depth);
|
|
461
|
-
const depthSeparation =
|
|
514
|
+
const depthSeparation = layoutSpacings.verticalDepth;
|
|
462
515
|
treeData.each(function(d) {
|
|
463
516
|
d.x = d.x + highestVerticalCoordinate;
|
|
464
517
|
d.y = depthAxisSign(orientation) === -1 ? (maximumLevel - d.depth) * depthSeparation : d.depth * depthSeparation + rootDepthOffset;
|
|
@@ -480,24 +533,30 @@ function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation
|
|
|
480
533
|
});
|
|
481
534
|
return treeData;
|
|
482
535
|
}
|
|
483
|
-
function createTreeLayout(d3Lib, root, orientation = "horizontal", depthSpacingScale = 1) {
|
|
536
|
+
function createTreeLayout(d3Lib, root, orientation = "horizontal", depthSpacingScale = 1, suppliedItemCountRange) {
|
|
537
|
+
const nodes = root.descendants();
|
|
538
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
539
|
+
nodes.map((d) => d.data)
|
|
540
|
+
);
|
|
541
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
484
542
|
const nodeSize = isVerticalOrientation(orientation) ? [
|
|
485
|
-
|
|
486
|
-
|
|
543
|
+
layoutSpacings.verticalSibling,
|
|
544
|
+
layoutSpacings.verticalDepth
|
|
487
545
|
] : [
|
|
488
|
-
|
|
489
|
-
|
|
546
|
+
layoutSpacings.verticalBreadth,
|
|
547
|
+
layoutSpacings.horizontalDepth
|
|
490
548
|
];
|
|
491
549
|
const treeLayout = d3Lib.tree().nodeSize(nodeSize).separation(() => 1);
|
|
492
550
|
return treeLayout(root);
|
|
493
551
|
}
|
|
494
|
-
function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNodeId, reparentForbiddenIds, rootColor, callbacks, orientation = "horizontal", hideRoot = false, selectedNodeId = null) {
|
|
552
|
+
function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNodeId, reparentForbiddenIds, rootColor, callbacks, orientation = "horizontal", hideRoot = false, selectedNodeId = null, suppliedItemCountRange) {
|
|
495
553
|
const { onReparentTargetSelect, onNodeClick } = callbacks;
|
|
496
554
|
if (!svgRef.current) {
|
|
497
555
|
throw new Error("SVG reference is null");
|
|
498
556
|
}
|
|
499
|
-
const
|
|
500
|
-
|
|
557
|
+
const treeNodes = treeData.descendants();
|
|
558
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
559
|
+
treeNodes.map((d) => d.data)
|
|
501
560
|
);
|
|
502
561
|
const svg = d3Lib.select(svgRef.current).append("svg").attr("width", svgWidth).attr("height", svgHeight).style("overflow", "visible").append("g");
|
|
503
562
|
const safeRootId = treeData.data.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
@@ -645,7 +704,7 @@ function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNod
|
|
|
645
704
|
}
|
|
646
705
|
|
|
647
706
|
// src/FloatingControls.tsx
|
|
648
|
-
var
|
|
707
|
+
var import_ui = require("@behindthemusictree/ui");
|
|
649
708
|
|
|
650
709
|
// src/BusyIndicator.tsx
|
|
651
710
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
@@ -658,7 +717,7 @@ function BusyIndicator() {
|
|
|
658
717
|
|
|
659
718
|
// src/FloatingControls.tsx
|
|
660
719
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
661
|
-
function FloatingControls({ panZoom, onFit, orientation, leading, busy }) {
|
|
720
|
+
function FloatingControls({ panZoom, onFit, orientation, zoomControls, leading, busy }) {
|
|
662
721
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
663
722
|
"div",
|
|
664
723
|
{
|
|
@@ -669,31 +728,18 @@ function FloatingControls({ panZoom, onFit, orientation, leading, busy }) {
|
|
|
669
728
|
children: [
|
|
670
729
|
busy && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BusyIndicator, {}),
|
|
671
730
|
leading && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "gtv-floating-controls-leading", children: leading }),
|
|
672
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
685
|
-
"button",
|
|
686
|
-
{
|
|
687
|
-
type: "button",
|
|
688
|
-
className: ["gtv-zoom-btn", !panZoom.canZoomOut && "gtv-zoom-btn--disabled"].filter(Boolean).join(" "),
|
|
689
|
-
disabled: !panZoom.canZoomOut,
|
|
690
|
-
onClick: panZoom.zoomOut,
|
|
691
|
-
"aria-label": "Zoom out",
|
|
692
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_md.MdZoomOut, { className: "gtv-icon", size: 18 })
|
|
693
|
-
}
|
|
694
|
-
),
|
|
695
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "gtv-zoom-btn", onClick: onFit, "aria-label": "Fit to frame", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_md.MdFitScreen, { className: "gtv-icon", size: 18 }) })
|
|
696
|
-
] })
|
|
731
|
+
zoomControls !== "never" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
732
|
+
import_ui.ZoomControls,
|
|
733
|
+
{
|
|
734
|
+
onZoomIn: panZoom.zoomIn,
|
|
735
|
+
onZoomOut: panZoom.zoomOut,
|
|
736
|
+
onFit,
|
|
737
|
+
canZoomIn: panZoom.canZoomIn,
|
|
738
|
+
canZoomOut: panZoom.canZoomOut,
|
|
739
|
+
orientation,
|
|
740
|
+
className: zoomControls === "auto" ? "gtv-zoom-controls gtv-zoom-controls--auto" : "gtv-zoom-controls"
|
|
741
|
+
}
|
|
742
|
+
)
|
|
697
743
|
]
|
|
698
744
|
}
|
|
699
745
|
);
|
|
@@ -1097,9 +1143,10 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1097
1143
|
const newScale = clampZoomScale(Math.max(targetScale, currentScale), minScaleRef.current);
|
|
1098
1144
|
const centerX = (rect.left + rect.width / 2 - viewportRect.left - view.x) / currentScale;
|
|
1099
1145
|
const centerY = (rect.top + rect.height / 2 - viewportRect.top - view.y) / currentScale;
|
|
1100
|
-
const availableCenterX =
|
|
1146
|
+
const availableCenterX = obscured?.side === "left" ? obscured.width + (viewportRect.width - obscured.width) / 2 : obscured?.side === "right" ? (viewportRect.width - obscured.width) / 2 : viewportRect.width / 2;
|
|
1147
|
+
const availableCenterY = obscured?.side === "bottom" ? (viewportRect.height - obscured.height) / 2 : viewportRect.height / 2;
|
|
1101
1148
|
const targetPanX = availableCenterX - centerX * newScale;
|
|
1102
|
-
const targetPanY =
|
|
1149
|
+
const targetPanY = availableCenterY - centerY * newScale;
|
|
1103
1150
|
zoomAnimationRef.current = null;
|
|
1104
1151
|
if (animationFrameIdRef.current !== null) {
|
|
1105
1152
|
cancelAnimationFrame(animationFrameIdRef.current);
|
|
@@ -1253,6 +1300,9 @@ function resolveInfoPanelSide() {
|
|
|
1253
1300
|
}
|
|
1254
1301
|
function resolveInfoPanelObscuredArea(element, viewport, panelWidth) {
|
|
1255
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
|
+
}
|
|
1256
1306
|
return { width: panelWidth, side: resolveInfoPanelSide() };
|
|
1257
1307
|
}
|
|
1258
1308
|
|
|
@@ -1335,10 +1385,10 @@ function useTreeIndex(nodes) {
|
|
|
1335
1385
|
|
|
1336
1386
|
// src/InfoPanel.tsx
|
|
1337
1387
|
var import_react6 = require("react");
|
|
1338
|
-
var
|
|
1388
|
+
var import_md4 = require("react-icons/md");
|
|
1339
1389
|
|
|
1340
1390
|
// src/InfoPanelExpandButton.tsx
|
|
1341
|
-
var
|
|
1391
|
+
var import_md = require("react-icons/md");
|
|
1342
1392
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1343
1393
|
function InfoPanelExpandButton({ expanded, onExpandedChange, style }) {
|
|
1344
1394
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -1349,14 +1399,14 @@ function InfoPanelExpandButton({ expanded, onExpandedChange, style }) {
|
|
|
1349
1399
|
style,
|
|
1350
1400
|
onClick: () => onExpandedChange(!expanded),
|
|
1351
1401
|
"aria-label": expanded ? "Collapse panel" : "Expand panel",
|
|
1352
|
-
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1402
|
+
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_md.MdCloseFullscreen, { className: "gtv-icon", size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_md.MdOpenInFull, { className: "gtv-icon", size: 16 })
|
|
1353
1403
|
}
|
|
1354
1404
|
);
|
|
1355
1405
|
}
|
|
1356
1406
|
|
|
1357
1407
|
// src/InfoPanelSection.tsx
|
|
1358
1408
|
var import_react4 = require("react");
|
|
1359
|
-
var
|
|
1409
|
+
var import_md2 = require("react-icons/md");
|
|
1360
1410
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1361
1411
|
function InfoPanelSection({ title, children }) {
|
|
1362
1412
|
const [open, setOpen] = (0, import_react4.useState)(true);
|
|
@@ -1370,7 +1420,7 @@ function InfoPanelSection({ title, children }) {
|
|
|
1370
1420
|
onClick: () => setOpen((isOpen) => !isOpen),
|
|
1371
1421
|
children: [
|
|
1372
1422
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "gtv-info-panel-children-title", children: title }),
|
|
1373
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1423
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_md2.MdExpandMore, { className: "gtv-icon gtv-info-panel-section-chevron", size: 16 })
|
|
1374
1424
|
]
|
|
1375
1425
|
}
|
|
1376
1426
|
),
|
|
@@ -1380,7 +1430,7 @@ function InfoPanelSection({ title, children }) {
|
|
|
1380
1430
|
|
|
1381
1431
|
// src/NodeToolbar.tsx
|
|
1382
1432
|
var import_react5 = require("react");
|
|
1383
|
-
var
|
|
1433
|
+
var import_md3 = require("react-icons/md");
|
|
1384
1434
|
var import_fa = require("react-icons/fa");
|
|
1385
1435
|
var import_pi2 = require("react-icons/pi");
|
|
1386
1436
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
@@ -1458,7 +1508,7 @@ function NodeToolbar({
|
|
|
1458
1508
|
"aria-haspopup": "menu",
|
|
1459
1509
|
"aria-expanded": menuOpen,
|
|
1460
1510
|
onClick: () => setMenuOpen((open) => !open),
|
|
1461
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1511
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_md3.MdMoreVert, { size: "0.6em" })
|
|
1462
1512
|
}
|
|
1463
1513
|
),
|
|
1464
1514
|
menuOpen && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "gtv-menu-card gtv-toolbar-overflow-menu", children: [
|
|
@@ -1472,7 +1522,7 @@ function NodeToolbar({
|
|
|
1472
1522
|
onRenameRequest?.(node);
|
|
1473
1523
|
},
|
|
1474
1524
|
children: [
|
|
1475
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1525
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_md3.MdModeEdit, { className: "gtv-icon", size: 13 }) }),
|
|
1476
1526
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-label", children: "Rename" })
|
|
1477
1527
|
]
|
|
1478
1528
|
}
|
|
@@ -1574,6 +1624,27 @@ function InfoPanel({
|
|
|
1574
1624
|
hideExpandButton = false
|
|
1575
1625
|
}) {
|
|
1576
1626
|
const panelRef = (0, import_react6.useRef)(null);
|
|
1627
|
+
const [sheetFull, setSheetFull] = (0, import_react6.useState)(false);
|
|
1628
|
+
const grabberDragRef = (0, import_react6.useRef)(null);
|
|
1629
|
+
const onGrabberPointerDown = (event) => {
|
|
1630
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1631
|
+
grabberDragRef.current = { startY: event.clientY, dragged: false };
|
|
1632
|
+
};
|
|
1633
|
+
const onGrabberPointerUp = (event) => {
|
|
1634
|
+
const drag = grabberDragRef.current;
|
|
1635
|
+
if (!drag) return;
|
|
1636
|
+
const dy = event.clientY - drag.startY;
|
|
1637
|
+
if (Math.abs(dy) < INFO_PANEL_SHEET_DRAG_THRESHOLD_PX) return;
|
|
1638
|
+
drag.dragged = true;
|
|
1639
|
+
if (dy < 0) setSheetFull(true);
|
|
1640
|
+
else if (sheetFull) setSheetFull(false);
|
|
1641
|
+
else if (!hideClose) onClose();
|
|
1642
|
+
};
|
|
1643
|
+
const onGrabberClick = () => {
|
|
1644
|
+
const dragged = grabberDragRef.current?.dragged ?? false;
|
|
1645
|
+
grabberDragRef.current = null;
|
|
1646
|
+
if (!dragged) setSheetFull((full) => !full);
|
|
1647
|
+
};
|
|
1577
1648
|
(0, import_react6.useEffect)(() => {
|
|
1578
1649
|
panelRef.current?.toggleAttribute("inert", collapsed);
|
|
1579
1650
|
}, [collapsed]);
|
|
@@ -1676,9 +1747,21 @@ function InfoPanel({
|
|
|
1676
1747
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1677
1748
|
"div",
|
|
1678
1749
|
{
|
|
1679
|
-
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" : ""}`,
|
|
1750
|
+
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" : ""}`,
|
|
1680
1751
|
ref: panelRef,
|
|
1681
1752
|
children: [
|
|
1753
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1754
|
+
"button",
|
|
1755
|
+
{
|
|
1756
|
+
type: "button",
|
|
1757
|
+
className: "gtv-info-panel-grabber",
|
|
1758
|
+
"aria-label": sheetFull ? "Lower panel" : "Raise panel",
|
|
1759
|
+
"aria-expanded": sheetFull,
|
|
1760
|
+
onPointerDown: onGrabberPointerDown,
|
|
1761
|
+
onPointerUp: onGrabberPointerUp,
|
|
1762
|
+
onClick: onGrabberClick
|
|
1763
|
+
}
|
|
1764
|
+
),
|
|
1682
1765
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1683
1766
|
"div",
|
|
1684
1767
|
{
|
|
@@ -1694,7 +1777,7 @@ function InfoPanel({
|
|
|
1694
1777
|
onClick: onPrevious,
|
|
1695
1778
|
"aria-label": previousLabel ? `Previous: ${previousLabel}` : "Previous",
|
|
1696
1779
|
title: previousLabel,
|
|
1697
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1780
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md4.MdArrowBack, { className: "gtv-icon", size: 16 })
|
|
1698
1781
|
}
|
|
1699
1782
|
),
|
|
1700
1783
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
@@ -1717,7 +1800,7 @@ function InfoPanel({
|
|
|
1717
1800
|
onClick: onZoomIn,
|
|
1718
1801
|
"aria-label": "Zoom in",
|
|
1719
1802
|
title: "Zoom in",
|
|
1720
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1803
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md4.MdZoomIn, { className: "gtv-icon", size: 16 })
|
|
1721
1804
|
}
|
|
1722
1805
|
),
|
|
1723
1806
|
onExpandedChange && !hideExpandButton && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoPanelExpandButton, { expanded, onExpandedChange, style: { color: textColor } }),
|
|
@@ -1729,7 +1812,7 @@ function InfoPanel({
|
|
|
1729
1812
|
style: { color: textColor },
|
|
1730
1813
|
onClick: onClose,
|
|
1731
1814
|
"aria-label": "Close",
|
|
1732
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1815
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md4.MdClose, { className: "gtv-icon", size: 16 })
|
|
1733
1816
|
}
|
|
1734
1817
|
)
|
|
1735
1818
|
]
|
|
@@ -1753,7 +1836,7 @@ function InfoPanel({
|
|
|
1753
1836
|
|
|
1754
1837
|
// src/OverlayPanel.tsx
|
|
1755
1838
|
var import_react7 = require("react");
|
|
1756
|
-
var
|
|
1839
|
+
var import_md5 = require("react-icons/md");
|
|
1757
1840
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1758
1841
|
function OverlayPanel({
|
|
1759
1842
|
title,
|
|
@@ -1793,7 +1876,7 @@ function OverlayPanel({
|
|
|
1793
1876
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "gtv-info-panel-header", children: [
|
|
1794
1877
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: `gtv-info-panel-title${hideTitle ? " gtv-info-panel-title--hidden" : ""}`, children: title }),
|
|
1795
1878
|
onExpandedChange && !hideExpandButton && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(InfoPanelExpandButton, { expanded, onExpandedChange }),
|
|
1796
|
-
!hideClose && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "gtv-info-panel-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1879
|
+
!hideClose && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "gtv-info-panel-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_md5.MdClose, { className: "gtv-icon", size: 16 }) })
|
|
1797
1880
|
] }),
|
|
1798
1881
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "gtv-info-panel-body", children: [
|
|
1799
1882
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "gtv-info-panel-aside gtv-overlay-content", children: content }),
|
|
@@ -1830,6 +1913,8 @@ function GenreTree({
|
|
|
1830
1913
|
hideInfoPanelClose = false,
|
|
1831
1914
|
hideInfoPanelTitle = false,
|
|
1832
1915
|
itemCountLabel,
|
|
1916
|
+
itemCountScaleMax,
|
|
1917
|
+
nodeCountForSizing,
|
|
1833
1918
|
readOnly,
|
|
1834
1919
|
renderDangerZone,
|
|
1835
1920
|
expanded,
|
|
@@ -1841,6 +1926,7 @@ function GenreTree({
|
|
|
1841
1926
|
defaultViewport,
|
|
1842
1927
|
onViewportChange,
|
|
1843
1928
|
controlsOrientation = "vertical",
|
|
1929
|
+
showZoomControls = "auto",
|
|
1844
1930
|
floatingControlsLeading,
|
|
1845
1931
|
busy
|
|
1846
1932
|
}) {
|
|
@@ -1850,6 +1936,10 @@ function GenreTree({
|
|
|
1850
1936
|
const stageRef = (0, import_react8.useRef)(null);
|
|
1851
1937
|
const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, onViewportChange });
|
|
1852
1938
|
const { panel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId: selectedNodeIdProp, onSelectedNodeChange });
|
|
1939
|
+
const itemCountRange = (0, import_react8.useMemo)(
|
|
1940
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: nodeCountForSizing ?? nodes.length }),
|
|
1941
|
+
[nodes, itemCountScaleMax, nodeCountForSizing]
|
|
1942
|
+
);
|
|
1853
1943
|
const showNodeInfoRef = (0, import_react8.useRef)(showNodeInfo);
|
|
1854
1944
|
(0, import_react8.useEffect)(() => {
|
|
1855
1945
|
showNodeInfoRef.current = showNodeInfo;
|
|
@@ -1864,7 +1954,8 @@ function GenreTree({
|
|
|
1864
1954
|
d3,
|
|
1865
1955
|
root,
|
|
1866
1956
|
orientation,
|
|
1867
|
-
depthSpacingScale
|
|
1957
|
+
depthSpacingScale,
|
|
1958
|
+
itemCountRange
|
|
1868
1959
|
);
|
|
1869
1960
|
const {
|
|
1870
1961
|
svgWidth: width,
|
|
@@ -1876,7 +1967,8 @@ function GenreTree({
|
|
|
1876
1967
|
originalTreeData,
|
|
1877
1968
|
orientation,
|
|
1878
1969
|
hideRoot,
|
|
1879
|
-
depthSpacingScale
|
|
1970
|
+
depthSpacingScale,
|
|
1971
|
+
itemCountRange
|
|
1880
1972
|
);
|
|
1881
1973
|
const reshapedTreeData = setupTreeLayout(
|
|
1882
1974
|
d3,
|
|
@@ -1885,7 +1977,8 @@ function GenreTree({
|
|
|
1885
1977
|
orientation,
|
|
1886
1978
|
rootDepthOffset,
|
|
1887
1979
|
width,
|
|
1888
|
-
depthSpacingScale
|
|
1980
|
+
depthSpacingScale,
|
|
1981
|
+
itemCountRange
|
|
1889
1982
|
);
|
|
1890
1983
|
return {
|
|
1891
1984
|
treeData: reshapedTreeData,
|
|
@@ -1893,7 +1986,7 @@ function GenreTree({
|
|
|
1893
1986
|
svgWidth: width,
|
|
1894
1987
|
svgHeight: height
|
|
1895
1988
|
};
|
|
1896
|
-
}, [nodes, rootColor, orientation, hideRoot, depthSpacingScale]);
|
|
1989
|
+
}, [nodes, rootColor, orientation, hideRoot, depthSpacingScale, itemCountRange]);
|
|
1897
1990
|
const reparentForbiddenIds = (0, import_react8.useMemo)(() => {
|
|
1898
1991
|
if (!reparentingNodeId) return [];
|
|
1899
1992
|
const reparentingHierarchyNode = treeData.descendants().find((d) => d.data.id === reparentingNodeId);
|
|
@@ -1944,7 +2037,8 @@ function GenreTree({
|
|
|
1944
2037
|
},
|
|
1945
2038
|
orientation,
|
|
1946
2039
|
hideRoot,
|
|
1947
|
-
effectiveSelectedNodeId
|
|
2040
|
+
effectiveSelectedNodeId,
|
|
2041
|
+
itemCountRange
|
|
1948
2042
|
);
|
|
1949
2043
|
}, [
|
|
1950
2044
|
treeData,
|
|
@@ -1965,7 +2059,8 @@ function GenreTree({
|
|
|
1965
2059
|
additionalActions,
|
|
1966
2060
|
orientation,
|
|
1967
2061
|
hideRoot,
|
|
1968
|
-
effectiveSelectedNodeId
|
|
2062
|
+
effectiveSelectedNodeId,
|
|
2063
|
+
itemCountRange
|
|
1969
2064
|
]);
|
|
1970
2065
|
(0, import_react8.useEffect)(() => {
|
|
1971
2066
|
if (!interactive || !selectedNodeIdProp) return;
|
|
@@ -2126,6 +2221,7 @@ function GenreTree({
|
|
|
2126
2221
|
{
|
|
2127
2222
|
panZoom,
|
|
2128
2223
|
orientation: controlsOrientation,
|
|
2224
|
+
zoomControls: showZoomControls,
|
|
2129
2225
|
leading: floatingControlsLeading,
|
|
2130
2226
|
busy,
|
|
2131
2227
|
onFit: () => panZoom.fitToFrame(queryTreeContentElements(svgRef.current))
|
|
@@ -2238,6 +2334,8 @@ function WheelCore({
|
|
|
2238
2334
|
hideInfoPanelClose = false,
|
|
2239
2335
|
hideInfoPanelTitle = false,
|
|
2240
2336
|
itemCountLabel,
|
|
2337
|
+
itemCountScaleMax,
|
|
2338
|
+
nodeCountForSizing,
|
|
2241
2339
|
readOnly,
|
|
2242
2340
|
renderDangerZone,
|
|
2243
2341
|
expanded,
|
|
@@ -2249,10 +2347,16 @@ function WheelCore({
|
|
|
2249
2347
|
defaultViewport,
|
|
2250
2348
|
onViewportChange,
|
|
2251
2349
|
controlsOrientation = "vertical",
|
|
2350
|
+
showZoomControls = "auto",
|
|
2252
2351
|
floatingControlsLeading,
|
|
2253
2352
|
busy,
|
|
2254
2353
|
selectedNodeId
|
|
2255
2354
|
}) {
|
|
2355
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
2356
|
+
const wheelSizeRange = (0, import_react9.useMemo)(
|
|
2357
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
2358
|
+
[nodes, itemCountScaleMax, fullNodeCountForSizing]
|
|
2359
|
+
);
|
|
2256
2360
|
const treeOrientation = direction === "left" ? "horizontal-anchored" : "vertical";
|
|
2257
2361
|
const landingAngle = direction === "left" ? 90 : 0;
|
|
2258
2362
|
const chipHalfExtentVar = direction === "left" ? "--gtv-wheel-chip-half-width" : "--gtv-wheel-chip-half-height";
|
|
@@ -2262,10 +2366,10 @@ function WheelCore({
|
|
|
2262
2366
|
return getGenreTreeColor(nodes.find((n) => n.id === rootId)?.wikidataId);
|
|
2263
2367
|
};
|
|
2264
2368
|
const wheelRadius = (0, import_react9.useMemo)(
|
|
2265
|
-
() => calculateWheelRadius(groups.length, MAX_NODE_WIDTH, WHEEL_RADIUS),
|
|
2266
|
-
[groups.length]
|
|
2369
|
+
() => calculateWheelRadius(groups.length, wheelSizeRange.maxNodeWidth ?? MAX_NODE_WIDTH, WHEEL_RADIUS),
|
|
2370
|
+
[groups.length, wheelSizeRange.maxNodeWidth]
|
|
2267
2371
|
);
|
|
2268
|
-
const wheelViewportHeight = wheelRadius * 2 + MAX_NODE_HEIGHT / 2;
|
|
2372
|
+
const wheelViewportHeight = wheelRadius * 2 + (wheelSizeRange.maxNodeHeight ?? MAX_NODE_HEIGHT) / 2;
|
|
2269
2373
|
const rootItemCountById = (0, import_react9.useMemo)(
|
|
2270
2374
|
() => new Map(groups.map((group) => [group.root.id, group.root.itemCount])),
|
|
2271
2375
|
[groups]
|
|
@@ -2312,16 +2416,18 @@ function WheelCore({
|
|
|
2312
2416
|
() => getItemCountRange(
|
|
2313
2417
|
groups.map((group) => ({
|
|
2314
2418
|
itemCount: rootItemCountById.get(group.root.id)
|
|
2315
|
-
}))
|
|
2419
|
+
})),
|
|
2420
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
2316
2421
|
),
|
|
2317
|
-
[groups, rootItemCountById]
|
|
2422
|
+
[groups, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
2318
2423
|
);
|
|
2319
2424
|
const rootChipHalfExtent = selectedGroup ? calculateRootAnchorClearance(
|
|
2320
2425
|
d32,
|
|
2321
2426
|
selectedGroup.nodes,
|
|
2322
2427
|
rootItemCountById.get(selectedGroup.root.id),
|
|
2323
2428
|
rootItemCountRange,
|
|
2324
|
-
direction === "left" ? "WIDTH" : "HEIGHT"
|
|
2429
|
+
direction === "left" ? "WIDTH" : "HEIGHT",
|
|
2430
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
2325
2431
|
) : 0;
|
|
2326
2432
|
const sectorFillGradient = (0, import_react9.useMemo)(
|
|
2327
2433
|
() => buildWheelSectorGradient(
|
|
@@ -2411,6 +2517,8 @@ function WheelCore({
|
|
|
2411
2517
|
{
|
|
2412
2518
|
nodes: selectedGroup.nodes,
|
|
2413
2519
|
itemCountLabel,
|
|
2520
|
+
itemCountScaleMax,
|
|
2521
|
+
nodeCountForSizing: fullNodeCountForSizing,
|
|
2414
2522
|
orientation: treeOrientation,
|
|
2415
2523
|
hideRoot: true,
|
|
2416
2524
|
interactive: false,
|
|
@@ -2622,6 +2730,7 @@ function WheelCore({
|
|
|
2622
2730
|
{
|
|
2623
2731
|
panZoom,
|
|
2624
2732
|
orientation: controlsOrientation,
|
|
2733
|
+
zoomControls: showZoomControls,
|
|
2625
2734
|
leading: floatingControlsLeading,
|
|
2626
2735
|
busy,
|
|
2627
2736
|
onFit: () => panZoom.fitToFrame([
|
|
@@ -2654,18 +2763,32 @@ var d33 = __toESM(require("d3"), 1);
|
|
|
2654
2763
|
// src/defer-click.ts
|
|
2655
2764
|
function createDeferredClick() {
|
|
2656
2765
|
let timeoutId = null;
|
|
2766
|
+
let pendingKey = void 0;
|
|
2767
|
+
let doubledAt = -Infinity;
|
|
2657
2768
|
const cancel = () => {
|
|
2658
2769
|
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
2659
2770
|
timeoutId = null;
|
|
2660
2771
|
};
|
|
2661
|
-
const schedule = (action) => {
|
|
2772
|
+
const schedule = (action, onDouble, key) => {
|
|
2773
|
+
if (onDouble && timeoutId !== null && key === pendingKey) {
|
|
2774
|
+
cancel();
|
|
2775
|
+
doubledAt = Date.now();
|
|
2776
|
+
onDouble();
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2662
2779
|
cancel();
|
|
2780
|
+
pendingKey = key;
|
|
2663
2781
|
timeoutId = setTimeout(() => {
|
|
2664
2782
|
timeoutId = null;
|
|
2665
2783
|
action();
|
|
2666
2784
|
}, DOUBLE_CLICK_DELAY_MS);
|
|
2667
2785
|
};
|
|
2668
|
-
|
|
2786
|
+
const doubleClick = (onDouble) => {
|
|
2787
|
+
cancel();
|
|
2788
|
+
if (Date.now() - doubledAt < DOUBLE_CLICK_DELAY_MS) return;
|
|
2789
|
+
onDouble();
|
|
2790
|
+
};
|
|
2791
|
+
return { schedule, cancel, doubleClick };
|
|
2669
2792
|
}
|
|
2670
2793
|
|
|
2671
2794
|
// src/radial-wheel-geometry.ts
|
|
@@ -2855,13 +2978,15 @@ function calculatePopSubtreeRadialExtent(hierarchy, coreRootCircleRadius = 0) {
|
|
|
2855
2978
|
POP_TREE_DEPTH_RADIAL_SPACING
|
|
2856
2979
|
) + MAX_NODE_WIDTH / 2 + POP_SUBTREE_OUTER_MARGIN;
|
|
2857
2980
|
}
|
|
2858
|
-
function computePopRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, coreRootCircleRadius, wedgeSpanDegrees = POP_WEDGE_SPAN_DEGREES, depthSpacing = POP_TREE_DEPTH_RADIAL_SPACING) {
|
|
2981
|
+
function computePopRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, coreRootCircleRadius, wedgeSpanDegrees = POP_WEDGE_SPAN_DEGREES, depthSpacing = POP_TREE_DEPTH_RADIAL_SPACING, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
2859
2982
|
return layoutWedge(
|
|
2860
2983
|
d3Lib,
|
|
2861
2984
|
hierarchy,
|
|
2862
2985
|
wedgeCenterAngleDegrees,
|
|
2863
2986
|
wedgeSpanDegrees,
|
|
2864
|
-
popRadiusOfDepth(coreRootCircleRadius, depthSpacing)
|
|
2987
|
+
popRadiusOfDepth(coreRootCircleRadius, depthSpacing),
|
|
2988
|
+
"center",
|
|
2989
|
+
dotRadius
|
|
2865
2990
|
);
|
|
2866
2991
|
}
|
|
2867
2992
|
var MIN_DOT_GAP = 5.6;
|
|
@@ -2871,15 +2996,15 @@ function popRadiusOfDepth(coreRootCircleRadius, depthSpacing) {
|
|
|
2871
2996
|
function coreRadiusOfDepth(coreRootCircleRadius, depthSpacing) {
|
|
2872
2997
|
return (depth) => getRadialDepthRadius(depth + 1, coreRootCircleRadius, depthSpacing);
|
|
2873
2998
|
}
|
|
2874
|
-
function measureWedge(_d3Lib, hierarchy, radiusOfDepth) {
|
|
2875
|
-
const
|
|
2999
|
+
function measureWedge(_d3Lib, hierarchy, radiusOfDepth, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
3000
|
+
const slotRadius = () => dotRadius;
|
|
2876
3001
|
const offsetFromParent = /* @__PURE__ */ new Map();
|
|
2877
3002
|
const shiftLevel = (level, by) => ({
|
|
2878
3003
|
left: { ...level.left, x: level.left.x + by },
|
|
2879
3004
|
right: { ...level.right, x: level.right.x + by }
|
|
2880
3005
|
});
|
|
2881
3006
|
const pack = (node) => {
|
|
2882
|
-
const own = { x: 0, dotRadius:
|
|
3007
|
+
const own = { x: 0, dotRadius: slotRadius() };
|
|
2883
3008
|
if (!node.children) return [{ left: own, right: own }];
|
|
2884
3009
|
const below = [];
|
|
2885
3010
|
const childOffsets = [];
|
|
@@ -2911,7 +3036,7 @@ function measureWedge(_d3Lib, hierarchy, radiusOfDepth) {
|
|
|
2911
3036
|
let min = Infinity;
|
|
2912
3037
|
let max = -Infinity;
|
|
2913
3038
|
hierarchy.each((d) => {
|
|
2914
|
-
const inset = (
|
|
3039
|
+
const inset = (slotRadius() + MIN_DOT_GAP / 2) / radiusOfDepth(d.depth);
|
|
2915
3040
|
min = Math.min(min, d.x - inset);
|
|
2916
3041
|
max = Math.max(max, d.x + inset);
|
|
2917
3042
|
});
|
|
@@ -2931,8 +3056,8 @@ function splitAtBranching(hierarchy) {
|
|
|
2931
3056
|
const children = branching.children;
|
|
2932
3057
|
return children && children.length > 1 ? { chain, branching, children } : null;
|
|
2933
3058
|
}
|
|
2934
|
-
function layoutWedge(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, radiusOfDepth, slack = "center") {
|
|
2935
|
-
const neededRad = measureWedge(d3Lib, hierarchy, radiusOfDepth);
|
|
3059
|
+
function layoutWedge(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, radiusOfDepth, slack = "center", dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
3060
|
+
const neededRad = measureWedge(d3Lib, hierarchy, radiusOfDepth, dotRadius);
|
|
2936
3061
|
const wedgeSpanRad = wedgeSpanDegrees * Math.PI / 180;
|
|
2937
3062
|
const scale = Math.min(MAX_WEDGE_STRETCH, wedgeSpanRad / neededRad);
|
|
2938
3063
|
const wedgeCenterRad = wedgeCenterAngleDegrees * Math.PI / 180;
|
|
@@ -3161,12 +3286,12 @@ function renderPopSubtree(d3Lib, svg, hierarchy, rootColor, reparentingNodeId, r
|
|
|
3161
3286
|
group.on("click", (event) => {
|
|
3162
3287
|
if (reparentingNodeId) return;
|
|
3163
3288
|
const element = event.currentTarget;
|
|
3164
|
-
deferredClick.schedule(
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
onNodeDoubleClick?.(d.data);
|
|
3289
|
+
deferredClick.schedule(
|
|
3290
|
+
() => onNodeClick?.(d.data, event, element),
|
|
3291
|
+
() => onNodeDoubleClick?.(d.data)
|
|
3292
|
+
);
|
|
3169
3293
|
});
|
|
3294
|
+
group.on("dblclick", () => deferredClick.doubleClick(() => onNodeDoubleClick?.(d.data)));
|
|
3170
3295
|
});
|
|
3171
3296
|
}
|
|
3172
3297
|
function hideCollidingLabels(d3Lib, container, itemCountRange, zoomScale) {
|
|
@@ -3243,14 +3368,15 @@ function buildCoreHierarchy(d3Lib, coreNodes) {
|
|
|
3243
3368
|
});
|
|
3244
3369
|
return hierarchy;
|
|
3245
3370
|
}
|
|
3246
|
-
function computeCoreRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, coreRootCircleRadius, depthSpacing) {
|
|
3371
|
+
function computeCoreRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, coreRootCircleRadius, depthSpacing, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
3247
3372
|
return layoutWedge(
|
|
3248
3373
|
d3Lib,
|
|
3249
3374
|
hierarchy,
|
|
3250
3375
|
wedgeCenterAngleDegrees,
|
|
3251
3376
|
wedgeSpanDegrees,
|
|
3252
3377
|
coreRadiusOfDepth(coreRootCircleRadius, depthSpacing),
|
|
3253
|
-
"between-children"
|
|
3378
|
+
"between-children",
|
|
3379
|
+
dotRadius
|
|
3254
3380
|
);
|
|
3255
3381
|
}
|
|
3256
3382
|
function calculateCoreSubtreeRadialExtent(hierarchy, depthSpacing, coreRootCircleRadius = 0) {
|
|
@@ -3324,6 +3450,8 @@ function WheelRadialCore({
|
|
|
3324
3450
|
hideInfoPanelClose = false,
|
|
3325
3451
|
hideInfoPanelTitle = false,
|
|
3326
3452
|
itemCountLabel,
|
|
3453
|
+
itemCountScaleMax,
|
|
3454
|
+
nodeCountForSizing,
|
|
3327
3455
|
readOnly,
|
|
3328
3456
|
renderDangerZone,
|
|
3329
3457
|
expanded,
|
|
@@ -3335,10 +3463,12 @@ function WheelRadialCore({
|
|
|
3335
3463
|
defaultViewport,
|
|
3336
3464
|
onViewportChange,
|
|
3337
3465
|
controlsOrientation = "vertical",
|
|
3466
|
+
showZoomControls = "auto",
|
|
3338
3467
|
floatingControlsLeading,
|
|
3339
3468
|
busy,
|
|
3340
3469
|
selectedNodeId
|
|
3341
3470
|
}) {
|
|
3471
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
3342
3472
|
const index = useTreeIndex(nodes);
|
|
3343
3473
|
const groups = (0, import_react10.useMemo)(() => groupNodesByRoot(nodes), [nodes]);
|
|
3344
3474
|
const rootColorOf = (node) => getGenreTreeColor(index.byId.get(index.rootIdById.get(node.id) ?? node.id)?.wikidataId);
|
|
@@ -3391,11 +3521,15 @@ function WheelRadialCore({
|
|
|
3391
3521
|
() => getItemCountRange(
|
|
3392
3522
|
groups.map((group) => ({
|
|
3393
3523
|
itemCount: rootItemCountById.get(group.root.id)
|
|
3394
|
-
}))
|
|
3524
|
+
})),
|
|
3525
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
3395
3526
|
),
|
|
3396
|
-
[groups, rootItemCountById]
|
|
3527
|
+
[groups, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
3528
|
+
);
|
|
3529
|
+
const wheelItemCountRange = (0, import_react10.useMemo)(
|
|
3530
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
3531
|
+
[nodes, itemCountScaleMax, fullNodeCountForSizing]
|
|
3397
3532
|
);
|
|
3398
|
-
const wheelItemCountRange = (0, import_react10.useMemo)(() => getItemCountRange(nodes), [nodes]);
|
|
3399
3533
|
const sectorSpanByRootId = (0, import_react10.useMemo)(() => {
|
|
3400
3534
|
const map = /* @__PURE__ */ new Map();
|
|
3401
3535
|
if (groups.length <= 1) return map;
|
|
@@ -3445,10 +3579,10 @@ function WheelRadialCore({
|
|
|
3445
3579
|
const chipClearanceFloor = (0, import_react10.useMemo)(
|
|
3446
3580
|
() => calculateWheelRadiusForAngles(
|
|
3447
3581
|
layout.map((slot) => slot.angle),
|
|
3448
|
-
MAX_NODE_WIDTH,
|
|
3582
|
+
wheelItemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH,
|
|
3449
3583
|
WHEEL_RADIUS
|
|
3450
3584
|
),
|
|
3451
|
-
[layout]
|
|
3585
|
+
[layout, wheelItemCountRange.maxNodeWidth]
|
|
3452
3586
|
);
|
|
3453
3587
|
const maxCoreExtentDelta = (0, import_react10.useMemo)(() => {
|
|
3454
3588
|
let extent = 0;
|
|
@@ -3818,6 +3952,7 @@ function WheelRadialCore({
|
|
|
3818
3952
|
{
|
|
3819
3953
|
panZoom,
|
|
3820
3954
|
orientation: controlsOrientation,
|
|
3955
|
+
zoomControls: showZoomControls,
|
|
3821
3956
|
leading: floatingControlsLeading,
|
|
3822
3957
|
busy,
|
|
3823
3958
|
onFit: () => panZoom.fitToFrame([wheelCircleRef.current, coreSvgRef.current])
|
|
@@ -3864,7 +3999,7 @@ function useFocusHistory(focusNodeId, isFocusable) {
|
|
|
3864
3999
|
|
|
3865
4000
|
// src/WheelBreadcrumb.tsx
|
|
3866
4001
|
var import_react12 = require("react");
|
|
3867
|
-
var
|
|
4002
|
+
var import_md6 = require("react-icons/md");
|
|
3868
4003
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3869
4004
|
var MAX_VISIBLE_ANCESTORS = 3;
|
|
3870
4005
|
var ANCESTORS_KEPT_WHEN_COLLAPSED = 2;
|
|
@@ -3882,7 +4017,7 @@ function WheelBreadcrumb({ ancestors, currentName, onHome, onAncestor }) {
|
|
|
3882
4017
|
"aria-label": "All genres",
|
|
3883
4018
|
title: "All genres",
|
|
3884
4019
|
onClick: onHome,
|
|
3885
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
4020
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_md6.MdHome, { className: "gtv-icon", size: 14 })
|
|
3886
4021
|
}
|
|
3887
4022
|
),
|
|
3888
4023
|
separator,
|
|
@@ -4001,6 +4136,8 @@ function WheelRadialPopCoreCore({
|
|
|
4001
4136
|
hideInfoPanelClose = false,
|
|
4002
4137
|
hideInfoPanelTitle = false,
|
|
4003
4138
|
itemCountLabel,
|
|
4139
|
+
itemCountScaleMax,
|
|
4140
|
+
nodeCountForSizing,
|
|
4004
4141
|
readOnly,
|
|
4005
4142
|
renderDangerZone,
|
|
4006
4143
|
expanded,
|
|
@@ -4012,6 +4149,7 @@ function WheelRadialPopCoreCore({
|
|
|
4012
4149
|
defaultViewport,
|
|
4013
4150
|
onViewportChange,
|
|
4014
4151
|
controlsOrientation = "vertical",
|
|
4152
|
+
showZoomControls = "auto",
|
|
4015
4153
|
floatingControlsLeading,
|
|
4016
4154
|
busy,
|
|
4017
4155
|
allowWheelRotation = true,
|
|
@@ -4019,6 +4157,7 @@ function WheelRadialPopCoreCore({
|
|
|
4019
4157
|
focusNodeId: requestedFocusNodeId = null,
|
|
4020
4158
|
onFocusChange
|
|
4021
4159
|
}) {
|
|
4160
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
4022
4161
|
const index = useTreeIndex(nodes);
|
|
4023
4162
|
const focusNodeId = (0, import_react13.useMemo)(
|
|
4024
4163
|
() => requestedFocusNodeId !== null && isZoomableNode(nodes, requestedFocusNodeId) ? requestedFocusNodeId : null,
|
|
@@ -4158,8 +4297,8 @@ function WheelRadialPopCoreCore({
|
|
|
4158
4297
|
() => getItemCountRange([
|
|
4159
4298
|
...nodes,
|
|
4160
4299
|
...[...rootItemCountById.values()].map((itemCount) => ({ itemCount }))
|
|
4161
|
-
]),
|
|
4162
|
-
[nodes, rootItemCountById]
|
|
4300
|
+
], { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
4301
|
+
[nodes, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
4163
4302
|
);
|
|
4164
4303
|
const isCenterMainstream = isMainstreamPopLineage(index, centerNode.id);
|
|
4165
4304
|
const centerNodeColor = isCenterMainstream ? "#ffffff" : getGenreNodeStyle(index, centerNode.id).fill;
|
|
@@ -4193,8 +4332,8 @@ function WheelRadialPopCoreCore({
|
|
|
4193
4332
|
centerSubtreeHierarchy,
|
|
4194
4333
|
0,
|
|
4195
4334
|
POP_TREE_DEPTH_RADIAL_SPACING
|
|
4196
|
-
) : NODE_LAYOUT_SLOT_RADIUS,
|
|
4197
|
-
[centerSubtreeHierarchy]
|
|
4335
|
+
) : wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS,
|
|
4336
|
+
[centerSubtreeHierarchy, wheelItemCountRange.maxDotRadius]
|
|
4198
4337
|
);
|
|
4199
4338
|
const maxCoreExtentDelta = (0, import_react13.useMemo)(() => {
|
|
4200
4339
|
let extent = 0;
|
|
@@ -4216,7 +4355,8 @@ function WheelRadialPopCoreCore({
|
|
|
4216
4355
|
(radius) => new Map(
|
|
4217
4356
|
groups.map((group) => {
|
|
4218
4357
|
const { pop, core } = branchHierarchiesByRootId.get(group.root.id);
|
|
4219
|
-
const
|
|
4358
|
+
const dotRadius = wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS;
|
|
4359
|
+
const chipWidth = 2 * dotRadius;
|
|
4220
4360
|
return [
|
|
4221
4361
|
group.root.id,
|
|
4222
4362
|
{
|
|
@@ -4224,14 +4364,16 @@ function WheelRadialPopCoreCore({
|
|
|
4224
4364
|
(hierarchy) => measureWedge(
|
|
4225
4365
|
d34,
|
|
4226
4366
|
hierarchy,
|
|
4227
|
-
popRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING)
|
|
4367
|
+
popRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING),
|
|
4368
|
+
dotRadius
|
|
4228
4369
|
)
|
|
4229
4370
|
),
|
|
4230
4371
|
core: core.map(
|
|
4231
4372
|
(hierarchy) => measureWedge(
|
|
4232
4373
|
d34,
|
|
4233
4374
|
hierarchy,
|
|
4234
|
-
coreRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING)
|
|
4375
|
+
coreRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING),
|
|
4376
|
+
dotRadius
|
|
4235
4377
|
)
|
|
4236
4378
|
),
|
|
4237
4379
|
chip: 2 * Math.asin(Math.min(1, (chipWidth / 2 + CHIP_GAP) / radius))
|
|
@@ -4239,7 +4381,7 @@ function WheelRadialPopCoreCore({
|
|
|
4239
4381
|
];
|
|
4240
4382
|
})
|
|
4241
4383
|
),
|
|
4242
|
-
[groups, branchHierarchiesByRootId]
|
|
4384
|
+
[groups, branchHierarchiesByRootId, wheelItemCountRange.maxDotRadius]
|
|
4243
4385
|
);
|
|
4244
4386
|
const coreRootCircleRadius = (0, import_react13.useMemo)(
|
|
4245
4387
|
() => solveRingRadius(
|
|
@@ -4249,11 +4391,11 @@ function WheelRadialPopCoreCore({
|
|
|
4249
4391
|
),
|
|
4250
4392
|
Math.max(
|
|
4251
4393
|
WHEEL_POP_CORE_RADIUS,
|
|
4252
|
-
mainstreamCircleRadius + NODE_DOT_MAX_RADIUS + POP_SUBTREE_OUTER_MARGIN,
|
|
4394
|
+
mainstreamCircleRadius + (wheelItemCountRange.maxDotRadius ?? NODE_DOT_MAX_RADIUS) + POP_SUBTREE_OUTER_MARGIN,
|
|
4253
4395
|
popReachRequiredRadius
|
|
4254
4396
|
)
|
|
4255
4397
|
),
|
|
4256
|
-
[measureBranchNeedsAt, mainstreamCircleRadius, popReachRequiredRadius]
|
|
4398
|
+
[measureBranchNeedsAt, mainstreamCircleRadius, popReachRequiredRadius, wheelItemCountRange.maxDotRadius]
|
|
4257
4399
|
);
|
|
4258
4400
|
const branchNeedsByRootId = (0, import_react13.useMemo)(
|
|
4259
4401
|
() => measureBranchNeedsAt(coreRootCircleRadius),
|
|
@@ -4357,7 +4499,8 @@ function WheelRadialPopCoreCore({
|
|
|
4357
4499
|
angle,
|
|
4358
4500
|
coreRootCircleRadius,
|
|
4359
4501
|
span,
|
|
4360
|
-
POP_TREE_DEPTH_RADIAL_SPACING
|
|
4502
|
+
POP_TREE_DEPTH_RADIAL_SPACING,
|
|
4503
|
+
wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS
|
|
4361
4504
|
);
|
|
4362
4505
|
const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
|
|
4363
4506
|
const branchGroup = sectorGroup.append("g").attr("class", "gtv-wheel-pop-branch").attr("data-gtv-branch-index", branchIndex);
|
|
@@ -4423,7 +4566,8 @@ function WheelRadialPopCoreCore({
|
|
|
4423
4566
|
angle,
|
|
4424
4567
|
span,
|
|
4425
4568
|
coreRootCircleRadius,
|
|
4426
|
-
POP_TREE_DEPTH_RADIAL_SPACING
|
|
4569
|
+
POP_TREE_DEPTH_RADIAL_SPACING,
|
|
4570
|
+
wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS
|
|
4427
4571
|
);
|
|
4428
4572
|
const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
|
|
4429
4573
|
const angleOf = (d) => {
|
|
@@ -4587,7 +4731,7 @@ function WheelRadialPopCoreCore({
|
|
|
4587
4731
|
originGroup.selectAll("g.gtv-root-label").select("text").style("pointer-events", "all").style("cursor", "pointer").on("click", (_event, d) => {
|
|
4588
4732
|
const chip = d.id === centerNode.id ? centerChipRef.current : viewportRef.current?.querySelector(`#group-${CSS.escape(d.id)}`);
|
|
4589
4733
|
chip?.click();
|
|
4590
|
-
}).on("dblclick", (_event, d) => focusOnNodeRef.current(d.id));
|
|
4734
|
+
}).on("dblclick", (_event, d) => chipClickRef.current.doubleClick(() => focusOnNodeRef.current(d.id)));
|
|
4591
4735
|
}, [
|
|
4592
4736
|
centerNode,
|
|
4593
4737
|
centerHasPopDisc,
|
|
@@ -4746,7 +4890,7 @@ function WheelRadialPopCoreCore({
|
|
|
4746
4890
|
)
|
|
4747
4891
|
};
|
|
4748
4892
|
});
|
|
4749
|
-
}, [groups, continuousAngleByRootId, sectorSpanByRootId]);
|
|
4893
|
+
}, [groups, continuousAngleByRootId, sectorSpanByRootId, index]);
|
|
4750
4894
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4751
4895
|
"div",
|
|
4752
4896
|
{
|
|
@@ -4856,9 +5000,9 @@ function WheelRadialPopCoreCore({
|
|
|
4856
5000
|
showNodeInfo(centerNode, chip, viewportRef.current);
|
|
4857
5001
|
onNodeClick?.(centerNode, nativeEvent);
|
|
4858
5002
|
}
|
|
4859
|
-
});
|
|
5003
|
+
}, () => focusOnNode(centerNode.id), centerNode.id);
|
|
4860
5004
|
},
|
|
4861
|
-
onDoubleClick: () => focusOnNode(centerNode.id),
|
|
5005
|
+
onDoubleClick: () => chipClickRef.current.doubleClick(() => focusOnNode(centerNode.id)),
|
|
4862
5006
|
"aria-label": centerNode.name
|
|
4863
5007
|
}
|
|
4864
5008
|
) }) }),
|
|
@@ -4918,9 +5062,9 @@ function WheelRadialPopCoreCore({
|
|
|
4918
5062
|
showNodeInfo(root, chip, viewportRef.current);
|
|
4919
5063
|
onNodeClick?.(root, nativeEvent);
|
|
4920
5064
|
}
|
|
4921
|
-
});
|
|
5065
|
+
}, () => focusOnNode(group.root.id), group.root.id);
|
|
4922
5066
|
},
|
|
4923
|
-
onDoubleClick: () => focusOnNode(group.root.id)
|
|
5067
|
+
onDoubleClick: () => chipClickRef.current.doubleClick(() => focusOnNode(group.root.id))
|
|
4924
5068
|
}
|
|
4925
5069
|
) })
|
|
4926
5070
|
},
|
|
@@ -5015,6 +5159,7 @@ function WheelRadialPopCoreCore({
|
|
|
5015
5159
|
{
|
|
5016
5160
|
panZoom,
|
|
5017
5161
|
orientation: controlsOrientation,
|
|
5162
|
+
zoomControls: showZoomControls,
|
|
5018
5163
|
leading: floatingControlsLeading,
|
|
5019
5164
|
busy,
|
|
5020
5165
|
onFit: () => panZoom.fitToFrame([
|