@tensor_1/react 2.17.0 → 2.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -50,6 +50,7 @@ __export(src_exports, {
50
50
  ConfirmDialog: () => ConfirmDialog,
51
51
  ContentList: () => ContentList,
52
52
  ContentListItem: () => ContentListItem,
53
+ DEFAULT_RELATIONSHIP_GRAPH_LABELS: () => DEFAULT_RELATIONSHIP_GRAPH_LABELS,
53
54
  DateRangePicker: () => DateRangePicker,
54
55
  DescriptionItem: () => DescriptionItem,
55
56
  DescriptionList: () => DescriptionList,
@@ -161,6 +162,8 @@ __export(src_exports, {
161
162
  buildThreeWayDiff: () => buildThreeWayDiff,
162
163
  clampMetricValue: () => clampMetricValue,
163
164
  clampWidth: () => clampWidth,
165
+ classifyLayeredEdges: () => classifyLayeredEdges,
166
+ clusterIdFor: () => clusterIdFor,
164
167
  computePosition: () => computePosition,
165
168
  contrastRatio: () => contrastRatio,
166
169
  cx: () => cx,
@@ -175,6 +178,8 @@ __export(src_exports, {
175
178
  isDateInRange: () => isDateInRange,
176
179
  isDateUnavailable: () => isDateUnavailable,
177
180
  isRuleGroup: () => isRuleGroup,
181
+ layeredNeighbor: () => layeredNeighbor,
182
+ layoutLayeredGraph: () => layoutLayeredGraph,
178
183
  layoutRelationshipGraph: () => layoutRelationshipGraph,
179
184
  mapRuleGroup: () => mapRuleGroup,
180
185
  matchFilter: () => matchFilter,
@@ -216,6 +221,7 @@ __export(src_exports, {
216
221
  useVirtualRows: () => useVirtualRows,
217
222
  validateRuleDefinition: () => validateRuleDefinition,
218
223
  virtualWindow: () => virtualWindow,
224
+ wrapEdgeLabel: () => wrapEdgeLabel,
219
225
  zonedDateKey: () => zonedDateKey,
220
226
  zonedDateSpan: () => zonedDateSpan
221
227
  });
@@ -4307,7 +4313,7 @@ function DateRangePicker({
4307
4313
  }
4308
4314
 
4309
4315
  // src/components/RelationshipGraph.tsx
4310
- var import_react40 = require("react");
4316
+ var import_react41 = require("react");
4311
4317
 
4312
4318
  // src/relationship-graph-model.ts
4313
4319
  function boundRelationshipGraph(nodes, edges, maxNodes = 40) {
@@ -4372,37 +4378,955 @@ function traverseRelationshipNode(nodes, currentId, direction) {
4372
4378
  const current = Math.max(0, nodes.findIndex((node) => node.id === currentId));
4373
4379
  return nodes[(current + direction + nodes.length) % nodes.length]?.id;
4374
4380
  }
4381
+ var LAYERED_GEOMETRY = {
4382
+ standard: { nodeWidth: 176, nodeHeight: 76, columnGap: 96, rowGap: 12 },
4383
+ compact: { nodeWidth: 160, nodeHeight: 60, columnGap: 80, rowGap: 8 }
4384
+ };
4385
+ var LAYERED_PADDING = 16;
4386
+ var LAYERED_HEADER = 28;
4387
+ var GROUP_HEADER = 28;
4388
+ var AUTO_LABEL_MAX_FANOUT = 4;
4389
+ var LABEL_CHAR_WIDTH = 5.6;
4390
+ var LABEL_MAX_LINES = 2;
4391
+ function wrapEdgeLabel(label, width) {
4392
+ const max = Math.max(4, Math.floor(width / LABEL_CHAR_WIDTH));
4393
+ const clip = (word) => word.length > max ? `${word.slice(0, max - 1)}\u2026` : word;
4394
+ const lines = [];
4395
+ let current = "";
4396
+ for (const word of label.split(/\s+/).filter(Boolean)) {
4397
+ const next = current ? `${current} ${word}` : word;
4398
+ if (next.length <= max) {
4399
+ current = next;
4400
+ continue;
4401
+ }
4402
+ if (current) lines.push(current);
4403
+ current = clip(word);
4404
+ if (lines.length === LABEL_MAX_LINES) break;
4405
+ }
4406
+ if (current && lines.length < LABEL_MAX_LINES) lines.push(current);
4407
+ if (lines.length === LABEL_MAX_LINES) {
4408
+ const consumed = lines.join(" ").replace(/…$/, "").length;
4409
+ if (consumed < label.replace(/\s+/g, " ").trim().length) {
4410
+ const last = lines[LABEL_MAX_LINES - 1];
4411
+ lines[LABEL_MAX_LINES - 1] = last.endsWith("\u2026") ? last : `${last.slice(0, Math.max(1, max - 1))}\u2026`;
4412
+ }
4413
+ }
4414
+ return lines;
4415
+ }
4416
+ var drawableEdge = (edge, ids) => ids.has(edge.source) && ids.has(edge.target) && edge.source !== edge.target;
4417
+ function classifyLayeredEdges(nodes, edges, rootId = nodes[0]?.id) {
4418
+ const ids = new Set(nodes.map((node) => node.id));
4419
+ const root = rootId !== void 0 && ids.has(rootId) ? rootId : nodes[0]?.id;
4420
+ const outgoing = /* @__PURE__ */ new Map();
4421
+ for (const edge of edges) {
4422
+ if (!drawableEdge(edge, ids)) continue;
4423
+ const list = outgoing.get(edge.source);
4424
+ if (list) list.push(edge.target);
4425
+ else outgoing.set(edge.source, [edge.target]);
4426
+ }
4427
+ const bfs = /* @__PURE__ */ new Map();
4428
+ if (root !== void 0) {
4429
+ bfs.set(root, 0);
4430
+ const queue = [root];
4431
+ for (let head = 0; head < queue.length; head += 1) {
4432
+ const current = queue[head];
4433
+ for (const next of outgoing.get(current) ?? []) {
4434
+ if (!bfs.has(next)) {
4435
+ bfs.set(next, bfs.get(current) + 1);
4436
+ queue.push(next);
4437
+ }
4438
+ }
4439
+ }
4440
+ }
4441
+ const depthById = /* @__PURE__ */ new Map();
4442
+ let deepest = 0;
4443
+ for (const node of nodes) {
4444
+ const supplied = node.depth !== void 0 && Number.isFinite(node.depth) ? Math.max(0, Math.floor(node.depth)) : void 0;
4445
+ const depth = supplied ?? bfs.get(node.id);
4446
+ if (depth === void 0) continue;
4447
+ depthById.set(node.id, depth);
4448
+ deepest = Math.max(deepest, depth);
4449
+ }
4450
+ for (const node of nodes) if (!depthById.has(node.id)) depthById.set(node.id, deepest + 1);
4451
+ const forwardOut = /* @__PURE__ */ new Map();
4452
+ const kindById = /* @__PURE__ */ new Map();
4453
+ const backEdges = [];
4454
+ for (const edge of edges) {
4455
+ if (!drawableEdge(edge, ids)) continue;
4456
+ const from = depthById.get(edge.source);
4457
+ const to = depthById.get(edge.target);
4458
+ if (to === from + 1) {
4459
+ kindById.set(edge.id, "forward");
4460
+ const list = forwardOut.get(edge.source);
4461
+ if (list) list.push(edge.target);
4462
+ else forwardOut.set(edge.source, [edge.target]);
4463
+ } else if (to < from) {
4464
+ backEdges.push(edge);
4465
+ } else {
4466
+ kindById.set(edge.id, "lateral");
4467
+ }
4468
+ }
4469
+ for (const edge of backEdges) {
4470
+ const limit = depthById.get(edge.source);
4471
+ const seen = /* @__PURE__ */ new Set([edge.target]);
4472
+ const stack = [edge.target];
4473
+ let closes = false;
4474
+ while (stack.length > 0 && !closes) {
4475
+ const current = stack.pop();
4476
+ for (const next of forwardOut.get(current) ?? []) {
4477
+ if (next === edge.source) {
4478
+ closes = true;
4479
+ break;
4480
+ }
4481
+ if (!seen.has(next) && depthById.get(next) < limit) {
4482
+ seen.add(next);
4483
+ stack.push(next);
4484
+ }
4485
+ }
4486
+ }
4487
+ kindById.set(edge.id, closes ? "cycle" : "lateral");
4488
+ }
4489
+ return { depthById, kindById };
4490
+ }
4491
+ function clusterIdFor(parentId, type, edgeType) {
4492
+ return `cluster:${parentId}:${type ?? ""}:${edgeType ?? ""}`;
4493
+ }
4494
+ var mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
4495
+ var round1 = (value) => Math.round(value * 10) / 10;
4496
+ function layoutLayeredGraph(nodes, edges, options = {}) {
4497
+ const { nodeWidth, nodeHeight, columnGap, rowGap } = LAYERED_GEOMETRY[options.density ?? "standard"];
4498
+ const threshold = options.clusterThreshold ?? 6;
4499
+ const expanded = options.expandedClusterIds ?? /* @__PURE__ */ new Set();
4500
+ const rootId = options.rootId !== void 0 && nodes.some((node) => node.id === options.rootId) ? options.rootId : nodes[0]?.id;
4501
+ if (nodes.length === 0) {
4502
+ return {
4503
+ rootId,
4504
+ items: [],
4505
+ edges: [],
4506
+ clusters: [],
4507
+ columns: [],
4508
+ itemOf: {},
4509
+ width: 0,
4510
+ height: 0,
4511
+ nodeWidth,
4512
+ nodeHeight,
4513
+ headerHeight: LAYERED_HEADER
4514
+ };
4515
+ }
4516
+ const { depthById, kindById } = classifyLayeredEdges(nodes, edges, rootId);
4517
+ const drawable = edges.filter((edge) => kindById.has(edge.id));
4518
+ const forwardParents = /* @__PURE__ */ new Map();
4519
+ const forwardChildCount = /* @__PURE__ */ new Map();
4520
+ const inCycle = /* @__PURE__ */ new Set();
4521
+ const nodeDegree = /* @__PURE__ */ new Map();
4522
+ for (const edge of drawable) {
4523
+ nodeDegree.set(edge.source, (nodeDegree.get(edge.source) ?? 0) + 1);
4524
+ nodeDegree.set(edge.target, (nodeDegree.get(edge.target) ?? 0) + 1);
4525
+ if (kindById.get(edge.id) === "forward") {
4526
+ const list = forwardParents.get(edge.target);
4527
+ if (list) list.push(edge);
4528
+ else forwardParents.set(edge.target, [edge]);
4529
+ forwardChildCount.set(edge.source, (forwardChildCount.get(edge.source) ?? 0) + 1);
4530
+ } else {
4531
+ inCycle.add(edge.source);
4532
+ inCycle.add(edge.target);
4533
+ }
4534
+ }
4535
+ const groups = /* @__PURE__ */ new Map();
4536
+ if (threshold > 0) {
4537
+ for (const node of nodes) {
4538
+ if (node.id === rootId || inCycle.has(node.id) || (forwardChildCount.get(node.id) ?? 0) > 0) continue;
4539
+ const parents = forwardParents.get(node.id) ?? [];
4540
+ if (parents.length !== 1) continue;
4541
+ const via = parents[0];
4542
+ const id = clusterIdFor(via.source, node.type, via.type);
4543
+ const group = groups.get(id);
4544
+ if (group) {
4545
+ group.members.push(node);
4546
+ continue;
4547
+ }
4548
+ const created = { id, parentId: via.source, depth: depthById.get(node.id), members: [node] };
4549
+ if (node.type !== void 0) created.type = node.type;
4550
+ if (via.type !== void 0) created.edgeType = via.type;
4551
+ const edgeLabel = via.label ?? via.type;
4552
+ if (edgeLabel !== void 0) created.edgeLabel = edgeLabel;
4553
+ groups.set(id, created);
4554
+ }
4555
+ }
4556
+ const clusterById = /* @__PURE__ */ new Map();
4557
+ const itemOf = {};
4558
+ const expandedGroupOf = /* @__PURE__ */ new Map();
4559
+ for (const group of groups.values()) {
4560
+ if (group.members.length < threshold) continue;
4561
+ if (expanded.has(group.id)) {
4562
+ for (const member of group.members) expandedGroupOf.set(member.id, group);
4563
+ continue;
4564
+ }
4565
+ clusterById.set(group.id, group);
4566
+ for (const member of group.members) itemOf[member.id] = group.id;
4567
+ }
4568
+ for (const node of nodes) if (itemOf[node.id] === void 0) itemOf[node.id] = node.id;
4569
+ const columnsByDepth = /* @__PURE__ */ new Map();
4570
+ const itemById = /* @__PURE__ */ new Map();
4571
+ for (const node of nodes) {
4572
+ const id = itemOf[node.id];
4573
+ if (itemById.has(id)) continue;
4574
+ const cluster = clusterById.get(id);
4575
+ const depth = depthById.get(node.id);
4576
+ const item = {
4577
+ id,
4578
+ kind: cluster ? "cluster" : "node",
4579
+ depth,
4580
+ index: 0,
4581
+ x: 0,
4582
+ y: 0,
4583
+ width: nodeWidth,
4584
+ height: nodeHeight,
4585
+ parents: [],
4586
+ children: [],
4587
+ inCycle: cluster ? false : inCycle.has(node.id),
4588
+ degree: cluster ? cluster.members.length : nodeDegree.get(node.id) ?? 0
4589
+ };
4590
+ if (cluster) item.cluster = cluster;
4591
+ else item.node = node;
4592
+ itemById.set(id, item);
4593
+ const column = columnsByDepth.get(depth);
4594
+ if (column) column.push(item);
4595
+ else columnsByDepth.set(depth, [item]);
4596
+ }
4597
+ for (const edge of drawable) {
4598
+ if (kindById.get(edge.id) !== "forward") continue;
4599
+ const from = itemById.get(itemOf[edge.source]);
4600
+ const to = itemById.get(itemOf[edge.target]);
4601
+ if (!from.children.includes(to.id)) from.children.push(to.id);
4602
+ if (!to.parents.includes(from.id)) to.parents.push(from.id);
4603
+ }
4604
+ const depths = [...columnsByDepth.keys()].sort((a, b) => a - b);
4605
+ const position = /* @__PURE__ */ new Map();
4606
+ const reindex = (column) => {
4607
+ column.forEach((item, index) => {
4608
+ item.index = index;
4609
+ position.set(item.id, index);
4610
+ });
4611
+ };
4612
+ for (const depth of depths) reindex(columnsByDepth.get(depth));
4613
+ const groupOfItem = (item) => item.node ? expandedGroupOf.get(item.node.id) : void 0;
4614
+ const sweep = (depth, neighbours) => {
4615
+ const keyed = columnsByDepth.get(depth).map((item) => {
4616
+ const around = neighbours(item).map((id) => position.get(id)).filter((value) => value !== void 0);
4617
+ return { item, key: around.length > 0 ? mean(around) : item.index, tie: item.index };
4618
+ });
4619
+ keyed.sort((a, b) => a.key - b.key || a.tie - b.tie);
4620
+ const ordered = [];
4621
+ const placed = /* @__PURE__ */ new Set();
4622
+ for (const { item } of keyed) {
4623
+ if (placed.has(item.id)) continue;
4624
+ const group = groupOfItem(item);
4625
+ for (const candidate of group ? keyed.map((entry) => entry.item).filter((other) => groupOfItem(other) === group) : [item]) {
4626
+ if (placed.has(candidate.id)) continue;
4627
+ ordered.push(candidate);
4628
+ placed.add(candidate.id);
4629
+ }
4630
+ }
4631
+ columnsByDepth.set(depth, ordered);
4632
+ reindex(ordered);
4633
+ };
4634
+ const inner = depths.slice(1);
4635
+ for (const depth of inner) sweep(depth, (item) => item.parents);
4636
+ for (const depth of [...inner].reverse()) sweep(depth, (item) => item.children);
4637
+ for (const depth of inner) sweep(depth, (item) => item.parents);
4638
+ const top = LAYERED_PADDING + LAYERED_HEADER;
4639
+ const columns = [];
4640
+ let width = 0;
4641
+ let height = 0;
4642
+ for (const depth of depths) {
4643
+ const column = columnsByDepth.get(depth);
4644
+ const x = LAYERED_PADDING + depth * (nodeWidth + columnGap);
4645
+ let cursor = top;
4646
+ let count = 0;
4647
+ const ledGroups = /* @__PURE__ */ new Set();
4648
+ for (const item of column) {
4649
+ const parentCentres = item.parents.map((id) => itemById.get(id)).filter((parent) => parent.depth < depth).map((parent) => parent.y + parent.height / 2);
4650
+ const group = groupOfItem(item);
4651
+ const leadsGroup = group !== void 0 && !ledGroups.has(group);
4652
+ if (group) ledGroups.add(group);
4653
+ const floor = cursor + (leadsGroup ? GROUP_HEADER : 0);
4654
+ const target = parentCentres.length > 0 ? mean(parentCentres) - item.height / 2 : floor;
4655
+ item.x = x;
4656
+ item.y = round1(Math.max(target, floor));
4657
+ if (leadsGroup) item.expandedGroup = group;
4658
+ cursor = item.y + item.height + rowGap;
4659
+ count += item.cluster ? item.cluster.members.length : 1;
4660
+ width = Math.max(width, x + item.width);
4661
+ height = Math.max(height, item.y + item.height);
4662
+ }
4663
+ columns.push({ depth, x, count });
4664
+ }
4665
+ const byPosition = (a, b) => (position.get(a) ?? 0) - (position.get(b) ?? 0);
4666
+ for (const item of itemById.values()) {
4667
+ item.children.sort(byPosition);
4668
+ item.parents.sort(byPosition);
4669
+ }
4670
+ const routed = /* @__PURE__ */ new Map();
4671
+ for (const edge of drawable) {
4672
+ const kind = kindById.get(edge.id);
4673
+ const from = itemById.get(itemOf[edge.source]);
4674
+ const to = itemById.get(itemOf[edge.target]);
4675
+ const key = from.kind === "cluster" || to.kind === "cluster" ? `${from.id}->${to.id}` : edge.id;
4676
+ const existing = routed.get(key);
4677
+ if (existing) {
4678
+ existing.edgeIds.push(edge.id);
4679
+ continue;
4680
+ }
4681
+ let startX;
4682
+ let startY;
4683
+ let endX;
4684
+ let endY;
4685
+ let path;
4686
+ let labelX;
4687
+ let labelY;
4688
+ let startAngle;
4689
+ let endAngle;
4690
+ if (kind === "forward") {
4691
+ startX = from.x + from.width;
4692
+ startY = from.y + from.height / 2;
4693
+ endX = to.x;
4694
+ endY = to.y + to.height / 2;
4695
+ const bend = Math.max(columnGap / 2, 24);
4696
+ path = `M${round1(startX)} ${round1(startY)} C${round1(startX + bend)} ${round1(startY)} ${round1(endX - bend)} ${round1(endY)} ${round1(endX)} ${round1(endY)}`;
4697
+ labelX = (startX + endX) / 2;
4698
+ labelY = (startY + endY) / 2;
4699
+ startAngle = Math.PI;
4700
+ endAngle = 0;
4701
+ } else if (from.depth === to.depth) {
4702
+ startX = from.x + from.width;
4703
+ startY = from.y + from.height / 2;
4704
+ endX = to.x + to.width;
4705
+ endY = to.y + to.height / 2;
4706
+ const bulge = Math.min(columnGap - 8, 20 + Math.abs(endY - startY) * 0.1);
4707
+ path = `M${round1(startX)} ${round1(startY)} C${round1(startX + bulge)} ${round1(startY)} ${round1(endX + bulge)} ${round1(endY)} ${round1(endX)} ${round1(endY)}`;
4708
+ labelX = startX + bulge * 0.75;
4709
+ labelY = (startY + endY) / 2;
4710
+ startAngle = Math.PI;
4711
+ endAngle = Math.PI;
4712
+ } else {
4713
+ startX = from.x + from.width / 2;
4714
+ startY = from.y + from.height;
4715
+ endX = to.x + to.width / 2;
4716
+ endY = to.y + to.height;
4717
+ const dip = 28 + Math.abs(endX - startX) * 0.12;
4718
+ const low = Math.max(startY, endY) + dip;
4719
+ path = `M${round1(startX)} ${round1(startY)} C${round1(startX)} ${round1(low)} ${round1(endX)} ${round1(low)} ${round1(endX)} ${round1(endY)}`;
4720
+ labelX = (startX + endX) / 2;
4721
+ labelY = low - dip * 0.25;
4722
+ startAngle = -Math.PI / 2;
4723
+ endAngle = -Math.PI / 2;
4724
+ height = Math.max(height, low);
4725
+ }
4726
+ const arrow = edge.arrow ?? "forward";
4727
+ const atStart = arrow === "backward";
4728
+ const entry = {
4729
+ id: key,
4730
+ source: from.id,
4731
+ target: to.id,
4732
+ kind,
4733
+ edgeIds: [edge.id],
4734
+ arrow,
4735
+ emphasis: edge.emphasis === true,
4736
+ path,
4737
+ labelX: round1(labelX),
4738
+ labelY: round1(labelY),
4739
+ arrowX: round1(atStart ? startX : endX),
4740
+ arrowY: round1(atStart ? startY : endY),
4741
+ arrowAngle: atStart ? startAngle : endAngle,
4742
+ labelLines: [],
4743
+ autoLabel: to.kind !== "cluster" && (edge.emphasis === true || kind === "forward" && from.children.length <= AUTO_LABEL_MAX_FANOUT)
4744
+ };
4745
+ if (edge.type !== void 0) entry.type = edge.type;
4746
+ const label = edge.label ?? edge.type;
4747
+ if (label !== void 0) entry.label = label;
4748
+ routed.set(key, entry);
4749
+ }
4750
+ const seenLabels = /* @__PURE__ */ new Set();
4751
+ for (const entry of routed.values()) {
4752
+ if (entry.label === void 0) continue;
4753
+ entry.labelLines = wrapEdgeLabel(entry.label, columnGap - 8);
4754
+ if (!entry.autoLabel || entry.emphasis || entry.kind !== "forward") continue;
4755
+ const key = `${entry.source}|${entry.label}`;
4756
+ if (seenLabels.has(key)) entry.autoLabel = false;
4757
+ else seenLabels.add(key);
4758
+ }
4759
+ return {
4760
+ rootId,
4761
+ items: depths.flatMap((depth) => columnsByDepth.get(depth)),
4762
+ edges: [...routed.values()],
4763
+ clusters: [...clusterById.values()],
4764
+ columns,
4765
+ itemOf,
4766
+ width: round1(width + LAYERED_PADDING),
4767
+ height: round1(height + LAYERED_PADDING + 8),
4768
+ nodeWidth,
4769
+ nodeHeight,
4770
+ headerHeight: LAYERED_HEADER
4771
+ };
4772
+ }
4773
+ function layeredNeighbor(layout, currentId, key) {
4774
+ const root = layout.rootId !== void 0 ? layout.itemOf[layout.rootId] : layout.items[0]?.id;
4775
+ const current = layout.items.find((item) => item.id === currentId);
4776
+ if (!current) return root ?? layout.items[0]?.id;
4777
+ const column = layout.items.filter((item) => item.depth === current.depth);
4778
+ switch (key) {
4779
+ case "ArrowUp":
4780
+ return column[current.index - 1]?.id;
4781
+ case "ArrowDown":
4782
+ return column[current.index + 1]?.id;
4783
+ case "ArrowRight":
4784
+ return current.children[0];
4785
+ case "ArrowLeft":
4786
+ return current.parents[0];
4787
+ case "Home":
4788
+ return root;
4789
+ case "End":
4790
+ return column[column.length - 1]?.id;
4791
+ default:
4792
+ return void 0;
4793
+ }
4794
+ }
4375
4795
 
4376
- // src/components/RelationshipGraph.tsx
4796
+ // src/components/RelationshipGraphLayered.tsx
4797
+ var import_react40 = require("react");
4798
+
4799
+ // src/components/RelationshipGraphList.tsx
4377
4800
  var import_jsx_runtime62 = require("react/jsx-runtime");
4801
+ function EquivalentRelationshipList({
4802
+ nodes,
4803
+ edges,
4804
+ selectedId,
4805
+ labels,
4806
+ onSelect,
4807
+ onExpandNeighbors,
4808
+ onOpenDetails
4809
+ }) {
4810
+ const labelById = new Map(nodes.map((node) => [node.id, node.label]));
4811
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "uix-relationship-graph__list", role: "region", "aria-label": labels.list, children: [
4812
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("h3", { children: labels.listHeading }),
4813
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("ul", { children: nodes.map((node) => {
4814
+ const neighbors = relationshipNeighbors(node.id, edges).map((id) => labelById.get(id) ?? id);
4815
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("li", { "data-selected": selectedId === node.id || void 0, children: [
4816
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("button", { type: "button", onClick: () => onSelect(node.id), "aria-pressed": selectedId === node.id, children: [
4817
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("strong", { children: node.label }),
4818
+ node.type && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: node.type }),
4819
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: neighbors.length ? labels.connectedTo(neighbors.join(", ")) : labels.noConnections })
4820
+ ] }),
4821
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "uix-relationship-graph__list-actions", children: [
4822
+ onExpandNeighbors && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => onExpandNeighbors(node), children: labels.expandNeighbors }),
4823
+ onOpenDetails && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => onOpenDetails(node), children: labels.openDetails })
4824
+ ] })
4825
+ ] }, node.id);
4826
+ }) })
4827
+ ] });
4828
+ }
4829
+
4830
+ // src/components/RelationshipGraphLayered.tsx
4831
+ var import_jsx_runtime63 = require("react/jsx-runtime");
4378
4832
  var EMPTY_IDS = /* @__PURE__ */ new Set();
