@procaaso/alphinity-ui-components 1.0.2 → 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
@@ -447,6 +447,7 @@ interface NodeInstance {
447
447
  x: number;
448
448
  y: number;
449
449
  };
450
+ labelFontSize?: number;
450
451
  metadata?: Record<string, unknown>;
451
452
  }
452
453
  interface Transform {
@@ -541,6 +542,7 @@ interface DiagramMetadata {
541
542
  modified?: string;
542
543
  tags?: string[];
543
544
  description?: string;
545
+ backgroundColor?: string;
544
546
  }
545
547
 
546
548
  interface SymbolDefinition {
@@ -681,6 +683,15 @@ interface UseViewBoxResult {
681
683
  zoomIn: () => void;
682
684
  /** Zoom out (increase viewBox size) */
683
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;
684
695
  /** Convert screen coordinates to world (SVG) coordinates */
685
696
  screenToWorld: (screenX: number, screenY: number) => {
686
697
  x: number;
@@ -888,6 +899,10 @@ interface PIDCanvasFeatures {
888
899
  allowSymbolDrop?: boolean;
889
900
  /** Enable connection drawing mode to create pipes between nodes (default: false) */
890
901
  connectionMode?: boolean;
902
+ /** Show background grid (default: true) */
903
+ showGrid?: boolean;
904
+ /** Background color for canvas (default: '#ffffff') */
905
+ backgroundColor?: string;
891
906
  }
892
907
  interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
893
908
  /** The diagram to render */
@@ -921,9 +936,19 @@ interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
921
936
  }) => void;
922
937
  /** Callback when a pipe is created via connection mode */
923
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;
924
941
  /** Telemetry data bindings for live values */
925
942
  telemetry?: TelemetryMap;
926
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
+ }
927
952
  /**
928
953
  * PIDCanvas - Interactive canvas for rendering P&ID diagrams
929
954
  *
@@ -933,7 +958,7 @@ interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
933
958
  *
934
959
  * Future: Editing tools, symbol library UI
935
960
  */
936
- 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;
937
962
 
938
963
  interface SymbolLibraryProps {
939
964
  /** Optional className for styling */
package/dist/index.d.ts CHANGED
@@ -447,6 +447,7 @@ interface NodeInstance {
447
447
  x: number;
448
448
  y: number;
449
449
  };
450
+ labelFontSize?: number;
450
451
  metadata?: Record<string, unknown>;
451
452
  }
452
453
  interface Transform {
@@ -541,6 +542,7 @@ interface DiagramMetadata {
541
542
  modified?: string;
542
543
  tags?: string[];
543
544
  description?: string;
545
+ backgroundColor?: string;
544
546
  }
545
547
 
546
548
  interface SymbolDefinition {
@@ -681,6 +683,15 @@ interface UseViewBoxResult {
681
683
  zoomIn: () => void;
682
684
  /** Zoom out (increase viewBox size) */
683
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;
684
695
  /** Convert screen coordinates to world (SVG) coordinates */
685
696
  screenToWorld: (screenX: number, screenY: number) => {
686
697
  x: number;
@@ -888,6 +899,10 @@ interface PIDCanvasFeatures {
888
899
  allowSymbolDrop?: boolean;
889
900
  /** Enable connection drawing mode to create pipes between nodes (default: false) */
890
901
  connectionMode?: boolean;
902
+ /** Show background grid (default: true) */
903
+ showGrid?: boolean;
904
+ /** Background color for canvas (default: '#ffffff') */
905
+ backgroundColor?: string;
891
906
  }
892
907
  interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
893
908
  /** The diagram to render */
@@ -921,9 +936,19 @@ interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
921
936
  }) => void;
922
937
  /** Callback when a pipe is created via connection mode */
923
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;
924
941
  /** Telemetry data bindings for live values */
925
942
  telemetry?: TelemetryMap;
926
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
+ }
927
952
  /**
928
953
  * PIDCanvas - Interactive canvas for rendering P&ID diagrams
929
954
  *
@@ -933,7 +958,7 @@ interface PIDCanvasProps extends react__default.HTMLAttributes<HTMLDivElement> {
933
958
  *
934
959
  * Future: Editing tools, symbol library UI
935
960
  */
936
- 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;
937
962
 
938
963
  interface SymbolLibraryProps {
939
964
  /** Optional className for styling */
package/dist/index.js CHANGED
@@ -1964,9 +1964,10 @@ function NodeRenderer({
1964
1964
  x: symbol.viewBox.width / 2 + (node.labelOffset?.x ?? 0),
1965
1965
  y: symbol.viewBox.height + 12 + (node.labelOffset?.y ?? 0),
1966
1966
  textAnchor: "middle",
1967
- fontSize: "10",
1967
+ fontSize: node.labelFontSize ?? 10,
1968
1968
  fontWeight: "600",
1969
1969
  fill: isSelected ? "#3b82f6" : "#1f2937",
1970
+ transform: node.transform.rotation ? `rotate(${-node.transform.rotation}, ${symbol.viewBox.width / 2 + (node.labelOffset?.x ?? 0)}, ${symbol.viewBox.height + 12 + (node.labelOffset?.y ?? 0)})` : void 0,
1970
1971
  children: node.label || node.id
1971
1972
  }
1972
1973
  )
@@ -2346,6 +2347,34 @@ function useViewBox(options) {
2346
2347
  };
2347
2348
  });
2348
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]);
2349
2378
  useEffect4(() => {
2350
2379
  if (!enableZoom || !svgRef.current) return;
2351
2380
  const svg = svgRef.current;
@@ -2577,6 +2606,8 @@ function useViewBox(options) {
2577
2606
  resetViewBox,
2578
2607
  zoomIn,
2579
2608
  zoomOut,
2609
+ fitToContent,
2610
+ setViewBox,
2580
2611
  screenToWorld,
2581
2612
  svgRef
2582
2613
  };
@@ -3231,6 +3262,7 @@ function PIDCanvas({
3231
3262
  onPaste,
3232
3263
  onSymbolDrop,
3233
3264
  onPipeCreated,
3265
+ onMounted,
3234
3266
  telemetry,
3235
3267
  ...props
3236
3268
  }) {
@@ -3248,12 +3280,72 @@ function PIDCanvas({
3248
3280
  const {
3249
3281
  viewBox: controlledViewBox,
3250
3282
  svgRef,
3251
- screenToWorld
3283
+ screenToWorld,
3284
+ fitToContent: fitToContentViewBox,
3285
+ setViewBox
3252
3286
  } = useViewBox({
3253
3287
  initialViewBox: diagram.viewBox,
3254
3288
  enablePan: features.pan ?? true,
3255
3289
  enableZoom: features.zoom ?? true
3256
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
+ ]);
3257
3349
  const { selection, selectNode, selectPipe, clearSelection } = useSelection({
3258
3350
  multiSelect: features.multiSelect ?? true,
3259
3351
  onSelectionChange
@@ -3544,21 +3636,32 @@ function PIDCanvas({
3544
3636
  onDrop: handleDrop,
3545
3637
  onDragLeave: handleDragLeave,
3546
3638
  children: [
3547
- /* @__PURE__ */ jsxs12("defs", { children: [
3548
- /* @__PURE__ */ jsx16("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx16("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
3549
- /* @__PURE__ */ jsxs12("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
3550
- /* @__PURE__ */ jsx16("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
3551
- /* @__PURE__ */ jsx16("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
3552
- ] })
3553
- ] }),
3554
- /* @__PURE__ */ jsx16(
3639
+ features.showGrid !== false ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
3640
+ /* @__PURE__ */ jsxs12("defs", { children: [
3641
+ /* @__PURE__ */ jsx16("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx16("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
3642
+ /* @__PURE__ */ jsxs12("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
3643
+ /* @__PURE__ */ jsx16("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
3644
+ /* @__PURE__ */ jsx16("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
3645
+ ] })
3646
+ ] }),
3647
+ /* @__PURE__ */ jsx16(
3648
+ "rect",
3649
+ {
3650
+ x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3651
+ y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3652
+ width: Math.ceil(viewBox.width * 3 / 25) * 25,
3653
+ height: Math.ceil(viewBox.height * 3 / 25) * 25,
3654
+ fill: "url(#grid-major)"
3655
+ }
3656
+ )
3657
+ ] }) : /* @__PURE__ */ jsx16(
3555
3658
  "rect",
3556
3659
  {
3557
3660
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3558
3661
  y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3559
3662
  width: Math.ceil(viewBox.width * 3 / 25) * 25,
3560
3663
  height: Math.ceil(viewBox.height * 3 / 25) * 25,
3561
- fill: "url(#grid-major)"
3664
+ fill: features.backgroundColor ?? "#ffffff"
3562
3665
  }
3563
3666
  ),
3564
3667
  /* @__PURE__ */ jsx16(
@@ -3991,6 +4094,54 @@ function NodeConfigPanel({
3991
4094
  }
3992
4095
  )
3993
4096
  ] })
4097
+ ] }),
4098
+ /* @__PURE__ */ jsxs14("div", { children: [
4099
+ /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
4100
+ /* @__PURE__ */ jsx18(
4101
+ "input",
4102
+ {
4103
+ type: "number",
4104
+ min: "6",
4105
+ max: "24",
4106
+ value: node.labelFontSize ?? 10,
4107
+ onChange: (e) => onUpdateNode({
4108
+ labelFontSize: parseFloat(e.target.value) || 10
4109
+ }),
4110
+ style: {
4111
+ width: "100%",
4112
+ padding: "6px 8px",
4113
+ borderRadius: "4px",
4114
+ border: "1px solid #cbd5e1",
4115
+ fontSize: "0.875rem"
4116
+ }
4117
+ }
4118
+ )
4119
+ ] }),
4120
+ /* @__PURE__ */ jsxs14("div", { children: [
4121
+ /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
4122
+ /* @__PURE__ */ jsx18(
4123
+ "input",
4124
+ {
4125
+ type: "number",
4126
+ min: "0",
4127
+ max: "360",
4128
+ step: "15",
4129
+ value: node.transform.rotation ?? 0,
4130
+ onChange: (e) => onUpdateNode({
4131
+ transform: {
4132
+ ...node.transform,
4133
+ rotation: parseFloat(e.target.value) || 0
4134
+ }
4135
+ }),
4136
+ style: {
4137
+ width: "100%",
4138
+ padding: "6px 8px",
4139
+ borderRadius: "4px",
4140
+ border: "1px solid #cbd5e1",
4141
+ fontSize: "0.875rem"
4142
+ }
4143
+ }
4144
+ )
3994
4145
  ] })
3995
4146
  ] }),
3996
4147
  /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [