@behindthemusictree/genre-tree-view 5.0.0 → 6.2.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.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // src/GenreTree.tsx
2
2
  import { useEffect as useEffect5, useRef as useRef5, useMemo as useMemo2 } from "react";
3
3
  import * as d3 from "d3";
4
- import { MdFitScreen, MdZoomIn as MdZoomIn2, MdZoomOut } from "react-icons/md";
5
4
 
6
5
  // src/NodeHelper.tsx
7
6
  import ReactDOMServer from "react-dom/server";
@@ -25,6 +24,9 @@ var MENU_WIDTH = 130;
25
24
  var PER_TREE_ACCENT_DOT = true;
26
25
  var ROOT_TINT_RATIO = 0.08;
27
26
  var POP_SECTOR_TINT_RATIO = 0.8;
27
+ var UNDERGROUND_DARKEN_RATIO = 0.62;
28
+ var UNDERGROUND_SECTOR_FILL_OPACITY = 0.15;
29
+ var SCENE_SECTOR_OUTER_RADIUS = 1e5;
28
30
  var CENTER_NODE_NAME = "Mainstream Pop";
29
31
  var TREE_COLORS = [
30
32
  "#4F46E5",
@@ -106,6 +108,11 @@ function isMainstreamPopLineage(index, nodeId) {
106
108
  const topRoot = index.ancestorsOf(nodeId)[0] ?? index.byId.get(nodeId);
107
109
  return topRoot?.name === CENTER_NODE_NAME;
108
110
  }
111
+ function shadeSurface(hex, ratio) {
112
+ const channel = (offset) => parseInt(hex.slice(offset, offset + 2), 16);
113
+ const toHex = (value) => Math.round(value * ratio).toString(16).padStart(2, "0");
114
+ return `#${toHex(channel(1))}${toHex(channel(3))}${toHex(channel(5))}`;
115
+ }
109
116
  function getGenreNodeStyle(index, nodeId) {
110
117
  if (isMainstreamPopLineage(index, nodeId)) {
111
118
  return { fill: tintSurface("#ffffff", POP_SECTOR_TINT_RATIO), textColor: TEXT_COLOR };
@@ -194,10 +201,13 @@ function calculateRootLabelFontSize(layoutFontSize, zoomScale) {
194
201
  return Math.max(layoutFontSize, ROOT_LABEL_MIN_SCREEN_PX / zoomScale);
195
202
  }
196
203
  var NODE_DOT_MIN_RADIUS = 3;
197
- var NODE_DOT_MAX_RADIUS = 50;
204
+ var NODE_DOT_MAX_RADIUS = 24;
198
205
  var NODE_DOT_LABEL_GAP = 6;
206
+ var NODE_LAYOUT_SLOT_RADIUS = 8;
199
207
  function calculateNodeDotRadius(itemCount, range) {
200
- return NODE_DOT_MIN_RADIUS + logarithmicPosition(itemCount, range) * (NODE_DOT_MAX_RADIUS - NODE_DOT_MIN_RADIUS);
208
+ const minArea = NODE_DOT_MIN_RADIUS ** 2;
209
+ const maxArea = NODE_DOT_MAX_RADIUS ** 2;
210
+ return Math.sqrt(minArea + logarithmicPosition(itemCount, range) * (maxArea - minArea));
201
211
  }
202
212
  var WHEEL_RADIUS = 260;
203
213
  var WHEEL_VIEWPORT_HEIGHT = WHEEL_RADIUS * 2 + MAX_NODE_HEIGHT / 2;
@@ -584,6 +594,61 @@ function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNod
584
594
  return svg;
585
595
  }
586
596
 
597
+ // src/FloatingControls.tsx
598
+ import { MdFitScreen, MdZoomIn, MdZoomOut } from "react-icons/md";
599
+
600
+ // src/BusyIndicator.tsx
601
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
602
+ function BusyIndicator() {
603
+ return /* @__PURE__ */ jsxs2("div", { className: "gtv-busy", role: "status", "aria-live": "polite", children: [
604
+ /* @__PURE__ */ jsx2("span", { className: "gtv-busy-ring", "aria-hidden": "true" }),
605
+ /* @__PURE__ */ jsx2("span", { className: "gtv-skeleton-sr-only", children: "Updating genre tree\u2026" })
606
+ ] });
607
+ }
608
+
609
+ // src/FloatingControls.tsx
610
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
611
+ function FloatingControls({ panZoom, onFit, orientation, leading, busy }) {
612
+ return /* @__PURE__ */ jsxs3(
613
+ "div",
614
+ {
615
+ className: [
616
+ "gtv-wheel-floating-controls",
617
+ orientation === "horizontal" && "gtv-wheel-floating-controls--horizontal"
618
+ ].filter(Boolean).join(" "),
619
+ children: [
620
+ busy && /* @__PURE__ */ jsx3(BusyIndicator, {}),
621
+ leading && /* @__PURE__ */ jsx3("div", { className: "gtv-floating-controls-leading", children: leading }),
622
+ /* @__PURE__ */ jsxs3("div", { className: "gtv-zoom-controls", children: [
623
+ /* @__PURE__ */ jsx3(
624
+ "button",
625
+ {
626
+ type: "button",
627
+ className: ["gtv-zoom-btn", !panZoom.canZoomIn && "gtv-zoom-btn--disabled"].filter(Boolean).join(" "),
628
+ disabled: !panZoom.canZoomIn,
629
+ onClick: panZoom.zoomIn,
630
+ "aria-label": "Zoom in",
631
+ children: /* @__PURE__ */ jsx3(MdZoomIn, { className: "gtv-icon", size: 18 })
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
+ ] })
647
+ ]
648
+ }
649
+ );
650
+ }
651
+
587
652
  // src/use-pan-zoom.ts
588
653
  import { useCallback, useEffect, useRef, useState } from "react";
589
654
 
@@ -1220,13 +1285,13 @@ function useTreeIndex(nodes) {
1220
1285
 
1221
1286
  // src/InfoPanel.tsx
1222
1287
  import { useEffect as useEffect3, useRef as useRef3 } from "react";
1223
- import { MdArrowBack, MdClose, MdZoomIn } from "react-icons/md";
1288
+ import { MdArrowBack, MdClose, MdZoomIn as MdZoomIn2 } from "react-icons/md";
1224
1289
 
1225
1290
  // src/InfoPanelExpandButton.tsx
1226
1291
  import { MdCloseFullscreen, MdOpenInFull } from "react-icons/md";
1227
- import { jsx as jsx2 } from "react/jsx-runtime";
1292
+ import { jsx as jsx4 } from "react/jsx-runtime";
1228
1293
  function InfoPanelExpandButton({ expanded, onExpandedChange, style }) {
1229
- return /* @__PURE__ */ jsx2(
1294
+ return /* @__PURE__ */ jsx4(
1230
1295
  "button",
1231
1296
  {
1232
1297
  type: "button",
@@ -1234,7 +1299,7 @@ function InfoPanelExpandButton({ expanded, onExpandedChange, style }) {
1234
1299
  style,
1235
1300
  onClick: () => onExpandedChange(!expanded),
1236
1301
  "aria-label": expanded ? "Collapse panel" : "Expand panel",
1237
- children: expanded ? /* @__PURE__ */ jsx2(MdCloseFullscreen, { className: "gtv-icon", size: 16 }) : /* @__PURE__ */ jsx2(MdOpenInFull, { className: "gtv-icon", size: 16 })
1302
+ children: expanded ? /* @__PURE__ */ jsx4(MdCloseFullscreen, { className: "gtv-icon", size: 16 }) : /* @__PURE__ */ jsx4(MdOpenInFull, { className: "gtv-icon", size: 16 })
1238
1303
  }
1239
1304
  );
1240
1305
  }
@@ -1242,11 +1307,11 @@ function InfoPanelExpandButton({ expanded, onExpandedChange, style }) {
1242
1307
  // src/InfoPanelSection.tsx
1243
1308
  import { useState as useState3 } from "react";
1244
1309
  import { MdExpandMore } from "react-icons/md";
1245
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1310
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1246
1311
  function InfoPanelSection({ title, children }) {
1247
1312
  const [open, setOpen] = useState3(true);
1248
- return /* @__PURE__ */ jsxs2("div", { className: "gtv-info-panel-children", children: [
1249
- /* @__PURE__ */ jsxs2(
1313
+ return /* @__PURE__ */ jsxs4("div", { className: "gtv-info-panel-children", children: [
1314
+ /* @__PURE__ */ jsxs4(
1250
1315
  "button",
1251
1316
  {
1252
1317
  type: "button",
@@ -1254,8 +1319,8 @@ function InfoPanelSection({ title, children }) {
1254
1319
  "aria-expanded": open,
1255
1320
  onClick: () => setOpen((isOpen) => !isOpen),
1256
1321
  children: [
1257
- /* @__PURE__ */ jsx3("span", { className: "gtv-info-panel-children-title", children: title }),
1258
- /* @__PURE__ */ jsx3(MdExpandMore, { className: "gtv-icon gtv-info-panel-section-chevron", size: 16 })
1322
+ /* @__PURE__ */ jsx5("span", { className: "gtv-info-panel-children-title", children: title }),
1323
+ /* @__PURE__ */ jsx5(MdExpandMore, { className: "gtv-icon gtv-info-panel-section-chevron", size: 16 })
1259
1324
  ]
1260
1325
  }
1261
1326
  ),
@@ -1268,7 +1333,7 @@ import { useState as useState4 } from "react";
1268
1333
  import { MdMoreVert, MdModeEdit } from "react-icons/md";
1269
1334
  import { FaPlus, FaTrashAlt, FaPlay, FaPause, FaSpinner } from "react-icons/fa";
1270
1335
  import { PiGraphFill as PiGraphFill2 } from "react-icons/pi";
1271
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1336
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1272
1337
  function NodeToolbar({
1273
1338
  node,
1274
1339
  playingNodeId,
@@ -1289,9 +1354,9 @@ function NodeToolbar({
1289
1354
  const isPlayable = node.itemCount > 0;
1290
1355
  const isThisPlaying = playingNodeId === node.id;
1291
1356
  const playLabel = isThisPlaying && playState === "playing" ? "Pause" : isThisPlaying && playState === "loading" ? "Loading..." : "Play";
1292
- const playIcon = isThisPlaying && playState === "playing" ? /* @__PURE__ */ jsx4(FaPause, { className: "gtv-icon", size: "0.6em" }) : isThisPlaying && playState === "loading" ? /* @__PURE__ */ jsx4(FaSpinner, { className: "gtv-icon gtv-icon--spin", size: "0.6em" }) : /* @__PURE__ */ jsx4(FaPlay, { className: "gtv-icon", size: "0.6em" });
1293
- return /* @__PURE__ */ jsxs3("div", { className: ["gtv-toolbar", className].filter(Boolean).join(" "), children: [
1294
- /* @__PURE__ */ jsx4(
1357
+ const playIcon = isThisPlaying && playState === "playing" ? /* @__PURE__ */ jsx6(FaPause, { className: "gtv-icon", size: "0.6em" }) : isThisPlaying && playState === "loading" ? /* @__PURE__ */ jsx6(FaSpinner, { className: "gtv-icon gtv-icon--spin", size: "0.6em" }) : /* @__PURE__ */ jsx6(FaPlay, { className: "gtv-icon", size: "0.6em" });
1358
+ return /* @__PURE__ */ jsxs5("div", { className: ["gtv-toolbar", className].filter(Boolean).join(" "), children: [
1359
+ /* @__PURE__ */ jsx6(
1295
1360
  "button",
1296
1361
  {
1297
1362
  type: "button",
@@ -1303,8 +1368,8 @@ function NodeToolbar({
1303
1368
  children: playIcon
1304
1369
  }
1305
1370
  ),
1306
- isActionable && /* @__PURE__ */ jsxs3(Fragment, { children: [
1307
- /* @__PURE__ */ jsx4(
1371
+ isActionable && /* @__PURE__ */ jsxs5(Fragment, { children: [
1372
+ /* @__PURE__ */ jsx6(
1308
1373
  "button",
1309
1374
  {
1310
1375
  type: "button",
@@ -1312,13 +1377,13 @@ function NodeToolbar({
1312
1377
  title: "Add sub-genre",
1313
1378
  "aria-label": "Add sub-genre",
1314
1379
  onClick: () => onAddChild?.(node.id),
1315
- children: /* @__PURE__ */ jsx4(FaPlus, { className: "gtv-icon", size: "0.6em" })
1380
+ children: /* @__PURE__ */ jsx6(FaPlus, { className: "gtv-icon", size: "0.6em" })
1316
1381
  }
1317
1382
  ),
1318
1383
  primaryExtraActions.map((action) => {
1319
1384
  const enabled = action.enabled ? action.enabled(node) : true;
1320
1385
  const label = action.label(node);
1321
- return /* @__PURE__ */ jsx4(
1386
+ return /* @__PURE__ */ jsx6(
1322
1387
  "button",
1323
1388
  {
1324
1389
  type: "button",
@@ -1332,8 +1397,8 @@ function NodeToolbar({
1332
1397
  action.key
1333
1398
  );
1334
1399
  }),
1335
- /* @__PURE__ */ jsxs3("div", { className: "gtv-toolbar-overflow", children: [
1336
- /* @__PURE__ */ jsx4(
1400
+ /* @__PURE__ */ jsxs5("div", { className: "gtv-toolbar-overflow", children: [
1401
+ /* @__PURE__ */ jsx6(
1337
1402
  "button",
1338
1403
  {
1339
1404
  type: "button",
@@ -1343,11 +1408,11 @@ function NodeToolbar({
1343
1408
  "aria-haspopup": "menu",
1344
1409
  "aria-expanded": menuOpen,
1345
1410
  onClick: () => setMenuOpen((open) => !open),
1346
- children: /* @__PURE__ */ jsx4(MdMoreVert, { size: "0.6em" })
1411
+ children: /* @__PURE__ */ jsx6(MdMoreVert, { size: "0.6em" })
1347
1412
  }
1348
1413
  ),
1349
- menuOpen && /* @__PURE__ */ jsxs3("div", { className: "gtv-menu-card gtv-toolbar-overflow-menu", children: [
1350
- /* @__PURE__ */ jsxs3(
1414
+ menuOpen && /* @__PURE__ */ jsxs5("div", { className: "gtv-menu-card gtv-toolbar-overflow-menu", children: [
1415
+ /* @__PURE__ */ jsxs5(
1351
1416
  "button",
1352
1417
  {
1353
1418
  type: "button",
@@ -1357,12 +1422,12 @@ function NodeToolbar({
1357
1422
  onRenameRequest?.(node);
1358
1423
  },
1359
1424
  children: [
1360
- /* @__PURE__ */ jsx4("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ jsx4(MdModeEdit, { className: "gtv-icon", size: 13 }) }),
1361
- /* @__PURE__ */ jsx4("span", { className: "gtv-menu-row-label", children: "Rename" })
1425
+ /* @__PURE__ */ jsx6("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ jsx6(MdModeEdit, { className: "gtv-icon", size: 13 }) }),
1426
+ /* @__PURE__ */ jsx6("span", { className: "gtv-menu-row-label", children: "Rename" })
1362
1427
  ]
1363
1428
  }
1364
1429
  ),
1365
- /* @__PURE__ */ jsxs3(
1430
+ /* @__PURE__ */ jsxs5(
1366
1431
  "button",
1367
1432
  {
1368
1433
  type: "button",
@@ -1372,14 +1437,14 @@ function NodeToolbar({
1372
1437
  onReparentRequest?.(node);
1373
1438
  },
1374
1439
  children: [
1375
- /* @__PURE__ */ jsx4("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ jsx4(PiGraphFill2, { className: "gtv-icon", size: 13 }) }),
1376
- /* @__PURE__ */ jsx4("span", { className: "gtv-menu-row-label", children: "Change parent" })
1440
+ /* @__PURE__ */ jsx6("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ jsx6(PiGraphFill2, { className: "gtv-icon", size: 13 }) }),
1441
+ /* @__PURE__ */ jsx6("span", { className: "gtv-menu-row-label", children: "Change parent" })
1377
1442
  ]
1378
1443
  }
1379
1444
  ),
1380
1445
  overflowExtraActions.map((action) => {
1381
1446
  const enabled = action.enabled ? action.enabled(node) : true;
1382
- return /* @__PURE__ */ jsxs3(
1447
+ return /* @__PURE__ */ jsxs5(
1383
1448
  "button",
1384
1449
  {
1385
1450
  type: "button",
@@ -1390,15 +1455,15 @@ function NodeToolbar({
1390
1455
  action.onClick(event, node);
1391
1456
  },
1392
1457
  children: [
1393
- /* @__PURE__ */ jsx4("span", { className: "gtv-menu-row-icon", children: action.icon(node) }),
1394
- /* @__PURE__ */ jsx4("span", { className: "gtv-menu-row-label", children: action.label(node) })
1458
+ /* @__PURE__ */ jsx6("span", { className: "gtv-menu-row-icon", children: action.icon(node) }),
1459
+ /* @__PURE__ */ jsx6("span", { className: "gtv-menu-row-label", children: action.label(node) })
1395
1460
  ]
1396
1461
  },
1397
1462
  action.key
1398
1463
  );
1399
1464
  }),
1400
- /* @__PURE__ */ jsx4("div", { className: "gtv-menu-divider" }),
1401
- /* @__PURE__ */ jsxs3(
1465
+ /* @__PURE__ */ jsx6("div", { className: "gtv-menu-divider" }),
1466
+ /* @__PURE__ */ jsxs5(
1402
1467
  "button",
1403
1468
  {
1404
1469
  type: "button",
@@ -1408,8 +1473,8 @@ function NodeToolbar({
1408
1473
  onDeleteRequest?.(node);
1409
1474
  },
1410
1475
  children: [
1411
- /* @__PURE__ */ jsx4("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ jsx4(FaTrashAlt, { className: "gtv-icon", size: 13 }) }),
1412
- /* @__PURE__ */ jsx4("span", { className: "gtv-menu-row-label", children: "Delete" })
1476
+ /* @__PURE__ */ jsx6("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ jsx6(FaTrashAlt, { className: "gtv-icon", size: 13 }) }),
1477
+ /* @__PURE__ */ jsx6("span", { className: "gtv-menu-row-label", children: "Delete" })
1413
1478
  ]
1414
1479
  }
1415
1480
  )
@@ -1420,7 +1485,7 @@ function NodeToolbar({
1420
1485
  }
1421
1486
 
1422
1487
  // src/InfoPanel.tsx
1423
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1488
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1424
1489
  var MINI_NODE_MIN_DIAMETER = 14;
1425
1490
  var MINI_NODE_MAX_DIAMETER = 32;
1426
1491
  var MINI_NODE_MIN_FONT_SIZE = 10;
@@ -1452,10 +1517,6 @@ function InfoPanel({
1452
1517
  onZoomIn,
1453
1518
  actions,
1454
1519
  readOnly = false,
1455
- editMode = false,
1456
- onEditModeChange,
1457
- renderTitleEditor,
1458
- renderParentsEditor,
1459
1520
  renderDangerZone,
1460
1521
  expanded = false,
1461
1522
  onExpandedChange,
@@ -1466,10 +1527,8 @@ function InfoPanel({
1466
1527
  useEffect3(() => {
1467
1528
  panelRef.current?.toggleAttribute("inert", collapsed);
1468
1529
  }, [collapsed]);
1469
- const editing = editMode && !readOnly;
1470
- const shownParent = editing && renderParentsEditor ? null : parentNode;
1471
1530
  const miniTreeRange = getItemCountRange([
1472
- ...shownParent ? [shownParent.node] : [],
1531
+ ...parentNode ? [parentNode.node] : [],
1473
1532
  node,
1474
1533
  ...childNodes.map((child) => child.node)
1475
1534
  ]);
@@ -1483,9 +1542,9 @@ function InfoPanel({
1483
1542
  };
1484
1543
  const miniNodeContent = (target, background, range) => {
1485
1544
  const { diameter, fontSize } = miniNodeSize(target, range);
1486
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1487
- /* @__PURE__ */ jsx5("span", { className: "gtv-info-panel-mini-dot", style: { background, width: diameter, height: diameter } }),
1488
- /* @__PURE__ */ jsx5("span", { className: "gtv-info-panel-mini-label", style: { fontSize }, children: target.name })
1545
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1546
+ /* @__PURE__ */ jsx7("span", { className: "gtv-info-panel-mini-dot", style: { background, width: diameter, height: diameter } }),
1547
+ /* @__PURE__ */ jsx7("span", { className: "gtv-info-panel-mini-label", style: { fontSize }, children: target.name })
1489
1548
  ] });
1490
1549
  };
1491
1550
  const sortedChildNodes = [...childNodes].sort((a, b) => b.node.itemCount - a.node.itemCount);
@@ -1498,9 +1557,9 @@ function InfoPanel({
1498
1557
  document.addEventListener("keydown", onKeyDown);
1499
1558
  return () => document.removeEventListener("keydown", onKeyDown);
1500
1559
  }, [expanded, onExpandedChange]);
1501
- const dangerZone = editing && renderDangerZone && /* @__PURE__ */ jsx5("div", { className: "gtv-info-panel-danger-zone", children: renderDangerZone(node) });
1502
- const toolbar = actions ? /* @__PURE__ */ jsx5(NodeToolbar, { node, className: "gtv-info-panel-toolbar", ...actions }, node.id) : null;
1503
- const ancestors = ancestorNodes.length > 0 ? /* @__PURE__ */ jsx5(InfoPanelSection, { title: "Ancestors", children: /* @__PURE__ */ jsx5("ul", { className: "gtv-info-panel-children-list gtv-info-panel-ancestors-list", children: ancestorNodes.map(({ node: ancestor, fill: fill2 }) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(
1560
+ const dangerZone = !readOnly && renderDangerZone && /* @__PURE__ */ jsx7("div", { className: "gtv-info-panel-danger-zone", children: renderDangerZone(node) });
1561
+ const toolbar = actions ? /* @__PURE__ */ jsx7(NodeToolbar, { node, className: "gtv-info-panel-toolbar", ...actions }, node.id) : null;
1562
+ const ancestors = ancestorNodes.length > 0 ? /* @__PURE__ */ jsx7(InfoPanelSection, { title: "Ancestors", children: /* @__PURE__ */ jsx7("ul", { className: "gtv-info-panel-children-list gtv-info-panel-ancestors-list", children: ancestorNodes.map(({ node: ancestor, fill: fill2 }) => /* @__PURE__ */ jsx7("li", { children: /* @__PURE__ */ jsx7(
1504
1563
  "button",
1505
1564
  {
1506
1565
  type: "button",
@@ -1510,76 +1569,73 @@ function InfoPanel({
1510
1569
  children: miniNodeContent(ancestor, fill2, ancestorRange)
1511
1570
  }
1512
1571
  ) }, ancestor.id)) }) }) : null;
1513
- const family = /* @__PURE__ */ jsxs4(Fragment2, { children: [
1514
- editing && renderParentsEditor && renderParentsEditor(node),
1515
- /* @__PURE__ */ jsx5(InfoPanelSection, { title: "Family", children: /* @__PURE__ */ jsxs4("div", { className: "gtv-info-panel-mini-tree", children: [
1516
- shownParent && /* @__PURE__ */ jsx5("div", { className: "gtv-info-panel-mini-tree-parent", children: /* @__PURE__ */ jsx5(
1572
+ const family = /* @__PURE__ */ jsx7(Fragment2, { children: /* @__PURE__ */ jsx7(InfoPanelSection, { title: "Family", children: /* @__PURE__ */ jsxs6("div", { className: "gtv-info-panel-mini-tree", children: [
1573
+ parentNode && /* @__PURE__ */ jsx7("div", { className: "gtv-info-panel-mini-tree-parent", children: /* @__PURE__ */ jsx7(
1574
+ "button",
1575
+ {
1576
+ type: "button",
1577
+ className: "gtv-info-panel-child",
1578
+ title: parentNode.node.name,
1579
+ onClick: () => onSelectNode(parentNode.node.id),
1580
+ children: miniNodeContent(parentNode.node, parentNode.fill, miniTreeRange)
1581
+ }
1582
+ ) }),
1583
+ /* @__PURE__ */ jsx7(
1584
+ "div",
1585
+ {
1586
+ className: `gtv-info-panel-mini-tree-node${childNodes.length === 0 ? " gtv-info-panel-mini-tree-node--leaf" : ""}`,
1587
+ children: /* @__PURE__ */ jsx7(
1588
+ "span",
1589
+ {
1590
+ className: "gtv-info-panel-child gtv-info-panel-mini-tree-current",
1591
+ title: node.name,
1592
+ children: miniNodeContent(node, fill, miniTreeRange)
1593
+ }
1594
+ )
1595
+ }
1596
+ ),
1597
+ /* @__PURE__ */ jsxs6("div", { className: "gtv-info-panel-mini-tree-children", children: [
1598
+ /* @__PURE__ */ jsxs6("span", { className: "gtv-info-panel-children-title", children: [
1599
+ childNodes.length,
1600
+ " ",
1601
+ childNodes.length <= 1 ? "child" : "children"
1602
+ ] }),
1603
+ childNodes.length > 0 && /* @__PURE__ */ jsx7("ul", { className: "gtv-info-panel-children-list", children: sortedChildNodes.map(({ node: child, fill: fill2 }) => /* @__PURE__ */ jsx7("li", { children: /* @__PURE__ */ jsx7(
1517
1604
  "button",
1518
1605
  {
1519
1606
  type: "button",
1520
1607
  className: "gtv-info-panel-child",
1521
- title: shownParent.node.name,
1522
- onClick: () => onSelectNode(shownParent.node.id),
1523
- children: miniNodeContent(shownParent.node, shownParent.fill, miniTreeRange)
1524
- }
1525
- ) }),
1526
- /* @__PURE__ */ jsx5(
1527
- "div",
1528
- {
1529
- className: `gtv-info-panel-mini-tree-node${childNodes.length === 0 ? " gtv-info-panel-mini-tree-node--leaf" : ""}`,
1530
- children: /* @__PURE__ */ jsx5(
1531
- "span",
1532
- {
1533
- className: "gtv-info-panel-child gtv-info-panel-mini-tree-current",
1534
- title: node.name,
1535
- children: miniNodeContent(node, fill, miniTreeRange)
1536
- }
1537
- )
1608
+ title: child.name,
1609
+ onClick: () => onSelectNode(child.id),
1610
+ children: miniNodeContent(child, fill2, miniTreeRange)
1538
1611
  }
1539
- ),
1540
- /* @__PURE__ */ jsxs4("div", { className: "gtv-info-panel-mini-tree-children", children: [
1541
- /* @__PURE__ */ jsxs4("span", { className: "gtv-info-panel-children-title", children: [
1542
- childNodes.length,
1543
- " ",
1544
- childNodes.length <= 1 ? "child" : "children"
1545
- ] }),
1546
- childNodes.length > 0 && /* @__PURE__ */ jsx5("ul", { className: "gtv-info-panel-children-list", children: sortedChildNodes.map(({ node: child, fill: fill2 }) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(
1547
- "button",
1548
- {
1549
- type: "button",
1550
- className: "gtv-info-panel-child",
1551
- title: child.name,
1552
- onClick: () => onSelectNode(child.id),
1553
- children: miniNodeContent(child, fill2, miniTreeRange)
1554
- }
1555
- ) }, child.id)) })
1556
- ] })
1557
- ] }) })
1558
- ] });
1559
- const itemCount = /* @__PURE__ */ jsxs4("dl", { className: "gtv-info-panel-fields", children: [
1560
- /* @__PURE__ */ jsx5("dt", { children: itemCountLabel }),
1561
- /* @__PURE__ */ jsx5("dd", { children: node.itemCount })
1612
+ ) }, child.id)) })
1613
+ ] })
1614
+ ] }) }) });
1615
+ const itemCount = /* @__PURE__ */ jsxs6("dl", { className: "gtv-info-panel-fields", children: [
1616
+ /* @__PURE__ */ jsx7("dt", { children: itemCountLabel }),
1617
+ /* @__PURE__ */ jsx7("dd", { children: node.itemCount })
1562
1618
  ] });
1563
- const { primary, aside } = renderInfoPanelBody ? renderInfoPanelBody(node, { toolbar, ancestors, family, itemCount }) : { primary: /* @__PURE__ */ jsxs4(Fragment2, { children: [
1619
+ const { primary, aside } = renderInfoPanelBody ? renderInfoPanelBody(node, { toolbar, ancestors, family, itemCount }) : { primary: /* @__PURE__ */ jsxs6(Fragment2, { children: [
1564
1620
  toolbar,
1565
1621
  ancestors,
1566
1622
  family,
1567
1623
  itemCount
1568
1624
  ] }) };
1569
1625
  const hasAside = aside !== void 0 && aside !== null && aside !== false;
1570
- return /* @__PURE__ */ jsxs4(
1626
+ return /* @__PURE__ */ jsxs6(
1571
1627
  "div",
1572
1628
  {
1573
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" : ""}`,
1574
1630
  ref: panelRef,
1575
1631
  children: [
1576
- /* @__PURE__ */ jsxs4(
1632
+ /* @__PURE__ */ jsxs6(
1577
1633
  "div",
1578
1634
  {
1579
1635
  className: "gtv-info-panel-header",
1580
1636
  style: { background: fill, color: textColor },
1581
1637
  children: [
1582
- onPrevious && /* @__PURE__ */ jsx5(
1638
+ onPrevious && /* @__PURE__ */ jsx7(
1583
1639
  "button",
1584
1640
  {
1585
1641
  type: "button",
@@ -1588,21 +1644,21 @@ function InfoPanel({
1588
1644
  onClick: onPrevious,
1589
1645
  "aria-label": previousLabel ? `Previous: ${previousLabel}` : "Previous",
1590
1646
  title: previousLabel,
1591
- children: /* @__PURE__ */ jsx5(MdArrowBack, { className: "gtv-icon", size: 16 })
1647
+ children: /* @__PURE__ */ jsx7(MdArrowBack, { className: "gtv-icon", size: 16 })
1592
1648
  }
1593
1649
  ),
1594
- editing && renderTitleEditor ? /* @__PURE__ */ jsx5("div", { className: "gtv-info-panel-title gtv-info-panel-title--editor", children: renderTitleEditor(node) }) : /* @__PURE__ */ jsxs4(
1650
+ /* @__PURE__ */ jsxs6(
1595
1651
  "span",
1596
1652
  {
1597
1653
  className: `gtv-info-panel-title${hideTitle ? " gtv-info-panel-title--hidden" : ""}`,
1598
1654
  style: { color: textColor },
1599
1655
  children: [
1600
1656
  node.name,
1601
- node.umbrella && /* @__PURE__ */ jsx5("span", { className: "gtv-info-panel-umbrella-badge", children: "umbrella" })
1657
+ node.umbrella && /* @__PURE__ */ jsx7("span", { className: "gtv-info-panel-umbrella-badge", children: "umbrella" })
1602
1658
  ]
1603
1659
  }
1604
1660
  ),
1605
- onZoomIn && /* @__PURE__ */ jsx5(
1661
+ onZoomIn && /* @__PURE__ */ jsx7(
1606
1662
  "button",
1607
1663
  {
1608
1664
  type: "button",
@@ -1611,22 +1667,11 @@ function InfoPanel({
1611
1667
  onClick: onZoomIn,
1612
1668
  "aria-label": "Zoom in",
1613
1669
  title: "Zoom in",
1614
- children: /* @__PURE__ */ jsx5(MdZoomIn, { className: "gtv-icon", size: 16 })
1615
- }
1616
- ),
1617
- onEditModeChange && !readOnly && /* @__PURE__ */ jsx5(
1618
- "button",
1619
- {
1620
- type: "button",
1621
- className: "gtv-info-panel-edit",
1622
- style: { color: textColor },
1623
- onClick: () => onEditModeChange(!editMode),
1624
- "aria-pressed": editMode,
1625
- children: editMode ? "Done" : "Edit"
1670
+ children: /* @__PURE__ */ jsx7(MdZoomIn2, { className: "gtv-icon", size: 16 })
1626
1671
  }
1627
1672
  ),
1628
- onExpandedChange && !hideExpandButton && /* @__PURE__ */ jsx5(InfoPanelExpandButton, { expanded, onExpandedChange, style: { color: textColor } }),
1629
- !hideClose && /* @__PURE__ */ jsx5(
1673
+ onExpandedChange && !hideExpandButton && /* @__PURE__ */ jsx7(InfoPanelExpandButton, { expanded, onExpandedChange, style: { color: textColor } }),
1674
+ !hideClose && /* @__PURE__ */ jsx7(
1630
1675
  "button",
1631
1676
  {
1632
1677
  type: "button",
@@ -1634,18 +1679,18 @@ function InfoPanel({
1634
1679
  style: { color: textColor },
1635
1680
  onClick: onClose,
1636
1681
  "aria-label": "Close",
1637
- children: /* @__PURE__ */ jsx5(MdClose, { className: "gtv-icon", size: 16 })
1682
+ children: /* @__PURE__ */ jsx7(MdClose, { className: "gtv-icon", size: 16 })
1638
1683
  }
1639
1684
  )
1640
1685
  ]
1641
1686
  }
1642
1687
  ),
1643
- /* @__PURE__ */ jsxs4("div", { className: "gtv-info-panel-body", children: [
1644
- /* @__PURE__ */ jsxs4("div", { className: "gtv-info-panel-primary", children: [
1688
+ /* @__PURE__ */ jsxs6("div", { className: "gtv-info-panel-body", children: [
1689
+ /* @__PURE__ */ jsxs6("div", { className: "gtv-info-panel-primary", children: [
1645
1690
  primary,
1646
1691
  expanded && !hasAside && dangerZone
1647
1692
  ] }),
1648
- hasAside && /* @__PURE__ */ jsxs4("div", { className: "gtv-info-panel-aside", children: [
1693
+ hasAside && /* @__PURE__ */ jsxs6("div", { className: "gtv-info-panel-aside", children: [
1649
1694
  aside,
1650
1695
  expanded && dangerZone
1651
1696
  ] })
@@ -1659,7 +1704,7 @@ function InfoPanel({
1659
1704
  // src/OverlayPanel.tsx
1660
1705
  import { useEffect as useEffect4, useRef as useRef4 } from "react";
1661
1706
  import { MdClose as MdClose2 } from "react-icons/md";
1662
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1707
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1663
1708
  function OverlayPanel({
1664
1709
  title,
1665
1710
  content,
@@ -1694,21 +1739,21 @@ function OverlayPanel({
1694
1739
  expanded && !extraContent && "gtv-info-panel--expanded-solo",
1695
1740
  collapsed && "gtv-info-panel--collapsed"
1696
1741
  ].filter(Boolean).join(" ");
1697
- return /* @__PURE__ */ jsxs5("div", { className, ref: panelRef, children: [
1698
- /* @__PURE__ */ jsxs5("div", { className: "gtv-info-panel-header", children: [
1699
- /* @__PURE__ */ jsx6("span", { className: `gtv-info-panel-title${hideTitle ? " gtv-info-panel-title--hidden" : ""}`, children: title }),
1700
- onExpandedChange && !hideExpandButton && /* @__PURE__ */ jsx6(InfoPanelExpandButton, { expanded, onExpandedChange }),
1701
- !hideClose && /* @__PURE__ */ jsx6("button", { type: "button", className: "gtv-info-panel-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx6(MdClose2, { className: "gtv-icon", size: 16 }) })
1742
+ return /* @__PURE__ */ jsxs7("div", { className, ref: panelRef, children: [
1743
+ /* @__PURE__ */ jsxs7("div", { className: "gtv-info-panel-header", children: [
1744
+ /* @__PURE__ */ jsx8("span", { className: `gtv-info-panel-title${hideTitle ? " gtv-info-panel-title--hidden" : ""}`, children: title }),
1745
+ onExpandedChange && !hideExpandButton && /* @__PURE__ */ jsx8(InfoPanelExpandButton, { expanded, onExpandedChange }),
1746
+ !hideClose && /* @__PURE__ */ jsx8("button", { type: "button", className: "gtv-info-panel-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx8(MdClose2, { className: "gtv-icon", size: 16 }) })
1702
1747
  ] }),
1703
- /* @__PURE__ */ jsxs5("div", { className: "gtv-info-panel-body", children: [
1704
- /* @__PURE__ */ jsx6("div", { className: "gtv-info-panel-aside gtv-overlay-content", children: content }),
1705
- extraContent && /* @__PURE__ */ jsx6("div", { className: "gtv-info-panel-primary gtv-overlay-content", children: extraContent })
1748
+ /* @__PURE__ */ jsxs7("div", { className: "gtv-info-panel-body", children: [
1749
+ /* @__PURE__ */ jsx8("div", { className: "gtv-info-panel-aside gtv-overlay-content", children: content }),
1750
+ extraContent && /* @__PURE__ */ jsx8("div", { className: "gtv-info-panel-primary gtv-overlay-content", children: extraContent })
1706
1751
  ] })
1707
1752
  ] });
1708
1753
  }
1709
1754
 
1710
1755
  // src/GenreTree.tsx
1711
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1756
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1712
1757
  function GenreTree({
1713
1758
  nodes,
1714
1759
  className,
@@ -1736,10 +1781,6 @@ function GenreTree({
1736
1781
  hideInfoPanelTitle = false,
1737
1782
  itemCountLabel,
1738
1783
  readOnly,
1739
- editMode,
1740
- onEditModeChange,
1741
- renderTitleEditor,
1742
- renderParentsEditor,
1743
1784
  renderDangerZone,
1744
1785
  expanded,
1745
1786
  onExpandedChange,
@@ -1749,7 +1790,9 @@ function GenreTree({
1749
1790
  wheelZoom,
1750
1791
  defaultViewport,
1751
1792
  onViewportChange,
1752
- controlsOrientation = "vertical"
1793
+ controlsOrientation = "vertical",
1794
+ floatingControlsLeading,
1795
+ busy
1753
1796
  }) {
1754
1797
  const index = useTreeIndex(nodes);
1755
1798
  const svgRef = useRef5(null);
@@ -1905,7 +1948,7 @@ function GenreTree({
1905
1948
  hasInitialFitRef.current = true;
1906
1949
  panZoom.fitToFrame(elements, { silent: true });
1907
1950
  });
1908
- const svg = /* @__PURE__ */ jsx7(
1951
+ const svg = /* @__PURE__ */ jsx9(
1909
1952
  "svg",
1910
1953
  {
1911
1954
  ref: svgRef,
@@ -1915,13 +1958,14 @@ function GenreTree({
1915
1958
  }
1916
1959
  );
1917
1960
  if (!interactive) {
1918
- return /* @__PURE__ */ jsx7("div", { className, children: svg });
1961
+ return /* @__PURE__ */ jsx9("div", { className, children: svg });
1919
1962
  }
1920
- return /* @__PURE__ */ jsxs6(
1963
+ return /* @__PURE__ */ jsxs8(
1921
1964
  "div",
1922
1965
  {
1923
1966
  ref: viewportRef,
1924
1967
  className,
1968
+ "aria-busy": busy,
1925
1969
  style: {
1926
1970
  position: "relative",
1927
1971
  overflow: "hidden",
@@ -1944,7 +1988,7 @@ function GenreTree({
1944
1988
  },
1945
1989
  onPointerDown: panZoom.handlePointerDown,
1946
1990
  children: [
1947
- /* @__PURE__ */ jsx7(
1991
+ /* @__PURE__ */ jsx9(
1948
1992
  "div",
1949
1993
  {
1950
1994
  ref: stageRef,
@@ -1958,7 +2002,7 @@ function GenreTree({
1958
2002
  children: svg
1959
2003
  }
1960
2004
  ),
1961
- panel && /* @__PURE__ */ jsx7(
2005
+ panel && /* @__PURE__ */ jsx9(
1962
2006
  InfoPanel,
1963
2007
  {
1964
2008
  node: panel.node,
@@ -2008,10 +2052,6 @@ function GenreTree({
2008
2052
  hideTitle: hideInfoPanelTitle,
2009
2053
  itemCountLabel,
2010
2054
  readOnly,
2011
- editMode,
2012
- onEditModeChange,
2013
- renderTitleEditor,
2014
- renderParentsEditor,
2015
2055
  renderDangerZone,
2016
2056
  expanded,
2017
2057
  onExpandedChange,
@@ -2030,54 +2070,15 @@ function GenreTree({
2030
2070
  } : void 0
2031
2071
  }
2032
2072
  ),
2033
- overlayPanel && /* @__PURE__ */ jsx7(OverlayPanel, { ...overlayPanel }),
2034
- /* @__PURE__ */ jsx7(
2035
- "div",
2073
+ overlayPanel && /* @__PURE__ */ jsx9(OverlayPanel, { ...overlayPanel }),
2074
+ /* @__PURE__ */ jsx9(
2075
+ FloatingControls,
2036
2076
  {
2037
- className: [
2038
- "gtv-wheel-floating-controls",
2039
- controlsOrientation === "horizontal" && "gtv-wheel-floating-controls--horizontal"
2040
- ].filter(Boolean).join(" "),
2041
- children: /* @__PURE__ */ jsxs6("div", { className: "gtv-zoom-controls", children: [
2042
- /* @__PURE__ */ jsx7(
2043
- "button",
2044
- {
2045
- type: "button",
2046
- className: [
2047
- "gtv-zoom-btn",
2048
- !panZoom.canZoomIn && "gtv-zoom-btn--disabled"
2049
- ].filter(Boolean).join(" "),
2050
- disabled: !panZoom.canZoomIn,
2051
- onClick: panZoom.zoomIn,
2052
- "aria-label": "Zoom in",
2053
- children: /* @__PURE__ */ jsx7(MdZoomIn2, { className: "gtv-icon", size: 18 })
2054
- }
2055
- ),
2056
- /* @__PURE__ */ jsx7(
2057
- "button",
2058
- {
2059
- type: "button",
2060
- className: [
2061
- "gtv-zoom-btn",
2062
- !panZoom.canZoomOut && "gtv-zoom-btn--disabled"
2063
- ].filter(Boolean).join(" "),
2064
- disabled: !panZoom.canZoomOut,
2065
- onClick: panZoom.zoomOut,
2066
- "aria-label": "Zoom out",
2067
- children: /* @__PURE__ */ jsx7(MdZoomOut, { className: "gtv-icon", size: 18 })
2068
- }
2069
- ),
2070
- /* @__PURE__ */ jsx7(
2071
- "button",
2072
- {
2073
- type: "button",
2074
- className: "gtv-zoom-btn",
2075
- onClick: () => panZoom.fitToFrame(queryTreeContentElements(svgRef.current)),
2076
- "aria-label": "Fit to frame",
2077
- children: /* @__PURE__ */ jsx7(MdFitScreen, { className: "gtv-icon", size: 18 })
2078
- }
2079
- )
2080
- ] })
2077
+ panZoom,
2078
+ orientation: controlsOrientation,
2079
+ leading: floatingControlsLeading,
2080
+ busy,
2081
+ onFit: () => panZoom.fitToFrame(queryTreeContentElements(svgRef.current))
2081
2082
  }
2082
2083
  )
2083
2084
  ]
@@ -2087,7 +2088,6 @@ function GenreTree({
2087
2088
 
2088
2089
  // src/GenreTreeWheelBase.tsx
2089
2090
  import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState5 } from "react";
2090
- import { MdFitScreen as MdFitScreen2, MdZoomIn as MdZoomIn3, MdZoomOut as MdZoomOut2 } from "react-icons/md";
2091
2091
  import * as d32 from "d3";
2092
2092
 
2093
2093
  // src/root-grouping.ts
@@ -2163,7 +2163,7 @@ function computeRotationForSelection(currentRotationDeg, targetAngle, landingAng
2163
2163
  }
2164
2164
 
2165
2165
  // src/GenreTreeWheelBase.tsx
2166
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2166
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2167
2167
  function WheelCore({
2168
2168
  nodes,
2169
2169
  className,
@@ -2189,10 +2189,6 @@ function WheelCore({
2189
2189
  hideInfoPanelTitle = false,
2190
2190
  itemCountLabel,
2191
2191
  readOnly,
2192
- editMode,
2193
- onEditModeChange,
2194
- renderTitleEditor,
2195
- renderParentsEditor,
2196
2192
  renderDangerZone,
2197
2193
  expanded,
2198
2194
  onExpandedChange,
@@ -2203,6 +2199,8 @@ function WheelCore({
2203
2199
  defaultViewport,
2204
2200
  onViewportChange,
2205
2201
  controlsOrientation = "vertical",
2202
+ floatingControlsLeading,
2203
+ busy,
2206
2204
  selectedNodeId
2207
2205
  }) {
2208
2206
  const treeOrientation = direction === "left" ? "horizontal-anchored" : "vertical";
@@ -2318,10 +2316,11 @@ function WheelCore({
2318
2316
  );
2319
2317
  showNodeInfo(targetNode, element, viewportRef.current, { notify: false });
2320
2318
  }, [selectedNodeId, effectiveRootId, nodes, panel, showNodeInfo]);
2321
- return /* @__PURE__ */ jsxs7(
2319
+ return /* @__PURE__ */ jsxs9(
2322
2320
  "div",
2323
2321
  {
2324
2322
  ref: viewportRef,
2323
+ "aria-busy": busy,
2325
2324
  className: [
2326
2325
  "gtv-wheel-container",
2327
2326
  direction === "left" && "gtv-wheel-container--left",
@@ -2336,7 +2335,7 @@ function WheelCore({
2336
2335
  },
2337
2336
  onPointerDown: panZoom.handlePointerDown,
2338
2337
  children: [
2339
- /* @__PURE__ */ jsx8(
2338
+ /* @__PURE__ */ jsx10(
2340
2339
  "div",
2341
2340
  {
2342
2341
  ref: stageRef,
@@ -2349,7 +2348,7 @@ function WheelCore({
2349
2348
  transform: panZoom.transform,
2350
2349
  transformOrigin: "0 0"
2351
2350
  },
2352
- children: /* @__PURE__ */ jsxs7(
2351
+ children: /* @__PURE__ */ jsxs9(
2353
2352
  "div",
2354
2353
  {
2355
2354
  className: "gtv-wheel-stage",
@@ -2357,7 +2356,7 @@ function WheelCore({
2357
2356
  [chipHalfExtentVar]: `${rootChipHalfExtent}px`
2358
2357
  },
2359
2358
  children: [
2360
- selectedGroup && /* @__PURE__ */ jsx8("div", { className: "gtv-wheel-tree-anchor", ref: treeAnchorRef, children: /* @__PURE__ */ jsx8(
2359
+ selectedGroup && /* @__PURE__ */ jsx10("div", { className: "gtv-wheel-tree-anchor", ref: treeAnchorRef, children: /* @__PURE__ */ jsx10(
2361
2360
  GenreTree,
2362
2361
  {
2363
2362
  nodes: selectedGroup.nodes,
@@ -2395,9 +2394,9 @@ function WheelCore({
2395
2394
  },
2396
2395
  selectedGroup.root.id
2397
2396
  ) }),
2398
- /* @__PURE__ */ jsx8("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
2399
- centerLabel && /* @__PURE__ */ jsx8("div", { className: "gtv-wheel-center-label", children: centerLabel }),
2400
- /* @__PURE__ */ jsxs7(
2397
+ /* @__PURE__ */ jsx10("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
2398
+ centerLabel && /* @__PURE__ */ jsx10("div", { className: "gtv-wheel-center-label", children: centerLabel }),
2399
+ /* @__PURE__ */ jsxs9(
2401
2400
  "div",
2402
2401
  {
2403
2402
  className: "gtv-wheel",
@@ -2405,7 +2404,7 @@ function WheelCore({
2405
2404
  "--gtv-wheel-rotation": `${rotationDeg}deg`
2406
2405
  },
2407
2406
  children: [
2408
- sectorFillGradient && /* @__PURE__ */ jsx8(
2407
+ sectorFillGradient && /* @__PURE__ */ jsx10(
2409
2408
  "div",
2410
2409
  {
2411
2410
  className: "gtv-wheel-sector-fill",
@@ -2414,7 +2413,7 @@ function WheelCore({
2414
2413
  }
2415
2414
  }
2416
2415
  ),
2417
- groups.length > 1 && groups.map((group, index) => /* @__PURE__ */ jsx8(
2416
+ groups.length > 1 && groups.map((group, index) => /* @__PURE__ */ jsx10(
2418
2417
  "div",
2419
2418
  {
2420
2419
  className: "gtv-wheel-divider",
@@ -2437,12 +2436,12 @@ function WheelCore({
2437
2436
  rootItemCount,
2438
2437
  rootItemCountRange
2439
2438
  );
2440
- return /* @__PURE__ */ jsx8(
2439
+ return /* @__PURE__ */ jsx10(
2441
2440
  "div",
2442
2441
  {
2443
2442
  className: "gtv-wheel-slot",
2444
2443
  style: { "--gtv-chip-angle": `${angle}deg` },
2445
- children: /* @__PURE__ */ jsx8("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ jsxs7(
2444
+ children: /* @__PURE__ */ jsx10("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ jsxs9(
2446
2445
  "button",
2447
2446
  {
2448
2447
  type: "button",
@@ -2478,8 +2477,8 @@ function WheelCore({
2478
2477
  }
2479
2478
  },
2480
2479
  children: [
2481
- PER_TREE_ACCENT_DOT && /* @__PURE__ */ jsx8("span", { className: "gtv-wheel-chip-dot" }),
2482
- /* @__PURE__ */ jsx8(
2480
+ PER_TREE_ACCENT_DOT && /* @__PURE__ */ jsx10("span", { className: "gtv-wheel-chip-dot" }),
2481
+ /* @__PURE__ */ jsx10(
2483
2482
  "span",
2484
2483
  {
2485
2484
  className: "gtv-node-label gtv-node-label--root",
@@ -2502,7 +2501,7 @@ function WheelCore({
2502
2501
  )
2503
2502
  }
2504
2503
  ),
2505
- panel && /* @__PURE__ */ jsx8(
2504
+ panel && /* @__PURE__ */ jsx10(
2506
2505
  InfoPanel,
2507
2506
  {
2508
2507
  node: panel.node,
@@ -2549,10 +2548,6 @@ function WheelCore({
2549
2548
  hideTitle: hideInfoPanelTitle,
2550
2549
  itemCountLabel,
2551
2550
  readOnly,
2552
- editMode,
2553
- onEditModeChange,
2554
- renderTitleEditor,
2555
- renderParentsEditor,
2556
2551
  renderDangerZone,
2557
2552
  expanded,
2558
2553
  onExpandedChange,
@@ -2571,57 +2566,18 @@ function WheelCore({
2571
2566
  } : void 0
2572
2567
  }
2573
2568
  ),
2574
- overlayPanel && /* @__PURE__ */ jsx8(OverlayPanel, { ...overlayPanel }),
2575
- /* @__PURE__ */ jsx8(
2576
- "div",
2569
+ overlayPanel && /* @__PURE__ */ jsx10(OverlayPanel, { ...overlayPanel }),
2570
+ /* @__PURE__ */ jsx10(
2571
+ FloatingControls,
2577
2572
  {
2578
- className: [
2579
- "gtv-wheel-floating-controls",
2580
- controlsOrientation === "horizontal" && "gtv-wheel-floating-controls--horizontal"
2581
- ].filter(Boolean).join(" "),
2582
- children: /* @__PURE__ */ jsxs7("div", { className: "gtv-zoom-controls", children: [
2583
- /* @__PURE__ */ jsx8(
2584
- "button",
2585
- {
2586
- type: "button",
2587
- className: [
2588
- "gtv-zoom-btn",
2589
- !panZoom.canZoomIn && "gtv-zoom-btn--disabled"
2590
- ].filter(Boolean).join(" "),
2591
- disabled: !panZoom.canZoomIn,
2592
- onClick: panZoom.zoomIn,
2593
- "aria-label": "Zoom in",
2594
- children: /* @__PURE__ */ jsx8(MdZoomIn3, { className: "gtv-icon", size: 18 })
2595
- }
2596
- ),
2597
- /* @__PURE__ */ jsx8(
2598
- "button",
2599
- {
2600
- type: "button",
2601
- className: [
2602
- "gtv-zoom-btn",
2603
- !panZoom.canZoomOut && "gtv-zoom-btn--disabled"
2604
- ].filter(Boolean).join(" "),
2605
- disabled: !panZoom.canZoomOut,
2606
- onClick: panZoom.zoomOut,
2607
- "aria-label": "Zoom out",
2608
- children: /* @__PURE__ */ jsx8(MdZoomOut2, { className: "gtv-icon", size: 18 })
2609
- }
2610
- ),
2611
- /* @__PURE__ */ jsx8(
2612
- "button",
2613
- {
2614
- type: "button",
2615
- className: "gtv-zoom-btn",
2616
- onClick: () => panZoom.fitToFrame([
2617
- wheelCircleRef.current,
2618
- ...queryTreeContentElements(treeAnchorRef.current)
2619
- ]),
2620
- "aria-label": "Fit to frame",
2621
- children: /* @__PURE__ */ jsx8(MdFitScreen2, { className: "gtv-icon", size: 18 })
2622
- }
2623
- )
2624
- ] })
2573
+ panZoom,
2574
+ orientation: controlsOrientation,
2575
+ leading: floatingControlsLeading,
2576
+ busy,
2577
+ onFit: () => panZoom.fitToFrame([
2578
+ wheelCircleRef.current,
2579
+ ...queryTreeContentElements(treeAnchorRef.current)
2580
+ ])
2625
2581
  }
2626
2582
  )
2627
2583
  ]
@@ -2630,20 +2586,19 @@ function WheelCore({
2630
2586
  }
2631
2587
 
2632
2588
  // src/GenreTreeWheel.tsx
2633
- import { jsx as jsx9 } from "react/jsx-runtime";
2589
+ import { jsx as jsx11 } from "react/jsx-runtime";
2634
2590
  function GenreTreeWheel(props) {
2635
- return /* @__PURE__ */ jsx9(WheelCore, { ...props, direction: "bottom" });
2591
+ return /* @__PURE__ */ jsx11(WheelCore, { ...props, direction: "bottom" });
2636
2592
  }
2637
2593
 
2638
2594
  // src/GenreTreeWheelRight.tsx
2639
- import { jsx as jsx10 } from "react/jsx-runtime";
2595
+ import { jsx as jsx12 } from "react/jsx-runtime";
2640
2596
  function GenreTreeWheelRight(props) {
2641
- return /* @__PURE__ */ jsx10(WheelCore, { ...props, direction: "left" });
2597
+ return /* @__PURE__ */ jsx12(WheelCore, { ...props, direction: "left" });
2642
2598
  }
2643
2599
 
2644
2600
  // src/GenreTreeWheelRadialBase.tsx
2645
2601
  import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef7, useState as useState6 } from "react";
2646
- import { MdFitScreen as MdFitScreen3, MdZoomIn as MdZoomIn4, MdZoomOut as MdZoomOut3 } from "react-icons/md";
2647
2602
  import * as d33 from "d3";
2648
2603
 
2649
2604
  // src/defer-click.ts
@@ -2827,6 +2782,7 @@ function pickEmptyLabelSpot(area, width, height, obstacles, prefer = () => true)
2827
2782
  var POP_WEDGE_SPAN_DEGREES = 80;
2828
2783
  var POP_SUBTREE_OUTER_MARGIN = 24;
2829
2784
  var POP_HIERARCHY_ROOT_ABSOLUTE_DEPTH = 1;
2785
+ var DOT_RADIUS_TRANSITION_MS = 250;
2830
2786
  function getRadialDepthRadius(depth, coreRootCircleRadius, depthSpacing) {
2831
2787
  return coreRootCircleRadius + depth * depthSpacing;
2832
2788
  }
@@ -2849,14 +2805,13 @@ function calculatePopSubtreeRadialExtent(hierarchy, coreRootCircleRadius = 0) {
2849
2805
  POP_TREE_DEPTH_RADIAL_SPACING
2850
2806
  ) + MAX_NODE_WIDTH / 2 + POP_SUBTREE_OUTER_MARGIN;
2851
2807
  }
2852
- function computePopRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, coreRootCircleRadius, wedgeSpanDegrees = POP_WEDGE_SPAN_DEGREES, depthSpacing = POP_TREE_DEPTH_RADIAL_SPACING, itemCountRange) {
2808
+ function computePopRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, coreRootCircleRadius, wedgeSpanDegrees = POP_WEDGE_SPAN_DEGREES, depthSpacing = POP_TREE_DEPTH_RADIAL_SPACING) {
2853
2809
  return layoutWedge(
2854
2810
  d3Lib,
2855
2811
  hierarchy,
2856
2812
  wedgeCenterAngleDegrees,
2857
2813
  wedgeSpanDegrees,
2858
- popRadiusOfDepth(coreRootCircleRadius, depthSpacing),
2859
- itemCountRange
2814
+ popRadiusOfDepth(coreRootCircleRadius, depthSpacing)
2860
2815
  );
2861
2816
  }
2862
2817
  var MIN_DOT_GAP = 5.6;
@@ -2866,15 +2821,15 @@ function popRadiusOfDepth(coreRootCircleRadius, depthSpacing) {
2866
2821
  function coreRadiusOfDepth(coreRootCircleRadius, depthSpacing) {
2867
2822
  return (depth) => getRadialDepthRadius(depth + 1, coreRootCircleRadius, depthSpacing);
2868
2823
  }
2869
- function measureWedge(_d3Lib, hierarchy, radiusOfDepth, itemCountRange) {
2870
- const dotRadius = (d) => itemCountRange ? calculateNodeDotRadius(d.data.itemCount, itemCountRange) : NODE_DOT_MAX_RADIUS;
2824
+ function measureWedge(_d3Lib, hierarchy, radiusOfDepth) {
2825
+ const dotRadius = () => NODE_LAYOUT_SLOT_RADIUS;
2871
2826
  const offsetFromParent = /* @__PURE__ */ new Map();
2872
2827
  const shiftLevel = (level, by) => ({
2873
2828
  left: { ...level.left, x: level.left.x + by },
2874
2829
  right: { ...level.right, x: level.right.x + by }
2875
2830
  });
2876
2831
  const pack = (node) => {
2877
- const own = { x: 0, dotRadius: dotRadius(node) };
2832
+ const own = { x: 0, dotRadius: dotRadius() };
2878
2833
  if (!node.children) return [{ left: own, right: own }];
2879
2834
  const below = [];
2880
2835
  const childOffsets = [];
@@ -2906,7 +2861,7 @@ function measureWedge(_d3Lib, hierarchy, radiusOfDepth, itemCountRange) {
2906
2861
  let min = Infinity;
2907
2862
  let max = -Infinity;
2908
2863
  hierarchy.each((d) => {
2909
- const inset = (dotRadius(d) + MIN_DOT_GAP / 2) / radiusOfDepth(d.depth);
2864
+ const inset = (dotRadius() + MIN_DOT_GAP / 2) / radiusOfDepth(d.depth);
2910
2865
  min = Math.min(min, d.x - inset);
2911
2866
  max = Math.max(max, d.x + inset);
2912
2867
  });
@@ -2916,13 +2871,44 @@ function measureWedge(_d3Lib, hierarchy, radiusOfDepth, itemCountRange) {
2916
2871
  return max - min;
2917
2872
  }
2918
2873
  var MAX_WEDGE_STRETCH = 1.42;
2919
- function layoutWedge(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, radiusOfDepth, itemCountRange) {
2920
- const neededRad = measureWedge(d3Lib, hierarchy, radiusOfDepth, itemCountRange);
2874
+ function splitAtBranching(hierarchy) {
2875
+ let branching = hierarchy;
2876
+ const chain = [];
2877
+ while (branching.children?.length === 1) {
2878
+ chain.push(branching);
2879
+ branching = branching.children[0];
2880
+ }
2881
+ const children = branching.children;
2882
+ return children && children.length > 1 ? { chain, branching, children } : null;
2883
+ }
2884
+ function layoutWedge(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, radiusOfDepth, slack = "center") {
2885
+ const neededRad = measureWedge(d3Lib, hierarchy, radiusOfDepth);
2921
2886
  const wedgeSpanRad = wedgeSpanDegrees * Math.PI / 180;
2922
2887
  const scale = Math.min(MAX_WEDGE_STRETCH, wedgeSpanRad / neededRad);
2923
- const wedgeStartRad = wedgeCenterAngleDegrees * Math.PI / 180 - neededRad * scale / 2;
2888
+ const wedgeCenterRad = wedgeCenterAngleDegrees * Math.PI / 180;
2889
+ const centeredStartRad = wedgeCenterRad - neededRad * scale / 2;
2890
+ const branchingSplit = slack === "between-children" ? splitAtBranching(hierarchy) : null;
2891
+ if (!branchingSplit) {
2892
+ hierarchy.each((d) => {
2893
+ const angleRad = centeredStartRad + d.x * scale;
2894
+ const radius = radiusOfDepth(d.depth);
2895
+ d.x = radius * Math.sin(angleRad);
2896
+ d.y = -radius * Math.cos(angleRad);
2897
+ });
2898
+ return hierarchy;
2899
+ }
2900
+ const { chain, branching, children } = branchingSplit;
2901
+ const gap = Math.max(0, wedgeSpanRad - neededRad * scale) / (children.length - 1);
2902
+ const wedgeStartRad = wedgeCenterRad - wedgeSpanRad / 2;
2903
+ const angleOffsetRad = /* @__PURE__ */ new Map();
2904
+ hierarchy.each((d) => angleOffsetRad.set(d, d.x * scale));
2905
+ children.forEach((child, i) => {
2906
+ child.each((d) => angleOffsetRad.set(d, angleOffsetRad.get(d) + i * gap));
2907
+ });
2908
+ const recenter = gap * (children.length - 1) / 2;
2909
+ [...chain, branching].forEach((d) => angleOffsetRad.set(d, angleOffsetRad.get(d) + recenter));
2924
2910
  hierarchy.each((d) => {
2925
- const angleRad = wedgeStartRad + d.x * scale;
2911
+ const angleRad = wedgeStartRad + angleOffsetRad.get(d);
2926
2912
  const radius = radiusOfDepth(d.depth);
2927
2913
  d.x = radius * Math.sin(angleRad);
2928
2914
  d.y = -radius * Math.cos(angleRad);
@@ -3009,7 +2995,8 @@ function renderPopSubtree(d3Lib, svg, hierarchy, rootColor, reparentingNodeId, r
3009
2995
  rootLinkOrigin,
3010
2996
  isMainstreamSector = false,
3011
2997
  selectedNodeId = null,
3012
- nodeFillOf
2998
+ nodeFillOf,
2999
+ dotRadiusMemory
3013
3000
  } = options;
3014
3001
  const { onReparentTargetSelect, onNodeClick, onNodeDoubleClick, onNodeHover } = callbacks;
3015
3002
  const isForbidden = (d) => reparentForbiddenIds.includes(d.data.id);
@@ -3072,13 +3059,18 @@ function renderPopSubtree(d3Lib, svg, hierarchy, rootColor, reparentingNodeId, r
3072
3059
  );
3073
3060
  d3Lib.select(this).attr("x", hitArea.x0).attr("y", hitArea.y0).attr("width", hitArea.x1 - hitArea.x0).attr("height", hitArea.y1 - hitArea.y0);
3074
3061
  }).attr("fill", "transparent").attr("pointer-events", "all");
3075
- nodes.append("circle").attr("class", "gtv-node-dot").attr("r", (d) => calculateNodeDotRadius(d.data.itemCount, itemCountRange)).attr("fill", nodeFill).attr(
3062
+ const targetDotRadius = (d) => calculateNodeDotRadius(d.data.itemCount, itemCountRange);
3063
+ const animateDots = dotRadiusMemory !== void 0 && !(typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches);
3064
+ const startDotRadius = (d) => (animateDots ? dotRadiusMemory?.get(d.data.id) : void 0) ?? targetDotRadius(d);
3065
+ nodes.append("circle").attr("class", "gtv-node-dot").attr("r", startDotRadius).attr("fill", nodeFill).attr(
3076
3066
  "stroke",
3077
3067
  (d) => isSelected(d) ? ACCENT_COLOR : isMainstreamSector ? TEXT_COLOR : SURFACE_BORDER_COLOR
3078
3068
  ).attr(
3079
3069
  "stroke-width",
3080
3070
  (d) => isSelected(d) ? calculateSelectedNodeBorderWidth(calculateNodeDotRadius(d.data.itemCount, itemCountRange)) : calculateNodeBorderWidth(d.data.itemCount, itemCountRange)
3081
- ).style("pointer-events", "none");
3071
+ ).style("pointer-events", "none").filter((d) => startDotRadius(d) !== targetDotRadius(d)).transition().duration(DOT_RADIUS_TRANSITION_MS).ease(d3Lib.easeCubicOut).attr("r", targetDotRadius);
3072
+ nodes.each((d) => dotRadiusMemory?.set(d.data.id, targetDotRadius(d)));
3073
+ nodes.sort((a, b) => targetDotRadius(b) - targetDotRadius(a));
3082
3074
  nodes.append("text").attr(
3083
3075
  "class",
3084
3076
  "gtv-node-label"
@@ -3201,14 +3193,14 @@ function buildCoreHierarchy(d3Lib, coreNodes) {
3201
3193
  });
3202
3194
  return hierarchy;
3203
3195
  }
3204
- function computeCoreRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, coreRootCircleRadius, depthSpacing, itemCountRange) {
3196
+ function computeCoreRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, coreRootCircleRadius, depthSpacing) {
3205
3197
  return layoutWedge(
3206
3198
  d3Lib,
3207
3199
  hierarchy,
3208
3200
  wedgeCenterAngleDegrees,
3209
3201
  wedgeSpanDegrees,
3210
3202
  coreRadiusOfDepth(coreRootCircleRadius, depthSpacing),
3211
- itemCountRange
3203
+ "between-children"
3212
3204
  );
3213
3205
  }
3214
3206
  function calculateCoreSubtreeRadialExtent(hierarchy, depthSpacing, coreRootCircleRadius = 0) {
@@ -3248,7 +3240,7 @@ function splitRootGroupBySide(group) {
3248
3240
  }
3249
3241
 
3250
3242
  // src/GenreTreeWheelRadialBase.tsx
3251
- import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
3243
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
3252
3244
  var LANDING_ANGLE = 90;
3253
3245
  function computeContinuousAngles(groups, layout, previous) {
3254
3246
  const angles = /* @__PURE__ */ new Map();
@@ -3283,10 +3275,6 @@ function WheelRadialCore({
3283
3275
  hideInfoPanelTitle = false,
3284
3276
  itemCountLabel,
3285
3277
  readOnly,
3286
- editMode,
3287
- onEditModeChange,
3288
- renderTitleEditor,
3289
- renderParentsEditor,
3290
3278
  renderDangerZone,
3291
3279
  expanded,
3292
3280
  onExpandedChange,
@@ -3297,6 +3285,8 @@ function WheelRadialCore({
3297
3285
  defaultViewport,
3298
3286
  onViewportChange,
3299
3287
  controlsOrientation = "vertical",
3288
+ floatingControlsLeading,
3289
+ busy,
3300
3290
  selectedNodeId
3301
3291
  }) {
3302
3292
  const index = useTreeIndex(nodes);
@@ -3455,8 +3445,7 @@ function WheelRadialCore({
3455
3445
  angle,
3456
3446
  span,
3457
3447
  wheelRadius,
3458
- POP_TREE_DEPTH_RADIAL_SPACING,
3459
- wheelItemCountRange
3448
+ POP_TREE_DEPTH_RADIAL_SPACING
3460
3449
  );
3461
3450
  const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
3462
3451
  const branchGroup = sectorGroup.append("g").attr("class", "gtv-wheel-core-branch").attr("data-gtv-branch-index", branchIndex);
@@ -3572,10 +3561,11 @@ function WheelRadialCore({
3572
3561
  };
3573
3562
  });
3574
3563
  }, [groups, continuousAngleByRootId, sectorSpanByRootId]);
3575
- return /* @__PURE__ */ jsxs8(
3564
+ return /* @__PURE__ */ jsxs10(
3576
3565
  "div",
3577
3566
  {
3578
3567
  ref: viewportRef,
3568
+ "aria-busy": busy,
3579
3569
  className: [
3580
3570
  "gtv-wheel-container",
3581
3571
  "gtv-wheel-container--radial",
@@ -3589,7 +3579,7 @@ function WheelRadialCore({
3589
3579
  },
3590
3580
  onPointerDown: panZoom.handlePointerDown,
3591
3581
  children: [
3592
- /* @__PURE__ */ jsx11(
3582
+ /* @__PURE__ */ jsx13(
3593
3583
  "div",
3594
3584
  {
3595
3585
  ref: stageRef,
@@ -3602,9 +3592,9 @@ function WheelRadialCore({
3602
3592
  transform: panZoom.transform,
3603
3593
  transformOrigin: "0 0"
3604
3594
  },
3605
- children: /* @__PURE__ */ jsxs8("div", { className: "gtv-wheel-stage", children: [
3606
- /* @__PURE__ */ jsx11("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
3607
- /* @__PURE__ */ jsx11("div", { className: "gtv-wheel", children: sectorFills.map((sector) => /* @__PURE__ */ jsx11(
3595
+ children: /* @__PURE__ */ jsxs10("div", { className: "gtv-wheel-stage", children: [
3596
+ /* @__PURE__ */ jsx13("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
3597
+ /* @__PURE__ */ jsx13("div", { className: "gtv-wheel", children: sectorFills.map((sector) => /* @__PURE__ */ jsx13(
3608
3598
  "div",
3609
3599
  {
3610
3600
  className: "gtv-wheel-sector",
@@ -3616,7 +3606,7 @@ function WheelRadialCore({
3616
3606
  },
3617
3607
  `sector-${sector.rootId}`
3618
3608
  )) }),
3619
- /* @__PURE__ */ jsx11(
3609
+ /* @__PURE__ */ jsx13(
3620
3610
  "svg",
3621
3611
  {
3622
3612
  ref: coreSvgRef,
@@ -3625,9 +3615,9 @@ function WheelRadialCore({
3625
3615
  height: wheelRadius * 2
3626
3616
  }
3627
3617
  ),
3628
- centerLabel && /* @__PURE__ */ jsx11("div", { className: "gtv-wheel-center-label", children: centerLabel }),
3629
- /* @__PURE__ */ jsxs8("div", { className: "gtv-wheel", children: [
3630
- dividerAngles.map((angle, index2) => /* @__PURE__ */ jsx11(
3618
+ centerLabel && /* @__PURE__ */ jsx13("div", { className: "gtv-wheel-center-label", children: centerLabel }),
3619
+ /* @__PURE__ */ jsxs10("div", { className: "gtv-wheel", children: [
3620
+ dividerAngles.map((angle, index2) => /* @__PURE__ */ jsx13(
3631
3621
  "div",
3632
3622
  {
3633
3623
  className: "gtv-wheel-divider",
@@ -3650,12 +3640,12 @@ function WheelRadialCore({
3650
3640
  rootItemCount,
3651
3641
  rootItemCountRange
3652
3642
  );
3653
- return /* @__PURE__ */ jsx11(
3643
+ return /* @__PURE__ */ jsx13(
3654
3644
  "div",
3655
3645
  {
3656
3646
  className: "gtv-wheel-slot",
3657
3647
  style: { "--gtv-chip-angle": `${angle}deg` },
3658
- children: /* @__PURE__ */ jsx11("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ jsxs8(
3648
+ children: /* @__PURE__ */ jsx13("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ jsxs10(
3659
3649
  "button",
3660
3650
  {
3661
3651
  type: "button",
@@ -3688,8 +3678,8 @@ function WheelRadialCore({
3688
3678
  }
3689
3679
  },
3690
3680
  children: [
3691
- PER_TREE_ACCENT_DOT && /* @__PURE__ */ jsx11("span", { className: "gtv-wheel-chip-dot" }),
3692
- /* @__PURE__ */ jsx11(
3681
+ PER_TREE_ACCENT_DOT && /* @__PURE__ */ jsx13("span", { className: "gtv-wheel-chip-dot" }),
3682
+ /* @__PURE__ */ jsx13(
3693
3683
  "span",
3694
3684
  {
3695
3685
  className: "gtv-node-label gtv-node-label--root",
@@ -3708,7 +3698,7 @@ function WheelRadialCore({
3708
3698
  ] })
3709
3699
  }
3710
3700
  ),
3711
- panel && /* @__PURE__ */ jsx11(
3701
+ panel && /* @__PURE__ */ jsx13(
3712
3702
  InfoPanel,
3713
3703
  {
3714
3704
  node: panel.node,
@@ -3754,10 +3744,6 @@ function WheelRadialCore({
3754
3744
  hideTitle: hideInfoPanelTitle,
3755
3745
  itemCountLabel,
3756
3746
  readOnly,
3757
- editMode,
3758
- onEditModeChange,
3759
- renderTitleEditor,
3760
- renderParentsEditor,
3761
3747
  renderDangerZone,
3762
3748
  expanded,
3763
3749
  onExpandedChange,
@@ -3776,54 +3762,15 @@ function WheelRadialCore({
3776
3762
  } : void 0
3777
3763
  }
3778
3764
  ),
3779
- overlayPanel && /* @__PURE__ */ jsx11(OverlayPanel, { ...overlayPanel }),
3780
- /* @__PURE__ */ jsx11(
3781
- "div",
3765
+ overlayPanel && /* @__PURE__ */ jsx13(OverlayPanel, { ...overlayPanel }),
3766
+ /* @__PURE__ */ jsx13(
3767
+ FloatingControls,
3782
3768
  {
3783
- className: [
3784
- "gtv-wheel-floating-controls",
3785
- controlsOrientation === "horizontal" && "gtv-wheel-floating-controls--horizontal"
3786
- ].filter(Boolean).join(" "),
3787
- children: /* @__PURE__ */ jsxs8("div", { className: "gtv-zoom-controls", children: [
3788
- /* @__PURE__ */ jsx11(
3789
- "button",
3790
- {
3791
- type: "button",
3792
- className: [
3793
- "gtv-zoom-btn",
3794
- !panZoom.canZoomIn && "gtv-zoom-btn--disabled"
3795
- ].filter(Boolean).join(" "),
3796
- disabled: !panZoom.canZoomIn,
3797
- onClick: panZoom.zoomIn,
3798
- "aria-label": "Zoom in",
3799
- children: /* @__PURE__ */ jsx11(MdZoomIn4, { className: "gtv-icon", size: 18 })
3800
- }
3801
- ),
3802
- /* @__PURE__ */ jsx11(
3803
- "button",
3804
- {
3805
- type: "button",
3806
- className: [
3807
- "gtv-zoom-btn",
3808
- !panZoom.canZoomOut && "gtv-zoom-btn--disabled"
3809
- ].filter(Boolean).join(" "),
3810
- disabled: !panZoom.canZoomOut,
3811
- onClick: panZoom.zoomOut,
3812
- "aria-label": "Zoom out",
3813
- children: /* @__PURE__ */ jsx11(MdZoomOut3, { className: "gtv-icon", size: 18 })
3814
- }
3815
- ),
3816
- /* @__PURE__ */ jsx11(
3817
- "button",
3818
- {
3819
- type: "button",
3820
- className: "gtv-zoom-btn",
3821
- onClick: () => panZoom.fitToFrame([wheelCircleRef.current, coreSvgRef.current]),
3822
- "aria-label": "Fit to frame",
3823
- children: /* @__PURE__ */ jsx11(MdFitScreen3, { className: "gtv-icon", size: 18 })
3824
- }
3825
- )
3826
- ] })
3769
+ panZoom,
3770
+ orientation: controlsOrientation,
3771
+ leading: floatingControlsLeading,
3772
+ busy,
3773
+ onFit: () => panZoom.fitToFrame([wheelCircleRef.current, coreSvgRef.current])
3827
3774
  }
3828
3775
  )
3829
3776
  ]
@@ -3832,14 +3779,13 @@ function WheelRadialCore({
3832
3779
  }
3833
3780
 
3834
3781
  // src/GenreTreeWheelRadial.tsx
3835
- import { jsx as jsx12 } from "react/jsx-runtime";
3782
+ import { jsx as jsx14 } from "react/jsx-runtime";
3836
3783
  function GenreTreeWheelRadial(props) {
3837
- return /* @__PURE__ */ jsx12(WheelRadialCore, { ...props });
3784
+ return /* @__PURE__ */ jsx14(WheelRadialCore, { ...props });
3838
3785
  }
3839
3786
 
3840
3787
  // src/GenreTreeWheelRadialPopCoreBase.tsx
3841
3788
  import { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo5, useRef as useRef8, useState as useState9 } from "react";
3842
- import { MdFitScreen as MdFitScreen4, MdZoomIn as MdZoomIn5, MdZoomOut as MdZoomOut4 } from "react-icons/md";
3843
3789
  import * as d34 from "d3";
3844
3790
 
3845
3791
  // src/use-focus-history.ts
@@ -3869,16 +3815,16 @@ function useFocusHistory(focusNodeId, isFocusable) {
3869
3815
  // src/WheelBreadcrumb.tsx
3870
3816
  import { Fragment as Fragment3, useState as useState8 } from "react";
3871
3817
  import { MdHome } from "react-icons/md";
3872
- import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
3818
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
3873
3819
  var MAX_VISIBLE_ANCESTORS = 3;
3874
3820
  var ANCESTORS_KEPT_WHEN_COLLAPSED = 2;
3875
3821
  function WheelBreadcrumb({ ancestors, currentName, onHome, onAncestor }) {
3876
3822
  const [expanded, setExpanded] = useState8(false);
3877
3823
  const collapsed = !expanded && ancestors.length > MAX_VISIBLE_ANCESTORS;
3878
3824
  const visibleAncestors = collapsed ? ancestors.slice(-ANCESTORS_KEPT_WHEN_COLLAPSED) : ancestors;
3879
- const separator = /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", children: "\u203A" });
3880
- return /* @__PURE__ */ jsxs9("nav", { className: "gtv-wheel-breadcrumb", "aria-label": "Breadcrumb", children: [
3881
- /* @__PURE__ */ jsx13(
3825
+ const separator = /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u203A" });
3826
+ return /* @__PURE__ */ jsxs11("nav", { className: "gtv-wheel-breadcrumb", "aria-label": "Breadcrumb", children: [
3827
+ /* @__PURE__ */ jsx15(
3882
3828
  "button",
3883
3829
  {
3884
3830
  type: "button",
@@ -3886,12 +3832,12 @@ function WheelBreadcrumb({ ancestors, currentName, onHome, onAncestor }) {
3886
3832
  "aria-label": "All genres",
3887
3833
  title: "All genres",
3888
3834
  onClick: onHome,
3889
- children: /* @__PURE__ */ jsx13(MdHome, { className: "gtv-icon", size: 14 })
3835
+ children: /* @__PURE__ */ jsx15(MdHome, { className: "gtv-icon", size: 14 })
3890
3836
  }
3891
3837
  ),
3892
3838
  separator,
3893
- collapsed && /* @__PURE__ */ jsxs9(Fragment4, { children: [
3894
- /* @__PURE__ */ jsx13(
3839
+ collapsed && /* @__PURE__ */ jsxs11(Fragment4, { children: [
3840
+ /* @__PURE__ */ jsx15(
3895
3841
  "button",
3896
3842
  {
3897
3843
  type: "button",
@@ -3904,11 +3850,11 @@ function WheelBreadcrumb({ ancestors, currentName, onHome, onAncestor }) {
3904
3850
  ),
3905
3851
  separator
3906
3852
  ] }),
3907
- visibleAncestors.map((ancestor) => /* @__PURE__ */ jsxs9(Fragment3, { children: [
3908
- /* @__PURE__ */ jsx13("button", { type: "button", className: "gtv-wheel-breadcrumb-crumb", onClick: () => onAncestor(ancestor.id), children: ancestor.name }),
3853
+ visibleAncestors.map((ancestor) => /* @__PURE__ */ jsxs11(Fragment3, { children: [
3854
+ /* @__PURE__ */ jsx15("button", { type: "button", className: "gtv-wheel-breadcrumb-crumb", onClick: () => onAncestor(ancestor.id), children: ancestor.name }),
3909
3855
  separator
3910
3856
  ] }, ancestor.id)),
3911
- /* @__PURE__ */ jsx13("span", { className: "gtv-wheel-breadcrumb-current", "aria-current": "page", children: currentName })
3857
+ /* @__PURE__ */ jsx15("span", { className: "gtv-wheel-breadcrumb-current", "aria-current": "page", children: currentName })
3912
3858
  ] });
3913
3859
  }
3914
3860
 
@@ -3969,7 +3915,7 @@ function resolvePopCoreFrame(nodes, focusNodeId) {
3969
3915
  }
3970
3916
 
3971
3917
  // src/GenreTreeWheelRadialPopCoreBase.tsx
3972
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
3918
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3973
3919
  var LANDING_ANGLE2 = 90;
3974
3920
  var CHIP_GAP = 16;
3975
3921
  var sum = (values) => values.reduce((total, value) => total + value, 0);
@@ -4006,10 +3952,6 @@ function WheelRadialPopCoreCore({
4006
3952
  hideInfoPanelTitle = false,
4007
3953
  itemCountLabel,
4008
3954
  readOnly,
4009
- editMode,
4010
- onEditModeChange,
4011
- renderTitleEditor,
4012
- renderParentsEditor,
4013
3955
  renderDangerZone,
4014
3956
  expanded,
4015
3957
  onExpandedChange,
@@ -4020,6 +3962,8 @@ function WheelRadialPopCoreCore({
4020
3962
  defaultViewport,
4021
3963
  onViewportChange,
4022
3964
  controlsOrientation = "vertical",
3965
+ floatingControlsLeading,
3966
+ busy,
4023
3967
  allowWheelRotation = true,
4024
3968
  selectedNodeId,
4025
3969
  focusNodeId: requestedFocusNodeId = null,
@@ -4092,6 +4036,7 @@ function WheelRadialPopCoreCore({
4092
4036
  }
4093
4037
  const viewportRef = useRef8(null);
4094
4038
  const stageRef = useRef8(null);
4039
+ const dotRadiusMemoryRef = useRef8(/* @__PURE__ */ new Map());
4095
4040
  const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, onViewportChange });
4096
4041
  const { panel: openPanel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId, onSelectedNodeChange });
4097
4042
  const isInFrame = (id) => id === focusNodeId || centerSubtreeNodeIds.has(id) || ringRootById.has(id);
@@ -4198,8 +4143,8 @@ function WheelRadialPopCoreCore({
4198
4143
  centerSubtreeHierarchy,
4199
4144
  0,
4200
4145
  POP_TREE_DEPTH_RADIAL_SPACING
4201
- ) : centerChipDiameter / 2,
4202
- [centerSubtreeHierarchy, centerChipDiameter]
4146
+ ) : NODE_LAYOUT_SLOT_RADIUS,
4147
+ [centerSubtreeHierarchy]
4203
4148
  );
4204
4149
  const maxCoreExtentDelta = useMemo5(() => {
4205
4150
  let extent = 0;
@@ -4207,11 +4152,7 @@ function WheelRadialPopCoreCore({
4207
4152
  core.forEach((hierarchy) => {
4208
4153
  extent = Math.max(
4209
4154
  extent,
4210
- calculateCoreSubtreeRadialExtent(
4211
- hierarchy,
4212
- POP_TREE_DEPTH_RADIAL_SPACING,
4213
- 0
4214
- )
4155
+ calculateCoreSubtreeRadialExtent(hierarchy, POP_TREE_DEPTH_RADIAL_SPACING, 0)
4215
4156
  );
4216
4157
  });
4217
4158
  });
@@ -4225,7 +4166,7 @@ function WheelRadialPopCoreCore({
4225
4166
  (radius) => new Map(
4226
4167
  groups.map((group) => {
4227
4168
  const { pop, core } = branchHierarchiesByRootId.get(group.root.id);
4228
- const chipWidth = 2 * calculateNodeDotRadius(rootItemCountById.get(group.root.id), wheelItemCountRange);
4169
+ const chipWidth = 2 * NODE_LAYOUT_SLOT_RADIUS;
4229
4170
  return [
4230
4171
  group.root.id,
4231
4172
  {
@@ -4233,16 +4174,14 @@ function WheelRadialPopCoreCore({
4233
4174
  (hierarchy) => measureWedge(
4234
4175
  d34,
4235
4176
  hierarchy,
4236
- popRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING),
4237
- wheelItemCountRange
4177
+ popRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING)
4238
4178
  )
4239
4179
  ),
4240
4180
  core: core.map(
4241
4181
  (hierarchy) => measureWedge(
4242
4182
  d34,
4243
4183
  hierarchy,
4244
- coreRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING),
4245
- wheelItemCountRange
4184
+ coreRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING)
4246
4185
  )
4247
4186
  ),
4248
4187
  chip: 2 * Math.asin(Math.min(1, (chipWidth / 2 + CHIP_GAP) / radius))
@@ -4250,7 +4189,7 @@ function WheelRadialPopCoreCore({
4250
4189
  ];
4251
4190
  })
4252
4191
  ),
4253
- [groups, branchHierarchiesByRootId, rootItemCountById, wheelItemCountRange]
4192
+ [groups, branchHierarchiesByRootId]
4254
4193
  );
4255
4194
  const coreRootCircleRadius = useMemo5(
4256
4195
  () => solveRingRadius(
@@ -4368,8 +4307,7 @@ function WheelRadialPopCoreCore({
4368
4307
  angle,
4369
4308
  coreRootCircleRadius,
4370
4309
  span,
4371
- POP_TREE_DEPTH_RADIAL_SPACING,
4372
- wheelItemCountRange
4310
+ POP_TREE_DEPTH_RADIAL_SPACING
4373
4311
  );
4374
4312
  const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
4375
4313
  const branchGroup = sectorGroup.append("g").attr("class", "gtv-wheel-pop-branch").attr("data-gtv-branch-index", branchIndex);
@@ -4413,6 +4351,7 @@ function WheelRadialPopCoreCore({
4413
4351
  {
4414
4352
  rootLinkOrigin,
4415
4353
  selectedNodeId: panel?.node.id ?? null,
4354
+ dotRadiusMemory: dotRadiusMemoryRef.current,
4416
4355
  nodeFillOf: (node) => getGenreNodeStyle(index, node.id).fill
4417
4356
  }
4418
4357
  );
@@ -4425,16 +4364,42 @@ function WheelRadialPopCoreCore({
4425
4364
  );
4426
4365
  const sectorGroup = originGroup.append("g").attr("class", "gtv-wheel-core-sector").attr("data-gtv-root-id", rootId);
4427
4366
  branches.forEach(({ hierarchy, angle, span }, branchIndex) => {
4367
+ const undergroundIds = new Set(
4368
+ hierarchy.descendants().filter((d) => d.ancestors().some((a) => a.data.scene === "underground")).map((d) => d.data.id)
4369
+ );
4428
4370
  const laidOut = computeCoreRadialLayout(
4429
4371
  d34,
4430
4372
  hierarchy,
4431
4373
  angle,
4432
4374
  span,
4433
4375
  coreRootCircleRadius,
4434
- POP_TREE_DEPTH_RADIAL_SPACING,
4435
- wheelItemCountRange
4376
+ POP_TREE_DEPTH_RADIAL_SPACING
4436
4377
  );
4437
4378
  const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
4379
+ const angleOf = (d) => {
4380
+ const deg = Math.atan2(d.x, -d.y) * 180 / Math.PI;
4381
+ return angle + ((deg - angle + 540) % 360 + 360) % 360 - 180;
4382
+ };
4383
+ const subtreeRange = (d) => {
4384
+ const angles = d.descendants().map(angleOf);
4385
+ return { min: Math.min(...angles), max: Math.max(...angles) };
4386
+ };
4387
+ laidOut.descendants().filter((d) => d.data.scene === "underground" && !d.ancestors().slice(1).some((a) => a.data.scene === "underground")).forEach((ug) => {
4388
+ const mine = subtreeRange(ug);
4389
+ const siblings = ug.parent?.children ?? [];
4390
+ const at = siblings.indexOf(ug);
4391
+ const prev = at > 0 ? siblings[at - 1] : void 0;
4392
+ const next = at >= 0 ? siblings[at + 1] : void 0;
4393
+ const startDeg = prev ? (subtreeRange(prev).max + mine.min) / 2 : angle - span / 2;
4394
+ const endDeg = next ? (mine.max + subtreeRange(next).min) / 2 : angle + span / 2;
4395
+ const arcPath = d34.arc()({
4396
+ innerRadius: coreRootCircleRadius,
4397
+ outerRadius: SCENE_SECTOR_OUTER_RADIUS,
4398
+ startAngle: startDeg * Math.PI / 180,
4399
+ endAngle: endDeg * Math.PI / 180
4400
+ });
4401
+ sectorGroup.append("path").attr("class", "gtv-wheel-scene-sector").attr("d", arcPath).attr("fill", shadeSurface(getGenreNodeColor(index, rootId), UNDERGROUND_DARKEN_RATIO)).attr("fill-opacity", UNDERGROUND_SECTOR_FILL_OPACITY);
4402
+ });
4438
4403
  const branchGroup = sectorGroup.append("g").attr("class", "gtv-wheel-core-branch").attr("data-gtv-branch-index", branchIndex);
4439
4404
  renderPopSubtree(
4440
4405
  d34,
@@ -4477,7 +4442,11 @@ function WheelRadialPopCoreCore({
4477
4442
  isCoreSector: true,
4478
4443
  rootLinkOrigin,
4479
4444
  selectedNodeId: panel?.node.id ?? null,
4480
- nodeFillOf: (node) => getGenreNodeStyle(index, node.id).fill
4445
+ dotRadiusMemory: dotRadiusMemoryRef.current,
4446
+ nodeFillOf: (node) => {
4447
+ const fill = getGenreNodeStyle(index, node.id).fill;
4448
+ return undergroundIds.has(node.id) ? shadeSurface(fill, UNDERGROUND_DARKEN_RATIO) : fill;
4449
+ }
4481
4450
  }
4482
4451
  );
4483
4452
  });
@@ -4532,6 +4501,7 @@ function WheelRadialPopCoreCore({
4532
4501
  skipRootNode: true,
4533
4502
  isMainstreamSector: isCenterMainstream,
4534
4503
  selectedNodeId: panel?.node.id ?? null,
4504
+ dotRadiusMemory: dotRadiusMemoryRef.current,
4535
4505
  nodeFillOf: (node) => getGenreNodeStyle(index, node.id).fill
4536
4506
  }
4537
4507
  );
@@ -4727,10 +4697,11 @@ function WheelRadialPopCoreCore({
4727
4697
  };
4728
4698
  });
4729
4699
  }, [groups, continuousAngleByRootId, sectorSpanByRootId]);
4730
- return /* @__PURE__ */ jsxs10(
4700
+ return /* @__PURE__ */ jsxs12(
4731
4701
  "div",
4732
4702
  {
4733
4703
  ref: viewportRef,
4704
+ "aria-busy": busy,
4734
4705
  className: [
4735
4706
  "gtv-wheel-container",
4736
4707
  "gtv-wheel-container--radial",
@@ -4745,7 +4716,7 @@ function WheelRadialPopCoreCore({
4745
4716
  },
4746
4717
  onPointerDown: panZoom.handlePointerDown,
4747
4718
  children: [
4748
- /* @__PURE__ */ jsxs10(
4719
+ /* @__PURE__ */ jsxs12(
4749
4720
  "div",
4750
4721
  {
4751
4722
  ref: stageRef,
@@ -4759,9 +4730,9 @@ function WheelRadialPopCoreCore({
4759
4730
  transformOrigin: "0 0"
4760
4731
  },
4761
4732
  children: [
4762
- /* @__PURE__ */ jsxs10("div", { className: "gtv-wheel-stage", children: [
4763
- /* @__PURE__ */ jsx14("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
4764
- isPopExpanded && /* @__PURE__ */ jsx14(
4733
+ /* @__PURE__ */ jsxs12("div", { className: "gtv-wheel-stage", children: [
4734
+ /* @__PURE__ */ jsx16("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
4735
+ isPopExpanded && /* @__PURE__ */ jsx16(
4765
4736
  "div",
4766
4737
  {
4767
4738
  className: "gtv-wheel-middle-circle",
@@ -4770,8 +4741,8 @@ function WheelRadialPopCoreCore({
4770
4741
  }
4771
4742
  }
4772
4743
  ),
4773
- /* @__PURE__ */ jsxs10("div", { className: "gtv-wheel", children: [
4774
- sectorFills.map((sector) => /* @__PURE__ */ jsx14(
4744
+ /* @__PURE__ */ jsxs12("div", { className: "gtv-wheel", children: [
4745
+ sectorFills.map((sector) => /* @__PURE__ */ jsx16(
4775
4746
  "div",
4776
4747
  {
4777
4748
  className: "gtv-wheel-sector",
@@ -4783,8 +4754,8 @@ function WheelRadialPopCoreCore({
4783
4754
  },
4784
4755
  `sector-${sector.rootId}`
4785
4756
  )),
4786
- /* @__PURE__ */ jsx14("div", { className: "gtv-wheel-inner-tint" }),
4787
- isPopExpanded && /* @__PURE__ */ jsx14(
4757
+ /* @__PURE__ */ jsx16("div", { className: "gtv-wheel-inner-tint" }),
4758
+ isPopExpanded && /* @__PURE__ */ jsx16(
4788
4759
  "div",
4789
4760
  {
4790
4761
  className: "gtv-wheel-middle-tint",
@@ -4794,7 +4765,7 @@ function WheelRadialPopCoreCore({
4794
4765
  }
4795
4766
  )
4796
4767
  ] }),
4797
- /* @__PURE__ */ jsx14(
4768
+ /* @__PURE__ */ jsx16(
4798
4769
  "svg",
4799
4770
  {
4800
4771
  ref: popSvgRef,
@@ -4803,7 +4774,7 @@ function WheelRadialPopCoreCore({
4803
4774
  height: svgCanvasRadius * 2
4804
4775
  }
4805
4776
  ),
4806
- /* @__PURE__ */ jsx14("div", { className: "gtv-wheel-center-node", children: /* @__PURE__ */ jsx14("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ jsx14(
4777
+ /* @__PURE__ */ jsx16("div", { className: "gtv-wheel-center-node", children: /* @__PURE__ */ jsx16("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ jsx16(
4807
4778
  "button",
4808
4779
  {
4809
4780
  ref: centerChipRef,
@@ -4841,8 +4812,8 @@ function WheelRadialPopCoreCore({
4841
4812
  "aria-label": centerNode.name
4842
4813
  }
4843
4814
  ) }) }),
4844
- /* @__PURE__ */ jsxs10("div", { className: "gtv-wheel", children: [
4845
- dividerAngles.map((angle, index2) => /* @__PURE__ */ jsx14(
4815
+ /* @__PURE__ */ jsxs12("div", { className: "gtv-wheel", children: [
4816
+ dividerAngles.map((angle, index2) => /* @__PURE__ */ jsx16(
4846
4817
  "div",
4847
4818
  {
4848
4819
  className: "gtv-wheel-divider",
@@ -4861,12 +4832,12 @@ function WheelRadialPopCoreCore({
4861
4832
  group.root.id
4862
4833
  );
4863
4834
  const diameter = 2 * calculateNodeDotRadius(rootItemCount, wheelItemCountRange);
4864
- return /* @__PURE__ */ jsx14(
4835
+ return /* @__PURE__ */ jsx16(
4865
4836
  "div",
4866
4837
  {
4867
4838
  className: "gtv-wheel-slot",
4868
4839
  style: { "--gtv-chip-angle": `${angle}deg` },
4869
- children: /* @__PURE__ */ jsx14("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ jsx14(
4840
+ children: /* @__PURE__ */ jsx16("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ jsx16(
4870
4841
  "button",
4871
4842
  {
4872
4843
  type: "button",
@@ -4908,7 +4879,7 @@ function WheelRadialPopCoreCore({
4908
4879
  })
4909
4880
  ] })
4910
4881
  ] }),
4911
- regionLabel && index.byId.has(regionLabel.rootId) && /* @__PURE__ */ jsx14(
4882
+ regionLabel && index.byId.has(regionLabel.rootId) && /* @__PURE__ */ jsx16(
4912
4883
  "div",
4913
4884
  {
4914
4885
  ref: regionLabelRef,
@@ -4926,7 +4897,7 @@ function WheelRadialPopCoreCore({
4926
4897
  ]
4927
4898
  }
4928
4899
  ),
4929
- panel && /* @__PURE__ */ jsx14(
4900
+ panel && /* @__PURE__ */ jsx16(
4930
4901
  InfoPanel,
4931
4902
  {
4932
4903
  node: panel.node,
@@ -4953,10 +4924,6 @@ function WheelRadialPopCoreCore({
4953
4924
  hideTitle: hideInfoPanelTitle,
4954
4925
  itemCountLabel,
4955
4926
  readOnly,
4956
- editMode,
4957
- onEditModeChange,
4958
- renderTitleEditor,
4959
- renderParentsEditor,
4960
4927
  renderDangerZone,
4961
4928
  expanded,
4962
4929
  onExpandedChange,
@@ -4976,8 +4943,8 @@ function WheelRadialPopCoreCore({
4976
4943
  onZoomIn: onFocusChange && panel.node.id !== focusNodeId && isZoomableNode(nodes, panel.node.id) ? () => onFocusChange(panel.node.id, "zoom-in") : void 0
4977
4944
  }
4978
4945
  ),
4979
- overlayPanel && /* @__PURE__ */ jsx14(OverlayPanel, { ...overlayPanel }),
4980
- onFocusChange && focusNodeId !== null && /* @__PURE__ */ jsx14(
4946
+ overlayPanel && /* @__PURE__ */ jsx16(OverlayPanel, { ...overlayPanel }),
4947
+ onFocusChange && focusNodeId !== null && /* @__PURE__ */ jsx16(
4981
4948
  WheelBreadcrumb,
4982
4949
  {
4983
4950
  ancestors: index.ancestorsOf(focusNodeId).filter((ancestor) => isZoomableNode(nodes, ancestor.id)),
@@ -4993,56 +4960,17 @@ function WheelRadialPopCoreCore({
4993
4960
  },
4994
4961
  focusNodeId
4995
4962
  ),
4996
- /* @__PURE__ */ jsx14(
4997
- "div",
4963
+ /* @__PURE__ */ jsx16(
4964
+ FloatingControls,
4998
4965
  {
4999
- className: [
5000
- "gtv-wheel-floating-controls",
5001
- controlsOrientation === "horizontal" && "gtv-wheel-floating-controls--horizontal"
5002
- ].filter(Boolean).join(" "),
5003
- children: /* @__PURE__ */ jsxs10("div", { className: "gtv-zoom-controls", children: [
5004
- /* @__PURE__ */ jsx14(
5005
- "button",
5006
- {
5007
- type: "button",
5008
- className: [
5009
- "gtv-zoom-btn",
5010
- !panZoom.canZoomIn && "gtv-zoom-btn--disabled"
5011
- ].filter(Boolean).join(" "),
5012
- disabled: !panZoom.canZoomIn,
5013
- onClick: panZoom.zoomIn,
5014
- "aria-label": "Zoom in",
5015
- children: /* @__PURE__ */ jsx14(MdZoomIn5, { className: "gtv-icon", size: 18 })
5016
- }
5017
- ),
5018
- /* @__PURE__ */ jsx14(
5019
- "button",
5020
- {
5021
- type: "button",
5022
- className: [
5023
- "gtv-zoom-btn",
5024
- !panZoom.canZoomOut && "gtv-zoom-btn--disabled"
5025
- ].filter(Boolean).join(" "),
5026
- disabled: !panZoom.canZoomOut,
5027
- onClick: panZoom.zoomOut,
5028
- "aria-label": "Zoom out",
5029
- children: /* @__PURE__ */ jsx14(MdZoomOut4, { className: "gtv-icon", size: 18 })
5030
- }
5031
- ),
5032
- /* @__PURE__ */ jsx14(
5033
- "button",
5034
- {
5035
- type: "button",
5036
- className: "gtv-zoom-btn",
5037
- onClick: () => panZoom.fitToFrame([
5038
- wheelCircleRef.current,
5039
- ...queryTreeContentElements(popSvgRef.current)
5040
- ]),
5041
- "aria-label": "Fit to frame",
5042
- children: /* @__PURE__ */ jsx14(MdFitScreen4, { className: "gtv-icon", size: 18 })
5043
- }
5044
- )
5045
- ] })
4966
+ panZoom,
4967
+ orientation: controlsOrientation,
4968
+ leading: floatingControlsLeading,
4969
+ busy,
4970
+ onFit: () => panZoom.fitToFrame([
4971
+ wheelCircleRef.current,
4972
+ ...queryTreeContentElements(popSvgRef.current)
4973
+ ])
5046
4974
  }
5047
4975
  )
5048
4976
  ]
@@ -5051,9 +4979,9 @@ function WheelRadialPopCoreCore({
5051
4979
  }
5052
4980
 
5053
4981
  // src/GenreTreeWheelRadialPopCore.tsx
5054
- import { jsx as jsx15 } from "react/jsx-runtime";
4982
+ import { jsx as jsx17 } from "react/jsx-runtime";
5055
4983
  function GenreTreeWheelRadialPopCore(props) {
5056
- return /* @__PURE__ */ jsx15(WheelRadialPopCoreCore, { ...props });
4984
+ return /* @__PURE__ */ jsx17(WheelRadialPopCoreCore, { ...props });
5057
4985
  }
5058
4986
 
5059
4987
  // src/GenreTreeOutline.tsx
@@ -5066,7 +4994,7 @@ import {
5066
4994
  useState as useState10,
5067
4995
  useSyncExternalStore
5068
4996
  } from "react";
5069
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
4997
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
5070
4998
  function createOutlineStore(initial) {
5071
4999
  let state = initial;
5072
5000
  const listeners = /* @__PURE__ */ new Set();
@@ -5140,9 +5068,9 @@ function OutlineNodeView({
5140
5068
  const { itemCount } = node;
5141
5069
  const children = model.index.childrenByParentId.get(nodeId) ?? [];
5142
5070
  const split = isRoot ? model.ringSplitById.get(nodeId) : void 0;
5143
- const row = /* @__PURE__ */ jsxs11("span", { className: ["gtv-outline-row", isSelected && "gtv-outline-row--selected"].filter(Boolean).join(" "), children: [
5144
- /* @__PURE__ */ jsx16("span", { className: "gtv-outline-dot", style: { background: model.styleOf(node).fill } }),
5145
- /* @__PURE__ */ jsx16(
5071
+ const row = /* @__PURE__ */ jsxs13("span", { className: ["gtv-outline-row", isSelected && "gtv-outline-row--selected"].filter(Boolean).join(" "), children: [
5072
+ /* @__PURE__ */ jsx18("span", { className: "gtv-outline-dot", style: { background: model.styleOf(node).fill } }),
5073
+ /* @__PURE__ */ jsx18(
5146
5074
  "button",
5147
5075
  {
5148
5076
  type: "button",
@@ -5159,16 +5087,16 @@ function OutlineNodeView({
5159
5087
  children: node.name
5160
5088
  }
5161
5089
  ),
5162
- /* @__PURE__ */ jsx16("span", { className: "gtv-outline-count", children: itemCount })
5090
+ /* @__PURE__ */ jsx18("span", { className: "gtv-outline-count", children: itemCount })
5163
5091
  ] });
5164
5092
  const itemProps = { model, store, callbacks };
5165
- return /* @__PURE__ */ jsx16("li", { className: "gtv-outline-item", "data-gtv-node-id": nodeId, children: children.length > 0 ? /* @__PURE__ */ jsxs11("details", { open: isOpen, children: [
5166
- /* @__PURE__ */ jsx16("summary", { onClick: (event) => handleSummaryClick(event, () => callbacks.toggleOpen(nodeId)), children: row }),
5167
- isOpen && /* @__PURE__ */ jsx16("ul", { className: "gtv-outline-list", children: split ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
5168
- /* @__PURE__ */ jsx16(OutlineSection, { rootId: nodeId, kind: "core", branches: split.coreBranches, ...itemProps }),
5169
- /* @__PURE__ */ jsx16(OutlineSection, { rootId: nodeId, kind: "pop", branches: split.popBranches, ...itemProps })
5170
- ] }) : children.map((child) => /* @__PURE__ */ jsx16(OutlineNode, { nodeId: child.id, isRoot: false, ...itemProps }, child.id)) })
5171
- ] }) : /* @__PURE__ */ jsx16("div", { className: "gtv-outline-leaf", children: row }) });
5093
+ return /* @__PURE__ */ jsx18("li", { className: "gtv-outline-item", "data-gtv-node-id": nodeId, children: children.length > 0 ? /* @__PURE__ */ jsxs13("details", { open: isOpen, children: [
5094
+ /* @__PURE__ */ jsx18("summary", { onClick: (event) => handleSummaryClick(event, () => callbacks.toggleOpen(nodeId)), children: row }),
5095
+ isOpen && /* @__PURE__ */ jsx18("ul", { className: "gtv-outline-list", children: split ? /* @__PURE__ */ jsxs13(Fragment5, { children: [
5096
+ /* @__PURE__ */ jsx18(OutlineSection, { rootId: nodeId, kind: "core", branches: split.coreBranches, ...itemProps }),
5097
+ /* @__PURE__ */ jsx18(OutlineSection, { rootId: nodeId, kind: "pop", branches: split.popBranches, ...itemProps })
5098
+ ] }) : children.map((child) => /* @__PURE__ */ jsx18(OutlineNode, { nodeId: child.id, isRoot: false, ...itemProps }, child.id)) })
5099
+ ] }) : /* @__PURE__ */ jsx18("div", { className: "gtv-outline-leaf", children: row }) });
5172
5100
  }
5173
5101
  var OutlineNode = memo(OutlineNodeView);
5174
5102
  var OutlineSection = memo(function OutlineSection2({
@@ -5182,9 +5110,9 @@ var OutlineSection = memo(function OutlineSection2({
5182
5110
  const key = sectionKey(kind, rootId);
5183
5111
  const isOpen = useOutlineState(store, (s) => s.openIds.has(key));
5184
5112
  if (branches.length === 0) return null;
5185
- return /* @__PURE__ */ jsx16("li", { className: "gtv-outline-item", children: /* @__PURE__ */ jsxs11("details", { open: isOpen, children: [
5186
- /* @__PURE__ */ jsx16("summary", { onClick: (event) => handleSummaryClick(event, () => callbacks.toggleOpen(key)), children: /* @__PURE__ */ jsx16("span", { className: "gtv-outline-section-label", children: kind === "core" ? "Core" : "Pop" }) }),
5187
- isOpen && /* @__PURE__ */ jsx16("ul", { className: "gtv-outline-list", children: branches.map(({ child }) => /* @__PURE__ */ jsx16(OutlineNode, { nodeId: child.id, isRoot: false, model, store, callbacks }, child.id)) })
5113
+ return /* @__PURE__ */ jsx18("li", { className: "gtv-outline-item", children: /* @__PURE__ */ jsxs13("details", { open: isOpen, children: [
5114
+ /* @__PURE__ */ jsx18("summary", { onClick: (event) => handleSummaryClick(event, () => callbacks.toggleOpen(key)), children: /* @__PURE__ */ jsx18("span", { className: "gtv-outline-section-label", children: kind === "core" ? "Core" : "Pop" }) }),
5115
+ isOpen && /* @__PURE__ */ jsx18("ul", { className: "gtv-outline-list", children: branches.map(({ child }) => /* @__PURE__ */ jsx18(OutlineNode, { nodeId: child.id, isRoot: false, model, store, callbacks }, child.id)) })
5188
5116
  ] }) });
5189
5117
  });
5190
5118
  function GenreTreeOutline(props) {
@@ -5201,10 +5129,6 @@ function GenreTreeOutline(props) {
5201
5129
  hideInfoPanelTitle = false,
5202
5130
  itemCountLabel,
5203
5131
  readOnly,
5204
- editMode,
5205
- onEditModeChange,
5206
- renderTitleEditor,
5207
- renderParentsEditor,
5208
5132
  renderDangerZone,
5209
5133
  expanded,
5210
5134
  onExpandedChange,
@@ -5305,12 +5229,12 @@ function GenreTreeOutline(props) {
5305
5229
  }, [selectedNodeId, store]);
5306
5230
  const selectedNode = selectedId === null ? void 0 : index.byId.get(selectedId);
5307
5231
  const itemProps = { model, store, callbacks };
5308
- return /* @__PURE__ */ jsxs11("div", { className: ["gtv-outline", selectedNode && "gtv-outline--panel-open", className].filter(Boolean).join(" "), children: [
5309
- /* @__PURE__ */ jsxs11("div", { className: "gtv-outline-scroll", ref: scrollRef, children: [
5310
- /* @__PURE__ */ jsx16("ul", { className: "gtv-outline-list gtv-outline-list--roots", children: [model.centerRoot, ...model.ringRoots].map((root) => /* @__PURE__ */ jsx16(OutlineNode, { nodeId: root.id, isRoot: true, ...itemProps }, root.id)) }),
5311
- model.detachedRoots.length > 0 && /* @__PURE__ */ jsx16("ul", { className: "gtv-outline-list gtv-outline-list--roots gtv-outline-list--detached", children: model.detachedRoots.map((root) => /* @__PURE__ */ jsx16(OutlineNode, { nodeId: root.id, isRoot: true, ...itemProps }, root.id)) })
5232
+ return /* @__PURE__ */ jsxs13("div", { className: ["gtv-outline", selectedNode && "gtv-outline--panel-open", className].filter(Boolean).join(" "), children: [
5233
+ /* @__PURE__ */ jsxs13("div", { className: "gtv-outline-scroll", ref: scrollRef, children: [
5234
+ /* @__PURE__ */ jsx18("ul", { className: "gtv-outline-list gtv-outline-list--roots", children: [model.centerRoot, ...model.ringRoots].map((root) => /* @__PURE__ */ jsx18(OutlineNode, { nodeId: root.id, isRoot: true, ...itemProps }, root.id)) }),
5235
+ model.detachedRoots.length > 0 && /* @__PURE__ */ jsx18("ul", { className: "gtv-outline-list gtv-outline-list--roots gtv-outline-list--detached", children: model.detachedRoots.map((root) => /* @__PURE__ */ jsx18(OutlineNode, { nodeId: root.id, isRoot: true, ...itemProps }, root.id)) })
5312
5236
  ] }),
5313
- selectedNode && /* @__PURE__ */ jsx16(
5237
+ selectedNode && /* @__PURE__ */ jsx18(
5314
5238
  InfoPanel,
5315
5239
  {
5316
5240
  node: selectedNode,
@@ -5331,10 +5255,6 @@ function GenreTreeOutline(props) {
5331
5255
  hideTitle: hideInfoPanelTitle,
5332
5256
  itemCountLabel,
5333
5257
  readOnly,
5334
- editMode,
5335
- onEditModeChange,
5336
- renderTitleEditor,
5337
- renderParentsEditor,
5338
5258
  renderDangerZone,
5339
5259
  expanded,
5340
5260
  onExpandedChange,
@@ -5353,13 +5273,13 @@ function GenreTreeOutline(props) {
5353
5273
  } : void 0
5354
5274
  }
5355
5275
  ),
5356
- overlayPanel && /* @__PURE__ */ jsx16(OverlayPanel, { ...overlayPanel })
5276
+ overlayPanel && /* @__PURE__ */ jsx18(OverlayPanel, { ...overlayPanel })
5357
5277
  ] });
5358
5278
  }
5359
5279
 
5360
5280
  // src/GenreTreeSkeleton.tsx
5361
5281
  import { useId } from "react";
5362
- import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
5282
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
5363
5283
  var CARD_FILL = "#F4F4F5";
5364
5284
  var CARD_BORDER_COLOR = "#E4E4E7";
5365
5285
  var CONNECTOR_COLOR2 = "#D4D4D8";
@@ -5464,8 +5384,8 @@ function SkeletonCard({
5464
5384
  card,
5465
5385
  rootAccent
5466
5386
  }) {
5467
- return /* @__PURE__ */ jsxs12(Fragment6, { children: [
5468
- /* @__PURE__ */ jsx17(
5387
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
5388
+ /* @__PURE__ */ jsx19(
5469
5389
  "rect",
5470
5390
  {
5471
5391
  x: card.x,
@@ -5479,7 +5399,7 @@ function SkeletonCard({
5479
5399
  strokeWidth: rootAccent ? 1.5 : 1
5480
5400
  }
5481
5401
  ),
5482
- rootAccent && /* @__PURE__ */ jsx17(
5402
+ rootAccent && /* @__PURE__ */ jsx19(
5483
5403
  "circle",
5484
5404
  {
5485
5405
  cx: card.x + 18,
@@ -5497,9 +5417,9 @@ function GenreTreeSkeleton() {
5497
5417
  const animationName = `genre-tree-skeleton-sweep-${uid}`;
5498
5418
  const shimmerBandClass = `genre-tree-skeleton-shimmer-band-${uid}`;
5499
5419
  const sweepDistance = VIEWBOX_WIDTH + SHIMMER_BAND_WIDTH;
5500
- return /* @__PURE__ */ jsxs12("div", { className: "gtv-skeleton", children: [
5501
- /* @__PURE__ */ jsx17("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
5502
- /* @__PURE__ */ jsxs12(
5420
+ return /* @__PURE__ */ jsxs14("div", { className: "gtv-skeleton", children: [
5421
+ /* @__PURE__ */ jsx19("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
5422
+ /* @__PURE__ */ jsxs14(
5503
5423
  "svg",
5504
5424
  {
5505
5425
  viewBox: `0 0 ${VIEWBOX_WIDTH} ${VIEWBOX_HEIGHT}`,
@@ -5508,7 +5428,7 @@ function GenreTreeSkeleton() {
5508
5428
  className: "gtv-skeleton-svg",
5509
5429
  "aria-hidden": "true",
5510
5430
  children: [
5511
- /* @__PURE__ */ jsx17("style", { children: `
5431
+ /* @__PURE__ */ jsx19("style", { children: `
5512
5432
  @keyframes ${animationName} {
5513
5433
  from { transform: translateX(0); }
5514
5434
  to { transform: translateX(${sweepDistance}px); }
@@ -5522,9 +5442,9 @@ function GenreTreeSkeleton() {
5522
5442
  }
5523
5443
  }
5524
5444
  ` }),
5525
- /* @__PURE__ */ jsxs12("defs", { children: [
5526
- /* @__PURE__ */ jsxs12("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "1", y2: "0", children: [
5527
- /* @__PURE__ */ jsx17(
5445
+ /* @__PURE__ */ jsxs14("defs", { children: [
5446
+ /* @__PURE__ */ jsxs14("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "1", y2: "0", children: [
5447
+ /* @__PURE__ */ jsx19(
5528
5448
  "stop",
5529
5449
  {
5530
5450
  offset: "0%",
@@ -5532,7 +5452,7 @@ function GenreTreeSkeleton() {
5532
5452
  stopOpacity: 0
5533
5453
  }
5534
5454
  ),
5535
- /* @__PURE__ */ jsx17(
5455
+ /* @__PURE__ */ jsx19(
5536
5456
  "stop",
5537
5457
  {
5538
5458
  offset: "50%",
@@ -5540,7 +5460,7 @@ function GenreTreeSkeleton() {
5540
5460
  stopOpacity: 0.9
5541
5461
  }
5542
5462
  ),
5543
- /* @__PURE__ */ jsx17(
5463
+ /* @__PURE__ */ jsx19(
5544
5464
  "stop",
5545
5465
  {
5546
5466
  offset: "100%",
@@ -5549,8 +5469,8 @@ function GenreTreeSkeleton() {
5549
5469
  }
5550
5470
  )
5551
5471
  ] }),
5552
- /* @__PURE__ */ jsxs12("mask", { id: maskId, children: [
5553
- ALL_LINKS.map((link, i) => /* @__PURE__ */ jsx17(
5472
+ /* @__PURE__ */ jsxs14("mask", { id: maskId, children: [
5473
+ ALL_LINKS.map((link, i) => /* @__PURE__ */ jsx19(
5554
5474
  "path",
5555
5475
  {
5556
5476
  d: connectorPath(link.from, link.to),
@@ -5560,7 +5480,7 @@ function GenreTreeSkeleton() {
5560
5480
  },
5561
5481
  `link-mask-${i}`
5562
5482
  )),
5563
- ALL_CARDS.map(({ card }, i) => /* @__PURE__ */ jsx17(
5483
+ ALL_CARDS.map(({ card }, i) => /* @__PURE__ */ jsx19(
5564
5484
  "rect",
5565
5485
  {
5566
5486
  x: card.x,
@@ -5575,7 +5495,7 @@ function GenreTreeSkeleton() {
5575
5495
  ))
5576
5496
  ] })
5577
5497
  ] }),
5578
- ALL_LINKS.map((link, i) => /* @__PURE__ */ jsx17(
5498
+ ALL_LINKS.map((link, i) => /* @__PURE__ */ jsx19(
5579
5499
  "path",
5580
5500
  {
5581
5501
  d: connectorPath(link.from, link.to),
@@ -5585,8 +5505,8 @@ function GenreTreeSkeleton() {
5585
5505
  },
5586
5506
  `link-${i}`
5587
5507
  )),
5588
- ALL_CARDS.map(({ card, rootAccent }, i) => /* @__PURE__ */ jsx17(SkeletonCard, { card, rootAccent }, `card-${i}`)),
5589
- /* @__PURE__ */ jsx17("g", { mask: `url(#${maskId})`, children: /* @__PURE__ */ jsx17(
5508
+ ALL_CARDS.map(({ card, rootAccent }, i) => /* @__PURE__ */ jsx19(SkeletonCard, { card, rootAccent }, `card-${i}`)),
5509
+ /* @__PURE__ */ jsx19("g", { mask: `url(#${maskId})`, children: /* @__PURE__ */ jsx19(
5590
5510
  "rect",
5591
5511
  {
5592
5512
  className: shimmerBandClass,
@@ -5605,7 +5525,7 @@ function GenreTreeSkeleton() {
5605
5525
 
5606
5526
  // src/GenreTreeWheelSkeleton.tsx
5607
5527
  import { useId as useId2, useLayoutEffect as useLayoutEffect2, useRef as useRef10 } from "react";
5608
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
5528
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
5609
5529
  var SHAPE_COLOR = "#D4D4D8";
5610
5530
  var SECTOR_FILL_OPACITY = 0.22;
5611
5531
  var INNER_TINT_OPACITY = 0.55;
@@ -5750,8 +5670,8 @@ function GenreTreeWheelSkeleton() {
5750
5670
  sharedStartTime ??= animation.startTime ?? document.timeline.currentTime;
5751
5671
  animation.startTime = sharedStartTime;
5752
5672
  }, []);
5753
- return /* @__PURE__ */ jsxs13("div", { className: "gtv-wheel-skeleton", children: [
5754
- /* @__PURE__ */ jsx18(
5673
+ return /* @__PURE__ */ jsxs15("div", { className: "gtv-wheel-skeleton", children: [
5674
+ /* @__PURE__ */ jsx20(
5755
5675
  "div",
5756
5676
  {
5757
5677
  className: "gtv-wheel-skeleton-sector-layer",
@@ -5759,8 +5679,8 @@ function GenreTreeWheelSkeleton() {
5759
5679
  "aria-hidden": "true"
5760
5680
  }
5761
5681
  ),
5762
- /* @__PURE__ */ jsx18("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
5763
- /* @__PURE__ */ jsxs13(
5682
+ /* @__PURE__ */ jsx20("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
5683
+ /* @__PURE__ */ jsxs15(
5764
5684
  "svg",
5765
5685
  {
5766
5686
  viewBox: `${CANVAS_ORIGIN} ${CANVAS_ORIGIN} ${CANVAS_SIZE} ${CANVAS_SIZE}`,
@@ -5768,14 +5688,14 @@ function GenreTreeWheelSkeleton() {
5768
5688
  className: "gtv-wheel-skeleton-svg",
5769
5689
  "aria-hidden": "true",
5770
5690
  children: [
5771
- /* @__PURE__ */ jsxs13("defs", { children: [
5772
- /* @__PURE__ */ jsxs13("radialGradient", { id: mainstreamId, children: [
5773
- /* @__PURE__ */ jsx18("stop", { offset: "0%", stopColor: "#FFFFFF", stopOpacity: 0.45 }),
5774
- /* @__PURE__ */ jsx18("stop", { offset: "65%", stopColor: "#FFFFFF", stopOpacity: 0.15 }),
5775
- /* @__PURE__ */ jsx18("stop", { offset: "100%", stopColor: "#FFFFFF", stopOpacity: 0 })
5691
+ /* @__PURE__ */ jsxs15("defs", { children: [
5692
+ /* @__PURE__ */ jsxs15("radialGradient", { id: mainstreamId, children: [
5693
+ /* @__PURE__ */ jsx20("stop", { offset: "0%", stopColor: "#FFFFFF", stopOpacity: 0.45 }),
5694
+ /* @__PURE__ */ jsx20("stop", { offset: "65%", stopColor: "#FFFFFF", stopOpacity: 0.15 }),
5695
+ /* @__PURE__ */ jsx20("stop", { offset: "100%", stopColor: "#FFFFFF", stopOpacity: 0 })
5776
5696
  ] }),
5777
- /* @__PURE__ */ jsxs13("g", { id: shapesId, children: [
5778
- LINKS.map((link, i) => /* @__PURE__ */ jsx18(
5697
+ /* @__PURE__ */ jsxs15("g", { id: shapesId, children: [
5698
+ LINKS.map((link, i) => /* @__PURE__ */ jsx20(
5779
5699
  "path",
5780
5700
  {
5781
5701
  d: link.d,
@@ -5787,21 +5707,21 @@ function GenreTreeWheelSkeleton() {
5787
5707
  },
5788
5708
  `link-${i}`
5789
5709
  )),
5790
- DOTS.map((dot, i) => /* @__PURE__ */ jsx18("circle", { cx: dot.point.x, cy: dot.point.y, r: dot.r, fill: "currentColor" }, `dot-${i}`))
5710
+ DOTS.map((dot, i) => /* @__PURE__ */ jsx20("circle", { cx: dot.point.x, cy: dot.point.y, r: dot.r, fill: "currentColor" }, `dot-${i}`))
5791
5711
  ] })
5792
5712
  ] }),
5793
- /* @__PURE__ */ jsx18("circle", { cx: 0, cy: 0, r: RING_RADIUS, fill: "#FFFFFF", fillOpacity: INNER_TINT_OPACITY }),
5794
- /* @__PURE__ */ jsx18("circle", { cx: 0, cy: 0, r: MAINSTREAM_RADIUS, fill: `url(#${mainstreamId})` }),
5795
- /* @__PURE__ */ jsx18("use", { href: `#${shapesId}`, color: SHAPE_COLOR })
5713
+ /* @__PURE__ */ jsx20("circle", { cx: 0, cy: 0, r: RING_RADIUS, fill: "#FFFFFF", fillOpacity: INNER_TINT_OPACITY }),
5714
+ /* @__PURE__ */ jsx20("circle", { cx: 0, cy: 0, r: MAINSTREAM_RADIUS, fill: `url(#${mainstreamId})` }),
5715
+ /* @__PURE__ */ jsx20("use", { href: `#${shapesId}`, color: SHAPE_COLOR })
5796
5716
  ]
5797
5717
  }
5798
5718
  ),
5799
- /* @__PURE__ */ jsx18("div", { className: "gtv-wheel-skeleton-shimmer", "aria-hidden": "true", children: /* @__PURE__ */ jsx18("div", { ref: wedgeRef, className: "gtv-wheel-skeleton-shimmer-wedge" }) })
5719
+ /* @__PURE__ */ jsx20("div", { className: "gtv-wheel-skeleton-shimmer", "aria-hidden": "true", children: /* @__PURE__ */ jsx20("div", { ref: wedgeRef, className: "gtv-wheel-skeleton-shimmer-wedge" }) })
5800
5720
  ] });
5801
5721
  }
5802
5722
 
5803
5723
  // src/GenreTreeOutlineSkeleton.tsx
5804
- import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
5724
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
5805
5725
  var ROWS = [
5806
5726
  { depth: 0, width: 38 },
5807
5727
  { depth: 0, width: 26 },
@@ -5813,9 +5733,9 @@ var ROWS = [
5813
5733
  { depth: 0, width: 22 }
5814
5734
  ];
5815
5735
  function GenreTreeOutlineSkeleton() {
5816
- return /* @__PURE__ */ jsxs14("div", { className: "gtv-skeleton gtv-outline-skeleton", children: [
5817
- /* @__PURE__ */ jsx19("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
5818
- ROWS.map((row, i) => /* @__PURE__ */ jsx19(
5736
+ return /* @__PURE__ */ jsxs16("div", { className: "gtv-skeleton gtv-outline-skeleton", children: [
5737
+ /* @__PURE__ */ jsx21("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
5738
+ ROWS.map((row, i) => /* @__PURE__ */ jsx21(
5819
5739
  "div",
5820
5740
  {
5821
5741
  className: "gtv-outline-skeleton-row",
@@ -5828,13 +5748,13 @@ function GenreTreeOutlineSkeleton() {
5828
5748
  }
5829
5749
 
5830
5750
  // src/GenreTreeViewSkeleton.tsx
5831
- import { jsx as jsx20 } from "react/jsx-runtime";
5751
+ import { jsx as jsx22 } from "react/jsx-runtime";
5832
5752
  function GenreTreeViewSkeleton({ viewMode }) {
5833
5753
  if (viewMode === "wheel" || viewMode === "pop-core") {
5834
- return /* @__PURE__ */ jsx20("div", { className: "gtv-view-skeleton-wheel-wrapper", children: /* @__PURE__ */ jsx20(GenreTreeWheelSkeleton, {}) });
5754
+ return /* @__PURE__ */ jsx22("div", { className: "gtv-view-skeleton-wheel-wrapper", children: /* @__PURE__ */ jsx22(GenreTreeWheelSkeleton, {}) });
5835
5755
  }
5836
- if (viewMode === "outline") return /* @__PURE__ */ jsx20(GenreTreeOutlineSkeleton, {});
5837
- return /* @__PURE__ */ jsx20(GenreTreeSkeleton, {});
5756
+ if (viewMode === "outline") return /* @__PURE__ */ jsx22(GenreTreeOutlineSkeleton, {});
5757
+ return /* @__PURE__ */ jsx22(GenreTreeSkeleton, {});
5838
5758
  }
5839
5759
  export {
5840
5760
  DEFAULT_FRAME_HEIGHT,