@almadar/ui 5.146.2 → 5.147.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.
@@ -29963,7 +29963,12 @@ function daysAgo(n) {
29963
29963
  d.setDate(d.getDate() - n);
29964
29964
  return d;
29965
29965
  }
29966
- var DEFAULT_PRESETS, DateRangePicker;
29966
+ function resolvePresetRange(preset) {
29967
+ if (typeof preset.range === "function") return preset.range();
29968
+ if (preset.range) return preset.range;
29969
+ return TOKEN_RANGES[preset.value]?.() ?? null;
29970
+ }
29971
+ var TOKEN_RANGES, DEFAULT_PRESETS, DateRangePicker;
29967
29972
  var init_DateRangePicker = __esm({
29968
29973
  "components/core/molecules/DateRangePicker.tsx"() {
29969
29974
  "use client";
@@ -29973,32 +29978,19 @@ var init_DateRangePicker = __esm({
29973
29978
  init_Stack();
29974
29979
  init_Typography();
29975
29980
  init_useEventBus();
29981
+ TOKEN_RANGES = {
29982
+ "7d": () => ({ from: toISODate(daysAgo(7)), to: toISODate(/* @__PURE__ */ new Date()) }),
29983
+ "30d": () => ({ from: toISODate(daysAgo(30)), to: toISODate(/* @__PURE__ */ new Date()) }),
29984
+ month: () => ({ from: toISODate(startOfMonth(/* @__PURE__ */ new Date())), to: toISODate(/* @__PURE__ */ new Date()) }),
29985
+ quarter: () => ({ from: toISODate(startOfQuarter(/* @__PURE__ */ new Date())), to: toISODate(/* @__PURE__ */ new Date()) }),
29986
+ ytd: () => ({ from: toISODate(startOfYear(/* @__PURE__ */ new Date())), to: toISODate(/* @__PURE__ */ new Date()) })
29987
+ };
29976
29988
  DEFAULT_PRESETS = [
29977
- {
29978
- label: "Last 7 days",
29979
- value: "7d",
29980
- range: () => ({ from: toISODate(daysAgo(7)), to: toISODate(/* @__PURE__ */ new Date()) })
29981
- },
29982
- {
29983
- label: "Last 30 days",
29984
- value: "30d",
29985
- range: () => ({ from: toISODate(daysAgo(30)), to: toISODate(/* @__PURE__ */ new Date()) })
29986
- },
29987
- {
29988
- label: "This Month",
29989
- value: "month",
29990
- range: () => ({ from: toISODate(startOfMonth(/* @__PURE__ */ new Date())), to: toISODate(/* @__PURE__ */ new Date()) })
29991
- },
29992
- {
29993
- label: "This Quarter",
29994
- value: "quarter",
29995
- range: () => ({ from: toISODate(startOfQuarter(/* @__PURE__ */ new Date())), to: toISODate(/* @__PURE__ */ new Date()) })
29996
- },
29997
- {
29998
- label: "YTD",
29999
- value: "ytd",
30000
- range: () => ({ from: toISODate(startOfYear(/* @__PURE__ */ new Date())), to: toISODate(/* @__PURE__ */ new Date()) })
30001
- }
29989
+ { label: "Last 7 days", value: "7d" },
29990
+ { label: "Last 30 days", value: "30d" },
29991
+ { label: "This Month", value: "month" },
29992
+ { label: "This Quarter", value: "quarter" },
29993
+ { label: "YTD", value: "ytd" }
30002
29994
  ];
30003
29995
  DateRangePicker = ({
30004
29996
  from: fromProp,
@@ -30039,7 +30031,8 @@ var init_DateRangePicker = __esm({
30039
30031
  );
30040
30032
  const handlePreset = React94.useCallback(
30041
30033
  (preset) => {
30042
- const range = preset.range();
30034
+ const range = resolvePresetRange(preset);
30035
+ if (range === null) return;
30043
30036
  setFrom(range.from);
30044
30037
  setTo(range.to);
30045
30038
  setActivePreset(preset.value);
@@ -30047,8 +30040,12 @@ var init_DateRangePicker = __esm({
30047
30040
  },
30048
30041
  [emit]
30049
30042
  );
30043
+ const renderablePresets = React94.useMemo(
30044
+ () => presets.filter((p) => p.range !== void 0 || TOKEN_RANGES[p.value] !== void 0),
30045
+ [presets]
30046
+ );
30050
30047
  const presetButtons = React94.useMemo(
30051
- () => presets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
30048
+ () => renderablePresets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
30052
30049
  Button,
30053
30050
  {
30054
30051
  variant: activePreset === preset.value ? "primary" : "ghost",
@@ -30058,7 +30055,7 @@ var init_DateRangePicker = __esm({
30058
30055
  },
30059
30056
  preset.value
30060
30057
  )),
30061
- [presets, activePreset, handlePreset]
30058
+ [renderablePresets, activePreset, handlePreset]
30062
30059
  );
30063
30060
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: cn(className), children: [
30064
30061
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "md", align: "end", children: [
@@ -30085,7 +30082,7 @@ var init_DateRangePicker = __esm({
30085
30082
  )
30086
30083
  ] })
30087
30084
  ] }),
30088
- presets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", wrap: true, children: presetButtons })
30085
+ renderablePresets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", wrap: true, children: presetButtons })
30089
30086
  ] });
30090
30087
  };
30091
30088
  DateRangePicker.displayName = "DateRangePicker";
@@ -30527,6 +30524,208 @@ var init_PhysicsCanvas = __esm({
30527
30524
  };
30528
30525
  }
30529
30526
  });
30527
+
30528
+ // lib/graphViewLayouts.ts
30529
+ function buildAdjacency(nodeIds, edges) {
30530
+ const known = new Set(nodeIds);
30531
+ const out = /* @__PURE__ */ new Map();
30532
+ const inMap = /* @__PURE__ */ new Map();
30533
+ for (const id of nodeIds) {
30534
+ out.set(id, []);
30535
+ inMap.set(id, []);
30536
+ }
30537
+ for (const edge of edges) {
30538
+ if (!known.has(edge.source) || !known.has(edge.target)) continue;
30539
+ out.get(edge.source)?.push(edge.target);
30540
+ inMap.get(edge.target)?.push(edge.source);
30541
+ }
30542
+ return { out, in: inMap };
30543
+ }
30544
+ function findRoots(nodeIds, adjacency) {
30545
+ return nodeIds.filter((id) => (adjacency.in.get(id)?.length ?? 0) === 0);
30546
+ }
30547
+ function assignLayers(nodeIds, adjacency, roots) {
30548
+ const layer = new Map(nodeIds.map((id) => [id, 0]));
30549
+ const onStack = /* @__PURE__ */ new Set();
30550
+ const visit = (id) => {
30551
+ onStack.add(id);
30552
+ const currentLayer = layer.get(id) ?? 0;
30553
+ for (const next of adjacency.out.get(id) ?? []) {
30554
+ if (onStack.has(next)) continue;
30555
+ const candidate = currentLayer + 1;
30556
+ if (candidate > (layer.get(next) ?? 0)) {
30557
+ layer.set(next, candidate);
30558
+ }
30559
+ visit(next);
30560
+ }
30561
+ onStack.delete(id);
30562
+ };
30563
+ for (const root of roots) visit(root);
30564
+ return layer;
30565
+ }
30566
+ function computeVisitOrder(nodeIds, adjacency, roots) {
30567
+ const order = /* @__PURE__ */ new Map();
30568
+ const visited = /* @__PURE__ */ new Set();
30569
+ let counter = 0;
30570
+ const visit = (id) => {
30571
+ if (visited.has(id)) return;
30572
+ visited.add(id);
30573
+ order.set(id, counter++);
30574
+ for (const next of adjacency.out.get(id) ?? []) visit(next);
30575
+ };
30576
+ for (const root of roots) visit(root);
30577
+ for (const id of nodeIds) {
30578
+ if (!visited.has(id)) order.set(id, counter++);
30579
+ }
30580
+ return order;
30581
+ }
30582
+ function bfsDepthAndOrder(nodeIds, adjacency, roots) {
30583
+ const depth = new Map(nodeIds.map((id) => [id, 0]));
30584
+ const visitOrder = /* @__PURE__ */ new Map();
30585
+ const visited = /* @__PURE__ */ new Set();
30586
+ const queue = [];
30587
+ let counter = 0;
30588
+ for (const root of roots) {
30589
+ if (visited.has(root)) continue;
30590
+ visited.add(root);
30591
+ depth.set(root, 0);
30592
+ visitOrder.set(root, counter++);
30593
+ queue.push(root);
30594
+ }
30595
+ let head = 0;
30596
+ while (head < queue.length) {
30597
+ const id = queue[head++];
30598
+ const d = depth.get(id) ?? 0;
30599
+ for (const next of adjacency.out.get(id) ?? []) {
30600
+ if (visited.has(next)) continue;
30601
+ visited.add(next);
30602
+ depth.set(next, d + 1);
30603
+ visitOrder.set(next, counter++);
30604
+ queue.push(next);
30605
+ }
30606
+ }
30607
+ for (const id of nodeIds) {
30608
+ if (!visited.has(id)) visitOrder.set(id, counter++);
30609
+ }
30610
+ return { depth, visitOrder };
30611
+ }
30612
+ function edgeWalkOrder(nodeIds, adjacency) {
30613
+ const visited = /* @__PURE__ */ new Set();
30614
+ const result = [];
30615
+ for (const start of nodeIds) {
30616
+ if (visited.has(start)) continue;
30617
+ let current = start;
30618
+ while (current !== void 0 && !visited.has(current)) {
30619
+ visited.add(current);
30620
+ result.push(current);
30621
+ const outs = adjacency.out.get(current) ?? [];
30622
+ current = outs.find((next) => !visited.has(next));
30623
+ }
30624
+ }
30625
+ return result;
30626
+ }
30627
+ function groupByTier(nodeIds, tierOf, maxTier) {
30628
+ const groups = Array.from({ length: maxTier + 1 }, () => []);
30629
+ for (const id of nodeIds) {
30630
+ groups[tierOf.get(id) ?? 0].push(id);
30631
+ }
30632
+ return groups;
30633
+ }
30634
+ function distributeAxis(count, start, end) {
30635
+ if (count <= 0) return [];
30636
+ const slot = (end - start) / count;
30637
+ return Array.from({ length: count }, (_, i) => start + slot * (i + 0.5));
30638
+ }
30639
+ function orderByParentPositionThenInput(group, adjacency, positioned, inputIndex) {
30640
+ const withKey = group.map((id) => {
30641
+ const parentValues = (adjacency.in.get(id) ?? []).map((p) => positioned.get(p)).filter((v) => v !== void 0);
30642
+ const avg = parentValues.length > 0 ? parentValues.reduce((a, b) => a + b, 0) / parentValues.length : Number.POSITIVE_INFINITY;
30643
+ return { id, avg, idx: inputIndex.get(id) ?? 0 };
30644
+ });
30645
+ withKey.sort((a, b) => a.avg !== b.avg ? a.avg - b.avg : a.idx - b.idx);
30646
+ return withKey.map((k) => k.id);
30647
+ }
30648
+ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
30649
+ const inputIndex = new Map(nodeIds.map((id, i) => [id, i]));
30650
+ const layers = assignLayers(nodeIds, adjacency, roots);
30651
+ const maxLayer = Math.max(...Array.from(layers.values()));
30652
+ const layerGroups = groupByTier(nodeIds, layers, maxLayer);
30653
+ const positions = /* @__PURE__ */ new Map();
30654
+ const yById = /* @__PURE__ */ new Map();
30655
+ for (let l = 0; l <= maxLayer; l++) {
30656
+ const x = margin + l * (width - 2 * margin) / Math.max(1, maxLayer);
30657
+ const ordered2 = orderByParentPositionThenInput(layerGroups[l], adjacency, yById, inputIndex);
30658
+ const ys = distributeAxis(ordered2.length, margin, height - margin);
30659
+ ordered2.forEach((id, i) => {
30660
+ positions.set(id, { id, x, y: ys[i] });
30661
+ yById.set(id, ys[i]);
30662
+ });
30663
+ }
30664
+ return nodeIds.map((id) => positions.get(id));
30665
+ }
30666
+ function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
30667
+ const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
30668
+ const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
30669
+ const maxLayer = Math.max(...Array.from(layers.values()));
30670
+ const visitOrder = computeVisitOrder(nodeIds, adjacency, effectiveRoots);
30671
+ const layerGroups = groupByTier(nodeIds, layers, maxLayer);
30672
+ const positions = /* @__PURE__ */ new Map();
30673
+ for (let l = 0; l <= maxLayer; l++) {
30674
+ const ordered2 = [...layerGroups[l]].sort(
30675
+ (a, b) => (visitOrder.get(a) ?? 0) - (visitOrder.get(b) ?? 0)
30676
+ );
30677
+ const y = margin + l * (height - 2 * margin) / Math.max(1, maxLayer);
30678
+ const xs = distributeAxis(ordered2.length, margin, width - margin);
30679
+ ordered2.forEach((id, i) => positions.set(id, { id, x: xs[i], y }));
30680
+ }
30681
+ return nodeIds.map((id) => positions.get(id));
30682
+ }
30683
+ function layoutRadial(nodeIds, adjacency, roots, width, height, margin) {
30684
+ const cx = width / 2;
30685
+ const cy = height / 2;
30686
+ const maxRadius = Math.min(width, height) / 2 - margin;
30687
+ if (roots.length === 0) {
30688
+ const order = edgeWalkOrder(nodeIds, adjacency);
30689
+ const k = order.length;
30690
+ const positions2 = /* @__PURE__ */ new Map();
30691
+ order.forEach((id, i) => {
30692
+ const angle = i / k * 2 * Math.PI;
30693
+ positions2.set(id, { id, x: cx + maxRadius * Math.cos(angle), y: cy + maxRadius * Math.sin(angle) });
30694
+ });
30695
+ return nodeIds.map((id) => positions2.get(id));
30696
+ }
30697
+ const { depth, visitOrder } = bfsDepthAndOrder(nodeIds, adjacency, roots);
30698
+ const maxDepth = Math.max(...Array.from(depth.values()));
30699
+ const ringGroups = groupByTier(nodeIds, depth, maxDepth);
30700
+ const positions = /* @__PURE__ */ new Map();
30701
+ for (let d = 0; d <= maxDepth; d++) {
30702
+ const ordered2 = [...ringGroups[d]].sort(
30703
+ (a, b) => (visitOrder.get(a) ?? 0) - (visitOrder.get(b) ?? 0)
30704
+ );
30705
+ const radius = d * maxRadius / Math.max(1, maxDepth);
30706
+ const k = ordered2.length;
30707
+ ordered2.forEach((id, i) => {
30708
+ const angle = i / k * 2 * Math.PI;
30709
+ positions.set(id, { id, x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
30710
+ });
30711
+ }
30712
+ return nodeIds.map((id) => positions.get(id));
30713
+ }
30714
+ function computeStaticLayout(mode, input) {
30715
+ const { nodeIds, edges, width, height } = input;
30716
+ const margin = input.margin ?? 40;
30717
+ if (nodeIds.length === 0) return [];
30718
+ if (nodeIds.length === 1) return [{ id: nodeIds[0], x: width / 2, y: height / 2 }];
30719
+ const adjacency = buildAdjacency(nodeIds, edges);
30720
+ const roots = findRoots(nodeIds, adjacency);
30721
+ if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
30722
+ if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
30723
+ return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
30724
+ }
30725
+ var init_graphViewLayouts = __esm({
30726
+ "lib/graphViewLayouts.ts"() {
30727
+ }
30728
+ });
30530
30729
  function resolveNodeColor(node, groups) {
30531
30730
  if (node.color) return node.color;
30532
30731
  if (node.group) {
@@ -30541,6 +30740,7 @@ var init_GraphView = __esm({
30541
30740
  "use client";
30542
30741
  init_cn();
30543
30742
  init_atoms();
30743
+ init_graphViewLayouts();
30544
30744
  GROUP_COLORS = [
30545
30745
  "#3b82f6",
30546
30746
  // blue-500
@@ -30571,11 +30771,13 @@ var init_GraphView = __esm({
30571
30771
  height: propHeight,
30572
30772
  className,
30573
30773
  showLabels = true,
30574
- zoomToFit = true
30774
+ zoomToFit = true,
30775
+ layout = "force"
30575
30776
  }) => {
30576
30777
  const { t } = hooks.useTranslate();
30577
30778
  const containerRef = React94.useRef(null);
30578
30779
  const animRef = React94.useRef(0);
30780
+ const arrowMarkerId = React94.useId();
30579
30781
  const [simNodes, setSimNodes] = React94.useState([]);
30580
30782
  const [settled, setSettled] = React94.useState(false);
30581
30783
  const [hoveredId, setHoveredId] = React94.useState(null);
@@ -30632,6 +30834,22 @@ var init_GraphView = __esm({
30632
30834
  fy: 0
30633
30835
  };
30634
30836
  });
30837
+ if (layout !== "force") {
30838
+ const points = computeStaticLayout(layout, {
30839
+ nodeIds: nodes.map((n) => n.id),
30840
+ edges,
30841
+ width: w,
30842
+ height: h
30843
+ });
30844
+ const pointById = new Map(points.map((p) => [p.id, p]));
30845
+ const laidOut = initialNodes.map((node) => {
30846
+ const point = pointById.get(node.id);
30847
+ return point ? { ...node, x: point.x, y: point.y } : node;
30848
+ });
30849
+ setSimNodes(laidOut);
30850
+ setSettled(true);
30851
+ return;
30852
+ }
30635
30853
  let iterations = 0;
30636
30854
  const maxIterations = 120;
30637
30855
  let currentNodes = initialNodes;
@@ -30700,7 +30918,7 @@ var init_GraphView = __esm({
30700
30918
  return () => {
30701
30919
  cancelAnimationFrame(animRef.current);
30702
30920
  };
30703
- }, [nodes, edges, w, h, groups]);
30921
+ }, [nodes, edges, w, h, groups, layout]);
30704
30922
  const viewBox = React94.useMemo(() => {
30705
30923
  if (!zoomToFit || !settled || simNodes.length === 0) {
30706
30924
  return `0 0 ${w} ${h}`;
@@ -30775,6 +30993,19 @@ var init_GraphView = __esm({
30775
30993
  viewBox,
30776
30994
  preserveAspectRatio: "xMidYMid meet",
30777
30995
  children: [
30996
+ layout !== "force" && /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx(
30997
+ "marker",
30998
+ {
30999
+ id: arrowMarkerId,
31000
+ viewBox: "0 0 10 10",
31001
+ refX: "9",
31002
+ refY: "5",
31003
+ markerWidth: "6",
31004
+ markerHeight: "6",
31005
+ orient: "auto-start-reverse",
31006
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M0,0 L10,5 L0,10 z", fill: "currentColor" })
31007
+ }
31008
+ ) }),
30778
31009
  edges.map((edge, idx) => {
30779
31010
  const source = nodeMap.get(edge.source);
30780
31011
  const target = nodeMap.get(edge.target);
@@ -30789,8 +31020,10 @@ var init_GraphView = __esm({
30789
31020
  x2: target.x,
30790
31021
  y2: target.y,
30791
31022
  stroke: edge.color ?? DEFAULT_EDGE_COLOR,
31023
+ color: edge.color ?? DEFAULT_EDGE_COLOR,
30792
31024
  strokeWidth: 1.5,
30793
- opacity: isHighlighted ? 0.8 : 0.15
31025
+ opacity: isHighlighted ? 0.8 : 0.15,
31026
+ markerEnd: layout !== "force" ? `url(#${arrowMarkerId})` : void 0
30794
31027
  }
30795
31028
  ),
30796
31029
  showLabels && edge.label && /* @__PURE__ */ jsxRuntime.jsx(
@@ -55863,7 +56096,9 @@ function FlowCanvasInner({
55863
56096
  behaviorEntries,
55864
56097
  behaviorWires,
55865
56098
  userType = "builder",
55866
- themeManifest
56099
+ themeManifest,
56100
+ nodePositions,
56101
+ onPositionsChange
55867
56102
  }) {
55868
56103
  const { t } = hooks.useTranslate();
55869
56104
  const NODE_TYPES2 = React94.useMemo(() => ({
@@ -55950,9 +56185,15 @@ function FlowCanvasInner({
55950
56185
  const [nodes, setNodes, onNodesChange] = react.useNodesState(activeNodes);
55951
56186
  const [edges, setEdges, onEdgesChange] = react.useEdgesState(activeEdges);
55952
56187
  const reactFlow = react.useReactFlow();
56188
+ const savedPositionsRef = React94__namespace.default.useRef(nodePositions);
56189
+ savedPositionsRef.current = nodePositions;
56190
+ const nodesRef = React94__namespace.default.useRef(nodes);
56191
+ nodesRef.current = nodes;
55953
56192
  React94.useEffect(() => {
55954
56193
  setEdges([]);
55955
- setNodes(activeNodes);
56194
+ const saved = savedPositionsRef.current;
56195
+ const merged = saved ? activeNodes.map((n) => saved[n.id] ? { ...n, position: saved[n.id] } : n) : activeNodes;
56196
+ setNodes(merged);
55956
56197
  setEdges(activeEdges);
55957
56198
  requestAnimationFrame(() => {
55958
56199
  reactFlow.fitView({ duration: 300, padding: 0.25 });
@@ -56102,6 +56343,14 @@ function FlowCanvasInner({
56102
56343
  targetTraitName: tgtData.traitName
56103
56344
  });
56104
56345
  }, [nodes, onEventWire, eventBus]);
56346
+ const handleNodeDragStop = React94.useCallback(() => {
56347
+ if (!onPositionsChange) return;
56348
+ const positions = {};
56349
+ for (const n of nodesRef.current) {
56350
+ positions[n.id] = { x: n.position.x, y: n.position.y };
56351
+ }
56352
+ onPositionsChange(positions);
56353
+ }, [onPositionsChange]);
56105
56354
  const screenSizeKeys = ["mobile", "tablet", "laptop", "wide"];
56106
56355
  const traitCardSelectionValue = React94.useMemo(() => ({
56107
56356
  selectTransition: (sel) => {
@@ -56133,6 +56382,7 @@ function FlowCanvasInner({
56133
56382
  onNodeDoubleClick: handleNodeDoubleClick,
56134
56383
  onNodeClick: handleNodeClick,
56135
56384
  onConnect: handleConnect,
56385
+ onNodeDragStop: handleNodeDragStop,
56136
56386
  minZoom: 0.1,
56137
56387
  maxZoom: 2,
56138
56388
  fitView: true,
@@ -1655,6 +1655,24 @@ interface FlowCanvasProps {
1655
1655
  * originates from the synthesized `__design_system__` schema.
1656
1656
  */
1657
1657
  themeManifest?: ThemeDefinition;
1658
+ /**
1659
+ * Persisted node positions keyed by node id. When the node set changes
1660
+ * (level/schema switch), any id present here overrides the computed layout
1661
+ * position, restoring a user's manual drag arrangement. Node ids are unique
1662
+ * per view level, so a single flat map covers overview + expanded.
1663
+ */
1664
+ nodePositions?: Record<string, {
1665
+ x: number;
1666
+ y: number;
1667
+ }>;
1668
+ /**
1669
+ * Fired on node drag-stop with the full {id → position} map of the current
1670
+ * node set, so the consumer can persist the arrangement across reloads.
1671
+ */
1672
+ onPositionsChange?: (positions: Record<string, {
1673
+ x: number;
1674
+ y: number;
1675
+ }>) => void;
1658
1676
  }
1659
1677
  declare const FlowCanvas: React__default.FC<FlowCanvasProps>;
1660
1678
 
@@ -1655,6 +1655,24 @@ interface FlowCanvasProps {
1655
1655
  * originates from the synthesized `__design_system__` schema.
1656
1656
  */
1657
1657
  themeManifest?: ThemeDefinition;
1658
+ /**
1659
+ * Persisted node positions keyed by node id. When the node set changes
1660
+ * (level/schema switch), any id present here overrides the computed layout
1661
+ * position, restoring a user's manual drag arrangement. Node ids are unique
1662
+ * per view level, so a single flat map covers overview + expanded.
1663
+ */
1664
+ nodePositions?: Record<string, {
1665
+ x: number;
1666
+ y: number;
1667
+ }>;
1668
+ /**
1669
+ * Fired on node drag-stop with the full {id → position} map of the current
1670
+ * node set, so the consumer can persist the arrangement across reloads.
1671
+ */
1672
+ onPositionsChange?: (positions: Record<string, {
1673
+ x: number;
1674
+ y: number;
1675
+ }>) => void;
1658
1676
  }
1659
1677
  declare const FlowCanvas: React__default.FC<FlowCanvasProps>;
1660
1678