4379
- var EMPTY_LEGEND = [];
4380
- function RelationshipGraph({
4833
+ var MIN_ZOOM = 0.4;
4834
+ var MAX_ZOOM = 1.5;
4835
+ var READABLE_ZOOM = 0.75;
4836
+ var ZOOM_STEP = 1.2;
4837
+ var PAN_STEP = 64;
4838
+ var FOCUS_MARGIN = 24;
4839
+ var NAV_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"];
4840
+ var clampZoom = (zoom) => Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
4841
+ function initialView(layout, size) {
4842
+ if (size.width === 0 || size.height === 0 || layout.width === 0) return { x: 0, y: 0, zoom: 1 };
4843
+ const fit = Math.min(size.width / layout.width, size.height / layout.height);
4844
+ if (fit < READABLE_ZOOM) return { x: 0, y: 0, zoom: READABLE_ZOOM };
4845
+ const zoom = Math.min(fit, 1);
4846
+ return { x: (size.width - layout.width * zoom) / 2, y: Math.max(0, (size.height - layout.height * zoom) / 2), zoom };
4847
+ }
4848
+ function fitView(layout, size) {
4849
+ if (size.width === 0 || layout.width === 0) return { x: 0, y: 0, zoom: 1 };
4850
+ const zoom = clampZoom(Math.min(size.width / layout.width, size.height / layout.height, 1));
4851
+ return { x: (size.width - layout.width * zoom) / 2, y: Math.max(0, (size.height - layout.height * zoom) / 2), zoom };
4852
+ }
4853
+ var arrowPoints = "0,0 -8,-4.5 -8,4.5";
4854
+ function LayeredRelationshipGraph({
4381
4855
  nodes,
4382
4856
  edges,
4383
4857
  selectedId,
4384
4858
  onSelect,
4385
4859
  onExpandNeighbors,
4386
4860
  onOpenDetails,
4387
- maxNodes = 40,
4861
+ maxNodes = 400,
4388
4862
  density = "standard",
4389
4863
  highlightedNodeIds = EMPTY_IDS,
4390
4864
  conflictedNodeIds = EMPTY_IDS,
4391
4865
  highlightedEdgeIds = EMPTY_IDS,
4392
4866
  conflictedEdgeIds = EMPTY_IDS,
4393
- legend = EMPTY_LEGEND,
4394
- loading,
4395
- error,
4396
- onRetry,
4397
- className
4867
+ legend = [],
4868
+ className,
4869
+ labels,
4870
+ rootId,
4871
+ renderNode,
4872
+ nodeAriaLabel,
4873
+ renderCluster,
4874
+ clusterThreshold,
4875
+ expandedClusterIds,
4876
+ onToggleCluster,
4877
+ dimmedNodeIds = EMPTY_IDS,
4878
+ edgeLabels = "auto",
4879
+ showList = true,
4880
+ height,
4881
+ focusId,
4882
+ onFocusChange
4398
4883
  }) {
4884
+ const hintId = (0, import_react40.useId)();
4399
4885
  const bounded = (0, import_react40.useMemo)(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
4400
- const positioned = (0, import_react40.useMemo)(() => layoutRelationshipGraph(bounded.nodes, bounded.edges, selectedId), [bounded, selectedId]);
4401
- const positionedById = (0, import_react40.useMemo)(() => new Map(positioned.map((node) => [node.id, node])), [positioned]);
4402
- const [zoom, setZoom] = (0, import_react40.useState)(1);
4403
- const [pan, setPan] = (0, import_react40.useState)({ x: 0, y: 0 });
4404
- const [focusedId, setFocusedId] = (0, import_react40.useState)(selectedId ?? positioned[0]?.id);
4405
- const svgRef = (0, import_react40.useRef)(null);
4886
+ const [localExpanded, setLocalExpanded] = (0, import_react40.useState)(EMPTY_IDS);
4887
+ const expanded = expandedClusterIds ?? localExpanded;
4888
+ const layout = (0, import_react40.useMemo)(
4889
+ () => layoutLayeredGraph(bounded.nodes, bounded.edges, {
4890
+ ...rootId !== void 0 ? { rootId } : {},
4891
+ ...clusterThreshold !== void 0 ? { clusterThreshold } : {},
4892
+ expandedClusterIds: expanded,
4893
+ density
4894
+ }),
4895
+ [bounded, rootId, clusterThreshold, expanded, density]
4896
+ );
4897
+ const itemById = (0, import_react40.useMemo)(() => new Map(layout.items.map((item) => [item.id, item])), [layout]);
4898
+ const viewportRef = (0, import_react40.useRef)(null);
4899
+ const itemRefs = (0, import_react40.useRef)(/* @__PURE__ */ new Map());
4900
+ const [size, setSize] = (0, import_react40.useState)({ width: 0, height: 0 });
4901
+ const [view, setView] = (0, import_react40.useState)({ x: 0, y: 0, zoom: 1 });
4902
+ const [animate, setAnimate] = (0, import_react40.useState)(false);
4903
+ const [localFocus, setLocalFocus] = (0, import_react40.useState)(void 0);
4904
+ const [hoverId, setHoverId] = (0, import_react40.useState)(void 0);
4905
+ const viewRef = (0, import_react40.useRef)(view);
4906
+ viewRef.current = view;
4907
+ const layoutRef = (0, import_react40.useRef)(layout);
4908
+ layoutRef.current = layout;
4909
+ const pendingFocus = (0, import_react40.useRef)(void 0);
4910
+ const initialisedFor = (0, import_react40.useRef)(void 0);
4911
+ const rootItemId = layout.rootId !== void 0 ? layout.itemOf[layout.rootId] : layout.items[0]?.id;
4912
+ const requestedFocus = focusId !== void 0 ? layout.itemOf[focusId] ?? focusId : localFocus;
4913
+ const focusedId = requestedFocus !== void 0 && itemById.has(requestedFocus) ? requestedFocus : rootItemId;
4914
+ const selectedItemId = selectedId !== void 0 ? layout.itemOf[selectedId] : void 0;
4915
+ const activeIds = (0, import_react40.useMemo)(
4916
+ () => new Set([hoverId, selectedItemId].filter((id) => id !== void 0)),
4917
+ [hoverId, selectedItemId]
4918
+ );
4919
+ (0, import_react40.useEffect)(() => {
4920
+ const element = viewportRef.current;
4921
+ if (!element) return;
4922
+ const measure = () => setSize({ width: element.clientWidth, height: element.clientHeight });
4923
+ measure();
4924
+ if (typeof ResizeObserver === "undefined") return;
4925
+ const observer = new ResizeObserver(measure);
4926
+ observer.observe(element);
4927
+ return () => observer.disconnect();
4928
+ }, []);
4929
+ (0, import_react40.useEffect)(() => {
4930
+ if (size.width === 0) return;
4931
+ const key = `${layout.rootId ?? ""}`;
4932
+ if (initialisedFor.current === key) return;
4933
+ initialisedFor.current = key;
4934
+ setView(initialView(layout, size));
4935
+ }, [layout, size]);
4936
+ const moveView = (0, import_react40.useCallback)((next, smooth) => {
4937
+ setAnimate(smooth);
4938
+ setView(next);
4939
+ }, []);
4940
+ const ensureVisible = (0, import_react40.useCallback)((id, smooth) => {
4941
+ const element = viewportRef.current;
4942
+ const item = layoutRef.current.items.find((candidate) => candidate.id === id);
4943
+ if (!element || !item) return;
4944
+ const { x, y, zoom } = viewRef.current;
4945
+ const width = element.clientWidth;
4946
+ const heightPx = element.clientHeight;
4947
+ const left = item.x * zoom + x;
4948
+ const top = item.y * zoom + y;
4949
+ const right = left + item.width * zoom;
4950
+ const bottom = top + item.height * zoom;
4951
+ let nextX = x;
4952
+ let nextY = y;
4953
+ if (left < FOCUS_MARGIN) nextX += FOCUS_MARGIN - left;
4954
+ else if (right > width - FOCUS_MARGIN) nextX -= right - (width - FOCUS_MARGIN);
4955
+ if (top < FOCUS_MARGIN) nextY += FOCUS_MARGIN - top;
4956
+ else if (bottom > heightPx - FOCUS_MARGIN) nextY -= bottom - (heightPx - FOCUS_MARGIN);
4957
+ if (nextX !== x || nextY !== y) moveView({ x: nextX, y: nextY, zoom }, smooth);
4958
+ }, [moveView]);
4959
+ const focusItem = (0, import_react40.useCallback)((id) => {
4960
+ setLocalFocus(id);
4961
+ onFocusChange?.(id);
4962
+ requestAnimationFrame(() => {
4963
+ itemRefs.current.get(id)?.focus({ preventScroll: true });
4964
+ ensureVisible(id, true);
4965
+ });
4966
+ }, [ensureVisible, onFocusChange]);
4967
+ (0, import_react40.useEffect)(() => {
4968
+ const wanted = pendingFocus.current;
4969
+ if (wanted !== void 0 && itemById.has(wanted)) {
4970
+ pendingFocus.current = void 0;
4971
+ focusItem(wanted);
4972
+ }
4973
+ }, [itemById, focusItem]);
4974
+ const controlledTarget = focusId !== void 0 ? layout.itemOf[focusId] ?? focusId : void 0;
4975
+ const controlledPresent = controlledTarget !== void 0 && itemById.has(controlledTarget);
4976
+ const controlledKey = `${controlledTarget ?? ""}|${controlledPresent}`;
4977
+ const lastControlledKey = (0, import_react40.useRef)(controlledKey);
4978
+ (0, import_react40.useEffect)(() => {
4979
+ if (lastControlledKey.current === controlledKey) return;
4980
+ lastControlledKey.current = controlledKey;
4981
+ if (!controlledPresent || controlledTarget === void 0) return;
4982
+ requestAnimationFrame(() => {
4983
+ itemRefs.current.get(controlledTarget)?.focus({ preventScroll: true });
4984
+ ensureVisible(controlledTarget, true);
4985
+ });
4986
+ }, [controlledKey, controlledTarget, controlledPresent, ensureVisible]);
4987
+ (0, import_react40.useEffect)(() => {
4988
+ const element = viewportRef.current;
4989
+ if (!element) return;
4990
+ const onWheel = (event) => {
4991
+ if (!event.ctrlKey && !event.metaKey) return;
4992
+ event.preventDefault();
4993
+ const rect = element.getBoundingClientRect();
4994
+ const px = event.clientX - rect.left;
4995
+ const py = event.clientY - rect.top;
4996
+ const current = viewRef.current;
4997
+ const zoom = clampZoom(current.zoom * Math.exp(-event.deltaY * 2e-3));
4998
+ const ratio = zoom / current.zoom;
4999
+ setAnimate(false);
5000
+ setView({ zoom, x: px - (px - current.x) * ratio, y: py - (py - current.y) * ratio });
5001
+ };
5002
+ element.addEventListener("wheel", onWheel, { passive: false });
5003
+ return () => element.removeEventListener("wheel", onWheel);
5004
+ }, []);
5005
+ const pointers = (0, import_react40.useRef)(/* @__PURE__ */ new Map());
5006
+ const pinchDistance = (0, import_react40.useRef)(void 0);
5007
+ const onPointerDown = (event) => {
5008
+ if (event.target.closest("button")) return;
5009
+ event.currentTarget.setPointerCapture?.(event.pointerId);
5010
+ pointers.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
5011
+ pinchDistance.current = void 0;
5012
+ setAnimate(false);
5013
+ };
5014
+ const onPointerMove = (event) => {
5015
+ const previous = pointers.current.get(event.pointerId);
5016
+ if (!previous) return;
5017
+ const current = { x: event.clientX, y: event.clientY };
5018
+ pointers.current.set(event.pointerId, current);
5019
+ const all = [...pointers.current.values()];
5020
+ if (all.length >= 2) {
5021
+ const [a, b] = all;
5022
+ const distance = Math.hypot(a.x - b.x, a.y - b.y);
5023
+ const rect = event.currentTarget.getBoundingClientRect();
5024
+ const mx = (a.x + b.x) / 2 - rect.left;
5025
+ const my = (a.y + b.y) / 2 - rect.top;
5026
+ if (pinchDistance.current !== void 0 && pinchDistance.current > 0) {
5027
+ const base = viewRef.current;
5028
+ const zoom = clampZoom(base.zoom * (distance / pinchDistance.current));
5029
+ const ratio = zoom / base.zoom;
5030
+ setView({ zoom, x: mx - (mx - base.x) * ratio, y: my - (my - base.y) * ratio });
5031
+ }
5032
+ pinchDistance.current = distance;
5033
+ return;
5034
+ }
5035
+ const dx = current.x - previous.x;
5036
+ const dy = current.y - previous.y;
5037
+ setView((value) => ({ ...value, x: value.x + dx, y: value.y + dy }));
5038
+ };
5039
+ const onPointerEnd = (event) => {
5040
+ pointers.current.delete(event.pointerId);
5041
+ pinchDistance.current = void 0;
5042
+ };
5043
+ const zoomBy = (factor) => {
5044
+ const current = viewRef.current;
5045
+ const zoom = clampZoom(current.zoom * factor);
5046
+ const ratio = zoom / current.zoom;
5047
+ const cx0 = size.width / 2;
5048
+ const cy0 = size.height / 2;
5049
+ moveView({ zoom, x: cx0 - (cx0 - current.x) * ratio, y: cy0 - (cy0 - current.y) * ratio }, true);
5050
+ };
5051
+ const panBy = (dx, dy) => moveView({ ...viewRef.current, x: viewRef.current.x + dx, y: viewRef.current.y + dy }, true);
5052
+ const toggleCluster = (clusterId, focusAfter) => {
5053
+ const isExpanded = expanded.has(clusterId);
5054
+ pendingFocus.current = focusAfter;
5055
+ if (expandedClusterIds === void 0) {
5056
+ setLocalExpanded((previous) => {
5057
+ const next = new Set(previous);
5058
+ if (isExpanded) next.delete(clusterId);
5059
+ else next.add(clusterId);
5060
+ return next;
5061
+ });
5062
+ }
5063
+ onToggleCluster?.(clusterId, !isExpanded);
5064
+ };
5065
+ const activate = (item) => {
5066
+ if (item.cluster) {
5067
+ toggleCluster(item.cluster.id, item.cluster.members[0]?.id ?? item.id);
5068
+ return;
5069
+ }
5070
+ if (item.node) {
5071
+ setLocalFocus(item.id);
5072
+ onFocusChange?.(item.id);
5073
+ onSelect?.(item.node);
5074
+ }
5075
+ };
5076
+ const onItemKeyDown = (event, item) => {
5077
+ if (NAV_KEYS.includes(event.key)) {
5078
+ event.preventDefault();
5079
+ const next = layeredNeighbor(layout, item.id, event.key);
5080
+ if (next !== void 0) focusItem(next);
5081
+ }
5082
+ };
5083
+ const labelFor = (item) => {
5084
+ if (item.cluster) {
5085
+ const cluster = item.cluster;
5086
+ return `${labels.cluster(cluster.members.length, cluster.type, cluster.edgeLabel)}, ${labels.expandCluster}`;
5087
+ }
5088
+ const node = item.node;
5089
+ const conflict = conflictedNodeIds.has(node.id) ? labels.conflict : void 0;
5090
+ if (nodeAriaLabel) return [nodeAriaLabel(node), item.inCycle ? labels.cycle : void 0, conflict].filter(Boolean).join(", ");
5091
+ return [node.label, node.type, node.description, labels.relationships(item.degree), item.inCycle ? labels.cycle : void 0, conflict].filter(Boolean).join(", ");
5092
+ };
5093
+ const showLabel = (edge) => {
5094
+ if (edgeLabels === "never" || edge.label === void 0) return false;
5095
+ if (edgeLabels === "always") return true;
5096
+ return edge.autoLabel || activeIds.has(edge.source) || activeIds.has(edge.target) || focusedId === edge.source || focusedId === edge.target;
5097
+ };
5098
+ const viewportStyle = height !== void 0 ? { "--uix-relationship-graph-height": height } : {};
5099
+ const stageStyle = {
5100
+ width: layout.width,
5101
+ height: layout.height,
5102
+ transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom})`
5103
+ };
5104
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("section", { className: cx("uix-relationship-graph", "uix-relationship-graph--layered", density === "compact" && "uix-relationship-graph--compact", className), "aria-label": labels.graph, children: [
5105
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "uix-relationship-graph__toolbar", role: "group", "aria-label": labels.controls, children: [
5106
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => zoomBy(ZOOM_STEP), disabled: view.zoom >= MAX_ZOOM, children: labels.zoomIn }),
5107
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => zoomBy(1 / ZOOM_STEP), disabled: view.zoom <= MIN_ZOOM, children: labels.zoomOut }),
5108
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => moveView(fitView(layout, size), true), children: labels.fit }),
5109
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => moveView(initialView(layout, size), true), children: labels.reset }),
5110
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => panBy(PAN_STEP, 0), "aria-label": labels.panLeft, children: labels.left }),
5111
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => panBy(0, PAN_STEP), "aria-label": labels.panUp, children: labels.up }),
5112
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => panBy(0, -PAN_STEP), "aria-label": labels.panDown, children: labels.down }),
5113
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => panBy(-PAN_STEP, 0), "aria-label": labels.panRight, children: labels.right }),
5114
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { "aria-live": "polite", children: labels.zoomLevel(Math.round(view.zoom * 100)) })
5115
+ ] }),
5116
+ bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { className: "uix-relationship-graph__bounded", role: "status", children: labels.bounded(bounded.nodes.length, bounded.omittedNodeCount) }),
5117
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { id: hintId, className: "uix-visually-hidden", children: labels.keyboardHint }),
5118
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5119
+ "div",
5120
+ {
5121
+ ref: viewportRef,
5122
+ className: "uix-relationship-graph__viewport",
5123
+ style: viewportStyle,
5124
+ onPointerDown,
5125
+ onPointerMove,
5126
+ onPointerUp: onPointerEnd,
5127
+ onPointerCancel: onPointerEnd,
5128
+ children: /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "uix-relationship-graph__stage", style: stageStyle, "data-animate": animate || void 0, onTransitionEnd: () => setAnimate(false), children: [
5129
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("svg", { className: "uix-relationship-graph__edges", width: layout.width, height: layout.height, "aria-hidden": "true", focusable: "false", children: layout.edges.map((edge) => {
5130
+ const active = activeIds.has(edge.source) || activeIds.has(edge.target) || edge.edgeIds.some((id) => highlightedEdgeIds.has(id));
5131
+ const angle = edge.arrowAngle * 180 / Math.PI;
5132
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(
5133
+ "g",
5134
+ {
5135
+ className: "uix-relationship-graph__link",
5136
+ "data-kind": edge.kind,
5137
+ "data-active": active || void 0,
5138
+ "data-emphasis": edge.emphasis || void 0,
5139
+ "data-conflicted": edge.edgeIds.some((id) => conflictedEdgeIds.has(id)) || void 0,
5140
+ "data-dimmed": dimmedNodeIds.has(edge.source) && dimmedNodeIds.has(edge.target) || void 0,
5141
+ children: [
5142
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("path", { d: edge.path }),
5143
+ edge.arrow !== "none" && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("polygon", { points: arrowPoints, transform: `translate(${edge.arrowX} ${edge.arrowY}) rotate(${angle})` }),
5144
+ showLabel(edge) && /* @__PURE__ */ (0, import_jsx_runtime63.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_runtime63.jsx)("tspan", { x: edge.labelX, dy: index === 0 ? `${-(lines.length - 1) * 0.55}em` : "1.1em", children: line }, index)) })
5145
+ ]
5146
+ },
5147
+ edge.id
5148
+ );
5149
+ }) }),
5150
+ layout.columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5151
+ "div",
5152
+ {
5153
+ className: "uix-relationship-graph__column",
5154
+ "aria-hidden": "true",
5155
+ style: { left: column.x, width: layout.nodeWidth },
5156
+ children: labels.column(column.depth, column.count)
5157
+ },
5158
+ column.depth
5159
+ )),
5160
+ /* @__PURE__ */ (0, import_jsx_runtime63.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) => {
5161
+ const selected = item.id === selectedItemId;
5162
+ const dimmed = item.node ? dimmedNodeIds.has(item.node.id) : item.cluster.members.every((member) => dimmedNodeIds.has(member.id));
5163
+ const body = item.cluster ? renderCluster?.(item.cluster) ?? /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(import_jsx_runtime63.Fragment, { children: [
5164
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-label", children: labels.cluster(item.cluster.members.length, item.cluster.type, item.cluster.edgeLabel) }),
5165
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-meta", children: labels.expandCluster })
5166
+ ] }) : renderNode?.(item.node, { selected, dimmed }) ?? /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(import_jsx_runtime63.Fragment, { children: [
5167
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-label", children: item.node.label }),
5168
+ (item.node.type || item.node.description) && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-meta", children: [item.node.type, item.node.description].filter(Boolean).join(" \xB7 ") })
5169
+ ] });
5170
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(
5171
+ "button",
5172
+ {
5173
+ ref: (element) => {
5174
+ if (element) itemRefs.current.set(item.id, element);
5175
+ else itemRefs.current.delete(item.id);
5176
+ },
5177
+ type: "button",
5178
+ className: "uix-relationship-graph__item",
5179
+ "data-kind": item.kind,
5180
+ "data-node-id": item.id,
5181
+ "data-selected": selected || void 0,
5182
+ "data-dimmed": dimmed || void 0,
5183
+ "data-highlighted": item.node && highlightedNodeIds.has(item.node.id) || void 0,
5184
+ "data-conflicted": item.node && conflictedNodeIds.has(item.node.id) || void 0,
5185
+ "data-root": item.id === rootItemId || void 0,
5186
+ style: { left: item.x, top: item.y, width: item.width, height: item.height },
5187
+ tabIndex: item.id === focusedId ? 0 : -1,
5188
+ "aria-label": labelFor(item),
5189
+ ...item.cluster ? { "aria-expanded": false } : { "aria-pressed": selected },
5190
+ onClick: () => activate(item),
5191
+ onKeyDown: (event) => onItemKeyDown(event, item),
5192
+ onFocus: () => {
5193
+ if (item.id === focusedId) return;
5194
+ setLocalFocus(item.id);
5195
+ onFocusChange?.(item.id);
5196
+ },
5197
+ onPointerEnter: () => setHoverId(item.id),
5198
+ onPointerLeave: () => setHoverId((current) => current === item.id ? void 0 : current),
5199
+ children: [
5200
+ body,
5201
+ item.inCycle && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-flag", "aria-hidden": "true", children: labels.cycle })
5202
+ ]
5203
+ },
5204
+ item.id
5205
+ );
5206
+ }) }),
5207
+ layout.items.filter((item) => item.expandedGroup).map((item) => {
5208
+ const group = item.expandedGroup;
5209
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5210
+ "button",
5211
+ {
5212
+ type: "button",
5213
+ className: "uix-relationship-graph__collapse",
5214
+ "aria-expanded": "true",
5215
+ style: { left: item.x, top: item.y - 26, width: item.width },
5216
+ onClick: () => toggleCluster(group.id, group.id),
5217
+ children: labels.collapseCluster(group.members.length, group.type)
5218
+ },
5219
+ `collapse:${group.id}`
5220
+ );
5221
+ })
5222
+ ] })
5223
+ }
5224
+ ),
5225
+ legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": labels.legend, children: legend.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("li", { "data-type": entry.id, children: entry.label }, entry.id)) }),
5226
+ showList && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5227
+ EquivalentRelationshipList,
5228
+ {
5229
+ nodes: bounded.nodes,
5230
+ edges: bounded.edges,
5231
+ selectedId,
5232
+ labels,
5233
+ onSelect: (id) => {
5234
+ const node = bounded.nodes.find((candidate) => candidate.id === id);
5235
+ if (node) onSelect?.(node);
5236
+ },
5237
+ onExpandNeighbors,
5238
+ onOpenDetails
5239
+ }
5240
+ )
5241
+ ] });
5242
+ }
5243
+
5244
+ // src/components/RelationshipGraph.tsx
5245
+ var import_jsx_runtime64 = require("react/jsx-runtime");
5246
+ var DEFAULT_RELATIONSHIP_GRAPH_LABELS = {
5247
+ graph: "Relationship graph",
5248
+ controls: "Graph view controls",
5249
+ list: "Accessible relationship list",
5250
+ listHeading: "Relationship list",
5251
+ legend: "Relationship types",
5252
+ panLeft: "Pan left",
5253
+ panRight: "Pan right",
5254
+ panUp: "Pan up",
5255
+ panDown: "Pan down",
5256
+ left: "Left",
5257
+ right: "Right",
5258
+ up: "Up",
5259
+ down: "Down",
5260
+ zoomIn: "Zoom in",
5261
+ zoomOut: "Zoom out",
5262
+ fit: "Fit",
5263
+ reset: "Reset view",
5264
+ zoomLevel: (percent) => `Zoom ${percent}%`,
5265
+ bounded: (shown, omitted) => `Showing ${shown} nodes; ${omitted} outside this bounded neighborhood.`,
5266
+ graphSummary: (nodes, edges) => `Graph with ${nodes} nodes and ${edges} relationships`,
5267
+ relationship: "Relationship",
5268
+ conflict: "conflict",
5269
+ connectedTo: (names) => `Connected to ${names}`,
5270
+ noConnections: "No visible connections",
5271
+ expandNeighbors: "Expand neighbors",
5272
+ openDetails: "Open details",
5273
+ column: (depth, count) => `${depth === 0 ? "Start" : `${depth} ${depth === 1 ? "hop" : "hops"}`} \xB7 ${count}`,
5274
+ cluster: (count, type, edgeLabel) => `${count} \xD7 ${type ?? "items"}${edgeLabel ? ` \xB7 ${edgeLabel}` : ""}`,
5275
+ expandCluster: "Show all",
5276
+ collapseCluster: (count, type) => `Collapse ${count} ${type ?? "items"}`,
5277
+ cycle: "cycle",
5278
+ relationships: (count) => `${count} ${count === 1 ? "relationship" : "relationships"}`,
5279
+ roleDescription: "relationship map",
5280
+ keyboardHint: "Arrow keys move between connected items, Enter selects, Home returns to the start.",
5281
+ loading: "Loading relationships\u2026",
5282
+ retry: "Try again",
5283
+ emptyTitle: "No relationships to display.",
5284
+ emptyHint: "Adjust the neighborhood or filters to include a node."
5285
+ };
5286
+ var EMPTY_IDS2 = /* @__PURE__ */ new Set();
5287
+ var EMPTY_LEGEND = [];
5288
+ function RelationshipGraph(props) {
5289
+ const labels = (0, import_react41.useMemo)(() => ({ ...DEFAULT_RELATIONSHIP_GRAPH_LABELS, ...props.labels }), [props.labels]);
5290
+ const stateClass = cx("uix-relationship-graph uix-relationship-graph--state", props.className);
5291
+ if (props.loading) return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("div", { className: stateClass, role: "status", children: labels.loading });
5292
+ if (props.error) {
5293
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: stateClass, role: "alert", children: [
5294
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("p", { children: props.error }),
5295
+ props.onRetry && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: props.onRetry, children: labels.retry })
5296
+ ] });
5297
+ }
5298
+ if (props.nodes.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: stateClass, children: [
5299
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("p", { children: labels.emptyTitle }),
5300
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("p", { children: labels.emptyHint })
5301
+ ] });
5302
+ if (props.layout === "layered") return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(LayeredRelationshipGraph, { ...props, labels });
5303
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(RadialRelationshipGraph, { ...props, labels });
5304
+ }
5305
+ function RadialRelationshipGraph({
5306
+ nodes,
5307
+ edges,
5308
+ selectedId,
5309
+ onSelect,
5310
+ onExpandNeighbors,
5311
+ onOpenDetails,
5312
+ maxNodes = 40,
5313
+ density = "standard",
5314
+ highlightedNodeIds = EMPTY_IDS2,
5315
+ conflictedNodeIds = EMPTY_IDS2,
5316
+ highlightedEdgeIds = EMPTY_IDS2,
5317
+ conflictedEdgeIds = EMPTY_IDS2,
5318
+ legend = EMPTY_LEGEND,
5319
+ className,
5320
+ labels,
5321
+ showList = true
5322
+ }) {
5323
+ const bounded = (0, import_react41.useMemo)(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
5324
+ const positioned = (0, import_react41.useMemo)(() => layoutRelationshipGraph(bounded.nodes, bounded.edges, selectedId), [bounded, selectedId]);
5325
+ const positionedById = (0, import_react41.useMemo)(() => new Map(positioned.map((node) => [node.id, node])), [positioned]);
5326
+ const [zoom, setZoom] = (0, import_react41.useState)(1);
5327
+ const [pan, setPan] = (0, import_react41.useState)({ x: 0, y: 0 });
5328
+ const [focusedId, setFocusedId] = (0, import_react41.useState)(selectedId ?? positioned[0]?.id);
5329
+ const svgRef = (0, import_react41.useRef)(null);
4406
5330
  const select = (id) => {
4407
5331
  const node = bounded.nodes.find((item) => item.id === id);
4408
5332
  if (node) onSelect?.(node);
@@ -4425,49 +5349,30 @@ function RelationshipGraph({
4425
5349
  setZoom(1);
4426
5350
  setPan({ x: 0, y: 0 });
4427
5351
  };
4428
- if (loading) return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), role: "status", children: "Loading relationships\u2026" });
4429
- if (error) return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), role: "alert", children: [
4430
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { children: error }),
4431
- onRetry && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
4432
- ] });
4433
- if (bounded.nodes.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), children: [
4434
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { children: "No relationships to display." }),
4435
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { children: "Adjust the neighborhood or filters to include a node." })
4436
- ] });
4437
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("section", { className: cx("uix-relationship-graph", `uix-relationship-graph--${density}`, className), "aria-label": "Relationship graph", children: [
4438
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "uix-relationship-graph__toolbar", "aria-label": "Graph view controls", children: [
4439
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x - 24 })), "aria-label": "Pan left", children: "Left" }),
4440
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y - 24 })), "aria-label": "Pan up", children: "Up" }),
4441
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y + 24 })), "aria-label": "Pan down", children: "Down" }),
4442
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x + 24 })), "aria-label": "Pan right", children: "Right" }),
4443
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.min(2, Number((value + 0.2).toFixed(1)))), "aria-label": "Zoom in", children: "Zoom in" }),
4444
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.max(0.5, Number((value - 0.2).toFixed(1)))), "aria-label": "Zoom out", children: "Zoom out" }),
4445
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: reset, children: "Reset view" }),
4446
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { "aria-live": "polite", children: [
4447
- "Zoom ",
4448
- Math.round(zoom * 100),
4449
- "%"
4450
- ] })
4451
- ] }),
4452
- bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("p", { className: "uix-relationship-graph__bounded", role: "status", children: [
4453
- "Showing ",
4454
- bounded.nodes.length,
4455
- " nodes; ",
4456
- bounded.omittedNodeCount,
4457
- " outside this bounded neighborhood."
5352
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("section", { className: cx("uix-relationship-graph", `uix-relationship-graph--${density}`, className), "aria-label": labels.graph, children: [
5353
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: "uix-relationship-graph__toolbar", "aria-label": labels.controls, children: [
5354
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x - 24 })), "aria-label": labels.panLeft, children: labels.left }),
5355
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y - 24 })), "aria-label": labels.panUp, children: labels.up }),
5356
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y + 24 })), "aria-label": labels.panDown, children: labels.down }),
5357
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x + 24 })), "aria-label": labels.panRight, children: labels.right }),
5358
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.min(2, Number((value + 0.2).toFixed(1)))), "aria-label": labels.zoomIn, children: labels.zoomIn }),
5359
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.max(0.5, Number((value - 0.2).toFixed(1)))), "aria-label": labels.zoomOut, children: labels.zoomOut }),
5360
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: reset, children: labels.reset }),
5361
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { "aria-live": "polite", children: labels.zoomLevel(Math.round(zoom * 100)) })
4458
5362
  ] }),
4459
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "uix-relationship-graph__visual", children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("svg", { ref: svgRef, viewBox: "0 0 600 400", role: "group", "aria-label": `Graph with ${bounded.nodes.length} nodes and ${bounded.edges.length} relationships`, children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("g", { transform: `translate(${pan.x} ${pan.y}) scale(${zoom})`, children: [
5363
+ bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("p", { className: "uix-relationship-graph__bounded", role: "status", children: labels.bounded(bounded.nodes.length, bounded.omittedNodeCount) }),
5364
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("div", { className: "uix-relationship-graph__visual", children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("svg", { ref: svgRef, viewBox: "0 0 600 400", role: "group", "aria-label": labels.graphSummary(bounded.nodes.length, bounded.edges.length), children: /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("g", { transform: `translate(${pan.x} ${pan.y}) scale(${zoom})`, children: [
4460
5365
  bounded.edges.map((edge) => {
4461
5366
  const source = positionedById.get(edge.source);
4462
5367
  const target = positionedById.get(edge.target);
4463
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("line", { x1: source.x * 6, y1: source.y * 4, x2: target.x * 6, y2: target.y * 4, className: "uix-relationship-graph__edge", "data-highlighted": highlightedEdgeIds.has(edge.id) || void 0, "data-conflicted": conflictedEdgeIds.has(edge.id) || void 0, children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("title", { children: edge.label ?? edge.type ?? "Relationship" }) }, edge.id);
5368
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("line", { x1: source.x * 6, y1: source.y * 4, x2: target.x * 6, y2: target.y * 4, className: "uix-relationship-graph__edge", "data-highlighted": highlightedEdgeIds.has(edge.id) || void 0, "data-conflicted": conflictedEdgeIds.has(edge.id) || void 0, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("title", { children: edge.label ?? edge.type ?? labels.relationship }) }, edge.id);
4464
5369
  }),
4465
- positioned.map((node) => /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(
5370
+ positioned.map((node) => /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(
4466
5371
  "g",
4467
5372
  {
4468
5373
  transform: `translate(${node.x * 6} ${node.y * 4})`,
4469
5374
  role: "button",
4470
- "aria-label": `${node.label}${node.type ? `, ${node.type}` : ""}${conflictedNodeIds.has(node.id) ? ", conflict" : ""}`,
5375
+ "aria-label": `${node.label}${node.type ? `, ${node.type}` : ""}${conflictedNodeIds.has(node.id) ? `, ${labels.conflict}` : ""}`,
4471
5376
  tabIndex: focusedId === node.id ? 0 : -1,
4472
5377
  "data-node-id": node.id,
4473
5378
  className: "uix-relationship-graph__node",
@@ -4478,37 +5383,21 @@ function RelationshipGraph({
4478
5383
  onKeyDown: (event) => onNodeKeyDown(event, node.id),
4479
5384
  onClick: () => select(node.id),
4480
5385
  children: [
4481
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("rect", { x: "-55", y: "-18", width: "110", height: "36", rx: "8" }),
4482
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("text", { textAnchor: "middle", dominantBaseline: "middle", children: node.label.length > 16 ? `${node.label.slice(0, 15)}\u2026` : node.label })
5386
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("rect", { x: "-55", y: "-18", width: "110", height: "36", rx: "8" }),
5387
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("text", { textAnchor: "middle", dominantBaseline: "middle", children: node.label.length > 16 ? `${node.label.slice(0, 15)}\u2026` : node.label })
4483
5388
  ]
4484
5389
  },
4485
5390
  node.id
4486
5391
  ))
4487
5392
  ] }) }) }),
4488
- legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": "Relationship types", children: legend.map((item) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("li", { "data-type": item.id, children: item.label }, item.id)) }),
4489
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "uix-relationship-graph__list", role: "region", "aria-label": "Accessible relationship list", children: [
4490
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("h3", { children: "Relationship list" }),
4491
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("ul", { children: bounded.nodes.map((node) => {
4492
- const neighbors = relationshipNeighbors(node.id, bounded.edges).map((id) => bounded.nodes.find((item) => item.id === id)?.label ?? id);
4493
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("li", { "data-selected": selectedId === node.id || void 0, children: [
4494
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("button", { type: "button", onClick: () => select(node.id), "aria-pressed": selectedId === node.id, children: [
4495
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("strong", { children: node.label }),
4496
- node.type && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: node.type }),
4497
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: neighbors.length ? `Connected to ${neighbors.join(", ")}` : "No visible connections" })
4498
- ] }),
4499
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "uix-relationship-graph__list-actions", children: [
4500
- onExpandNeighbors && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => onExpandNeighbors(node), children: "Expand neighbors" }),
4501
- onOpenDetails && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => onOpenDetails(node), children: "Open details" })
4502
- ] })
4503
- ] }, node.id);
4504
- }) })
4505
- ] })
5393
+ legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": labels.legend, children: legend.map((item) => /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("li", { "data-type": item.id, children: item.label }, item.id)) }),
5394
+ showList && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(EquivalentRelationshipList, { nodes: bounded.nodes, edges: bounded.edges, selectedId, labels, onSelect: select, onExpandNeighbors, onOpenDetails })
4506
5395
  ] });
4507
5396
  }
4508
5397
 
4509
5398
  // src/components/MatchReview.tsx
4510
- var import_react41 = require("react");
4511
- var import_jsx_runtime63 = require("react/jsx-runtime");
5399
+ var import_react42 = require("react");
5400
+ var import_jsx_runtime65 = require("react/jsx-runtime");
4512
5401
  var valueText = (value) => value == null ? "\u2014" : typeof value === "string" ? value : JSON.stringify(value);
4513
5402
  function MatchReview({
4514
5403
  incoming,
@@ -4524,11 +5413,11 @@ function MatchReview({
4524
5413
  onRetry,
4525
5414
  className
4526
5415
  }) {
4527
- const [internalSelection, setInternalSelection] = (0, import_react41.useState)(/* @__PURE__ */ new Set());
4528
- const [announcement, setAnnouncement] = (0, import_react41.useState)("");
4529
- const [bulkPending, setBulkPending] = (0, import_react41.useState)(false);
5416
+ const [internalSelection, setInternalSelection] = (0, import_react42.useState)(/* @__PURE__ */ new Set());
5417
+ const [announcement, setAnnouncement] = (0, import_react42.useState)("");
5418
+ const [bulkPending, setBulkPending] = (0, import_react42.useState)(false);
4530
5419
  const selection = selectedIds ?? internalSelection;
4531
- const pending = (0, import_react41.useMemo)(() => candidates.filter((candidate) => candidate.status !== "resolved"), [candidates]);
5420
+ const pending = (0, import_react42.useMemo)(() => candidates.filter((candidate) => candidate.status !== "resolved"), [candidates]);
4532
5421
  const setSelection = (next) => {
4533
5422
  if (selectedIds === void 0) setInternalSelection(next);
4534
5423
  onSelectionChange?.(next);
@@ -4556,55 +5445,55 @@ function MatchReview({
4556
5445
  setBulkPending(false);
4557
5446
  }
4558
5447
  };
4559
- if (loading) return /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: "Loading match candidates\u2026" });
4560
- if (error) return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "alert", children: [
4561
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { children: error }),
4562
- onRetry && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
5448
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: "Loading match candidates\u2026" });
5449
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "alert", children: [
5450
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("p", { children: error }),
5451
+ onRetry && /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
4563
5452
  ] });
4564
- if (candidates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), children: [
4565
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { children: "No candidate matches found." }),
4566
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { children: "Choose another record source or continue without a match." })
5453
+ if (candidates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), children: [
5454
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("p", { children: "No candidate matches found." }),
5455
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("p", { children: "Choose another record source or continue without a match." })
4567
5456
  ] });
4568
- return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("section", { className: cx("uix-match-review", className), "aria-label": "Match review", children: [
4569
- /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "uix-match-review__summary", children: [
4570
- /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { children: [
4571
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("strong", { children: incomingLabel }),
4572
- /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("span", { children: [
5457
+ return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("section", { className: cx("uix-match-review", className), "aria-label": "Match review", children: [
5458
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "uix-match-review__summary", children: [
5459
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { children: [
5460
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("strong", { children: incomingLabel }),
5461
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("span", { children: [
4573
5462
  pending.length,
4574
5463
  " pending of ",
4575
5464
  candidates.length
4576
5465
  ] })
4577
5466
  ] }),
4578
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "uix-match-review__incoming", children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("span", { children: [
4579
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("b", { children: field.label }),
5467
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "uix-match-review__incoming", children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("span", { children: [
5468
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("b", { children: field.label }),
4580
5469
  field.render?.(incoming[field.id], incoming) ?? valueText(incoming[field.id])
4581
5470
  ] }, field.id)) })
4582
5471
  ] }),
4583
- onBulkDecision && /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "uix-match-review__bulk", "aria-label": "Bulk candidate actions", children: [
4584
- /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("span", { children: [
5472
+ onBulkDecision && /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "uix-match-review__bulk", "aria-label": "Bulk candidate actions", children: [
5473
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("span", { children: [
4585
5474
  selection.size,
4586
5475
  " selected"
4587
5476
  ] }),
4588
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("accept"), children: bulkPending ? "Applying\u2026" : "Accept selected" }),
4589
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("dismiss"), children: bulkPending ? "Applying\u2026" : "Dismiss selected" })
5477
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("accept"), children: bulkPending ? "Applying\u2026" : "Accept selected" }),
5478
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("dismiss"), children: bulkPending ? "Applying\u2026" : "Dismiss selected" })
4590
5479
  ] }),
4591
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("table", { className: "uix-table uix-match-review__table", children: [
4592
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("caption", { className: "uix-visually-hidden", children: "Candidate matches and available decisions" }),
4593
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("tr", { children: [
4594
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-visually-hidden", children: "Select" }) }),
4595
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: "Confidence" }),
4596
- fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: field.label }, field.id)),
4597
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: "Status" }),
4598
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: "Actions" })
5480
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("table", { className: "uix-table uix-match-review__table", children: [
5481
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("caption", { className: "uix-visually-hidden", children: "Candidate matches and available decisions" }),
5482
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("tr", { children: [
5483
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "uix-visually-hidden", children: "Select" }) }),
5484
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: "Confidence" }),
5485
+ fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: field.label }, field.id)),
5486
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: "Status" }),
5487
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: "Actions" })
4599
5488
  ] }) }),
4600
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("tbody", { children: candidates.map((candidate) => {
5489
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("tbody", { children: candidates.map((candidate) => {
4601
5490
  const resolved = candidate.status === "resolved";
4602
- return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("tr", { "data-status": candidate.status ?? "pending", children: [
4603
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": `Select candidate ${candidate.id}` }) }),
4604
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? "Not scored" : `${Math.round(candidate.confidence * 100)}%` }) }),
4605
- fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: field.render?.(candidate.record[field.id], candidate.record) ?? valueText(candidate.record[field.id]) }, field.id)),
4606
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ?? "Resolved" : "Pending" }) }),
4607
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "uix-match-review__actions", children: ["accept", "dismiss", "split", "pick-other"].map((action) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5491
+ return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("tr", { "data-status": candidate.status ?? "pending", children: [
5492
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": `Select candidate ${candidate.id}` }) }),
5493
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? "Not scored" : `${Math.round(candidate.confidence * 100)}%` }) }),
5494
+ fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: field.render?.(candidate.record[field.id], candidate.record) ?? valueText(candidate.record[field.id]) }, field.id)),
5495
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ?? "Resolved" : "Pending" }) }),
5496
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "uix-match-review__actions", children: ["accept", "dismiss", "split", "pick-other"].map((action) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
4608
5497
  "button",
4609
5498
  {
4610
5499
  type: "button",
@@ -4618,12 +5507,12 @@ function MatchReview({
4618
5507
  ] }, candidate.id);
4619
5508
  }) })
4620
5509
  ] }) }),
4621
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
5510
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
4622
5511
  ] });
4623
5512
  }
4624
5513
 
4625
5514
  // src/components/MetricInput.tsx
4626
- var import_react42 = require("react");
5515
+ var import_react43 = require("react");
4627
5516
 
4628
5517
  // src/metric-model.ts
4629
5518
  function decimals(value) {
@@ -4648,7 +5537,7 @@ function parseMetricValue(value) {
4648
5537
  }
4649
5538
 
4650
5539
  // src/components/MetricInput.tsx
4651
- var import_jsx_runtime64 = require("react/jsx-runtime");
5540
+ var import_jsx_runtime66 = require("react/jsx-runtime");
4652
5541
  var defaultMetricFormat = (input) => input == null ? "" : String(input);
4653
5542
  function MetricInput({
4654
5543
  value,
@@ -4667,11 +5556,11 @@ function MetricInput({
4667
5556
  className,
4668
5557
  id: providedId
4669
5558
  }) {
4670
- const generatedId = (0, import_react42.useId)();
5559
+ const generatedId = (0, import_react43.useId)();
4671
5560
  const id = providedId ?? generatedId;
4672
5561
  const errorId = `${id}-error`;
4673
- const [text, setText] = (0, import_react42.useState)(() => format(value));
4674
- (0, import_react42.useEffect)(() => setText(format(value)), [value, format]);
5562
+ const [text, setText] = (0, import_react43.useState)(() => format(value));
5563
+ (0, import_react43.useEffect)(() => setText(format(value)), [value, format]);
4675
5564
  const commit = (input) => {
4676
5565
  const parsed = parse(input);
4677
5566
  if (input.trim() === "" || parsed !== null) {
@@ -4697,10 +5586,10 @@ function MetricInput({
4697
5586
  const parsed = parse(event.currentTarget.value);
4698
5587
  setText(parsed === null && event.currentTarget.value.trim() !== "" ? format(value) : format(parsed));
4699
5588
  };
4700
- return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: cx("uix-metric-input", invalid && "uix-metric-input--invalid", className), children: [
4701
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("label", { className: "uix-field__label", htmlFor: id, children: label }),
4702
- /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: "uix-metric-input__control", children: [
4703
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
5589
+ return /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: cx("uix-metric-input", invalid && "uix-metric-input--invalid", className), children: [
5590
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("label", { className: "uix-field__label", htmlFor: id, children: label }),
5591
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "uix-metric-input__control", children: [
5592
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
4704
5593
  Input,
4705
5594
  {
4706
5595
  id,
@@ -4719,22 +5608,22 @@ function MetricInput({
4719
5608
  readOnly
4720
5609
  }
4721
5610
  ),
4722
- unit && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { className: "uix-metric-input__unit", "aria-hidden": "true", children: unit }),
4723
- /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("span", { className: "uix-metric-input__steps", children: [
4724
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(1), disabled: disabled || readOnly, "aria-label": `Increase ${label}`, children: "+" }),
4725
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(-1), disabled: disabled || readOnly, "aria-label": `Decrease ${label}`, children: "\u2212" })
5611
+ unit && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "uix-metric-input__unit", "aria-hidden": "true", children: unit }),
5612
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "uix-metric-input__steps", children: [
5613
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(1), disabled: disabled || readOnly, "aria-label": `Increase ${label}`, children: "+" }),
5614
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(-1), disabled: disabled || readOnly, "aria-label": `Decrease ${label}`, children: "\u2212" })
4726
5615
  ] })
4727
5616
  ] }),
4728
- unit && /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("span", { className: "uix-visually-hidden", children: [
5617
+ unit && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "uix-visually-hidden", children: [
4729
5618
  "Unit: ",
4730
5619
  unit
4731
5620
  ] }),
4732
- invalid && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { id: errorId, className: "uix-field__error", children: errorMessage })
5621
+ invalid && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { id: errorId, className: "uix-field__error", children: errorMessage })
4733
5622
  ] });
4734
5623
  }
4735
5624
 
4736
5625
  // src/components/LicensePositionBar.tsx
4737
- var import_jsx_runtime65 = require("react/jsx-runtime");
5626
+ var import_jsx_runtime67 = require("react/jsx-runtime");
4738
5627
  var defaultCapacityFormat = (value) => new Intl.NumberFormat().format(value);
4739
5628
  function LicensePositionBar({
4740
5629
  consumed,
@@ -4755,18 +5644,18 @@ function LicensePositionBar({
4755
5644
  const percent = safeEntitled === 0 ? safeConsumed > 0 ? 100 : 0 : safeConsumed / safeEntitled * 100;
4756
5645
  const format = (value) => `${formatValue(value)}${unit ? ` ${unit}` : ""}`;
4757
5646
  const summary = `${consumedLabel}: ${format(safeConsumed)}. ${entitledLabel}: ${format(safeEntitled)}. ${over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}`}.`;
4758
- return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: cx("uix-license-position", over > 0 && "uix-license-position--over", className), ...props, children: [
4759
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "uix-license-position__header", children: [
4760
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("span", { className: "uix-license-position__value", children: [
5647
+ return /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: cx("uix-license-position", over > 0 && "uix-license-position--over", className), ...props, children: [
5648
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-license-position__header", children: [
5649
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("span", { className: "uix-license-position__value", children: [
4761
5650
  format(safeConsumed),
4762
5651
  " / ",
4763
5652
  format(safeEntitled)
4764
5653
  ] }),
4765
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(StatusPill, { tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", children: over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}` })
5654
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(StatusPill, { tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", children: over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}` })
4766
5655
  ] }),
4767
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "uix-license-position__track", children: [
4768
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(Meter, { value: Math.min(100, percent), tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", "aria-label": summary, "aria-valuetext": summary }),
4769
- over > 0 && /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
5656
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-license-position__track", children: [
5657
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(Meter, { value: Math.min(100, percent), tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", "aria-label": summary, "aria-valuetext": summary }),
5658
+ over > 0 && /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(
4770
5659
  "span",
4771
5660
  {
4772
5661
  className: "uix-license-position__overflow",
@@ -4775,12 +5664,12 @@ function LicensePositionBar({
4775
5664
  }
4776
5665
  )
4777
5666
  ] }),
4778
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "uix-visually-hidden", children: summary })
5667
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { className: "uix-visually-hidden", children: summary })
4779
5668
  ] });
4780
5669
  }
4781
5670
 
4782
5671
  // src/components/BrandProfiles.tsx
4783
- var import_react44 = require("react");
5672
+ var import_react45 = require("react");
4784
5673
 
4785
5674
  // src/color-model.ts
4786
5675
  var clamp2 = (value, min, max) => Math.min(max, Math.max(min, value));
@@ -4849,8 +5738,8 @@ function meetsContrast(background, foreground, minimum = 4.5) {
4849
5738
  }
4850
5739
 
4851
5740
  // src/components/ColorPicker.tsx
4852
- var import_react43 = require("react");
4853
- var import_jsx_runtime66 = require("react/jsx-runtime");
5741
+ var import_react44 = require("react");
5742
+ var import_jsx_runtime68 = require("react/jsx-runtime");
4854
5743
  var EMPTY_COLORS = [];
4855
5744
  function ColorPicker({
4856
5745
  value,
@@ -4863,17 +5752,17 @@ function ColorPicker({
4863
5752
  disabled,
4864
5753
  className
4865
5754
  }) {
4866
- const id = (0, import_react43.useId)();
4867
- const triggerRef = (0, import_react43.useRef)(null);
4868
- const dialogRef = (0, import_react43.useRef)(null);
4869
- const [open, setOpen] = (0, import_react43.useState)(false);
5755
+ const id = (0, import_react44.useId)();
5756
+ const triggerRef = (0, import_react44.useRef)(null);
5757
+ const dialogRef = (0, import_react44.useRef)(null);
5758
+ const [open, setOpen] = (0, import_react44.useState)(false);
4870
5759
  const normalized = normalizeHex(value) ?? "#000000";
4871
- const [hexText, setHexText] = (0, import_react43.useState)(normalized);
4872
- const hsv = (0, import_react43.useMemo)(() => rgbToHsv(hexToRgb(normalized)), [normalized]);
5760
+ const [hexText, setHexText] = (0, import_react44.useState)(normalized);
5761
+ const hsv = (0, import_react44.useMemo)(() => rgbToHsv(hexToRgb(normalized)), [normalized]);
4873
5762
  const ratio = contrastRatio(normalized, foreground) ?? 0;
4874
5763
  const passes = ratio >= minimumContrast;
4875
- (0, import_react43.useEffect)(() => setHexText(normalized), [normalized]);
4876
- (0, import_react43.useEffect)(() => {
5764
+ (0, import_react44.useEffect)(() => setHexText(normalized), [normalized]);
5765
+ (0, import_react44.useEffect)(() => {
4877
5766
  if (!open) return;
4878
5767
  const dialog = dialogRef.current;
4879
5768
  dialog?.querySelector("input,button")?.focus();
@@ -4916,8 +5805,8 @@ function ColorPicker({
4916
5805
  if (next) onChange(next);
4917
5806
  };
4918
5807
  const sampleStyle = { backgroundColor: normalized };
4919
- return /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: cx("uix-color-picker", className), children: [
4920
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
5808
+ return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: cx("uix-color-picker", className), children: [
5809
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(
4921
5810
  "button",
4922
5811
  {
4923
5812
  ref: triggerRef,
@@ -4929,12 +5818,12 @@ function ColorPicker({
4929
5818
  onClick: () => setOpen((current) => !current),
4930
5819
  disabled,
4931
5820
  children: [
4932
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "uix-color-picker__swatch", style: sampleStyle, "aria-hidden": "true" }),
4933
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { children: normalized })
5821
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("span", { className: "uix-color-picker__swatch", style: sampleStyle, "aria-hidden": "true" }),
5822
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("span", { children: normalized })
4934
5823
  ]
4935
5824
  }
4936
5825
  ),
4937
- open && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
5826
+ open && /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(
4938
5827
  "div",
4939
5828
  {
4940
5829
  ref: dialogRef,
@@ -4945,57 +5834,57 @@ function ColorPicker({
4945
5834
  className: "uix-color-picker__popover",
4946
5835
  onKeyDown: onDialogKeyDown,
4947
5836
  children: [
4948
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { className: "uix-color-picker__preview", style: sampleStyle, children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { style: { color: foreground }, children: "Aa" }) }),
4949
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("label", { className: "uix-color-picker__range", children: [
5837
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { className: "uix-color-picker__preview", style: sampleStyle, children: /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("span", { style: { color: foreground }, children: "Aa" }) }),
5838
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__range", children: [
4950
5839
  "Hue ",
4951
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { children: [
5840
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4952
5841
  Math.round(hsv.h),
4953
5842
  "\xB0"
4954
5843
  ] }),
4955
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("input", { type: "range", min: "0", max: "359", value: Math.round(hsv.h), onChange: (event) => updateHsv({ h: Number(event.currentTarget.value) }) })
5844
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("input", { type: "range", min: "0", max: "359", value: Math.round(hsv.h), onChange: (event) => updateHsv({ h: Number(event.currentTarget.value) }) })
4956
5845
  ] }),
4957
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("label", { className: "uix-color-picker__range", children: [
5846
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__range", children: [
4958
5847
  "Saturation ",
4959
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { children: [
5848
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4960
5849
  Math.round(hsv.s * 100),
4961
5850
  "%"
4962
5851
  ] }),
4963
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.s * 100), onChange: (event) => updateHsv({ s: Number(event.currentTarget.value) / 100 }) })
5852
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.s * 100), onChange: (event) => updateHsv({ s: Number(event.currentTarget.value) / 100 }) })
4964
5853
  ] }),
4965
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("label", { className: "uix-color-picker__range", children: [
5854
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__range", children: [
4966
5855
  "Brightness ",
4967
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { children: [
5856
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4968
5857
  Math.round(hsv.v * 100),
4969
5858
  "%"
4970
5859
  ] }),
4971
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.v * 100), onChange: (event) => updateHsv({ v: Number(event.currentTarget.value) / 100 }) })
5860
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.v * 100), onChange: (event) => updateHsv({ v: Number(event.currentTarget.value) / 100 }) })
4972
5861
  ] }),
4973
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("label", { className: "uix-color-picker__hex", children: [
5862
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__hex", children: [
4974
5863
  "Hex",
4975
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "uix-color-picker__hex-row", children: [
4976
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("input", { className: "uix-input", value: hexText, onChange: (event) => setHexText(event.currentTarget.value), onBlur: commitHex, onKeyDown: (event) => {
5864
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { className: "uix-color-picker__hex-row", children: [
5865
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("input", { className: "uix-input", value: hexText, onChange: (event) => setHexText(event.currentTarget.value), onBlur: commitHex, onKeyDown: (event) => {
4977
5866
  if (event.key === "Enter") commitHex();
4978
5867
  }, "aria-invalid": !normalizeHex(hexText) }),
4979
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: commitHex, disabled: !normalizeHex(hexText), children: "Set" })
5868
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: commitHex, disabled: !normalizeHex(hexText), children: "Set" })
4980
5869
  ] })
4981
5870
  ] }),
4982
- (presets.length > 0 || recent.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "uix-color-picker__palettes", children: [
4983
- presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("fieldset", { children: [
4984
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("legend", { children: "Presets" }),
4985
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { children: presets.map((color) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use preset ${color}` }, color)) })
5871
+ (presets.length > 0 || recent.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "uix-color-picker__palettes", children: [
5872
+ presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("fieldset", { children: [
5873
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("legend", { children: "Presets" }),
5874
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { children: presets.map((color) => /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use preset ${color}` }, color)) })
4986
5875
  ] }),
4987
- recent.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("fieldset", { children: [
4988
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("legend", { children: "Recent" }),
4989
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { children: recent.map((color) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use recent color ${color}` }, color)) })
5876
+ recent.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("fieldset", { children: [
5877
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("legend", { children: "Recent" }),
5878
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { children: recent.map((color) => /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use recent color ${color}` }, color)) })
4990
5879
  ] })
