@procaaso/alphinity-ui-components 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -683,6 +683,15 @@ interface UseViewBoxResult {
683
683
  zoomIn: () => void;
684
684
  /** Zoom out (increase viewBox size) */
685
685
  zoomOut: () => void;
686
+ /** Fit viewBox to content bounds with optional padding */
687
+ fitToContent: (bounds: {
688
+ minX: number;
689
+ minY: number;
690
+ maxX: number;
691
+ maxY: number;
692
+ }, padding?: number) => void;
693
+ /** Set viewBox directly */
694
+ setViewBox: (viewBox: ViewBox) => void;
686
695
  /** Convert screen coordinates to world (SVG) coordinates */
687
696
  screenToWorld: (screenX: number, screenY: number) => {
688
697
  x: number;
@@ -927,9 +936,19 @@ interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
927
936
  }) => void;
928
937
  /** Callback when a pipe is created via connection mode */
929
938
  onPipeCreated?: (sourceNodeId: string, targetNodeId: string, sourcePortId?: string, targetPortId?: string) => void;
939
+ /** Callback when canvas is mounted, provides control functions */
940
+ onMounted?: (controls: PIDCanvasControls) => void;
930
941
  /** Telemetry data bindings for live values */
931
942
  telemetry?: TelemetryMap;
932
943
  }
944
+ interface PIDCanvasControls {
945
+ /** Fit view to show all diagram content */
946
+ fitToContent: (padding?: number) => void;
947
+ /** Set zoom level (1 = 100%, 2 = 200%, etc.) */
948
+ setZoom: (zoomLevel: number) => void;
949
+ /** Get current zoom level */
950
+ getZoom: () => number;
951
+ }
933
952
  /**
934
953
  * PIDCanvas - Interactive canvas for rendering P&ID diagrams
935
954
  *
@@ -939,7 +958,7 @@ interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
939
958
  *
940
959
  * Future: Editing tools, symbol library UI
941
960
  */
942
- declare function PIDCanvas({ diagram, className, style, features, onSelectionChange, onNodeClick, onPipeClick, onNodeMove, onDiagramChange, onDelete, onItemsCopied, onPaste, onSymbolDrop, onPipeCreated, telemetry, ...props }: PIDCanvasProps): react__default.ReactElement;
961
+ declare function PIDCanvas({ diagram, className, style, features, onSelectionChange, onNodeClick, onPipeClick, onNodeMove, onDiagramChange, onDelete, onItemsCopied, onPaste, onSymbolDrop, onPipeCreated, onMounted, telemetry, ...props }: PIDCanvasProps): react__default.ReactElement;
943
962
 
944
963
  interface SymbolLibraryProps {
945
964
  /** Optional className for styling */
package/dist/index.d.ts CHANGED
@@ -683,6 +683,15 @@ interface UseViewBoxResult {
683
683
  zoomIn: () => void;
684
684
  /** Zoom out (increase viewBox size) */
685
685
  zoomOut: () => void;
686
+ /** Fit viewBox to content bounds with optional padding */
687
+ fitToContent: (bounds: {
688
+ minX: number;
689
+ minY: number;
690
+ maxX: number;
691
+ maxY: number;
692
+ }, padding?: number) => void;
693
+ /** Set viewBox directly */
694
+ setViewBox: (viewBox: ViewBox) => void;
686
695
  /** Convert screen coordinates to world (SVG) coordinates */
687
696
  screenToWorld: (screenX: number, screenY: number) => {
688
697
  x: number;
@@ -927,9 +936,19 @@ interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
927
936
  }) => void;
928
937
  /** Callback when a pipe is created via connection mode */
929
938
  onPipeCreated?: (sourceNodeId: string, targetNodeId: string, sourcePortId?: string, targetPortId?: string) => void;
939
+ /** Callback when canvas is mounted, provides control functions */
940
+ onMounted?: (controls: PIDCanvasControls) => void;
930
941
  /** Telemetry data bindings for live values */
931
942
  telemetry?: TelemetryMap;
932
943
  }
944
+ interface PIDCanvasControls {
945
+ /** Fit view to show all diagram content */
946
+ fitToContent: (padding?: number) => void;
947
+ /** Set zoom level (1 = 100%, 2 = 200%, etc.) */
948
+ setZoom: (zoomLevel: number) => void;
949
+ /** Get current zoom level */
950
+ getZoom: () => number;
951
+ }
933
952
  /**
934
953
  * PIDCanvas - Interactive canvas for rendering P&ID diagrams
935
954
  *
@@ -939,7 +958,7 @@ interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
939
958
  *
940
959
  * Future: Editing tools, symbol library UI
941
960
  */
942
- declare function PIDCanvas({ diagram, className, style, features, onSelectionChange, onNodeClick, onPipeClick, onNodeMove, onDiagramChange, onDelete, onItemsCopied, onPaste, onSymbolDrop, onPipeCreated, telemetry, ...props }: PIDCanvasProps): react__default.ReactElement;
961
+ declare function PIDCanvas({ diagram, className, style, features, onSelectionChange, onNodeClick, onPipeClick, onNodeMove, onDiagramChange, onDelete, onItemsCopied, onPaste, onSymbolDrop, onPipeCreated, onMounted, telemetry, ...props }: PIDCanvasProps): react__default.ReactElement;
943
962
 
944
963
  interface SymbolLibraryProps {
945
964
  /** Optional className for styling */
package/dist/index.js CHANGED
@@ -2347,6 +2347,34 @@ function useViewBox(options) {
2347
2347
  };
2348
2348
  });
