@railtownai/railtracks-visualizer 0.0.29 → 0.0.31

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # RailTracks Visualizer
2
2
 
3
+ [![Tests](https://github.com/RailtownAI/railtracks-visualizer/actions/workflows/ci.yaml/badge.svg)](https://github.com/RailtownAI/railtracks-visualizer/actions/workflows/ci.yaml)
4
+
3
5
  A React-based visualizer for RailTracks agentic flows
4
6
 
5
7
  ## 🚀 Quick Start
package/dist/cjs/index.js CHANGED
@@ -16351,6 +16351,8 @@ const InputItemTextarea = styled(index$1)`
16351
16351
  `;
16352
16352
 
16353
16353
  const DEFAULT_COUNTUP_DURATION = 0.6;
16354
+ const DEFAULT_POPOVER_WIDTH = 350;
16355
+ const DEFAULT_POPOVER_HEIGHT = 500;
16354
16356
  // Helper component for animating currency values
16355
16357
  const CountUpCurrency = ({ value, prefix = "", suffix = "" })=>{
16356
16358
  return /*#__PURE__*/ React.createElement(CountUp, {
@@ -16453,8 +16455,8 @@ const NodeDetailsPopover = ({ isVisible, onClose, nodeData, triggerRef })=>{
16453
16455
  const rect = triggerRef.current.getBoundingClientRect();
16454
16456
  const viewportWidth = window.innerWidth;
16455
16457
  const viewportHeight = window.innerHeight;
16456
- const popoverWidth = 400; // Match the width from styled component
16457
- const popoverHeight = 600; // Approximate max height
16458
+ const popoverWidth = DEFAULT_POPOVER_WIDTH; // Match the width from styled component
16459
+ const popoverHeight = DEFAULT_POPOVER_HEIGHT; // Approximate max height
16458
16460
  const gap = 12; // Gap between node and popover
16459
16461
  // Check if the rect is valid (has non-zero dimensions and is in viewport)
16460
16462
  const isValidRect = rect.width > 0 && rect.height > 0 && rect.top >= 0 && rect.left >= 0;
@@ -16702,8 +16704,8 @@ const PopoverPortal = styled.div`
16702
16704
  pointer-events: none;
16703
16705
  `;
16704
16706
  const PopoverContent = styled.div`
16705
- width: 400px;
16706
- max-height: 600px;
16707
+ width: ${DEFAULT_POPOVER_WIDTH}px;
16708
+ max-height: ${DEFAULT_POPOVER_HEIGHT}px;
16707
16709
  background-color: ${(props)=>props.theme?.colors?.background || "hsl(0 0% 100%)"};
16708
16710
  border: 1px solid ${(props)=>props.theme?.colors?.border || "hsl(214.3 31.8% 91.4%)"};
16709
16711
  border-radius: 0.5rem;
@@ -20149,7 +20151,6 @@ SheetDescription.displayName = Description.displayName;
20149
20151
  }
20150
20152
  // Lay out each tree, centered
20151
20153
  // Calculate the starting position to center the entire layout in the container
20152
- // Ensure we don't have negative startX which would cause rightward offset
20153
20154
  const startX = Math.max(0, (containerWidth - totalWidth) / 2);
20154
20155
  let xCursor = startX;
20155
20156
  for (const root of roots){
@@ -20195,16 +20196,28 @@ const nodeTypes = {
20195
20196
  Tool: Node$1,
20196
20197
  Coordinator: Node$1
20197
20198
  };
20198
- const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height = "1000px", className = "", defaultZoom = 1, defaultPan = {
20199
+ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100dvw", height = "100dvh", className = "", defaultZoom = 1, defaultPan = {
20199
20200
  x: 0,
20200
20201
  y: 0
20201
- }, disableAutoFit = true, showTimeline = false, minNodeSpacing = 400, onInspect })=>{
20202
+ }, disableAutoFit = true, defaultAutoFitDuration = 1000, defaultAutoFitDelay = 250, showTimeline = false, minNodeSpacing = 300, onInspect })=>{
20202
20203
  const { theme, isDarkMode } = useTheme();
20203
20204
  const themeColors = theme.colors;
20205
+ // Helper function to determine if we should apply minimum dimensions
20206
+ const shouldApplyMinDimensions = React.useCallback(()=>{
20207
+ const widthStr = typeof width === "string" ? width : `${width}px`;
20208
+ const heightStr = typeof height === "string" ? height : `${height}px`;
20209
+ // Don't apply minimum dimensions if using viewport units
20210
+ const hasViewportUnits = /\b\d*\.?\d+(vw|vh|vmin|vmax)\b/.test(widthStr) || /\b\d*\.?\d+(vw|vh|vmin|vmax)\b/.test(heightStr);
20211
+ return !hasViewportUnits;
20212
+ }, [
20213
+ width,
20214
+ height
20215
+ ]);
20204
20216
  // Use prop data directly, no hooks needed
20205
20217
  const flowData = propFlowData;
20206
20218
  // Show no data state if no flowData is available
20207
20219
  if (!flowData) {
20220
+ const applyMinDimensions = shouldApplyMinDimensions();
20208
20221
  return /*#__PURE__*/ React.createElement("div", {
20209
20222
  style: {
20210
20223
  width: typeof width === "number" ? `${width}px` : width,
@@ -20213,8 +20226,10 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20213
20226
  borderRadius: "8px",
20214
20227
  background: themeColors.muted,
20215
20228
  position: "relative",
20216
- minWidth: "800px",
20217
- minHeight: "600px",
20229
+ ...applyMinDimensions && {
20230
+ minWidth: "800px",
20231
+ minHeight: "600px"
20232
+ },
20218
20233
  display: "flex",
20219
20234
  alignItems: "center",
20220
20235
  justifyContent: "center"
@@ -20324,9 +20339,8 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20324
20339
  if (!flowData.nodes) {
20325
20340
  return new Map();
20326
20341
  }
20327
- // Use container dimensions for centering, fallback to reasonable defaults
20328
- // Ensure we have a reasonable minimum width for proper centering
20329
- const containerWidth = Math.max(containerDimensions.width || 800, 1200);
20342
+ // Use actual container dimensions for proper centering
20343
+ const containerWidth = containerDimensions.width || 800;
20330
20344
  return calculateAutoLayout(flowData.nodes, flowData.edges || [], {
20331
20345
  minNodeSpacing,
20332
20346
  containerWidth
@@ -20351,6 +20365,22 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20351
20365
  }, [
20352
20366
  flowData.edges
20353
20367
  ]);
20368
+ const reactFlowInstance = useReactFlow();
20369
+ // Pan function that can be called by child components
20370
+ React.useCallback((direction, amount)=>{
20371
+ if (!reactFlowInstance) return;
20372
+ const currentViewport = reactFlowInstance.getViewport();
20373
+ const panAmount = direction === "right" ? amount : -amount;
20374
+ reactFlowInstance.setViewport({
20375
+ x: currentViewport.x - panAmount,
20376
+ y: currentViewport.y,
20377
+ zoom: currentViewport.zoom
20378
+ }, {
20379
+ duration: 300
20380
+ });
20381
+ }, [
20382
+ reactFlowInstance
20383
+ ]);
20354
20384
  // Convert flow data to ReactFlow format with step filtering
20355
20385
  const nodes = React.useMemo(()=>{
20356
20386
  const stepNodes = getNodesForStep(currentStep);
@@ -20461,7 +20491,6 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20461
20491
  y: svgP.y
20462
20492
  };
20463
20493
  };
20464
- const reactFlowInstance = useReactFlow();
20465
20494
  // Pan to hub node (most connected node) on initial load, unless disabled or custom settings provided
20466
20495
  React.useEffect(()=>{
20467
20496
  if (disableAutoFit || !reactFlowInstance || nodes.length === 0) {
@@ -20500,31 +20529,34 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20500
20529
  id: hubNodeId
20501
20530
  }
20502
20531
  ],
20503
- duration: 1000,
20532
+ duration: defaultAutoFitDuration,
20504
20533
  padding: 0.2,
20505
20534
  minZoom: defaultZoom,
20506
- maxZoom: 1
20535
+ maxZoom: defaultZoom
20507
20536
  });
20508
- }, 500); // Small delay to ensure nodes are rendered
20537
+ }, defaultAutoFitDelay); // Small delay to ensure nodes are rendered
20509
20538
  }
20510
20539
  }, [
20511
20540
  nodes,
20512
20541
  edges,
20513
20542
  reactFlowInstance,
20514
20543
  disableAutoFit,
20515
- defaultZoom
20544
+ defaultZoom,
20545
+ defaultAutoFitDuration,
20546
+ defaultAutoFitDelay
20516
20547
  ]);
20517
20548
  return /*#__PURE__*/ React.createElement("div", {
20518
20549
  ref: containerRef,
20519
20550
  style: {
20520
20551
  width: typeof width === "number" ? `${width}px` : width,
20521
20552
  height: typeof height === "number" ? `${height}px` : height,
20553
+ minWidth: "800px",
20554
+ minHeight: "600px",
20555
+ boxSizing: "border-box",
20522
20556
  border: `1px solid ${themeColors.border}`,
20523
20557
  borderRadius: "8px",
20524
20558
  overflow: "hidden",
20525
20559
  position: "relative",
20526
- minWidth: "800px",
20527
- minHeight: "600px",
20528
20560
  background: themeColors.background
20529
20561
  },
20530
20562
  className: className
@@ -24446,6 +24478,7 @@ const SelectContent = styled(Content2)`
24446
24478
  z-index: 50;
24447
24479
  min-width: 8rem;
24448
24480
  overflow: hidden;
24481
+ max-height: ${(props)=>props.$maxHeight || "none"};
24449
24482
  border-radius: ${(props)=>props.$theme.borderRadius.lg};
24450
24483
  border: 1px solid ${(props)=>props.$theme.colors.border};
24451
24484
  background-color: ${(props)=>props.$theme.colors.background};
@@ -24552,16 +24585,45 @@ const SelectScrollDownButtonWithRef = /*#__PURE__*/ React__namespace.forwardRef(
24552
24585
  }));
24553
24586
  });
24554
24587
  SelectScrollDownButtonWithRef.displayName = ScrollDownButton.displayName;
24555
- const SelectContentWithRef = /*#__PURE__*/ React__namespace.forwardRef(({ children, position = "popper", ...props }, ref)=>{
24588
+ const SelectContentWithRef = /*#__PURE__*/ React__namespace.forwardRef(({ children, position = "popper", maxItems = 6, itemHeight = "2.5rem", ...props }, ref)=>{
24556
24589
  const { theme } = useTheme();
24590
+ const viewportRef = React__namespace.useRef(null);
24591
+ // Calculate max height based on maxItems and itemHeight
24592
+ const maxHeight = maxItems ? `calc(${itemHeight} * ${maxItems} + 8px)` : "none";
24593
+ // Handle scroll wheel events for smooth scrolling
24594
+ const handleWheel = React__namespace.useCallback((e)=>{
24595
+ if (viewportRef.current) {
24596
+ e.preventDefault();
24597
+ const scrollAmount = e.deltaY;
24598
+ viewportRef.current.scrollTop += scrollAmount;
24599
+ }
24600
+ }, []);
24601
+ React__namespace.useEffect(()=>{
24602
+ const viewport = viewportRef.current;
24603
+ if (viewport) {
24604
+ viewport.addEventListener("wheel", handleWheel, {
24605
+ passive: false
24606
+ });
24607
+ return ()=>{
24608
+ viewport.removeEventListener("wheel", handleWheel);
24609
+ };
24610
+ }
24611
+ }, [
24612
+ handleWheel
24613
+ ]);
24557
24614
  return /*#__PURE__*/ React__namespace.createElement(Portal$1, null, /*#__PURE__*/ React__namespace.createElement(SelectContent, {
24558
24615
  ref: ref,
24559
24616
  position: position,
24560
24617
  $theme: theme,
24618
+ $maxHeight: maxHeight,
24561
24619
  ...props
24562
24620
  }, /*#__PURE__*/ React__namespace.createElement(SelectScrollUpButtonWithRef, null), /*#__PURE__*/ React__namespace.createElement(Viewport, {
24621
+ ref: viewportRef,
24563
24622
  style: {
24564
- padding: "4px"
24623
+ padding: "4px",
24624
+ maxHeight: maxHeight !== "none" ? `calc(${maxHeight} - 8px)` : "none",
24625
+ overflowY: "auto",
24626
+ scrollBehavior: "smooth"
24565
24627
  }
24566
24628
  }, children), /*#__PURE__*/ React__namespace.createElement(SelectScrollDownButtonWithRef, null)));
24567
24629
  });
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import React__default, { memo, useState, useCallback, useMemo, forwardRef, createContext, useContext, useRef, useLayoutEffect, useEffect, createElement } from 'react';
2
+ import React__default, { memo, useMemo, useState, useCallback, forwardRef, createContext, useContext, useRef, useLayoutEffect, useEffect, createElement } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import * as ReactDOM from 'react-dom';
5
5
  import ReactDOM__default, { createPortal } from 'react-dom';
@@ -16331,6 +16331,8 @@ const InputItemTextarea = styled(index$1)`
16331
16331
  `;
16332
16332
 
16333
16333
  const DEFAULT_COUNTUP_DURATION = 0.6;
16334
+ const DEFAULT_POPOVER_WIDTH = 350;
16335
+ const DEFAULT_POPOVER_HEIGHT = 500;
16334
16336
  // Helper component for animating currency values
16335
16337
  const CountUpCurrency = ({ value, prefix = "", suffix = "" })=>{
16336
16338
  return /*#__PURE__*/ React__default.createElement(CountUp, {
@@ -16433,8 +16435,8 @@ const NodeDetailsPopover = ({ isVisible, onClose, nodeData, triggerRef })=>{
16433
16435
  const rect = triggerRef.current.getBoundingClientRect();
16434
16436
  const viewportWidth = window.innerWidth;
16435
16437
  const viewportHeight = window.innerHeight;
16436
- const popoverWidth = 400; // Match the width from styled component
16437
- const popoverHeight = 600; // Approximate max height
16438
+ const popoverWidth = DEFAULT_POPOVER_WIDTH; // Match the width from styled component
16439
+ const popoverHeight = DEFAULT_POPOVER_HEIGHT; // Approximate max height
16438
16440
  const gap = 12; // Gap between node and popover
16439
16441
  // Check if the rect is valid (has non-zero dimensions and is in viewport)
16440
16442
  const isValidRect = rect.width > 0 && rect.height > 0 && rect.top >= 0 && rect.left >= 0;
@@ -16682,8 +16684,8 @@ const PopoverPortal = styled.div`
16682
16684
  pointer-events: none;
16683
16685
  `;
16684
16686
  const PopoverContent = styled.div`
16685
- width: 400px;
16686
- max-height: 600px;
16687
+ width: ${DEFAULT_POPOVER_WIDTH}px;
16688
+ max-height: ${DEFAULT_POPOVER_HEIGHT}px;
16687
16689
  background-color: ${(props)=>props.theme?.colors?.background || "hsl(0 0% 100%)"};
16688
16690
  border: 1px solid ${(props)=>props.theme?.colors?.border || "hsl(214.3 31.8% 91.4%)"};
16689
16691
  border-radius: 0.5rem;
@@ -20129,7 +20131,6 @@ SheetDescription.displayName = Description.displayName;
20129
20131
  }
20130
20132
  // Lay out each tree, centered
20131
20133
  // Calculate the starting position to center the entire layout in the container
20132
- // Ensure we don't have negative startX which would cause rightward offset
20133
20134
  const startX = Math.max(0, (containerWidth - totalWidth) / 2);
20134
20135
  let xCursor = startX;
20135
20136
  for (const root of roots){
@@ -20175,16 +20176,28 @@ const nodeTypes = {
20175
20176
  Tool: Node$1,
20176
20177
  Coordinator: Node$1
20177
20178
  };
20178
- const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height = "1000px", className = "", defaultZoom = 1, defaultPan = {
20179
+ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100dvw", height = "100dvh", className = "", defaultZoom = 1, defaultPan = {
20179
20180
  x: 0,
20180
20181
  y: 0
20181
- }, disableAutoFit = true, showTimeline = false, minNodeSpacing = 400, onInspect })=>{
20182
+ }, disableAutoFit = true, defaultAutoFitDuration = 1000, defaultAutoFitDelay = 250, showTimeline = false, minNodeSpacing = 300, onInspect })=>{
20182
20183
  const { theme, isDarkMode } = useTheme();
20183
20184
  const themeColors = theme.colors;
20185
+ // Helper function to determine if we should apply minimum dimensions
20186
+ const shouldApplyMinDimensions = useCallback(()=>{
20187
+ const widthStr = typeof width === "string" ? width : `${width}px`;
20188
+ const heightStr = typeof height === "string" ? height : `${height}px`;
20189
+ // Don't apply minimum dimensions if using viewport units
20190
+ const hasViewportUnits = /\b\d*\.?\d+(vw|vh|vmin|vmax)\b/.test(widthStr) || /\b\d*\.?\d+(vw|vh|vmin|vmax)\b/.test(heightStr);
20191
+ return !hasViewportUnits;
20192
+ }, [
20193
+ width,
20194
+ height
20195
+ ]);
20184
20196
  // Use prop data directly, no hooks needed
20185
20197
  const flowData = propFlowData;
20186
20198
  // Show no data state if no flowData is available
20187
20199
  if (!flowData) {
20200
+ const applyMinDimensions = shouldApplyMinDimensions();
20188
20201
  return /*#__PURE__*/ React__default.createElement("div", {
20189
20202
  style: {
20190
20203
  width: typeof width === "number" ? `${width}px` : width,
@@ -20193,8 +20206,10 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20193
20206
  borderRadius: "8px",
20194
20207
  background: themeColors.muted,
20195
20208
  position: "relative",
20196
- minWidth: "800px",
20197
- minHeight: "600px",
20209
+ ...applyMinDimensions && {
20210
+ minWidth: "800px",
20211
+ minHeight: "600px"
20212
+ },
20198
20213
  display: "flex",
20199
20214
  alignItems: "center",
20200
20215
  justifyContent: "center"
@@ -20304,9 +20319,8 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20304
20319
  if (!flowData.nodes) {
20305
20320
  return new Map();
20306
20321
  }
20307
- // Use container dimensions for centering, fallback to reasonable defaults
20308
- // Ensure we have a reasonable minimum width for proper centering
20309
- const containerWidth = Math.max(containerDimensions.width || 800, 1200);
20322
+ // Use actual container dimensions for proper centering
20323
+ const containerWidth = containerDimensions.width || 800;
20310
20324
  return calculateAutoLayout(flowData.nodes, flowData.edges || [], {
20311
20325
  minNodeSpacing,
20312
20326
  containerWidth
@@ -20331,6 +20345,22 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20331
20345
  }, [
20332
20346
  flowData.edges
20333
20347
  ]);
20348
+ const reactFlowInstance = useReactFlow();
20349
+ // Pan function that can be called by child components
20350
+ useCallback((direction, amount)=>{
20351
+ if (!reactFlowInstance) return;
20352
+ const currentViewport = reactFlowInstance.getViewport();
20353
+ const panAmount = direction === "right" ? amount : -amount;
20354
+ reactFlowInstance.setViewport({
20355
+ x: currentViewport.x - panAmount,
20356
+ y: currentViewport.y,
20357
+ zoom: currentViewport.zoom
20358
+ }, {
20359
+ duration: 300
20360
+ });
20361
+ }, [
20362
+ reactFlowInstance
20363
+ ]);
20334
20364
  // Convert flow data to ReactFlow format with step filtering
20335
20365
  const nodes = useMemo(()=>{
20336
20366
  const stepNodes = getNodesForStep(currentStep);
@@ -20441,7 +20471,6 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20441
20471
  y: svgP.y
20442
20472
  };
20443
20473
  };
20444
- const reactFlowInstance = useReactFlow();
20445
20474
  // Pan to hub node (most connected node) on initial load, unless disabled or custom settings provided
20446
20475
  useEffect(()=>{
20447
20476
  if (disableAutoFit || !reactFlowInstance || nodes.length === 0) {
@@ -20480,31 +20509,34 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20480
20509
  id: hubNodeId
20481
20510
  }
20482
20511
  ],
20483
- duration: 1000,
20512
+ duration: defaultAutoFitDuration,
20484
20513
  padding: 0.2,
20485
20514
  minZoom: defaultZoom,
20486
- maxZoom: 1
20515
+ maxZoom: defaultZoom
20487
20516
  });
20488
- }, 500); // Small delay to ensure nodes are rendered
20517
+ }, defaultAutoFitDelay); // Small delay to ensure nodes are rendered
20489
20518
  }
20490
20519
  }, [
20491
20520
  nodes,
20492
20521
  edges,
20493
20522
  reactFlowInstance,
20494
20523
  disableAutoFit,
20495
- defaultZoom
20524
+ defaultZoom,
20525
+ defaultAutoFitDuration,
20526
+ defaultAutoFitDelay
20496
20527
  ]);
20497
20528
  return /*#__PURE__*/ React__default.createElement("div", {
20498
20529
  ref: containerRef,
20499
20530
  style: {
20500
20531
  width: typeof width === "number" ? `${width}px` : width,
20501
20532
  height: typeof height === "number" ? `${height}px` : height,
20533
+ minWidth: "800px",
20534
+ minHeight: "600px",
20535
+ boxSizing: "border-box",
20502
20536
  border: `1px solid ${themeColors.border}`,
20503
20537
  borderRadius: "8px",
20504
20538
  overflow: "hidden",
20505
20539
  position: "relative",
20506
- minWidth: "800px",
20507
- minHeight: "600px",
20508
20540
  background: themeColors.background
20509
20541
  },
20510
20542
  className: className
@@ -24426,6 +24458,7 @@ const SelectContent = styled(Content2)`
24426
24458
  z-index: 50;
24427
24459
  min-width: 8rem;
24428
24460
  overflow: hidden;
24461
+ max-height: ${(props)=>props.$maxHeight || "none"};
24429
24462
  border-radius: ${(props)=>props.$theme.borderRadius.lg};
24430
24463
  border: 1px solid ${(props)=>props.$theme.colors.border};
24431
24464
  background-color: ${(props)=>props.$theme.colors.background};
@@ -24532,16 +24565,45 @@ const SelectScrollDownButtonWithRef = /*#__PURE__*/ React.forwardRef(({ ...props
24532
24565
  }));
24533
24566
  });
24534
24567
  SelectScrollDownButtonWithRef.displayName = ScrollDownButton.displayName;
24535
- const SelectContentWithRef = /*#__PURE__*/ React.forwardRef(({ children, position = "popper", ...props }, ref)=>{
24568
+ const SelectContentWithRef = /*#__PURE__*/ React.forwardRef(({ children, position = "popper", maxItems = 6, itemHeight = "2.5rem", ...props }, ref)=>{
24536
24569
  const { theme } = useTheme();
24570
+ const viewportRef = React.useRef(null);
24571
+ // Calculate max height based on maxItems and itemHeight
24572
+ const maxHeight = maxItems ? `calc(${itemHeight} * ${maxItems} + 8px)` : "none";
24573
+ // Handle scroll wheel events for smooth scrolling
24574
+ const handleWheel = React.useCallback((e)=>{
24575
+ if (viewportRef.current) {
24576
+ e.preventDefault();
24577
+ const scrollAmount = e.deltaY;
24578
+ viewportRef.current.scrollTop += scrollAmount;
24579
+ }
24580
+ }, []);
24581
+ React.useEffect(()=>{
24582
+ const viewport = viewportRef.current;
24583
+ if (viewport) {
24584
+ viewport.addEventListener("wheel", handleWheel, {
24585
+ passive: false
24586
+ });
24587
+ return ()=>{
24588
+ viewport.removeEventListener("wheel", handleWheel);
24589
+ };
24590
+ }
24591
+ }, [
24592
+ handleWheel
24593
+ ]);
24537
24594
  return /*#__PURE__*/ React.createElement(Portal$1, null, /*#__PURE__*/ React.createElement(SelectContent, {
24538
24595
  ref: ref,
24539
24596
  position: position,
24540
24597
  $theme: theme,
24598
+ $maxHeight: maxHeight,
24541
24599
  ...props
24542
24600
  }, /*#__PURE__*/ React.createElement(SelectScrollUpButtonWithRef, null), /*#__PURE__*/ React.createElement(Viewport, {
24601
+ ref: viewportRef,
24543
24602
  style: {
24544
- padding: "4px"
24603
+ padding: "4px",
24604
+ maxHeight: maxHeight !== "none" ? `calc(${maxHeight} - 8px)` : "none",
24605
+ overflowY: "auto",
24606
+ scrollBehavior: "smooth"
24545
24607
  }
24546
24608
  }, children), /*#__PURE__*/ React.createElement(SelectScrollDownButtonWithRef, null)));
24547
24609
  });
@@ -12,6 +12,8 @@ export interface AgenticFlowVisualizerProps {
12
12
  y: number;
13
13
  };
14
14
  disableAutoFit?: boolean;
15
+ defaultAutoFitDuration?: number;
16
+ defaultAutoFitDelay?: number;
15
17
  showTimeline?: boolean;
16
18
  minNodeSpacing?: number;
17
19
  onInspect?: (nodeData: any) => void;
@@ -6,7 +6,11 @@ declare const SelectValue: React.ForwardRefExoticComponent<SelectPrimitive.Selec
6
6
  declare const SelectTriggerWithRef: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
7
7
  declare const SelectScrollUpButtonWithRef: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
8
8
  declare const SelectScrollDownButtonWithRef: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
9
- declare const SelectContentWithRef: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
9
+ interface SelectContentProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> {
10
+ maxItems?: number;
11
+ itemHeight?: string;
12
+ }
13
+ declare const SelectContentWithRef: React.ForwardRefExoticComponent<SelectContentProps & React.RefAttributes<HTMLDivElement>>;
10
14
  declare const SelectLabelWithRef: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
11
15
  declare const SelectItemWithRef: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
12
16
  declare const SelectSeparatorWithRef: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@railtownai/railtracks-visualizer",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "license": "MIT",
5
5
  "author": "Railtown AI",
6
6
  "description": "A visualizer for RailTracks agentic flows",
@@ -43,7 +43,7 @@
43
43
  "@radix-ui/react-checkbox": "1.3.3",
44
44
  "@radix-ui/react-dialog": "1.1.15",
45
45
  "@radix-ui/react-select": "2.2.6",
46
- "@railtownai/railtracks-timeline": "0.0.5",
46
+ "@railtownai/railtracks-timeline": "0.0.10",
47
47
  "@xyflow/react": "12.8.4",
48
48
  "lucide-react": "0.542.0",
49
49
  "moment": "2.30.1",
@@ -53,18 +53,19 @@
53
53
  },
54
54
  "scripts": {
55
55
  "build:lib": "rollup -c",
56
+ "build:size": "npm pack --dry-run",
56
57
  "build:spa": "vite build",
57
58
  "build:types": "tsc -p tsconfig.types.json",
58
- "build:size": "npm pack --dry-run",
59
59
  "build": "npm run build:types && npm run build:lib && npm run build:spa",
60
60
  "clean": "rm -rf dist build test-results .railtracks/ui/*",
61
61
  "dev": "rm -rf .railtracks/ui/* && vite build && cp -r build/* .railtracks/ui",
62
62
  "lint:fix": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
63
+ "lint:license": "license-checker --summary --excludePrivatePackages --failOn 'AGPL-1.0;AGPL-3.0;EPL-1.0;EPL-2.0;GPL-1.0;GPL-2.0;GPL-3.0;LGPL-2.0;LGPL-2.1;LGPL-3.0;'",
63
64
  "lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
64
65
  "start": "storybook dev -p 6006",
65
66
  "storybook:build": "cross-env NODE_ENV=production storybook build",
66
- "storybook:preview": "npx http-server ./storybook-static",
67
67
  "storybook:deploy": "gh-pages -d storybook-static",
68
+ "storybook:preview": "npx http-server ./storybook-static",
68
69
  "test:ui": "vitest --ui",
69
70
  "test": "vitest run --silent",
70
71
  "up": "ncu -u -x react -x react-dom -x @types/react -x @types/react-dom && npm install"
@@ -76,11 +77,11 @@
76
77
  "@rollup/plugin-node-resolve": "16.0.1",
77
78
  "@rollup/plugin-swc": "0.4.0",
78
79
  "@rollup/plugin-terser": "0.4.4",
79
- "@storybook/addon-a11y": "9.1.5",
80
- "@storybook/addon-docs": "9.1.5",
81
- "@storybook/addon-onboarding": "9.1.5",
82
- "@storybook/addon-vitest": "9.1.5",
83
- "@storybook/react-vite": "9.1.5",
80
+ "@storybook/addon-a11y": "9.1.10",
81
+ "@storybook/addon-docs": "9.1.10",
82
+ "@storybook/addon-onboarding": "9.1.10",
83
+ "@storybook/addon-vitest": "9.1.10",
84
+ "@storybook/react-vite": "9.1.10",
84
85
  "@testing-library/jest-dom": "6.8.0",
85
86
  "@testing-library/react": "16.3.0",
86
87
  "@testing-library/user-event": "14.6.1",
@@ -95,12 +96,13 @@
95
96
  "cross-env": "^10.0.0",
96
97
  "gh-pages": "6.3.0",
97
98
  "jsdom": "26.1.0",
99
+ "license-checker": "25.0.1",
98
100
  "npm-check-updates": "18.1.0",
99
101
  "playwright": "^1.55.0",
100
102
  "prettier": "3.6.2",
101
103
  "rollup": "4.50.1",
102
104
  "rollup-plugin-postcss": "4.0.2",
103
- "storybook": "9.1.5",
105
+ "storybook": "9.1.10",
104
106
  "tslib": "2.8.1",
105
107
  "typescript": "5.9.2",
106
108
  "vite": "7.1.5",