4991
5880
  ] }),
4992
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("p", { className: cx("uix-color-picker__contrast", passes ? "uix-color-picker__contrast--pass" : "uix-color-picker__contrast--fail"), role: "status", "aria-live": "polite", children: [
5881
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("p", { className: cx("uix-color-picker__contrast", passes ? "uix-color-picker__contrast--pass" : "uix-color-picker__contrast--fail"), role: "status", "aria-live": "polite", children: [
4993
5882
  "Contrast ",
4994
5883
  ratio.toFixed(2),
4995
5884
  ":1 \u2014 ",
4996
5885
  passes ? `passes WCAG AA (${minimumContrast}:1)` : `does not meet WCAG AA (${minimumContrast}:1)`
4997
5886
  ] }),
4998
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", onClick: close, children: "Done" })
5887
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", onClick: close, children: "Done" })
4999
5888
  ]
5000
5889
  }
5001
5890
  )
@@ -5003,7 +5892,7 @@ function ColorPicker({
5003
5892
  }
5004
5893
 
5005
5894
  // src/components/BrandProfiles.tsx
5006
- var import_jsx_runtime67 = require("react/jsx-runtime");
5895
+ var import_jsx_runtime69 = require("react/jsx-runtime");
5007
5896
  var PROFILE_PROPERTIES = {
5008
5897
  brand: "--uix-brand",
5009
5898
  brandForeground: "--uix-brand-fg",
@@ -5072,7 +5961,7 @@ function BrandProfileEditor({
5072
5961
  colorPresets = EMPTY_COLORS2,
5073
5962
  className
5074
5963
  }) {
5075
- const [announcement, setAnnouncement] = (0, import_react44.useState)("");
5964
+ const [announcement, setAnnouncement] = (0, import_react45.useState)("");
5076
5965
  const update = (patch) => onChange({ ...value, ...patch });
5077
5966
  const apply = () => {
5078
5967
  if (applyTarget !== null) applyBrandProfile(value, applyTarget);
@@ -5089,54 +5978,54 @@ function BrandProfileEditor({
5089
5978
  ...value.typography?.sans ? { "--uix-font-sans": value.typography.sans } : {},
5090
5979
  ...value.typography?.heading ? { "--uix-font-heading": value.typography.heading } : {}
5091
5980
  };
5092
- return /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("section", { className: cx("uix-brand-profiles", className), "aria-label": "Brand profile editor", children: [
5093
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__form", children: [
5094
- profiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-field__label", children: [
5981
+ return /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("section", { className: cx("uix-brand-profiles", className), "aria-label": "Brand profile editor", children: [
5982
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__form", children: [
5983
+ profiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
5095
5984
  "Profile",
5096
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("select", { className: "uix-select", value: value.id, onChange: (event) => {
5985
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("select", { className: "uix-select", value: value.id, onChange: (event) => {
5097
5986
  const selected = profiles.find((profile) => profile.id === event.currentTarget.value);
5098
5987
  if (selected) onSelectProfile?.(selected);
5099
- }, children: profiles.map((profile) => /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("option", { value: profile.id, children: profile.name }, profile.id)) })
5988
+ }, children: profiles.map((profile) => /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("option", { value: profile.id, children: profile.name }, profile.id)) })
5100
5989
  ] }),
5101
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-field__label", children: [
5990
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
5102
5991
  "Name",
5103
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("input", { className: "uix-input", value: value.name, onChange: (event) => update({ name: event.currentTarget.value }) })
5992
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { className: "uix-input", value: value.name, onChange: (event) => update({ name: event.currentTarget.value }) })
5104
5993
  ] }),
5105
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__colors", children: [
5106
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { children: [
5994
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__colors", children: [
5995
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { children: [
5107
5996
  "Brand",
5108
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(ColorPicker, { label: "Brand color", value: value.brand, foreground: value.brandForeground, presets: colorPresets, onChange: (brand) => update({ brand }) })
5997
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(ColorPicker, { label: "Brand color", value: value.brand, foreground: value.brandForeground, presets: colorPresets, onChange: (brand) => update({ brand }) })
5109
5998
  ] }),
5110
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { children: [
5999
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { children: [
5111
6000
  "On brand",
5112
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(ColorPicker, { label: "Brand foreground color", value: value.brandForeground, foreground: value.brand, presets: colorPresets, onChange: (brandForeground) => update({ brandForeground }) })
6001
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(ColorPicker, { label: "Brand foreground color", value: value.brandForeground, foreground: value.brand, presets: colorPresets, onChange: (brandForeground) => update({ brandForeground }) })
5113
6002
  ] })
5114
6003
  ] }),
5115
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-field__label", children: [
6004
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
5116
6005
  "Body font",
5117
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("input", { className: "uix-input", value: value.typography?.sans ?? "", onChange: (event) => update({ typography: { ...value.typography, sans: event.currentTarget.value || void 0 } }) })
6006
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { className: "uix-input", value: value.typography?.sans ?? "", onChange: (event) => update({ typography: { ...value.typography, sans: event.currentTarget.value || void 0 } }) })
5118
6007
  ] }),
