@tensor_1/react 2.44.1 → 2.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2358,8 +2358,13 @@ var glyphCircleCheck = [["circle", { "cx": "12", "cy": "12", "r": "10" }], ["pat
2358
2358
  var glyphCircleQuestionMark = [["circle", { "cx": "12", "cy": "12", "r": "10" }], ["path", { "d": "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }], ["path", { "d": "M12 17h.01" }]];
2359
2359
  var glyphGripVertical = [["circle", { "cx": "9", "cy": "12", "r": "1" }], ["circle", { "cx": "9", "cy": "5", "r": "1" }], ["circle", { "cx": "9", "cy": "19", "r": "1" }], ["circle", { "cx": "15", "cy": "12", "r": "1" }], ["circle", { "cx": "15", "cy": "5", "r": "1" }], ["circle", { "cx": "15", "cy": "19", "r": "1" }]];
2360
2360
  var glyphMaximize2 = [["path", { "d": "M15 3h6v6" }], ["path", { "d": "m21 3-7 7" }], ["path", { "d": "m3 21 7-7" }], ["path", { "d": "M9 21H3v-6" }]];
2361
+ var glyphMinimize2 = [["path", { "d": "m14 10 7-7" }], ["path", { "d": "M20 10h-6V4" }], ["path", { "d": "m3 21 7-7" }], ["path", { "d": "M4 14h6v6" }]];
2362
+ var glyphMinus = [["path", { "d": "M5 12h14" }]];
2361
2363
  var glyphOctagonAlert = [["path", { "d": "M12 16h.01" }], ["path", { "d": "M12 8v4" }], ["path", { "d": "M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z" }]];
2362
2364
  var glyphPencil = [["path", { "d": "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" }], ["path", { "d": "m15 5 4 4" }]];
2365
+ var glyphPlus = [["path", { "d": "M5 12h14" }], ["path", { "d": "M12 5v14" }]];
2366
+ var glyphRotateCcw = [["path", { "d": "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }], ["path", { "d": "M3 3v5h5" }]];
2367
+ var glyphScan = [["path", { "d": "M3 7V5a2 2 0 0 1 2-2h2" }], ["path", { "d": "M17 3h2a2 2 0 0 1 2 2v2" }], ["path", { "d": "M21 17v2a2 2 0 0 1-2 2h-2" }], ["path", { "d": "M7 21H5a2 2 0 0 1-2-2v-2" }]];
2363
2368
  var glyphTriangleAlert = [["path", { "d": "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }], ["path", { "d": "M12 9v4" }], ["path", { "d": "M12 17h.01" }]];
2364
2369
  var glyphX = [["path", { "d": "M18 6 6 18" }], ["path", { "d": "m6 6 12 12" }]];
2365
2370
 
@@ -2373,8 +2378,13 @@ var CircleCheckIcon = /* @__PURE__ */ createIcon("CircleCheckIcon", glyphCircleC
2373
2378
  var CircleQuestionMarkIcon = /* @__PURE__ */ createIcon("CircleQuestionMarkIcon", glyphCircleQuestionMark);
2374
2379
  var GripVerticalIcon = /* @__PURE__ */ createIcon("GripVerticalIcon", glyphGripVertical);
2375
2380
  var Maximize2Icon = /* @__PURE__ */ createIcon("Maximize2Icon", glyphMaximize2);
2381
+ var Minimize2Icon = /* @__PURE__ */ createIcon("Minimize2Icon", glyphMinimize2);
2382
+ var MinusIcon = /* @__PURE__ */ createIcon("MinusIcon", glyphMinus);
2376
2383
  var OctagonAlertIcon = /* @__PURE__ */ createIcon("OctagonAlertIcon", glyphOctagonAlert);
2377
2384
  var PencilIcon = /* @__PURE__ */ createIcon("PencilIcon", glyphPencil);
2385
+ var PlusIcon = /* @__PURE__ */ createIcon("PlusIcon", glyphPlus);
2386
+ var RotateCcwIcon = /* @__PURE__ */ createIcon("RotateCcwIcon", glyphRotateCcw);
2387
+ var ScanIcon = /* @__PURE__ */ createIcon("ScanIcon", glyphScan);
2378
2388
  var TriangleAlertIcon = /* @__PURE__ */ createIcon("TriangleAlertIcon", glyphTriangleAlert);
2379
2389
  var XIcon = /* @__PURE__ */ createIcon("XIcon", glyphX);
2380
2390
 
@@ -9949,8 +9959,8 @@ var idRef = (chartId, nodeId, part) => {
9949
9959
  };
9950
9960
  var isExpandable = (node, known, lazy) => known ? known.length > 0 : lazy && (node.reportCount ?? 0) > 0;
9951
9961
  var ChevronIcon3 = () => /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("path", { d: "M2 4l4 4 4-4" }) });
9952
- var PlusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("path", { d: "M8 3v10M3 8h10" }) });
9953
- var MinusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("path", { d: "M3 8h10" }) });
9962
+ var PlusIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("path", { d: "M8 3v10M3 8h10" }) });
9963
+ var MinusIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("path", { d: "M3 8h10" }) });
9954
9964
  var FitIcon = () => /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("path", { d: "M2 6V2h4M14 6V2h-4M2 10v4h4M14 10v4h-4" }) });