2349
2349
  }, [initialViewBox, maxZoom]);
2350
+ const fitToContent = useCallback3((bounds, padding = 50) => {
2351
+ const contentWidth = bounds.maxX - bounds.minX;
2352
+ const contentHeight = bounds.maxY - bounds.minY;
2353
+ if (contentWidth === 0 || contentHeight === 0) {
2354
+ return;
2355
+ }
2356
+ const paddedWidth = contentWidth + padding * 2;
2357
+ const paddedHeight = contentHeight + padding * 2;
2358
+ const aspectRatio = initialViewBox.width / initialViewBox.height;
2359
+ const contentAspectRatio = paddedWidth / paddedHeight;
2360
+ let newWidth;
2361
+ let newHeight;
2362
+ if (contentAspectRatio > aspectRatio) {
2363
+ newWidth = paddedWidth;
2364
+ newHeight = paddedWidth / aspectRatio;
2365
+ } else {
2366
+ newHeight = paddedHeight;
2367
+ newWidth = paddedHeight * aspectRatio;
2368
+ }
2369
+ const centerX = bounds.minX + contentWidth / 2;
2370
+ const centerY = bounds.minY + contentHeight / 2;
2371
+ setViewBox({
2372
+ x: centerX - newWidth / 2,
2373
+ y: centerY - newHeight / 2,
2374
+ width: newWidth,
2375
+ height: newHeight
2376
+ });
2377
+ }, [initialViewBox]);
2350
2378
  useEffect4(() => {
2351
2379
  if (!enableZoom || !svgRef.current) return;
2352
2380
  const svg = svgRef.current;
@@ -2578,6 +2606,8 @@ function useViewBox(options) {
2578
2606
  resetViewBox,
2579
2607
  zoomIn,
2580
2608
  zoomOut,
2609
+ fitToContent,
2610
+ setViewBox,
2581
2611
  screenToWorld,
2582
2612
  svgRef
2583
2613
  };
@@ -3232,6 +3262,7 @@ function PIDCanvas({
3232
3262
  onPaste,
3233
3263
  onSymbolDrop,
3234
3264
  onPipeCreated,
3265
+ onMounted,
3235
3266
  telemetry,
3236
3267
  ...props
3237
3268
  }) {
@@ -3249,12 +3280,72 @@ function PIDCanvas({
3249
3280
  const {
3250
3281
  viewBox: controlledViewBox,
3251
3282
  svgRef,
3252
- screenToWorld
3283
+ screenToWorld,
3284
+ fitToContent: fitToContentViewBox,
3285
+ setViewBox
3253
3286
  } = useViewBox({
3254
3287
  initialViewBox: diagram.viewBox,
3255
3288
  enablePan: features.pan ?? true,
3256
3289
  enableZoom: features.zoom ?? true
3257
3290
  });
3291
+ const calculateContentBounds = useCallback7(() => {
3292
+ const nodes2 = localDiagram.nodes.filter((n) => n.visible !== false);
3293
+ if (nodes2.length === 0) {
3294
+ return null;
3295
+ }
3296
+ let minX = Infinity;
3297
+ let minY = Infinity;
3298
+ let maxX = -Infinity;
3299
+ let maxY = -Infinity;
3300
+ nodes2.forEach((node) => {
3301
+ const symbol = getSymbolDefinition(node.symbolId);
3302
+ if (!symbol) return;
3303
+ const nodeMinX = node.transform.x;
3304
+ const nodeMinY = node.transform.y;
3305
+ const nodeMaxX = node.transform.x + symbol.viewBox.width;
3306
+ const nodeMaxY = node.transform.y + symbol.viewBox.height;
3307
+ minX = Math.min(minX, nodeMinX);
3308
+ minY = Math.min(minY, nodeMinY);
3309
+ maxX = Math.max(maxX, nodeMaxX);
3310
+ maxY = Math.max(maxY, nodeMaxY);
3311
+ });
3312
+ return { minX, minY, maxX, maxY };
3313
+ }, [localDiagram.nodes]);
3314
+ useEffect8(() => {
3315
+ if (onMounted) {
3316
+ const controls = {
3317
+ fitToContent: (padding = 50) => {
3318
+ const bounds = calculateContentBounds();
3319
+ if (bounds) {
3320
+ fitToContentViewBox(bounds, padding);
3321
+ }
3322
+ },
3323
+ setZoom: (zoomLevel) => {
3324
+ const centerX = controlledViewBox.x + controlledViewBox.width / 2;
3325
+ const centerY = controlledViewBox.y + controlledViewBox.height / 2;
3326
+ const newWidth = diagram.viewBox.width / zoomLevel;
3327
+ const newHeight = diagram.viewBox.height / zoomLevel;
3328
+ setViewBox({
3329
+ x: centerX - newWidth / 2,
3330
+ y: centerY - newHeight / 2,
3331
+ width: newWidth,
3332
+ height: newHeight
3333
+ });
3334
+ },
3335
+ getZoom: () => {
3336
+ return diagram.viewBox.width / controlledViewBox.width;
3337
+ }
3338
+ };
3339
+ onMounted(controls);
3340
+ }
3341
+ }, [
3342
+ onMounted,
3343
+ calculateContentBounds,
3344
+ fitToContentViewBox,
3345
+ setViewBox,
3346
+ controlledViewBox,
3347
+ diagram.viewBox
3348
+ ]);
3258
3349
  const { selection, selectNode, selectPipe, clearSelection } = useSelection({
3259
3350
  multiSelect: features.multiSelect ?? true,
3260
3351
  onSelectionChange