5119
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-field__label", children: [
6008
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
5120
6009
  "Heading font",
5121
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("input", { className: "uix-input", value: value.typography?.heading ?? "", onChange: (event) => update({ typography: { ...value.typography, heading: event.currentTarget.value || void 0 } }) })
6010
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { className: "uix-input", value: value.typography?.heading ?? "", onChange: (event) => update({ typography: { ...value.typography, heading: event.currentTarget.value || void 0 } }) })
5122
6011
  ] }),
5123
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-file-upload uix-brand-profiles__upload", children: [
6012
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-file-upload uix-brand-profiles__upload", children: [
5124
6013
  "Logo file",
5125
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("input", { type: "file", accept: "image/*", onChange: onFile })
6014
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { type: "file", accept: "image/*", onChange: onFile })
5126
6015
  ] }),
5127
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", onClick: apply, children: "Apply profile" }),
5128
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
6016
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", onClick: apply, children: "Apply profile" }),
6017
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
5129
6018
  ] }),
5130
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__preview", style: previewStyle, "aria-label": `Live preview of ${value.name}`, children: [
5131
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__preview-header", children: [
5132
- value.logo ? /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("img", { src: value.logo.src, alt: value.logo.alt }) : /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { className: "uix-brand-profiles__logo-placeholder", children: "Logo" }),
5133
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("strong", { children: value.name || "Brand preview" })
6019
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__preview", style: previewStyle, "aria-label": `Live preview of ${value.name}`, children: [
6020
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__preview-header", children: [
6021
+ value.logo ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("img", { src: value.logo.src, alt: value.logo.alt }) : /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "uix-brand-profiles__logo-placeholder", children: "Logo" }),
6022
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("strong", { children: value.name || "Brand preview" })
5134
6023
  ] }),
5135
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__preview-body", children: [
5136
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("h3", { children: "Readable, serializable branding" }),
5137
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("p", { children: "Accent, links, rings, and muted states continue through the existing UIx token chain." }),
5138
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", children: "Primary action" }),
5139
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("a", { href: "#brand-profile-preview", children: "Preview link" })
6024
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__preview-body", children: [
6025
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("h3", { children: "Readable, serializable branding" }),
6026
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("p", { children: "Accent, links, rings, and muted states continue through the existing UIx token chain." }),
6027
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", children: "Primary action" }),
6028
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("a", { href: "#brand-profile-preview", children: "Preview link" })
5140
6029
  ] })
5141
6030
  ] })
5142
6031
  ] });
@@ -5144,7 +6033,7 @@ function BrandProfileEditor({
5144
6033
  var BrandProfiles = BrandProfileEditor;
5145
6034
 
5146
6035
  // src/components/DiffViewer.tsx
5147
- var import_react45 = require("react");
6036
+ var import_react46 = require("react");
5148
6037
 
5149
6038
  // src/diff-model.ts
5150
6039
  function isJsonObject(value) {
@@ -5195,7 +6084,7 @@ function summarizeDiff(entries, resolutions = {}) {
5195
6084
  }
5196
6085
 
5197
6086
  // src/components/DiffViewer.tsx
5198
- var import_jsx_runtime68 = require("react/jsx-runtime");
6087
+ var import_jsx_runtime70 = require("react/jsx-runtime");
5199
6088
  var GROUPS = [
5200
6089
  { kind: "conflicted", label: "Conflicted" },
5201
6090
  { kind: "changed", label: "Changed" },
@@ -5214,65 +6103,65 @@ function DiffViewer({
5214
6103
  labels = DEFAULT_LABELS,
5215
6104
  className
5216
6105
  }) {
5217
- const id = (0, import_react45.useId)();
5218
- const entries = (0, import_react45.useMemo)(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
5219
- const [internalResolutions, setInternalResolutions] = (0, import_react45.useState)({});
6106
+ const id = (0, import_react46.useId)();
6107
+ const entries = (0, import_react46.useMemo)(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
6108
+ const [internalResolutions, setInternalResolutions] = (0, import_react46.useState)({});
5220
6109
  const resolutions = controlledResolutions ?? internalResolutions;
5221
6110
  const summary = summarizeDiff(entries, resolutions);
5222
6111
  const resolve2 = (path, resolution) => {
5223
6112
  if (controlledResolutions === void 0) setInternalResolutions((currentState) => ({ ...currentState, [path]: resolution }));
5224
6113
  onResolutionChange?.(path, resolution);
5225
6114
  };
5226
- if (entries.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": "Configuration differences", children: [
5227
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("h3", { children: "No differences" }),
5228
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { children: "Base, current, and incoming values are aligned." })
6115
+ if (entries.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": "Configuration differences", children: [
6116
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("h3", { children: "No differences" }),
6117
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("p", { children: "Base, current, and incoming values are aligned." })
5229
6118
  ] });
5230
- return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("section", { className: cx("uix-diff-viewer", className), "aria-label": "Configuration differences", children: [
5231
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "uix-diff-viewer__summary", "aria-label": "Difference summary", children: [
5232
- GROUPS.map((group) => /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
5233
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("b", { children: summary[group.kind] }),
6119
+ return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("section", { className: cx("uix-diff-viewer", className), "aria-label": "Configuration differences", children: [
6120
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "uix-diff-viewer__summary", "aria-label": "Difference summary", children: [
6121
+ GROUPS.map((group) => /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("span", { children: [
6122
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("b", { children: summary[group.kind] }),
5234
6123
  " ",
5235
6124
  group.label.toLowerCase()
5236
6125
  ] }, group.kind)),
5237
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
5238
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("b", { children: summary.resolved }),
6126
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("span", { children: [
6127
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("b", { children: summary.resolved }),
5239
6128
  " resolved"
5240
6129
  ] }),
5241
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
5242
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("b", { children: summary.pending }),
6130
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("span", { children: [
6131
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("b", { children: summary.pending }),
5243
6132
  " pending"
5244
6133
  ] })
5245
6134
  ] }),
5246
6135
  GROUPS.map((group) => {
5247
6136
  const grouped = entries.filter((entry) => entry.kind === group.kind);
5248
6137
  if (!grouped.length) return null;
5249
- return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${group.kind}`, children: [
5250
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("h3", { id: `${id}-${group.kind}`, children: [
6138
+ return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${group.kind}`, children: [
6139
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("h3", { id: `${id}-${group.kind}`, children: [
5251
6140
  group.label,
5252
6141
  " ",
5253
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("span", { children: grouped.length })
6142
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("span", { children: grouped.length })
5254
6143
  ] }),
5255
6144
  grouped.map((entry) => {
5256
6145
  const resolution = resolutions[entry.path] ?? "pending";
5257
- return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("details", { className: "uix-diff-viewer__entry", "data-kind": entry.kind, open: entry.kind === "conflicted", children: [
5258
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("summary", { children: [
5259
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("code", { children: entry.path }),
5260
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolution })
6146
+ return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("details", { className: "uix-diff-viewer__entry", "data-kind": entry.kind, open: entry.kind === "conflicted", children: [
6147
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("summary", { children: [
6148
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("code", { children: entry.path }),
6149
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolution })
5261
6150
  ] }),
5262
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { role: "region", "aria-label": `${labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}`} value for ${entry.path}`, children: [
5263
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("strong", { children: labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}` }),
5264
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("pre", { children: renderValue(entry[version]) })
6151
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { role: "region", "aria-label": `${labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}`} value for ${entry.path}`, children: [
6152
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("strong", { children: labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}` }),
6153
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("pre", { children: renderValue(entry[version]) })
5265
6154
  ] }, version)) }),
5266
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "uix-diff-viewer__actions", "aria-label": `Resolve ${entry.path}`, children: [
5267
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve2(entry.path, "accept"), children: "Accept incoming" }),
5268
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve2(entry.path, "skip"), children: "Keep current" }),
5269
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve2(entry.path, "pending"), children: "Mark pending" })
6155
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "uix-diff-viewer__actions", "aria-label": `Resolve ${entry.path}`, children: [
6156
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve2(entry.path, "accept"), children: "Accept incoming" }),
6157
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve2(entry.path, "skip"), children: "Keep current" }),
6158
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve2(entry.path, "pending"), children: "Mark pending" })
5270
6159
  ] })