9955
9965
  var useIsoLayoutEffect = typeof window === "undefined" ? import_react64.useEffect : import_react64.useLayoutEffect;
9956
9966
  var Ring2 = () => /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("span", { className: "uix-spinner uix-spinner--sm", "aria-hidden": "true" });
@@ -10495,7 +10505,7 @@ function OrgChart({
10495
10505
  zoomTo(orgChartZoomStep(zoom, -1));
10496
10506
  }
10497
10507
  },
10498
- children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(MinusIcon, {})
10508
+ children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(MinusIcon2, {})
10499
10509
  }
10500
10510
  ),
10501
10511
  /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(
@@ -10529,7 +10539,7 @@ function OrgChart({
10529
10539
  zoomTo(orgChartZoomStep(zoom, 1));
10530
10540
  }
10531
10541
  },
10532
- children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(PlusIcon, {})
10542
+ children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(PlusIcon2, {})
10533
10543
  }
10534
10544
  ),
10535
10545
  /* @__PURE__ */ (0, import_jsx_runtime88.jsxs)(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
@@ -15527,6 +15537,7 @@ var READABLE_ZOOM = 0.75;
15527
15537
  var ZOOM_STEP = 1.2;
15528
15538
  var PAN_STEP = 64;
15529
15539
  var FOCUS_MARGIN = 24;
15540
+ var HINT_MS = 1600;
15530
15541
  var NAV_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"];
15531
15542
  var clampZoom2 = (zoom) => Math.min(MAX_ZOOM2, Math.max(MIN_ZOOM2, zoom));
15532
15543
  function initialView(layout, size) {
@@ -15570,9 +15581,13 @@ function LayeredRelationshipGraph({
15570
15581
  showList = true,
15571
15582
  height,
15572
15583
  focusId,
15573
- onFocusChange
15584
+ onFocusChange,
15585
+ controls = "full",
15586
+ initialView: initialViewMode = "readable"
15574
15587
  }) {
15575
15588
  const hintId = (0, import_react74.useId)();
15589
+ const viewportHintId = (0, import_react74.useId)();
15590
+ const compact = controls === "compact";
15576
15591
  const bounded = (0, import_react74.useMemo)(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
15577
15592
  const [localExpanded, setLocalExpanded] = (0, import_react74.useState)(EMPTY_IDS2);
15578
15593
  const expanded = expandedClusterIds ?? localExpanded;
@@ -15586,19 +15601,33 @@ function LayeredRelationshipGraph({
15586
15601
  [bounded, rootId, clusterThreshold, expanded, density]
15587
15602
  );
15588
15603
  const itemById = (0, import_react74.useMemo)(() => new Map(layout.items.map((item) => [item.id, item])), [layout]);
15604
+ const startView = (0, import_react74.useCallback)(
15605
+ (forLayout, forSize) => initialViewMode === "fit" ? fitView(forLayout, forSize) : initialView(forLayout, forSize),
15606
+ [initialViewMode]
15607
+ );
15589
15608
  const viewportRef = (0, import_react74.useRef)(null);
15590
15609
  const itemRefs = (0, import_react74.useRef)(/* @__PURE__ */ new Map());
15610
+ const fullscreenButtonRef = (0, import_react74.useRef)(null);
15591
15611
  const [size, setSize] = (0, import_react74.useState)({ width: 0, height: 0 });
15592
15612
  const [view, setView] = (0, import_react74.useState)({ x: 0, y: 0, zoom: 1 });
15593
15613
  const [animate, setAnimate] = (0, import_react74.useState)(false);
15594
15614
  const [localFocus, setLocalFocus] = (0, import_react74.useState)(void 0);
15595
15615
  const [hoverId, setHoverId] = (0, import_react74.useState)(void 0);
15616
+ const [fullscreen, setFullscreen] = (0, import_react74.useState)(false);
15617
+ const [wheelHint, setWheelHint] = (0, import_react74.useState)(false);
15596
15618
  const viewRef = (0, import_react74.useRef)(view);
15597
15619
  viewRef.current = view;
15598
15620
  const layoutRef = (0, import_react74.useRef)(layout);
15599
15621
  layoutRef.current = layout;
15622
+ const fullscreenRef = (0, import_react74.useRef)(fullscreen);
15623
+ fullscreenRef.current = fullscreen;
15624
+ const compactRef = (0, import_react74.useRef)(compact);
15625
+ compactRef.current = compact;
15600
15626
  const pendingFocus = (0, import_react74.useRef)(void 0);
15601
15627
  const initialisedFor = (0, import_react74.useRef)(void 0);
15628
+ const refitOnResize = (0, import_react74.useRef)(false);
15629
+ const hintTimer = (0, import_react74.useRef)(void 0);
15630
+ const modifier = useKbdPlatform() === "mac" ? "\u2318" : "Ctrl";
15602
15631
  const rootItemId = layout.rootId !== void 0 ? layout.itemOf[layout.rootId] : layout.items[0]?.id;
15603
15632
  const requestedFocus = focusId !== void 0 ? layout.itemOf[focusId] ?? focusId : localFocus;
15604
15633
  const focusedId = requestedFocus !== void 0 && itemById.has(requestedFocus) ? requestedFocus : rootItemId;
@@ -15622,8 +15651,24 @@ function LayeredRelationshipGraph({
15622
15651
  const key = `${layout.rootId ?? ""}`;
15623
15652
  if (initialisedFor.current === key) return;
15624
15653
  initialisedFor.current = key;
15625
- setView(initialView(layout, size));
15626
- }, [layout, size]);
15654
+ setView(startView(layout, size));
15655
+ }, [layout, size, startView]);
15656
+ (0, import_react74.useEffect)(() => {
15657
+ if (!refitOnResize.current || size.width === 0) return;
15658
+ refitOnResize.current = false;
15659
+ setAnimate(false);
15660
+ setView(startView(layoutRef.current, size));
15661
+ }, [size, startView]);
15662
+ (0, import_react74.useEffect)(() => {
15663
+ if (!fullscreen) return;
15664
+ const root = document.documentElement;
15665
+ const previous = root.style.overflow;
15666
+ root.style.overflow = "hidden";
15667
+ return () => {
15668
+ root.style.overflow = previous;
15669
+ };
15670
+ }, [fullscreen]);
15671
+ (0, import_react74.useEffect)(() => () => clearTimeout(hintTimer.current), []);
15627
15672
  const moveView = (0, import_react74.useCallback)((next, smooth) => {
15628
15673
  setAnimate(smooth);
15629
15674
  setView(next);
@@ -15679,7 +15724,14 @@ function LayeredRelationshipGraph({
15679
15724
  const element = viewportRef.current;
15680
15725
  if (!element) return;
15681
15726
  const onWheel = (event) => {
15682
- if (!event.ctrlKey && !event.metaKey) return;
15727
+ if (!event.ctrlKey && !event.metaKey && !fullscreenRef.current) {
15728
+ if (compactRef.current) {
15729
+ setWheelHint(true);
15730
+ clearTimeout(hintTimer.current);
15731
+ hintTimer.current = setTimeout(() => setWheelHint(false), HINT_MS);
15732
+ }
15733
+ return;
15734
+ }
15683
15735
  event.preventDefault();
15684
15736
  const rect = element.getBoundingClientRect();
15685
15737
  const px = event.clientX - rect.left;
@@ -15740,6 +15792,27 @@ function LayeredRelationshipGraph({
15740
15792
  moveView({ zoom, x: cx0 - (cx0 - current.x) * ratio, y: cy0 - (cy0 - current.y) * ratio }, true);
15741
15793
  };
15742
15794
  const panBy = (dx, dy) => moveView({ ...viewRef.current, x: viewRef.current.x + dx, y: viewRef.current.y + dy }, true);
15795
+ const resetView = () => moveView(startView(layout, size), true);
15796
+ const toggleFullscreen = () => {
15797
+ const next = !fullscreenRef.current;
15798
+ refitOnResize.current = true;
15799
+ setFullscreen(next);
15800
+ requestAnimationFrame(() => (next ? viewportRef.current : fullscreenButtonRef.current)?.focus({ preventScroll: true }));
15801
+ };
15802
+ const onViewportKeyDown = (event) => {
15803
+ if (!compact || event.altKey || event.ctrlKey || event.metaKey) return;
15804
+ const onMap = event.target === event.currentTarget;
15805
+ if (onMap && event.key === "ArrowLeft") panBy(PAN_STEP, 0);
15806
+ else if (onMap && event.key === "ArrowRight") panBy(-PAN_STEP, 0);
15807
+ else if (onMap && event.key === "ArrowUp") panBy(0, PAN_STEP);
15808
+ else if (onMap && event.key === "ArrowDown") panBy(0, -PAN_STEP);
15809
+ else if (event.key === "+" || event.key === "=") zoomBy(ZOOM_STEP);
15810
+ else if (event.key === "-" || event.key === "_") zoomBy(1 / ZOOM_STEP);
15811
+ else if (event.key === "0") resetView();
15812
+ else if (event.key === "Escape" && fullscreen) toggleFullscreen();
15813
+ else return;
15814
+ event.preventDefault();
15815
+ };
15743
15816
  const toggleCluster = (clusterId, focusAfter) => {
15744
15817
  const isExpanded = expanded.has(clusterId);
15745
15818
  pendingFocus.current = focusAfter;
@@ -15792,144 +15865,191 @@ function LayeredRelationshipGraph({
15792
15865
  height: layout.height,
15793
15866
  transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom})`
15794
15867
  };
15795
- return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)("section", { className: cx("uix-relationship-graph", "uix-relationship-graph--layered", density === "compact" && "uix-relationship-graph--compact", className), "aria-label": labels.graph, children: [
15796
- /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)("div", { className: "uix-relationship-graph__toolbar", role: "group", "aria-label": labels.controls, children: [
15797
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => zoomBy(ZOOM_STEP), disabled: view.zoom >= MAX_ZOOM2, children: labels.zoomIn }),
15798
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => zoomBy(1 / ZOOM_STEP), disabled: view.zoom <= MIN_ZOOM2, children: labels.zoomOut }),
15799
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => moveView(fitView(layout, size), true), children: labels.fit }),
15800
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => moveView(initialView(layout, size), true), children: labels.reset }),
15801
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => panBy(PAN_STEP, 0), "aria-label": labels.panLeft, children: labels.left }),
15802
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => panBy(0, PAN_STEP), "aria-label": labels.panUp, children: labels.up }),
15803
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => panBy(0, -PAN_STEP), "aria-label": labels.panDown, children: labels.down }),
15804
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => panBy(-PAN_STEP, 0), "aria-label": labels.panRight, children: labels.right }),
15805
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { "aria-live": "polite", children: labels.zoomLevel(Math.round(view.zoom * 100)) })
15806
- ] }),
15807
- bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("p", { className: "uix-relationship-graph__bounded", role: "status", children: labels.bounded(bounded.nodes.length, bounded.omittedNodeCount) }),
15808
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("p", { id: hintId, className: "uix-visually-hidden", children: labels.keyboardHint }),
15809
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
15810
- "div",
15811
- {
15812
- ref: viewportRef,
15813
- className: "uix-relationship-graph__viewport",
15814
- style: viewportStyle,
15815
- onPointerDown,
15816
- onPointerMove,
15817
- onPointerUp: onPointerEnd,
15818
- onPointerCancel: onPointerEnd,
15819
- children: /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)("div", { className: "uix-relationship-graph__stage", style: stageStyle, "data-animate": animate || void 0, onTransitionEnd: () => setAnimate(false), children: [
15820
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("svg", { className: "uix-relationship-graph__edges", width: layout.width, height: layout.height, "aria-hidden": "true", focusable: "false", children: layout.edges.map((edge) => {
15821
- const active = activeIds.has(edge.source) || activeIds.has(edge.target) || edge.edgeIds.some((id) => highlightedEdgeIds.has(id));
15822
- const angle = edge.arrowAngle * 180 / Math.PI;
15823
- return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
15824
- "g",
15825
- {
15826
- className: "uix-relationship-graph__link",
15827
- "data-kind": edge.kind,
15828
- "data-active": active || void 0,
15829
- "data-emphasis": edge.emphasis || void 0,
15830
- "data-conflicted": edge.edgeIds.some((id) => conflictedEdgeIds.has(id)) || void 0,
15831
- "data-dimmed": dimmedNodeIds.has(edge.source) && dimmedNodeIds.has(edge.target) || void 0,
15832
- children: [
15833
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("path", { d: edge.path }),
15834
- edge.arrow !== "none" && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("polygon", { points: arrowPoints, transform: `translate(${edge.arrowX} ${edge.arrowY}) rotate(${angle})` }),
15835
- showLabel(edge) && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("text", { x: edge.labelX, y: edge.labelY, textAnchor: "middle", dominantBaseline: "middle", children: (edge.kind === "cycle" ? [...edge.labelLines, labels.cycle] : edge.labelLines).map((line, index, lines) => /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("tspan", { x: edge.labelX, dy: index === 0 ? `${-(lines.length - 1) * 0.55}em` : "1.1em", children: line }, index)) })
15836
- ]
15837
- },
15838
- edge.id
15839
- );
15840
- }) }),
15841
- layout.columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
15842
- "div",
15843
- {
15844
- className: "uix-relationship-graph__column",
15845
- "aria-hidden": "true",
15846
- style: { left: column.x, width: layout.nodeWidth },
15847
- children: labels.column(column.depth, column.count)
15868
+ const zoomPercent = Math.round(view.zoom * 100);
15869
+ const control = (label, icon, onClick, { disabled = false, keys, ref } = {}) => /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(Tooltip, { label, placement: "top", ...keys ? { shortcut: keys } : {}, children: /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { ref, type: "button", className: "uix-relationship-graph__control", "aria-label": label, onClick, disabled, children: icon }) });
15870
+ return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
15871
+ "section",
15872
+ {
15873
+ className: cx(
15874
+ "uix-relationship-graph",
15875
+ "uix-relationship-graph--layered",
15876
+ compact && "uix-relationship-graph--map",
15877
+ fullscreen && "uix-relationship-graph--fullscreen",
15878
+ density === "compact" && "uix-relationship-graph--compact",
15879
+ className
15880
+ ),
15881
+ "aria-label": labels.graph,
15882
+ children: [
15883
+ controls === "full" && /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)("div", { className: "uix-relationship-graph__toolbar", role: "group", "aria-label": labels.controls, children: [
15884
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => zoomBy(ZOOM_STEP), disabled: view.zoom >= MAX_ZOOM2, children: labels.zoomIn }),
15885
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => zoomBy(1 / ZOOM_STEP), disabled: view.zoom <= MIN_ZOOM2, children: labels.zoomOut }),
15886
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => moveView(fitView(layout, size), true), children: labels.fit }),
15887
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: resetView, children: labels.reset }),
15888
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => panBy(PAN_STEP, 0), "aria-label": labels.panLeft, children: labels.left }),
15889
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => panBy(0, PAN_STEP), "aria-label": labels.panUp, children: labels.up }),
15890
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => panBy(0, -PAN_STEP), "aria-label": labels.panDown, children: labels.down }),
15891
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("button", { type: "button", onClick: () => panBy(-PAN_STEP, 0), "aria-label": labels.panRight, children: labels.right }),
15892
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { "aria-live": "polite", children: labels.zoomLevel(zoomPercent) })
15893
+ ] }),
15894
+ bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("p", { className: "uix-relationship-graph__bounded", role: "status", children: labels.bounded(bounded.nodes.length, bounded.omittedNodeCount) }),
15895
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("p", { id: hintId, className: "uix-visually-hidden", children: labels.keyboardHint }),
15896
+ compact && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("p", { id: viewportHintId, className: "uix-visually-hidden", children: labels.viewportKeyboardHint }),
15897
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
15898
+ "div",
15899
+ {
15900
+ ref: viewportRef,
15901
+ className: "uix-relationship-graph__viewport",
15902
+ style: viewportStyle,
15903
+ onPointerDown,
15904
+ onPointerMove,
15905
+ onPointerUp: onPointerEnd,
15906
+ onPointerCancel: onPointerEnd,
15907
+ ...compact ? {
15908
+ tabIndex: 0,
15909
+ role: "group",
15910
+ "aria-label": labels.graph,
15911
+ "aria-describedby": viewportHintId,
15912
+ onKeyDown: onViewportKeyDown
15913
+ } : {},
15914
+ children: [
15915
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)("div", { className: "uix-relationship-graph__stage", style: stageStyle, "data-animate": animate || void 0, onTransitionEnd: () => setAnimate(false), children: [
15916
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("svg", { className: "uix-relationship-graph__edges", width: layout.width, height: layout.height, "aria-hidden": "true", focusable: "false", children: layout.edges.map((edge) => {
15917
+ const active = activeIds.has(edge.source) || activeIds.has(edge.target) || edge.edgeIds.some((id) => highlightedEdgeIds.has(id));
15918
+ const angle = edge.arrowAngle * 180 / Math.PI;
15919
+ return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
15920
+ "g",
15921
+ {
15922
+ className: "uix-relationship-graph__link",
15923
+ "data-kind": edge.kind,
15924
+ "data-active": active || void 0,
15925
+ "data-emphasis": edge.emphasis || void 0,
15926
+ "data-conflicted": edge.edgeIds.some((id) => conflictedEdgeIds.has(id)) || void 0,
15927
+ "data-dimmed": dimmedNodeIds.has(edge.source) && dimmedNodeIds.has(edge.target) || void 0,
15928
+ children: [
15929
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("path", { d: edge.path }),
15930
+ edge.arrow !== "none" && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("polygon", { points: arrowPoints, transform: `translate(${edge.arrowX} ${edge.arrowY}) rotate(${angle})` }),
15931
+ showLabel(edge) && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("text", { x: edge.labelX, y: edge.labelY, textAnchor: "middle", dominantBaseline: "middle", children: (edge.kind === "cycle" ? [...edge.labelLines, labels.cycle] : edge.labelLines).map((line, index, lines) => /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("tspan", { x: edge.labelX, dy: index === 0 ? `${-(lines.length - 1) * 0.55}em` : "1.1em", children: line }, index)) })
15932
+ ]
15933
+ },
15934
+ edge.id
15935
+ );
15936
+ }) }),
15937
+ layout.columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
15938
+ "div",
15939
+ {
15940
+ className: "uix-relationship-graph__column",
15941
+ "aria-hidden": "true",
15942
+ style: { left: column.x, width: layout.nodeWidth },
15943
+ children: labels.column(column.depth, column.count)
15944
+ },
15945
+ column.depth
15946
+ )),
15947
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("div", { role: "group", "aria-roledescription": labels.roleDescription, "aria-label": labels.graphSummary(bounded.nodes.length, bounded.edges.length), "aria-describedby": hintId, children: layout.items.map((item) => {
15948
+ const selected = item.id === selectedItemId;
15949
+ const dimmed = item.node ? dimmedNodeIds.has(item.node.id) : item.cluster.members.every((member) => dimmedNodeIds.has(member.id));
15950
+ const body = item.cluster ? renderCluster?.(item.cluster) ?? /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(import_jsx_runtime100.Fragment, { children: [
15951
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-label", children: labels.cluster(item.cluster.members.length, item.cluster.type, item.cluster.edgeLabel) }),
15952
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-meta", children: labels.expandCluster })
15953
+ ] }) : renderNode?.(item.node, { selected, dimmed }) ?? /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(import_jsx_runtime100.Fragment, { children: [
15954
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-label", children: item.node.label }),
15955
+ (item.node.type || item.node.description) && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-meta", children: [item.node.type, item.node.description].filter(Boolean).join(" \xB7 ") })
15956
+ ] });
15957
+ return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
15958
+ "button",
15959
+ {
15960
+ ref: (element) => {
15961
+ if (element) itemRefs.current.set(item.id, element);
15962
+ else itemRefs.current.delete(item.id);
15963
+ },
15964
+ type: "button",
15965
+ className: "uix-relationship-graph__item",
15966
+ "data-kind": item.kind,
15967
+ "data-node-id": item.id,
15968
+ "data-selected": selected || void 0,
15969
+ "data-dimmed": dimmed || void 0,
15970
+ "data-highlighted": item.node && highlightedNodeIds.has(item.node.id) || void 0,
15971
+ "data-conflicted": item.node && conflictedNodeIds.has(item.node.id) || void 0,
15972
+ "data-root": item.id === rootItemId || void 0,
15973
+ style: { left: item.x, top: item.y, width: item.width, height: item.height },
15974
+ tabIndex: item.id === focusedId ? 0 : -1,
15975
+ "aria-label": labelFor(item),
15976
+ ...item.cluster ? { "aria-expanded": false } : { "aria-pressed": selected },
15977
+ onClick: () => activate(item),
15978
+ onKeyDown: (event) => onItemKeyDown(event, item),
15979
+ onFocus: () => {
15980
+ if (item.id === focusedId) return;
15981
+ setLocalFocus(item.id);
15982
+ onFocusChange?.(item.id);
15983
+ },
15984
+ onPointerEnter: () => setHoverId(item.id),
15985
+ onPointerLeave: () => setHoverId((current) => current === item.id ? void 0 : current),
15986
+ children: [
15987
+ body,
15988
+ item.inCycle && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-flag", "aria-hidden": "true", children: labels.cycle })
15989
+ ]
15990
+ },
15991
+ item.id
15992
+ );
15993
+ }) }),
15994
+ layout.items.filter((item) => item.expandedGroup).map((item) => {
15995
+ const group = item.expandedGroup;
15996
+ return /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
15997
+ "button",
15998
+ {
15999
+ type: "button",
16000
+ className: "uix-relationship-graph__collapse",
16001
+ "aria-expanded": "true",
16002
+ style: { left: item.x, top: item.y - 26, width: item.width },
16003
+ onClick: () => toggleCluster(group.id, group.id),
16004
+ children: labels.collapseCluster(group.members.length, group.type)
16005
+ },
16006
+ `collapse:${group.id}`
16007
+ );
16008
+ })
16009
+ ] }),
16010
+ compact && /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(import_jsx_runtime100.Fragment, { children: [
16011
+ wheelHint && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("p", { className: "uix-relationship-graph__toast", role: "status", children: labels.navigationHint(modifier) }),
16012
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)("div", { className: "uix-relationship-graph__footer", children: [
16013
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("p", { className: "uix-relationship-graph__hint", "aria-hidden": "true", children: labels.navigationHint(modifier) }),
16014
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)("div", { className: "uix-relationship-graph__controls", role: "group", "aria-label": labels.controls, children: [
16015
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__zoom-level", "aria-live": "polite", children: labels.zoomLevel(zoomPercent) }),
16016
+ /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)("div", { className: "uix-relationship-graph__control-cluster", children: [
16017
+ control(labels.zoomIn, /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(PlusIcon, { "aria-hidden": "true" }), () => zoomBy(ZOOM_STEP), { disabled: view.zoom >= MAX_ZOOM2, keys: ["+"] }),
16018
+ control(labels.zoomOut, /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(MinusIcon, { "aria-hidden": "true" }), () => zoomBy(1 / ZOOM_STEP), { disabled: view.zoom <= MIN_ZOOM2, keys: ["\u2212"] }),
16019
+ control(labels.fit, /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(ScanIcon, { "aria-hidden": "true" }), () => moveView(fitView(layout, size), true)),
16020
+ control(labels.reset, /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(RotateCcwIcon, { "aria-hidden": "true" }), resetView, { keys: ["0"] }),
16021
+ control(
16022
+ fullscreen ? labels.exitFullscreen : labels.fullscreen,
16023
+ fullscreen ? /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(Minimize2Icon, { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(Maximize2Icon, { "aria-hidden": "true" }),
16024
+ toggleFullscreen,
16025
+ { ref: fullscreenButtonRef, ...fullscreen ? { keys: ["Esc"] } : {} }
16026
+ )
16027
+ ] })
16028
+ ] })
16029
+ ] })
16030
+ ] })
16031
+ ]
16032
+ }
16033
+ ),
16034
+ legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": labels.legend, children: legend.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("li", { "data-type": entry.id, children: entry.label }, entry.id)) }),
16035
+ showList && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
16036
+ EquivalentRelationshipList,
16037
+ {
16038
+ nodes: bounded.nodes,
16039
+ edges: bounded.edges,
16040
+ selectedId,
16041
+ labels,
16042
+ onSelect: (id) => {
16043
+ const node = bounded.nodes.find((candidate) => candidate.id === id);
16044
+ if (node) onSelect?.(node);
15848
16045
  },
15849
- column.depth
15850
- )),
15851
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("div", { role: "group", "aria-roledescription": labels.roleDescription, "aria-label": labels.graphSummary(bounded.nodes.length, bounded.edges.length), "aria-describedby": hintId, children: layout.items.map((item) => {
15852
- const selected = item.id === selectedItemId;
15853
- const dimmed = item.node ? dimmedNodeIds.has(item.node.id) : item.cluster.members.every((member) => dimmedNodeIds.has(member.id));
15854
- const body = item.cluster ? renderCluster?.(item.cluster) ?? /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(import_jsx_runtime100.Fragment, { children: [
15855
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-label", children: labels.cluster(item.cluster.members.length, item.cluster.type, item.cluster.edgeLabel) }),
15856
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-meta", children: labels.expandCluster })
15857
- ] }) : renderNode?.(item.node, { selected, dimmed }) ?? /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(import_jsx_runtime100.Fragment, { children: [
15858
- /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-label", children: item.node.label }),
15859
- (item.node.type || item.node.description) && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-meta", children: [item.node.type, item.node.description].filter(Boolean).join(" \xB7 ") })
15860
- ] });
15861
- return /* @__PURE__ */ (0, import_jsx_runtime100.jsxs)(
15862
- "button",
15863
- {
15864
- ref: (element) => {
15865
- if (element) itemRefs.current.set(item.id, element);
15866
- else itemRefs.current.delete(item.id);
15867
- },
15868
- type: "button",
15869
- className: "uix-relationship-graph__item",
15870
- "data-kind": item.kind,
15871
- "data-node-id": item.id,
15872
- "data-selected": selected || void 0,
15873
- "data-dimmed": dimmed || void 0,
15874
- "data-highlighted": item.node && highlightedNodeIds.has(item.node.id) || void 0,
15875
- "data-conflicted": item.node && conflictedNodeIds.has(item.node.id) || void 0,
15876
- "data-root": item.id === rootItemId || void 0,
15877
- style: { left: item.x, top: item.y, width: item.width, height: item.height },
15878
- tabIndex: item.id === focusedId ? 0 : -1,
15879
- "aria-label": labelFor(item),
15880
- ...item.cluster ? { "aria-expanded": false } : { "aria-pressed": selected },
15881
- onClick: () => activate(item),
15882
- onKeyDown: (event) => onItemKeyDown(event, item),
15883
- onFocus: () => {
15884
- if (item.id === focusedId) return;
15885
- setLocalFocus(item.id);
15886
- onFocusChange?.(item.id);
15887
- },
15888
- onPointerEnter: () => setHoverId(item.id),
15889
- onPointerLeave: () => setHoverId((current) => current === item.id ? void 0 : current),
15890
- children: [
15891
- body,
15892
- item.inCycle && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("span", { className: "uix-relationship-graph__item-flag", "aria-hidden": "true", children: labels.cycle })
15893
- ]
15894
- },
15895
- item.id
15896
- );
15897
- }) }),
15898
- layout.items.filter((item) => item.expandedGroup).map((item) => {
15899
- const group = item.expandedGroup;
15900
- return /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
15901
- "button",
15902
- {
15903
- type: "button",
15904
- className: "uix-relationship-graph__collapse",
15905
- "aria-expanded": "true",
15906
- style: { left: item.x, top: item.y - 26, width: item.width },
15907
- onClick: () => toggleCluster(group.id, group.id),
15908
- children: labels.collapseCluster(group.members.length, group.type)
15909
- },
15910
- `collapse:${group.id}`
15911
- );
15912
- })
15913
- ] })
15914
- }
15915
- ),
15916
- legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": labels.legend, children: legend.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime100.jsx)("li", { "data-type": entry.id, children: entry.label }, entry.id)) }),
15917
- showList && /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
15918
- EquivalentRelationshipList,
15919
- {
15920
- nodes: bounded.nodes,
15921
- edges: bounded.edges,
15922
- selectedId,
15923
- labels,
15924
- onSelect: (id) => {
15925
- const node = bounded.nodes.find((candidate) => candidate.id === id);
15926
- if (node) onSelect?.(node);
15927
- },
15928
- onExpandNeighbors,
15929
- onOpenDetails
15930
- }
15931
- )
15932
- ] });
16046
+ onExpandNeighbors,
16047
+ onOpenDetails
16048
+ }
16049
+ )
16050
+ ]
16051
+ }
16052
+ );
15933
16053
  }
15934
16054
 
15935
16055
  // src/components/RelationshipGraph.tsx
@@ -15953,6 +16073,10 @@ var DEFAULT_RELATIONSHIP_GRAPH_LABELS = {
15953
16073
  fit: "Fit",
15954
16074
  reset: "Reset view",
15955
16075
  zoomLevel: (percent) => `Zoom ${percent}%`,
16076
+ fullscreen: "Full screen",
16077
+ exitFullscreen: "Exit full screen",
16078
+ navigationHint: (modifier) => `Drag to move \xB7 ${modifier} + scroll to zoom`,
16079
+ viewportKeyboardHint: "Arrow keys move the map, + and \u2212 zoom, 0 resets the view, Escape leaves full screen.",
15956
16080
  bounded: (shown, omitted) => `Showing ${shown} nodes; ${omitted} outside this bounded neighborhood.`,
15957
16081
  graphSummary: (nodes, edges) => `Graph with ${nodes} nodes and ${edges} relationships`,
15958
16082
  relationship: "Relationship",
package/dist/index.d.cts CHANGED
@@ -6053,6 +6053,13 @@ interface RelationshipGraphLabels {
6053
6053
  fit: string;
6054
6054
  reset: string;
6055
6055
  zoomLevel: (percent: number) => string;
6056
+ /** Layered `controls="compact"`: the full-screen toggle, entering and leaving. */
6057
+ fullscreen: string;
6058
+ exitFullscreen: string;
6059
+ /** Layered `controls="compact"`: how to move the map; `modifier` is "Ctrl" or "⌘" for the viewer's platform. */
6060
+ navigationHint: (modifier: string) => string;
6061
+ /** Layered `controls="compact"`: the map's keyboard, read when the map itself has focus. */
6062
+ viewportKeyboardHint: string;
6056
6063
  bounded: (shown: number, omitted: number) => string;
6057
6064
  graphSummary: (nodes: number, edges: number) => string;
6058
6065
  relationship: string;
@@ -6122,6 +6129,21 @@ interface RelationshipGraphProps {
6122
6129
  showList?: boolean;
6123
6130
  /** Layered: CSS length for the viewport height. */
6124
6131
  height?: string;
6132
+ /**
6133
+ * Layered: the view controls. `full` (default) is the labelled toolbar above the map.
6134
+ * `compact` is a map-style icon cluster in the map's corner (+, −, fit, reset, full screen,
6135
+ * each with a tooltip) with the zoom level beside it; the map itself takes keyboard focus
6136
+ * (arrows pan, + and − zoom, 0 resets, Escape leaves full screen), a plain wheel scrolls the
6137
+ * page and says how to zoom, and Ctrl/⌘ + wheel or a pinch zooms (a plain wheel too, in full
6138
+ * screen). `none` draws no controls: drag, pinch and Ctrl/⌘ + wheel still work.
6139
+ */
6140
+ controls?: 'full' | 'compact' | 'none';
6141
+ /**
6142
+ * Layered: the first view, and the one Reset returns to. `readable` (default) fits the graph
6143
+ * but never below 75 %, starting at the top-left when it does not fit (ADR-0003). `fit` shows
6144
+ * every item on load, down to the minimum zoom.
6145
+ */
6146
+ initialView?: 'readable' | 'fit';
6125
6147
  /** Layered: controlled roving focus. */
6126
6148
  focusId?: string;
6127
6149
  onFocusChange?: (id: string) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tensor_1/react",
3
- "version": "2.44.1",
3
+ "version": "2.45.1",
4
4
  "description": "Thin React wrappers over the UIx v2 CSS component library.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",