@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/README.md +29 -0
- package/dist/components/RelationshipGraph.d.ts +81 -3
- package/dist/components/RelationshipGraph.js +81 -57
- package/dist/components/RelationshipGraphLayered.d.ts +8 -0
- package/dist/components/RelationshipGraphLayered.js +420 -0
- package/dist/components/RelationshipGraphList.d.ts +16 -0
- package/dist/components/RelationshipGraphList.js +33 -0
- package/dist/index.cjs +1130 -235
- package/dist/index.d.cts +187 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -2
- package/dist/relationship-graph-model.d.ts +112 -1
- package/dist/relationship-graph-model.js +420 -1
- package/package.json +1 -1
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
|
|
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/
|
|
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
|
|
4380
|
-
|
|
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 =
|
|
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 =
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
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
|
|
4401
|
-
const
|
|
4402
|
-
const
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
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
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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) ?
|
|
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,
|
|
4482
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4489
|
-
/* @__PURE__ */ (0,
|
|
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
|
|
4511
|
-
var
|
|
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,
|
|
4528
|
-
const [announcement, setAnnouncement] = (0,
|
|
4529
|
-
const [bulkPending, setBulkPending] = (0,
|
|
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,
|
|
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,
|
|
4560
|
-
if (error) return /* @__PURE__ */ (0,
|
|
4561
|
-
/* @__PURE__ */ (0,
|
|
4562
|
-
onRetry && /* @__PURE__ */ (0,
|
|
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,
|
|
4565
|
-
/* @__PURE__ */ (0,
|
|
4566
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4569
|
-
/* @__PURE__ */ (0,
|
|
4570
|
-
/* @__PURE__ */ (0,
|
|
4571
|
-
/* @__PURE__ */ (0,
|
|
4572
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4579
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4584
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4589
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4592
|
-
/* @__PURE__ */ (0,
|
|
4593
|
-
/* @__PURE__ */ (0,
|
|
4594
|
-
/* @__PURE__ */ (0,
|
|
4595
|
-
/* @__PURE__ */ (0,
|
|
4596
|
-
fields.map((field) => /* @__PURE__ */ (0,
|
|
4597
|
-
/* @__PURE__ */ (0,
|
|
4598
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5489
|
+
/* @__PURE__ */ (0, import_jsx_runtime65.jsx)("tbody", { children: candidates.map((candidate) => {
|
|
4601
5490
|
const resolved = candidate.status === "resolved";
|
|
4602
|
-
return /* @__PURE__ */ (0,
|
|
4603
|
-
/* @__PURE__ */ (0,
|
|
4604
|
-
/* @__PURE__ */ (0,
|
|
4605
|
-
fields.map((field) => /* @__PURE__ */ (0,
|
|
4606
|
-
/* @__PURE__ */ (0,
|
|
4607
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
5559
|
+
const generatedId = (0, import_react43.useId)();
|
|
4671
5560
|
const id = providedId ?? generatedId;
|
|
4672
5561
|
const errorId = `${id}-error`;
|
|
4673
|
-
const [text, setText] = (0,
|
|
4674
|
-
(0,
|
|
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,
|
|
4701
|
-
/* @__PURE__ */ (0,
|
|
4702
|
-
/* @__PURE__ */ (0,
|
|
4703
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4723
|
-
/* @__PURE__ */ (0,
|
|
4724
|
-
/* @__PURE__ */ (0,
|
|
4725
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
4759
|
-
/* @__PURE__ */ (0,
|
|
4760
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
4768
|
-
/* @__PURE__ */ (0,
|
|
4769
|
-
over > 0 && /* @__PURE__ */ (0,
|
|
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,
|
|
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
|
|
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
|
|
4853
|
-
var
|
|
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,
|
|
4867
|
-
const triggerRef = (0,
|
|
4868
|
-
const dialogRef = (0,
|
|
4869
|
-
const [open, setOpen] = (0,
|
|
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,
|
|
4872
|
-
const hsv = (0,
|
|
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,
|
|
4876
|
-
(0,
|
|
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,
|
|
4920
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4933
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
4949
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5840
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
|
|
4952
5841
|
Math.round(hsv.h),
|
|
4953
5842
|
"\xB0"
|
|
4954
5843
|
] }),
|
|
4955
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5846
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__range", children: [
|
|
4958
5847
|
"Saturation ",
|
|
4959
|
-
/* @__PURE__ */ (0,
|
|
5848
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
|
|
4960
5849
|
Math.round(hsv.s * 100),
|
|
4961
5850
|
"%"
|
|
4962
5851
|
] }),
|
|
4963
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5854
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__range", children: [
|
|
4966
5855
|
"Brightness ",
|
|
4967
|
-
/* @__PURE__ */ (0,
|
|
5856
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
|
|
4968
5857
|
Math.round(hsv.v * 100),
|
|
4969
5858
|
"%"
|
|
4970
5859
|
] }),
|
|
4971
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5862
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__hex", children: [
|
|
4974
5863
|
"Hex",
|
|
4975
|
-
/* @__PURE__ */ (0,
|
|
4976
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
4983
|
-
presets.length > 0 && /* @__PURE__ */ (0,
|
|
4984
|
-
/* @__PURE__ */ (0,
|
|
4985
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
4988
|
-
/* @__PURE__ */ (0,
|
|
4989
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
5093
|
-
/* @__PURE__ */ (0,
|
|
5094
|
-
profiles.length > 0 && /* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
5990
|
+
/* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
|
|
5102
5991
|
"Name",
|
|
5103
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5106
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
5999
|
+
/* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { children: [
|
|
5111
6000
|
"On brand",
|
|
5112
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
6004
|
+
/* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
|
|
5116
6005
|
"Body font",
|
|
5117
|
-
/* @__PURE__ */ (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,
|
|
6008
|
+
/* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
|
|
5120
6009
|
"Heading font",
|
|
5121
|
-
/* @__PURE__ */ (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,
|
|
6012
|
+
/* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-file-upload uix-brand-profiles__upload", children: [
|
|
5124
6013
|
"Logo file",
|
|
5125
|
-
/* @__PURE__ */ (0,
|
|
6014
|
+
/* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { type: "file", accept: "image/*", onChange: onFile })
|
|
5126
6015
|
] }),
|
|
5127
|
-
/* @__PURE__ */ (0,
|
|
5128
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5131
|
-
/* @__PURE__ */ (0,
|
|
5132
|
-
value.logo ? /* @__PURE__ */ (0,
|
|
5133
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5136
|
-
/* @__PURE__ */ (0,
|
|
5137
|
-
/* @__PURE__ */ (0,
|
|
5138
|
-
/* @__PURE__ */ (0,
|
|
5139
|
-
/* @__PURE__ */ (0,
|
|
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
|
|
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
|
|
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,
|
|
5218
|
-
const entries = (0,
|
|
5219
|
-
const [internalResolutions, setInternalResolutions] = (0,
|
|
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,
|
|
5227
|
-
/* @__PURE__ */ (0,
|
|
5228
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5231
|
-
/* @__PURE__ */ (0,
|
|
5232
|
-
GROUPS.map((group) => /* @__PURE__ */ (0,
|
|
5233
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5238
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5242
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5250
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
5258
|
-
/* @__PURE__ */ (0,
|
|
5259
|
-
/* @__PURE__ */ (0,
|
|
5260
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5263
|
-
/* @__PURE__ */ (0,
|
|
5264
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
5267
|
-
/* @__PURE__ */ (0,
|
|
5268
|
-
/* @__PURE__ */ (0,
|
|
5269
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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
|
});
|