5271
6160
  ] }, entry.path);
5272
6161
  })
5273
6162
  ] }, group.kind);
5274
6163
  }),
5275
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: [
6164
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: [
5276
6165
  summary.resolved,
5277
6166
  " of ",
5278
6167
  entries.length,
@@ -5312,6 +6201,7 @@ function DiffViewer({
5312
6201
  ConfirmDialog,
5313
6202
  ContentList,
5314
6203
  ContentListItem,
6204
+ DEFAULT_RELATIONSHIP_GRAPH_LABELS,
5315
6205
  DateRangePicker,
5316
6206
  DescriptionItem,
5317
6207
  DescriptionList,
@@ -5423,6 +6313,8 @@ function DiffViewer({
5423
6313
  buildThreeWayDiff,
5424
6314
  clampMetricValue,
5425
6315
  clampWidth,
6316
+ classifyLayeredEdges,
6317
+ clusterIdFor,
5426
6318
  computePosition,
5427
6319
  contrastRatio,
5428
6320
  cx,
@@ -5437,6 +6329,8 @@ function DiffViewer({
5437
6329
  isDateInRange,
5438
6330
  isDateUnavailable,
5439
6331
  isRuleGroup,
6332
+ layeredNeighbor,
6333
+ layoutLayeredGraph,
5440
6334
  layoutRelationshipGraph,
5441
6335
  mapRuleGroup,
5442
6336
  matchFilter,
@@ -5478,6 +6372,7 @@ function DiffViewer({
5478
6372
  useVirtualRows,
5479
6373
  validateRuleDefinition,
5480
6374
  virtualWindow,
6375
+ wrapEdgeLabel,
5481
6376
  zonedDateKey,
5482
6377
  zonedDateSpan
5483
6378
  });