@almadar/ui 5.58.0 → 5.59.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.
@@ -40199,6 +40199,9 @@ var init_GraphCanvas = __esm({
40199
40199
  actions,
40200
40200
  onNodeClick,
40201
40201
  nodeClickEvent,
40202
+ selectedNodeId,
40203
+ repulsion = 800,
40204
+ linkDistance = 100,
40202
40205
  layout = "force",
40203
40206
  entity,
40204
40207
  isLoading = false,
@@ -40214,6 +40217,35 @@ var init_GraphCanvas = __esm({
40214
40217
  const [hoveredNode, setHoveredNode] = React90.useState(null);
40215
40218
  const nodesRef = React90.useRef([]);
40216
40219
  const [, forceUpdate] = React90.useState(0);
40220
+ const interactionRef = React90.useRef({
40221
+ mode: "none",
40222
+ dragNodeId: null,
40223
+ startMouse: { x: 0, y: 0 },
40224
+ startOffset: { x: 0, y: 0 },
40225
+ downPos: { x: 0, y: 0 }
40226
+ });
40227
+ const toCoords = React90.useCallback(
40228
+ (e) => {
40229
+ const canvas = canvasRef.current;
40230
+ if (!canvas) return null;
40231
+ const rect = canvas.getBoundingClientRect();
40232
+ const screenX = e.clientX - rect.left;
40233
+ const screenY = e.clientY - rect.top;
40234
+ return {
40235
+ screenX,
40236
+ screenY,
40237
+ graphX: (screenX - offset.x) / zoom,
40238
+ graphY: (screenY - offset.y) / zoom
40239
+ };
40240
+ },
40241
+ [offset, zoom]
40242
+ );
40243
+ const nodeAt = React90.useCallback((graphX, graphY) => {
40244
+ return nodesRef.current.find((n) => {
40245
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
40246
+ return dist < (n.size || 8) + 4;
40247
+ });
40248
+ }, []);
40217
40249
  const handleAction = React90.useCallback(
40218
40250
  (action) => {
40219
40251
  if (action.event) {
@@ -40279,7 +40311,7 @@ var init_GraphCanvas = __esm({
40279
40311
  const dx = nodes[j].x - nodes[i].x;
40280
40312
  const dy = nodes[j].y - nodes[i].y;
40281
40313
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
40282
- const force = 800 / (dist * dist);
40314
+ const force = repulsion / (dist * dist);
40283
40315
  const fx = dx / dist * force;
40284
40316
  const fy = dy / dist * force;
40285
40317
  nodes[i].fx -= fx;
@@ -40295,7 +40327,7 @@ var init_GraphCanvas = __esm({
40295
40327
  const dx = target.x - source.x;
40296
40328
  const dy = target.y - source.y;
40297
40329
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
40298
- const force = (dist - 100) * 0.05;
40330
+ const force = (dist - linkDistance) * 0.05;
40299
40331
  const fx = dx / dist * force;
40300
40332
  const fy = dy / dist * force;
40301
40333
  source.fx += fx;
@@ -40329,7 +40361,7 @@ var init_GraphCanvas = __esm({
40329
40361
  return () => {
40330
40362
  cancelAnimationFrame(animRef.current);
40331
40363
  };
40332
- }, [propNodes, propEdges, layout]);
40364
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
40333
40365
  React90.useEffect(() => {
40334
40366
  const canvas = canvasRef.current;
40335
40367
  if (!canvas) return;
@@ -40365,18 +40397,25 @@ var init_GraphCanvas = __esm({
40365
40397
  const size = node.size || 8;
40366
40398
  const color = node.color || getGroupColor(node.group, groups);
40367
40399
  const isHovered = hoveredNode === node.id;
40400
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
40401
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
40368
40402
  ctx.beginPath();
40369
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
40403
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
40370
40404
  ctx.fillStyle = color;
40371
40405
  ctx.fill();
40372
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
40373
- ctx.lineWidth = isHovered ? 2 : 1;
40406
+ if (isSelected) {
40407
+ ctx.strokeStyle = "var(--color-accent)";
40408
+ ctx.lineWidth = 3;
40409
+ } else {
40410
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
40411
+ ctx.lineWidth = isHovered ? 2 : 1;
40412
+ }
40374
40413
  ctx.stroke();
40375
40414
  if (showLabels && node.label) {
40376
40415
  ctx.fillStyle = "#666666";
40377
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
40416
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
40378
40417
  ctx.textAlign = "center";
40379
- ctx.fillText(node.label, node.x, node.y + size + 12);
40418
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
40380
40419
  }
40381
40420
  }
40382
40421
  ctx.restore();
@@ -40387,6 +40426,97 @@ var init_GraphCanvas = __esm({
40387
40426
  setZoom(1);
40388
40427
  setOffset({ x: 0, y: 0 });
40389
40428
  }, []);
40429
+ const handleWheel = React90.useCallback(
40430
+ (e) => {
40431
+ if (!interactive) return;
40432
+ e.preventDefault();
40433
+ const coords = toCoords(e);
40434
+ if (!coords) return;
40435
+ const oldZoom = zoom;
40436
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
40437
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
40438
+ if (newZoom === oldZoom) return;
40439
+ setOffset((o) => ({
40440
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
40441
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
40442
+ }));
40443
+ setZoom(newZoom);
40444
+ },
40445
+ [interactive, toCoords, zoom]
40446
+ );
40447
+ const handleMouseDown = React90.useCallback(
40448
+ (e) => {
40449
+ const coords = toCoords(e);
40450
+ if (!coords) return;
40451
+ const node = nodeAt(coords.graphX, coords.graphY);
40452
+ const state = interactionRef.current;
40453
+ state.downPos = { x: e.clientX, y: e.clientY };
40454
+ state.startMouse = { x: e.clientX, y: e.clientY };
40455
+ state.startOffset = { ...offset };
40456
+ if (draggable && node) {
40457
+ state.mode = "dragging";
40458
+ state.dragNodeId = node.id;
40459
+ } else if (interactive) {
40460
+ state.mode = "panning";
40461
+ state.dragNodeId = null;
40462
+ } else {
40463
+ state.mode = "none";
40464
+ state.dragNodeId = null;
40465
+ }
40466
+ },
40467
+ [toCoords, nodeAt, draggable, interactive, offset]
40468
+ );
40469
+ const handleMouseMove = React90.useCallback(
40470
+ (e) => {
40471
+ const state = interactionRef.current;
40472
+ if (state.mode === "panning") {
40473
+ const dx = e.clientX - state.startMouse.x;
40474
+ const dy = e.clientY - state.startMouse.y;
40475
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
40476
+ return;
40477
+ }
40478
+ if (state.mode === "dragging" && state.dragNodeId) {
40479
+ const coords2 = toCoords(e);
40480
+ if (!coords2) return;
40481
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
40482
+ if (node2) {
40483
+ node2.x = coords2.graphX;
40484
+ node2.y = coords2.graphY;
40485
+ node2.vx = 0;
40486
+ node2.vy = 0;
40487
+ forceUpdate((n) => n + 1);
40488
+ }
40489
+ return;
40490
+ }
40491
+ const coords = toCoords(e);
40492
+ if (!coords) return;
40493
+ const node = nodeAt(coords.graphX, coords.graphY);
40494
+ setHoveredNode(node?.id ?? null);
40495
+ },
40496
+ [toCoords, nodeAt]
40497
+ );
40498
+ const handleMouseUp = React90.useCallback(
40499
+ (e) => {
40500
+ const state = interactionRef.current;
40501
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
40502
+ state.mode = "none";
40503
+ state.dragNodeId = null;
40504
+ if (moved < 4) {
40505
+ const coords = toCoords(e);
40506
+ if (!coords) return;
40507
+ const node = nodeAt(coords.graphX, coords.graphY);
40508
+ if (node) {
40509
+ handleNodeClick(node);
40510
+ }
40511
+ }
40512
+ },
40513
+ [toCoords, nodeAt, handleNodeClick]
40514
+ );
40515
+ const handleMouseLeave = React90.useCallback(() => {
40516
+ interactionRef.current.mode = "none";
40517
+ interactionRef.current.dragNodeId = null;
40518
+ setHoveredNode(null);
40519
+ }, []);
40390
40520
  if (isLoading) {
40391
40521
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
40392
40522
  }
@@ -40448,20 +40578,11 @@ var init_GraphCanvas = __esm({
40448
40578
  height,
40449
40579
  className: "w-full cursor-grab active:cursor-grabbing",
40450
40580
  style: { height },
40451
- onClick: (e) => {
40452
- const canvas = canvasRef.current;
40453
- if (!canvas) return;
40454
- const rect = canvas.getBoundingClientRect();
40455
- const x = (e.clientX - rect.left - offset.x) / zoom;
40456
- const y = (e.clientY - rect.top - offset.y) / zoom;
40457
- const clickedNode = nodesRef.current.find((n) => {
40458
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
40459
- return dist < (n.size || 8) + 4;
40460
- });
40461
- if (clickedNode) {
40462
- handleNodeClick(clickedNode);
40463
- }
40464
- }
40581
+ onWheel: handleWheel,
40582
+ onMouseDown: handleMouseDown,
40583
+ onMouseMove: handleMouseMove,
40584
+ onMouseUp: handleMouseUp,
40585
+ onMouseLeave: handleMouseLeave
40465
40586
  }
40466
40587
  ) }),
40467
40588
  groups.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
package/dist/avl/index.js CHANGED
@@ -40152,6 +40152,9 @@ var init_GraphCanvas = __esm({
40152
40152
  actions,
40153
40153
  onNodeClick,
40154
40154
  nodeClickEvent,
40155
+ selectedNodeId,
40156
+ repulsion = 800,
40157
+ linkDistance = 100,
40155
40158
  layout = "force",
40156
40159
  entity,
40157
40160
  isLoading = false,
@@ -40167,6 +40170,35 @@ var init_GraphCanvas = __esm({
40167
40170
  const [hoveredNode, setHoveredNode] = useState(null);
40168
40171
  const nodesRef = useRef([]);
40169
40172
  const [, forceUpdate] = useState(0);
40173
+ const interactionRef = useRef({
40174
+ mode: "none",
40175
+ dragNodeId: null,
40176
+ startMouse: { x: 0, y: 0 },
40177
+ startOffset: { x: 0, y: 0 },
40178
+ downPos: { x: 0, y: 0 }
40179
+ });
40180
+ const toCoords = useCallback(
40181
+ (e) => {
40182
+ const canvas = canvasRef.current;
40183
+ if (!canvas) return null;
40184
+ const rect = canvas.getBoundingClientRect();
40185
+ const screenX = e.clientX - rect.left;
40186
+ const screenY = e.clientY - rect.top;
40187
+ return {
40188
+ screenX,
40189
+ screenY,
40190
+ graphX: (screenX - offset.x) / zoom,
40191
+ graphY: (screenY - offset.y) / zoom
40192
+ };
40193
+ },
40194
+ [offset, zoom]
40195
+ );
40196
+ const nodeAt = useCallback((graphX, graphY) => {
40197
+ return nodesRef.current.find((n) => {
40198
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
40199
+ return dist < (n.size || 8) + 4;
40200
+ });
40201
+ }, []);
40170
40202
  const handleAction = useCallback(
40171
40203
  (action) => {
40172
40204
  if (action.event) {
@@ -40232,7 +40264,7 @@ var init_GraphCanvas = __esm({
40232
40264
  const dx = nodes[j].x - nodes[i].x;
40233
40265
  const dy = nodes[j].y - nodes[i].y;
40234
40266
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
40235
- const force = 800 / (dist * dist);
40267
+ const force = repulsion / (dist * dist);
40236
40268
  const fx = dx / dist * force;
40237
40269
  const fy = dy / dist * force;
40238
40270
  nodes[i].fx -= fx;
@@ -40248,7 +40280,7 @@ var init_GraphCanvas = __esm({
40248
40280
  const dx = target.x - source.x;
40249
40281
  const dy = target.y - source.y;
40250
40282
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
40251
- const force = (dist - 100) * 0.05;
40283
+ const force = (dist - linkDistance) * 0.05;
40252
40284
  const fx = dx / dist * force;
40253
40285
  const fy = dy / dist * force;
40254
40286
  source.fx += fx;
@@ -40282,7 +40314,7 @@ var init_GraphCanvas = __esm({
40282
40314
  return () => {
40283
40315
  cancelAnimationFrame(animRef.current);
40284
40316
  };
40285
- }, [propNodes, propEdges, layout]);
40317
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
40286
40318
  useEffect(() => {
40287
40319
  const canvas = canvasRef.current;
40288
40320
  if (!canvas) return;
@@ -40318,18 +40350,25 @@ var init_GraphCanvas = __esm({
40318
40350
  const size = node.size || 8;
40319
40351
  const color = node.color || getGroupColor(node.group, groups);
40320
40352
  const isHovered = hoveredNode === node.id;
40353
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
40354
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
40321
40355
  ctx.beginPath();
40322
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
40356
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
40323
40357
  ctx.fillStyle = color;
40324
40358
  ctx.fill();
40325
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
40326
- ctx.lineWidth = isHovered ? 2 : 1;
40359
+ if (isSelected) {
40360
+ ctx.strokeStyle = "var(--color-accent)";
40361
+ ctx.lineWidth = 3;
40362
+ } else {
40363
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
40364
+ ctx.lineWidth = isHovered ? 2 : 1;
40365
+ }
40327
40366
  ctx.stroke();
40328
40367
  if (showLabels && node.label) {
40329
40368
  ctx.fillStyle = "#666666";
40330
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
40369
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
40331
40370
  ctx.textAlign = "center";
40332
- ctx.fillText(node.label, node.x, node.y + size + 12);
40371
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
40333
40372
  }
40334
40373
  }
40335
40374
  ctx.restore();
@@ -40340,6 +40379,97 @@ var init_GraphCanvas = __esm({
40340
40379
  setZoom(1);
40341
40380
  setOffset({ x: 0, y: 0 });
40342
40381
  }, []);
40382
+ const handleWheel = useCallback(
40383
+ (e) => {
40384
+ if (!interactive) return;
40385
+ e.preventDefault();
40386
+ const coords = toCoords(e);
40387
+ if (!coords) return;
40388
+ const oldZoom = zoom;
40389
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
40390
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
40391
+ if (newZoom === oldZoom) return;
40392
+ setOffset((o) => ({
40393
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
40394
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
40395
+ }));
40396
+ setZoom(newZoom);
40397
+ },
40398
+ [interactive, toCoords, zoom]
40399
+ );
40400
+ const handleMouseDown = useCallback(
40401
+ (e) => {
40402
+ const coords = toCoords(e);
40403
+ if (!coords) return;
40404
+ const node = nodeAt(coords.graphX, coords.graphY);
40405
+ const state = interactionRef.current;
40406
+ state.downPos = { x: e.clientX, y: e.clientY };
40407
+ state.startMouse = { x: e.clientX, y: e.clientY };
40408
+ state.startOffset = { ...offset };
40409
+ if (draggable && node) {
40410
+ state.mode = "dragging";
40411
+ state.dragNodeId = node.id;
40412
+ } else if (interactive) {
40413
+ state.mode = "panning";
40414
+ state.dragNodeId = null;
40415
+ } else {
40416
+ state.mode = "none";
40417
+ state.dragNodeId = null;
40418
+ }
40419
+ },
40420
+ [toCoords, nodeAt, draggable, interactive, offset]
40421
+ );
40422
+ const handleMouseMove = useCallback(
40423
+ (e) => {
40424
+ const state = interactionRef.current;
40425
+ if (state.mode === "panning") {
40426
+ const dx = e.clientX - state.startMouse.x;
40427
+ const dy = e.clientY - state.startMouse.y;
40428
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
40429
+ return;
40430
+ }
40431
+ if (state.mode === "dragging" && state.dragNodeId) {
40432
+ const coords2 = toCoords(e);
40433
+ if (!coords2) return;
40434
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
40435
+ if (node2) {
40436
+ node2.x = coords2.graphX;
40437
+ node2.y = coords2.graphY;
40438
+ node2.vx = 0;
40439
+ node2.vy = 0;
40440
+ forceUpdate((n) => n + 1);
40441
+ }
40442
+ return;
40443
+ }
40444
+ const coords = toCoords(e);
40445
+ if (!coords) return;
40446
+ const node = nodeAt(coords.graphX, coords.graphY);
40447
+ setHoveredNode(node?.id ?? null);
40448
+ },
40449
+ [toCoords, nodeAt]
40450
+ );
40451
+ const handleMouseUp = useCallback(
40452
+ (e) => {
40453
+ const state = interactionRef.current;
40454
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
40455
+ state.mode = "none";
40456
+ state.dragNodeId = null;
40457
+ if (moved < 4) {
40458
+ const coords = toCoords(e);
40459
+ if (!coords) return;
40460
+ const node = nodeAt(coords.graphX, coords.graphY);
40461
+ if (node) {
40462
+ handleNodeClick(node);
40463
+ }
40464
+ }
40465
+ },
40466
+ [toCoords, nodeAt, handleNodeClick]
40467
+ );
40468
+ const handleMouseLeave = useCallback(() => {
40469
+ interactionRef.current.mode = "none";
40470
+ interactionRef.current.dragNodeId = null;
40471
+ setHoveredNode(null);
40472
+ }, []);
40343
40473
  if (isLoading) {
40344
40474
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
40345
40475
  }
@@ -40401,20 +40531,11 @@ var init_GraphCanvas = __esm({
40401
40531
  height,
40402
40532
  className: "w-full cursor-grab active:cursor-grabbing",
40403
40533
  style: { height },
40404
- onClick: (e) => {
40405
- const canvas = canvasRef.current;
40406
- if (!canvas) return;
40407
- const rect = canvas.getBoundingClientRect();
40408
- const x = (e.clientX - rect.left - offset.x) / zoom;
40409
- const y = (e.clientY - rect.top - offset.y) / zoom;
40410
- const clickedNode = nodesRef.current.find((n) => {
40411
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
40412
- return dist < (n.size || 8) + 4;
40413
- });
40414
- if (clickedNode) {
40415
- handleNodeClick(clickedNode);
40416
- }
40417
- }
40534
+ onWheel: handleWheel,
40535
+ onMouseDown: handleMouseDown,
40536
+ onMouseMove: handleMouseMove,
40537
+ onMouseUp: handleMouseUp,
40538
+ onMouseLeave: handleMouseLeave
40418
40539
  }
40419
40540
  ) }),
40420
40541
  groups.length > 1 && /* @__PURE__ */ jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
@@ -59,6 +59,12 @@ export interface GraphCanvasProps {
59
59
  nodeClickEvent?: EventEmit<{
60
60
  id: string;
61
61
  }>;
62
+ /** Currently selected node id (rendered emphasized) */
63
+ selectedNodeId?: string;
64
+ /** Force-sim repulsion strength between nodes (larger ⇒ more spread out) */
65
+ repulsion?: number;
66
+ /** Force-sim target edge length (larger ⇒ more spread out) */
67
+ linkDistance?: number;
62
68
  /** Layout algorithm */
63
69
  layout?: "force" | "circular" | "grid";
64
70
  /** Entity name for schema-driven auto-fetch */