@procaaso/alphinity-ui-components 1.0.3 → 1.0.5

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.js CHANGED
@@ -1694,35 +1694,10 @@ var ThemeToggle = ({
1694
1694
  };
1695
1695
 
1696
1696
  // src/components/PIDCanvas/PIDCanvas.tsx
1697
- import { useCallback as useCallback7, useState as useState11, useEffect as useEffect8 } from "react";
1697
+ import { useCallback as useCallback7, useState as useState12, useEffect as useEffect10, useRef as useRef5 } from "react";
1698
1698
 
1699
1699
  // src/diagram/symbols/mockSymbolLibrary.ts
1700
1700
  var mockSymbolLibrary = {
1701
- "valve-ball": {
1702
- id: "valve-ball",
1703
- name: "Ball Valve",
1704
- category: "valve",
1705
- viewBox: { x: 0, y: 0, width: 50, height: 50 },
1706
- ports: [
1707
- { id: "inlet", x: 0, y: 25, type: "inlet", direction: "in" },
1708
- { id: "outlet", x: 50, y: 25, type: "outlet", direction: "out" }
1709
- ],
1710
- svgContent: `
1711
- <g>
1712
- <!-- Valve body -->
1713
- <rect x="15" y="10" width="20" height="30" fill="#546e7a" stroke="#263238" stroke-width="2" rx="2"/>
1714
- <!-- Ball indicator -->
1715
- <circle cx="25" cy="25" r="6" fill="#ffa726" stroke="#e65100" stroke-width="1.5"/>
1716
- <!-- Inlet/outlet lines -->
1717
- <line x1="0" y1="25" x2="15" y2="25" stroke="#263238" stroke-width="3" stroke-linecap="round"/>
1718
- <line x1="35" y1="25" x2="50" y2="25" stroke="#263238" stroke-width="3" stroke-linecap="round"/>
1719
- </g>
1720
- `,
1721
- metadata: {
1722
- description: "Ball valve for flow control",
1723
- tags: ["valve", "control", "shutoff"]
1724
- }
1725
- },
1726
1701
  "valve-gate": {
1727
1702
  id: "valve-gate",
1728
1703
  name: "Gate Valve (Powered)",
@@ -1735,11 +1710,11 @@ var mockSymbolLibrary = {
1735
1710
  svgContent: `
1736
1711
  <g>
1737
1712
  <!-- Actuator box -->
1738
- <rect x="16.74" y="0" width="18.02" height="18.02" fill="#edeeef" stroke="#666666" pointer-events="all"/>
1713
+ <rect x="16.74" y="0" width="18.02" height="18.02" fill="var(--symbol-fill, #edeeef)" stroke="var(--symbol-stroke, #666666)" pointer-events="all"/>
1739
1714
  <!-- Actuator stem -->
1740
- <path d="M 25.75 18.02 L 25.75 36.05" fill="none" stroke="#666666" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
1715
+ <path d="M 25.75 18.02 L 25.75 36.05" fill="none" stroke="var(--symbol-stroke, #666666)" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
1741
1716
  <!-- Valve body (two triangles forming diamond) -->
1742
- <path d="M 0 20.6 L 25.75 36.05 L 0 51.5 Z M 51.5 20.6 L 25.75 36.05 L 51.5 51.5 Z" fill="#edeeef" stroke="#666666" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
1717
+ <path d="M 0 20.6 L 25.75 36.05 L 0 51.5 Z M 51.5 20.6 L 25.75 36.05 L 51.5 51.5 Z" fill="var(--symbol-fill, #edeeef)" stroke="var(--symbol-stroke, #666666)" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
1743
1718
  </g>
1744
1719
  `,
1745
1720
  metadata: {
@@ -1747,84 +1722,1143 @@ var mockSymbolLibrary = {
1747
1722
  tags: ["valve", "gate", "powered", "actuator"]
1748
1723
  }
1749
1724
  },
1750
- "pump-centrifugal": {
1751
- id: "pump-centrifugal",
1752
- name: "Centrifugal Pump",
1725
+ "pump-positive-displacement": {
1726
+ id: "pump-positive-displacement",
1727
+ name: "Positive Displacement Pump",
1753
1728
  category: "pump",
1754
- viewBox: { x: 0, y: 0, width: 80, height: 60 },
1729
+ viewBox: { x: 0, y: 0, width: 56.25, height: 56.25 },
1755
1730
  ports: [
1756
- { id: "inlet", x: 10, y: 30, type: "inlet", direction: "in" },
1757
- { id: "outlet", x: 70, y: 30, type: "outlet", direction: "out" }
1731
+ { id: "inlet", x: 0, y: 28.125, type: "inlet", direction: "in" },
1732
+ { id: "outlet", x: 56.25, y: 28.125, type: "outlet", direction: "out" }
1733
+ ],
1734
+ svgContent: `
1735
+ <g transform="scale(0.5625)">
1736
+ <!-- Outer circle -->
1737
+ <ellipse
1738
+ cx="50"
1739
+ cy="50"
1740
+ rx="50"
1741
+ ry="50"
1742
+ fill="var(--symbol-fill, #edeeef)"
1743
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1744
+ stroke-width="1"
1745
+ />
1746
+
1747
+ <!-- Diagonal line from top to bottom-right -->
1748
+ <path
1749
+ d="M 50 0 L 100 50 L 50 100"
1750
+ fill="none"
1751
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1752
+ stroke-width="1"
1753
+ stroke-miterlimit="10"
1754
+ />
1755
+
1756
+ <!-- Center square -->
1757
+ <rect
1758
+ x="35"
1759
+ y="35"
1760
+ width="30"
1761
+ height="30"
1762
+ fill="none"
1763
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1764
+ stroke-width="1"
1765
+ />
1766
+ </g>
1767
+ `,
1768
+ metadata: {
1769
+ description: "Positive displacement pump (ISO standard)",
1770
+ tags: ["pump", "positive-displacement", "iso"]
1771
+ }
1772
+ },
1773
+ "vannefold-bottom": {
1774
+ id: "vannefold-bottom",
1775
+ name: "Vannefold Block (Bottom)",
1776
+ category: "valve",
1777
+ viewBox: { x: -0.5, y: -0.5, width: 88, height: 67.785156 },
1778
+ ports: [
1779
+ { id: "bottom-inlet", x: 0, y: 25, type: "control", direction: "in" },
1780
+ { id: "top-outlet", x: 48, y: 65, type: "control", direction: "out" }
1758
1781
  ],
1759
1782
  svgContent: `
1760
1783
  <g>
1761
- <!-- Pump casing -->
1762
- <circle cx="40" cy="30" r="22" fill="#4fc3f7" stroke="#0277bd" stroke-width="2.5"/>
1763
- <!-- Impeller -->
1764
- <path d="M 40 30 L 50 20 M 40 30 L 50 40 M 40 30 L 30 40 M 40 30 L 30 20"
1765
- stroke="#01579b" stroke-width="2.5" stroke-linecap="round"/>
1766
- <!-- Center hub -->
1767
- <circle cx="40" cy="30" r="5" fill="#0277bd" stroke="#01579b" stroke-width="1.5"/>
1768
- <!-- Inlet pipe -->
1769
- <line x1="0" y1="30" x2="18" y2="30" stroke="#0277bd" stroke-width="4" stroke-linecap="round"/>
1770
- <!-- Outlet pipe -->
1771
- <line x1="62" y1="30" x2="80" y2="30" stroke="#0277bd" stroke-width="4" stroke-linecap="round"/>
1784
+ <!-- Bottom inlet pipe -->
1785
+ <path
1786
+ d="M 3,29.785156 H 0 v -10 h 3 m 14,10 h 3 v -10 h -3 m -15,8 h 16 m -16,-6 h 16"
1787
+ fill="none"
1788
+ stroke="var(--symbol-stroke, #000000)"
1789
+ stroke-miterlimit="10"
1790
+ />
1791
+
1792
+ <!-- Top outlet pipe -->
1793
+ <path
1794
+ d="m 43,49.785156 v -3 h 10 v 3 m -10,14 v 3 h 10 v -3 m -8,-15 v 16 m 6,-16 v 16"
1795
+ fill="none"
1796
+ stroke="var(--symbol-stroke, #000000)"
1797
+ stroke-miterlimit="10"
1798
+ />
1799
+
1800
+ <!-- Main block outer -->
1801
+ <rect
1802
+ x="7"
1803
+ y="0"
1804
+ width="80"
1805
+ height="59.785156"
1806
+ fill="var(--symbol-fill, #eaeaea)"
1807
+ stroke="var(--symbol-stroke, #000000)"
1808
+ />
1809
+
1810
+ <!-- Main block inner -->
1811
+ <rect
1812
+ x="9"
1813
+ y="2"
1814
+ width="76"
1815
+ height="55.785156"
1816
+ fill="var(--symbol-fill, #eaeaea)"
1817
+ stroke="var(--symbol-stroke, #000000)"
1818
+ />
1819
+
1820
+ <!-- Bolt holes -->
1821
+ <ellipse cx="15" cy="7" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1822
+ <ellipse cx="15" cy="7" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1823
+ <ellipse cx="79" cy="7" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1824
+ <ellipse cx="79" cy="7" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1825
+ <ellipse cx="15" cy="51.785156" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1826
+ <ellipse cx="15" cy="51.785156" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1827
+ <ellipse cx="79" cy="51.785156" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1828
+ <ellipse cx="79" cy="51.785156" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1829
+
1772
1830
  </g>
1773
1831
  `,
1774
1832
  metadata: {
1775
- description: "Centrifugal pump for fluid transfer",
1776
- tags: ["pump", "centrifugal", "transfer"]
1833
+ description: "Vannefold heat exchanger block with bottom inlet configuration",
1834
+ tags: ["heat-exchanger", "vannefold", "fold", "bottom"]
1777
1835
  }
1778
1836
  },
1779
- "vessel-tank": {
1780
- id: "vessel-tank",
1781
- name: "Storage Tank",
1782
- category: "vessel",
1783
- viewBox: { x: 0, y: 0, width: 60, height: 80 },
1837
+ "vannefold-middle": {
1838
+ id: "vannefold-middle",
1839
+ name: "Vannefold Block (Middle)",
1840
+ category: "valve",
1841
+ viewBox: { x: -0.5, y: -0.5, width: 95, height: 53.097656 },
1784
1842
  ports: [
1785
- { id: "inlet", x: 30, y: 10, type: "inlet", direction: "in" },
1786
- { id: "outlet", x: 30, y: 70, type: "outlet", direction: "out" }
1843
+ { id: "left-inlet", x: 0, y: 27.3125, type: "control", direction: "in" },
1844
+ { id: "right-outlet", x: 95, y: 27.3125, type: "control", direction: "out" }
1787
1845
  ],
1788
1846
  svgContent: `
1789
1847
  <g>
1790
- <!-- Tank body -->
1791
- <rect x="10" y="15" width="40" height="50" fill="#66bb6a" stroke="#2e7d32" stroke-width="2.5" rx="3"/>
1792
- <!-- Tank bottom (rounded) -->
1793
- <ellipse cx="30" cy="65" rx="20" ry="8" fill="#66bb6a" stroke="#2e7d32" stroke-width="2.5"/>
1794
- <!-- Liquid level indicator -->
1795
- <rect x="12" y="35" width="36" height="28" fill="#4dd0e1" opacity="0.6"/>
1796
- <!-- Inlet pipe -->
1797
- <line x1="30" y1="0" x2="30" y2="15" stroke="#2e7d32" stroke-width="3" stroke-linecap="round"/>
1798
- <!-- Outlet pipe -->
1799
- <line x1="30" y1="65" x2="30" y2="80" stroke="#2e7d32" stroke-width="3" stroke-linecap="round"/>
1848
+ <!-- Left inlet pipe -->
1849
+ <path
1850
+ d="M 3,22.3125 H 0 v 10 H 3 m 14,-10 h 3 v 10 h -3 m -15,-8 h 16 m -16,6 h 16"
1851
+ fill="none"
1852
+ stroke="var(--symbol-stroke, #000000)"
1853
+ stroke-miterlimit="10"
1854
+ />
1855
+
1856
+ <!-- Main block outer -->
1857
+ <rect
1858
+ x="7"
1859
+ y="0"
1860
+ width="80"
1861
+ height="52.097656"
1862
+ fill="var(--symbol-fill, #eaeaea)"
1863
+ stroke="var(--symbol-stroke, #000000)"
1864
+ />
1865
+
1866
+ <!-- Main block inner -->
1867
+ <rect
1868
+ x="9"
1869
+ y="0"
1870
+ width="76"
1871
+ height="52.097656"
1872
+ fill="var(--symbol-fill, #eaeaea)"
1873
+ stroke="var(--symbol-stroke, #000000)"
1874
+ />
1875
+
1876
+ <!-- Bolt holes -->
1877
+ <ellipse cx="15" cy="5.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1878
+ <ellipse cx="15" cy="5.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1879
+ <ellipse cx="79" cy="5.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1880
+ <ellipse cx="79" cy="5.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1881
+ <ellipse cx="15" cy="47.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1882
+ <ellipse cx="15" cy="47.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1883
+ <ellipse cx="79" cy="47.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1884
+ <ellipse cx="79" cy="47.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1800
1885
  </g>
1801
1886
  `,
1802
1887
  metadata: {
1803
- description: "Storage vessel for liquids",
1804
- tags: ["vessel", "tank", "storage"]
1888
+ description: "Vannefold heat exchanger block with side inlet configuration",
1889
+ tags: ["heat-exchanger", "vannefold", "fold", "middle"]
1805
1890
  }
1806
1891
  },
1807
- "instrument-indicator": {
1808
- id: "instrument-indicator",
1809
- name: "Instrument Indicator",
1810
- category: "instrument",
1811
- viewBox: { x: 0, y: 0, width: 40, height: 40 },
1892
+ "vannefold-top": {
1893
+ id: "vannefold-top",
1894
+ name: "Vannefold Block (Top)",
1895
+ category: "valve",
1896
+ viewBox: { x: -0.5, y: -0.5, width: 95, height: 60.785156 },
1812
1897
  ports: [
1813
- { id: "signal", x: 20, y: 40, type: "signal", direction: "bi" }
1898
+ { id: "bottom-inlet", x: 0, y: 35, type: "control", direction: "in" },
1899
+ { id: "top-outlet", x: 48, y: 0, type: "control", direction: "out" }
1814
1900
  ],
1815
1901
  svgContent: `
1816
1902
  <g>
1817
- <!-- Instrument circle -->
1818
- <circle cx="20" cy="20" r="15" fill="#fff" stroke="#616161" stroke-width="2"/>
1819
- <!-- Inner circle -->
1820
- <circle cx="20" cy="20" r="12" fill="none" stroke="#616161" stroke-width="1"/>
1821
- <!-- Signal line -->
1822
- <line x1="20" y1="35" x2="20" y2="40" stroke="#616161" stroke-width="2" stroke-linecap="round"/>
1903
+ <!-- Bottom inlet pipe -->
1904
+ <path
1905
+ d="M 3,30 H 0 V 40 H 3 M 17,30 h 3 V 40 H 17 M 2,32 H 18 M 2,38 H 18"
1906
+ fill="none"
1907
+ stroke="var(--symbol-stroke, #000000)"
1908
+ stroke-miterlimit="10"
1909
+ />
1910
+
1911
+ <!-- Main block outer -->
1912
+ <rect
1913
+ x="7"
1914
+ y="0"
1915
+ width="80"
1916
+ height="59.785156"
1917
+ fill="var(--symbol-fill, #eaeaea)"
1918
+ stroke="var(--symbol-stroke, #000000)"
1919
+ />
1920
+
1921
+ <!-- Main block inner -->
1922
+ <rect
1923
+ x="9"
1924
+ y="2"
1925
+ width="76"
1926
+ height="57.785156"
1927
+ fill="var(--symbol-fill, #eaeaea)"
1928
+ stroke="var(--symbol-stroke, #000000)"
1929
+ />
1930
+
1931
+ <!-- Bolt holes -->
1932
+ <ellipse cx="15" cy="8" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1933
+ <ellipse cx="15" cy="8" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1934
+ <ellipse cx="79" cy="8" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1935
+ <ellipse cx="79" cy="8" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1936
+ <ellipse cx="15" cy="55" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1937
+ <ellipse cx="15" cy="55" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1938
+ <ellipse cx="79" cy="55" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1939
+ <ellipse cx="79" cy="55" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1940
+
1823
1941
  </g>
1824
1942
  `,
1825
1943
  metadata: {
1826
- description: "Generic instrument indicator",
1827
- tags: ["instrument", "indicator", "measurement"]
1944
+ description: "Vannefold heat exchanger block with bottom inlet and top outlet configuration",
1945
+ tags: ["heat-exchanger", "vannefold", "fold", "top"]
1946
+ }
1947
+ },
1948
+ "vannefold-top-top-inlet": {
1949
+ id: "vannefold-top-top-inlet",
1950
+ name: "Vannefold Block (Top, Top Inlet)",
1951
+ category: "valve",
1952
+ viewBox: { x: 0, y: 0, width: 88, height: 67.576004 },
1953
+ ports: [
1954
+ { id: "top-inlet", x: 47, y: 0, type: "control", direction: "in" },
1955
+ { id: "bottom-outlet", x: 0, y: 42, type: "control", direction: "out" }
1956
+ ],
1957
+ svgContent: `
1958
+ <g>
1959
+ <!-- Top inlet pipe flange -->
1960
+ <path
1961
+ d="M 52,3 V 1.3598666e-8 H 42 V 3 m 10,14 v 3 H 42 V 17 M 50,2 V 18 M 44,2 v 16"
1962
+ fill="none"
1963
+ stroke="var(--symbol-stroke, #000000)"
1964
+ stroke-miterlimit="10"
1965
+ />
1966
+
1967
+ <!-- Bottom outlet pipe flange -->
1968
+ <path
1969
+ d="M 3,36.79085 H 0 v 10 h 3 m 14,-10 h 3 v 10 h -3 m -15,-8 h 16 m -16,6 h 16"
1970
+ fill="none"
1971
+ stroke="var(--symbol-stroke, #000000)"
1972
+ stroke-miterlimit="10"
1973
+ />
1974
+
1975
+ <!-- Main block outer -->
1976
+ <rect
1977
+ x="7"
1978
+ y="6.7908498"
1979
+ width="80"
1980
+ height="59.785156"
1981
+ fill="var(--symbol-fill, #eaeaea)"
1982
+ stroke="var(--symbol-stroke, #000000)"
1983
+ />
1984
+
1985
+ <!-- Main block inner -->
1986
+ <rect
1987
+ x="9"
1988
+ y="8.7908498"
1989
+ width="76"
1990
+ height="57.785156"
1991
+ fill="var(--symbol-fill, #eaeaea)"
1992
+ stroke="var(--symbol-stroke, #000000)"
1993
+ />
1994
+
1995
+ <!-- Bolt holes -->
1996
+ <ellipse cx="15" cy="14.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1997
+ <ellipse cx="15" cy="14.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1998
+ <ellipse cx="15" cy="61.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1999
+ <ellipse cx="15" cy="61.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2000
+ <ellipse cx="79" cy="14.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2001
+ <ellipse cx="79" cy="14.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2002
+ <ellipse cx="79" cy="61.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2003
+ <ellipse cx="79" cy="61.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2004
+
2005
+ </g>
2006
+ `,
2007
+ metadata: {
2008
+ description: "Vannefold heat exchanger block with top inlet and bottom outlet configuration",
2009
+ tags: ["heat-exchanger", "vannefold", "fold", "top", "top-inlet"]
2010
+ }
2011
+ },
2012
+ "vannefold-left": {
2013
+ id: "vannefold-left",
2014
+ name: "Vannefold Block (Left)",
2015
+ category: "valve",
2016
+ viewBox: { x: 0, y: 0, width: 67.785156, height: 88.5 },
2017
+ ports: [
2018
+ { id: "left-inlet", x: 0, y: 41.5, type: "control", direction: "in" },
2019
+ { id: "right-outlet", x: 42, y: 87, type: "control", direction: "out" }
2020
+ ],
2021
+ svgContent: `
2022
+ <g>
2023
+ <!-- Left inlet pipe flange -->
2024
+ <path
2025
+ d="M 3,46.5 H 2.8789924e-8 v -10 H 3 m 14,10 h 3 v -10 h -3 m -15,8 h 16 m -16,-6 h 16"
2026
+ fill="none"
2027
+ stroke="var(--symbol-stroke, #000000)"
2028
+ stroke-miterlimit="10"
2029
+ />
2030
+
2031
+ <!-- Right outlet pipe flange -->
2032
+ <path
2033
+ d="m 37,70.5 v -3 h 10 v 3 m -10,14 v 3 h 10 v -3 m -8,-15 v 16 m 6,-16 v 16"
2034
+ fill="none"
2035
+ stroke="var(--symbol-stroke, #000000)"
2036
+ stroke-miterlimit="10"
2037
+ />
2038
+
2039
+ <!-- Main block outer -->
2040
+ <rect
2041
+ x="7"
2042
+ y="0.5"
2043
+ width="59.785156"
2044
+ height="80"
2045
+ fill="var(--symbol-fill, #eaeaea)"
2046
+ stroke="var(--symbol-stroke, #000000)"
2047
+ />
2048
+
2049
+ <!-- Main block inner -->
2050
+ <rect
2051
+ x="9"
2052
+ y="2.5"
2053
+ width="57.785156"
2054
+ height="76"
2055
+ fill="var(--symbol-fill, #eaeaea)"
2056
+ stroke="var(--symbol-stroke, #000000)"
2057
+ />
2058
+
2059
+ <!-- Bolt holes -->
2060
+ <ellipse cx="15" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2061
+ <ellipse cx="15" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2062
+ <ellipse cx="62" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2063
+ <ellipse cx="62" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2064
+ <ellipse cx="15" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2065
+ <ellipse cx="15" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2066
+ <ellipse cx="62" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2067
+ <ellipse cx="62" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2068
+
2069
+ `,
2070
+ metadata: {
2071
+ description: "Vannefold heat exchanger block with left inlet and right outlet, vertical orientation",
2072
+ tags: ["heat-exchanger", "vannefold", "fold", "left", "vertical"]
2073
+ }
2074
+ },
2075
+ "vannefold-right": {
2076
+ id: "vannefold-right",
2077
+ name: "Vannefold Block (Right)",
2078
+ category: "valve",
2079
+ viewBox: { x: 0, y: 0, width: 67.785156, height: 88.5 },
2080
+ ports: [
2081
+ { id: "right-inlet", x: 67.785156, y: 41.5, type: "control", direction: "in" },
2082
+ { id: "left-outlet", x: 24.785156, y: 87.5, type: "control", direction: "out" }
2083
+ ],
2084
+ svgContent: `
2085
+ <g>
2086
+ <!-- Right inlet pipe flange -->
2087
+ <path
2088
+ d="m 63.785156,46.5 h 3 v -10 h -3 m -14,10 h -3 v -10 h 3 m 15,8 h -16 m 16,-6 h -16"
2089
+ fill="none"
2090
+ stroke="var(--symbol-stroke, #000000)"
2091
+ stroke-miterlimit="10"
2092
+ />
2093
+
2094
+ <!-- Left outlet pipe flange -->
2095
+ <path
2096
+ d="m 29.785156,70.5 v -3 h -10 v 3 m 10,14 v 3 h -10 v -3 m 8,-15 v 16 m -6,-16 v 16"
2097
+ fill="none"
2098
+ stroke="var(--symbol-stroke, #000000)"
2099
+ stroke-miterlimit="10"
2100
+ />
2101
+
2102
+ <!-- Main block outer -->
2103
+ <rect
2104
+ x="0"
2105
+ y="0.5"
2106
+ width="59.785156"
2107
+ height="80"
2108
+ fill="var(--symbol-fill, #eaeaea)"
2109
+ stroke="var(--symbol-stroke, #000000)"
2110
+ />
2111
+
2112
+ <!-- Main block inner -->
2113
+ <rect
2114
+ x="0"
2115
+ y="2.5"
2116
+ width="57.785156"
2117
+ height="76"
2118
+ fill="var(--symbol-fill, #eaeaea)"
2119
+ stroke="var(--symbol-stroke, #000000)"
2120
+ />
2121
+
2122
+ <!-- Bolt holes -->
2123
+ <ellipse cx="51.785156" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2124
+ <ellipse cx="51.785156" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2125
+ <ellipse cx="4.785156" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2126
+ <ellipse cx="4.785156" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2127
+ <ellipse cx="51.785156" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2128
+ <ellipse cx="51.785156" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2129
+ <ellipse cx="4.785156" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2130
+ <ellipse cx="4.785156" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2131
+
2132
+ `,
2133
+ metadata: {
2134
+ description: "Vannefold heat exchanger block with right inlet and left outlet, vertical orientation",
2135
+ tags: ["heat-exchanger", "vannefold", "fold", "right", "vertical"]
2136
+ }
2137
+ },
2138
+ "pixer": {
2139
+ id: "pixer",
2140
+ name: "Pixer",
2141
+ category: "pump",
2142
+ viewBox: { x: 0, y: 0, width: 50.625, height: 46.6875 },
2143
+ ports: [
2144
+ { id: "right-outlet", x: 50.625, y: 30.9375, type: "control", direction: "out" },
2145
+ { id: "left-outlet", x: 0, y: 30.9375, type: "control", direction: "out" },
2146
+ { id: "left-inlet", x: 50.625, y: 19.6875, type: "control", direction: "in" },
2147
+ { id: "right-inlet", x: 0, y: 19.6875, type: "control", direction: "in" },
2148
+ { id: "top-inlet", x: 25.3125, y: 0, type: "control", direction: "in" }
2149
+ ],
2150
+ svgContent: `
2151
+ <g transform="scale(0.196875) translate(-195, -186)">
2152
+ <!-- Top mounting flange -->
2153
+ <rect
2154
+ width="141.313"
2155
+ height="9.872"
2156
+ transform="translate(252.392 185.89)"
2157
+ stroke="var(--symbol-stroke, #000000)"
2158
+ stroke-miterlimit="10"
2159
+ stroke-width="1"
2160
+ fill="var(--symbol-fill, #eaeaea)"
2161
+ />
2162
+
2163
+ <!-- Motor housing top -->
2164
+ <rect
2165
+ width="106.614"
2166
+ height="2.468"
2167
+ transform="translate(269.742 199.918)"
2168
+ stroke="var(--symbol-stroke, #000000)"
2169
+ stroke-miterlimit="10"
2170
+ stroke-width="1"
2171
+ fill="var(--symbol-fill, #e0e0e0)"
2172
+ />
2173
+ <rect
2174
+ width="106.614"
2175
+ height="5.835"
2176
+ transform="translate(269.742 202.384)"
2177
+ stroke="var(--symbol-stroke, #000000)"
2178
+ stroke-miterlimit="10"
2179
+ stroke-width="1"
2180
+ fill="var(--symbol-fill, #e0e0e0)"
2181
+ />
2182
+ <rect
2183
+ width="106.614"
2184
+ height="2.468"
2185
+ transform="translate(269.742 208.222)"
2186
+ stroke="var(--symbol-stroke, #000000)"
2187
+ stroke-miterlimit="10"
2188
+ stroke-width="1"
2189
+ fill="var(--symbol-fill, #e0e0e0)"
2190
+ />
2191
+
2192
+ <!-- Bottom mounting flange -->
2193
+ <rect
2194
+ width="221.702"
2195
+ height="2.401"
2196
+ transform="translate(212.199 395.587)"
2197
+ stroke="var(--symbol-stroke, #000000)"
2198
+ stroke-miterlimit="10"
2199
+ stroke-width="1"
2200
+ fill="var(--symbol-fill, #e0e0e0)"
2201
+ />
2202
+ <rect
2203
+ width="221.702"
2204
+ height="6.269"
2205
+ transform="translate(212.199 397.988)"
2206
+ stroke="var(--symbol-stroke, #000000)"
2207
+ stroke-miterlimit="10"
2208
+ stroke-width="1"
2209
+ fill="var(--symbol-fill, #e0e0e0)"
2210
+ />
2211
+ <rect
2212
+ width="221.702"
2213
+ height="2.65"
2214
+ transform="translate(212.199 404.257)"
2215
+ stroke="var(--symbol-stroke, #000000)"
2216
+ stroke-miterlimit="10"
2217
+ stroke-width="1"
2218
+ fill="var(--symbol-fill, #e0e0e0)"
2219
+ />
2220
+
2221
+ <!-- Motor housing transition -->
2222
+ <path
2223
+ d="M444.7,228.776l17.117,4.1H568.429l17.089-4.085a.032.032,0,0,0,0-.062H444.705a.028.028,0,0,0-.007.046Z"
2224
+ transform="translate(-192.059 -32.959)"
2225
+ stroke="var(--symbol-stroke, #000000)"
2226
+ stroke-miterlimit="10"
2227
+ stroke-width="1"
2228
+ fill="var(--symbol-fill, #e0e0e0)"
2229
+ />
2230
+
2231
+ <!-- Bottom flange -->
2232
+ <rect
2233
+ width="256"
2234
+ height="9.872"
2235
+ transform="translate(195.05 411.066)"
2236
+ stroke="var(--symbol-stroke, #000000)"
2237
+ stroke-miterlimit="10"
2238
+ stroke-width="1"
2239
+ fill="var(--symbol-fill, #eaeaea)"
2240
+ />
2241
+ <path
2242
+ d="M195.05,1148.286l17.149-4.156H433.9l17.149,4.156Z"
2243
+ transform="translate(0 -737.221)"
2244
+ fill="var(--symbol-fill, #e2e2e2)"
2245
+ stroke="var(--symbol-stroke, #000000)"
2246
+ stroke-miterlimit="10"
2247
+ stroke-width="1"
2248
+ />
2249
+
2250
+ <!-- Vessel top cone -->
2251
+ <path
2252
+ d="M451.05,328.356h-256l74.692-34.946H376.358Z"
2253
+ transform="translate(0 -82.72)"
2254
+ stroke="var(--symbol-stroke, #000000)"
2255
+ stroke-miterlimit="10"
2256
+ stroke-width="1"
2257
+ fill="var(--symbol-fill, #eaeaea)"
2258
+ />
2259
+
2260
+ <!-- Main vessel body -->
2261
+ <rect
2262
+ width="256"
2263
+ height="59.803"
2264
+ transform="translate(195.05 245.636)"
2265
+ stroke="var(--symbol-stroke, #000000)"
2266
+ stroke-miterlimit="10"
2267
+ stroke-width="1"
2268
+ fill="var(--symbol-fill, #eaeaea)"
2269
+ />
2270
+ <rect
2271
+ width="256"
2272
+ height="76.892"
2273
+ transform="translate(195.05 308.417)"
2274
+ stroke="var(--symbol-stroke, #000000)"
2275
+ stroke-miterlimit="10"
2276
+ stroke-width="1"
2277
+ fill="var(--symbol-fill, #eaeaea)"
2278
+ />
2279
+
2280
+ <!-- Vessel bottom transitions -->
2281
+ <path
2282
+ d="M449.265,705.7H196.773l-1.654-1.465a.014.014,0,0,1,0-.023H451.078Z"
2283
+ transform="translate(-0.049 -398.769)"
2284
+ stroke="var(--symbol-stroke, #000000)"
2285
+ stroke-miterlimit="10"
2286
+ stroke-width="1"
2287
+ fill="var(--symbol-fill, #e0e0e0)"
2288
+ />
2289
+ <path
2290
+ d="M449.278,710.66H196.793l-1.663,1.462v.016H451.086s.012,0,0-.016Z"
2291
+ transform="translate(-0.062 -403.731)"
2292
+ stroke="var(--symbol-stroke, #000000)"
2293
+ stroke-miterlimit="10"
2294
+ stroke-width="1"
2295
+ fill="var(--symbol-fill, #e0e0e0)"
2296
+ />
2297
+ <path
2298
+ d="M433.9,1060.758H212.2L195.05,1050.48h256Z"
2299
+ transform="translate(0 -665.171)"
2300
+ stroke="var(--symbol-stroke, #000000)"
2301
+ stroke-miterlimit="10"
2302
+ stroke-width="1"
2303
+ fill="var(--symbol-fill, #e0e0e0)"
2304
+ />
2305
+
2306
+ <!-- Agitator blades/impellers (simplified without gradients) -->
2307
+ <ellipse
2308
+ cx="15.454"
2309
+ cy="6.096"
2310
+ rx="15.454"
2311
+ ry="6.096"
2312
+ transform="translate(307.486 228.161)"
2313
+ stroke="var(--symbol-stroke, #000000)"
2314
+ stroke-miterlimit="10"
2315
+ stroke-width="1"
2316
+ fill="var(--symbol-fill, #c0c0c0)"
2317
+ />
2318
+ <ellipse
2319
+ cx="15.454"
2320
+ cy="5.3"
2321
+ rx="15.454"
2322
+ ry="5.3"
2323
+ transform="translate(307.486 229.753)"
2324
+ stroke="var(--symbol-stroke, #000000)"
2325
+ stroke-miterlimit="10"
2326
+ stroke-width="1"
2327
+ fill="var(--symbol-fill, #d0d0d0)"
2328
+ />
2329
+ <ellipse
2330
+ cx="5.174"
2331
+ cy="15.117"
2332
+ rx="5.174"
2333
+ ry="15.117"
2334
+ transform="translate(367.144 236.079) rotate(-77.61)"
2335
+ stroke="var(--symbol-stroke, #000000)"
2336
+ stroke-miterlimit="10"
2337
+ stroke-width="1"
2338
+ fill="var(--symbol-fill, #d0d0d0)"
2339
+ />
2340
+ <path
2341
+ d="M947,380.271s1.631-2.83,8.084-2.235c5.427.5,16.927,2.855,20.574,8.26"
2342
+ transform="translate(-578.512 -147.764)"
2343
+ fill="none"
2344
+ stroke="var(--symbol-stroke, #000000)"
2345
+ stroke-miterlimit="10"
2346
+ stroke-width="1"
2347
+ />
2348
+ <ellipse
2349
+ cx="15.117"
2350
+ cy="5.174"
2351
+ rx="15.117"
2352
+ ry="5.174"
2353
+ transform="translate(247 232.438) rotate(-12.39)"
2354
+ stroke="var(--symbol-stroke, #000000)"
2355
+ stroke-miterlimit="10"
2356
+ stroke-width="1"
2357
+ fill="var(--symbol-fill, #d0d0d0)"
2358
+ />
2359
+ <path
2360
+ d="M456.439,380.271s-1.631-2.83-8.073-2.235c-5.43.5-16.93,2.855-20.576,8.26"
2361
+ transform="translate(-179.058 -147.764)"
2362
+ fill="none"
2363
+ stroke="var(--symbol-stroke, #000000)"
2364
+ stroke-miterlimit="10"
2365
+ stroke-width="1"
2366
+ />
2367
+ <ellipse
2368
+ cx="1.651"
2369
+ cy="15.082"
2370
+ rx="1.651"
2371
+ ry="15.082"
2372
+ transform="translate(406.71 230.174) rotate(-68.13)"
2373
+ stroke="var(--symbol-stroke, #000000)"
2374
+ stroke-miterlimit="10"
2375
+ stroke-width="1"
2376
+ fill="var(--symbol-fill, #d0d0d0)"
2377
+ />
2378
+ <path
2379
+ d="M1119,375.62a119.876,119.876,0,0,1,26.193,10.419"
2380
+ transform="translate(-710.84 -145.969)"
2381
+ fill="none"
2382
+ stroke="var(--symbol-stroke, #000000)"
2383
+ stroke-miterlimit="10"
2384
+ stroke-width="1"
2385
+ />
2386
+ <ellipse
2387
+ cx="15.082"
2388
+ cy="1.651"
2389
+ rx="15.082"
2390
+ ry="1.651"
2391
+ transform="translate(210.225 238.342) rotate(-21.87)"
2392
+ stroke="var(--symbol-stroke, #000000)"
2393
+ stroke-miterlimit="10"
2394
+ stroke-width="1"
2395
+ fill="var(--symbol-fill, #d0d0d0)"
2396
+ />
2397
+ <path
2398
+ d="M293.943,375.62a119.829,119.829,0,0,0-26.193,10.419"
2399
+ transform="translate(-55.932 -145.969)"
2400
+ fill="none"
2401
+ stroke="var(--symbol-stroke, #000000)"
2402
+ stroke-miterlimit="10"
2403
+ stroke-width="1"
2404
+ />
2405
+ </g>
2406
+ `,
2407
+ metadata: {
2408
+ description: "Pixer mixer/agitator vessel with top-mounted drive",
2409
+ tags: ["vessel", "mixer", "agitator", "pixer", "tank"]
2410
+ }
2411
+ },
2412
+ "pixer-barb": {
2413
+ id: "pixer-barb",
2414
+ name: "Pixer (Hose Barb)",
2415
+ category: "pump",
2416
+ viewBox: { x: 0, y: 0, width: 50.625, height: 46.6875 },
2417
+ ports: [
2418
+ { id: "right-outlet", x: 50.625, y: 30.9375, type: "control", direction: "out" },
2419
+ { id: "left-outlet", x: 0, y: 30.9375, type: "control", direction: "out" },
2420
+ { id: "left-inlet", x: 50.625, y: 19.6875, type: "control", direction: "in" },
2421
+ { id: "right-inlet", x: 0, y: 19.6875, type: "control", direction: "in" },
2422
+ { id: "top-inlet", x: 25.3125, y: 0, type: "control", direction: "in" }
2423
+ ],
2424
+ svgContent: `
2425
+ <g transform="scale(0.196875) translate(-195, -186)">
2426
+ <!-- Hose barb ridges at top -->
2427
+ <rect
2428
+ width="20"
2429
+ height="2"
2430
+ transform="translate(313 173)"
2431
+ stroke="var(--symbol-stroke, #000000)"
2432
+ stroke-miterlimit="10"
2433
+ stroke-width="0.5"
2434
+ fill="var(--symbol-fill, #d0d0d0)"
2435
+ />
2436
+ <rect
2437
+ width="20"
2438
+ height="2"
2439
+ transform="translate(313 177)"
2440
+ stroke="var(--symbol-stroke, #000000)"
2441
+ stroke-miterlimit="10"
2442
+ stroke-width="0.5"
2443
+ fill="var(--symbol-fill, #d0d0d0)"
2444
+ />
2445
+ <rect
2446
+ width="20"
2447
+ height="2"
2448
+ transform="translate(313 181)"
2449
+ stroke="var(--symbol-stroke, #000000)"
2450
+ stroke-miterlimit="10"
2451
+ stroke-width="0.5"
2452
+ fill="var(--symbol-fill, #d0d0d0)"
2453
+ />
2454
+
2455
+ <!-- Taper from barb to motor housing -->
2456
+ <path
2457
+ d="M 313,183 L 269.742,199.918 L 376.356,199.918 L 333,183 Z"
2458
+ stroke="var(--symbol-stroke, #000000)"
2459
+ stroke-miterlimit="10"
2460
+ stroke-width="1"
2461
+ fill="var(--symbol-fill, #d0d0d0)"
2462
+ />
2463
+
2464
+ <!-- Motor housing top -->
2465
+ <rect
2466
+ width="106.614"
2467
+ height="2.468"
2468
+ transform="translate(269.742 199.918)"
2469
+ stroke="var(--symbol-stroke, #000000)"
2470
+ stroke-miterlimit="10"
2471
+ stroke-width="1"
2472
+ fill="var(--symbol-fill, #e0e0e0)"
2473
+ />
2474
+ <rect
2475
+ width="106.614"
2476
+ height="5.835"
2477
+ transform="translate(269.742 202.384)"
2478
+ stroke="var(--symbol-stroke, #000000)"
2479
+ stroke-miterlimit="10"
2480
+ stroke-width="1"
2481
+ fill="var(--symbol-fill, #e0e0e0)"
2482
+ />
2483
+ <rect
2484
+ width="106.614"
2485
+ height="2.468"
2486
+ transform="translate(269.742 208.222)"
2487
+ stroke="var(--symbol-stroke, #000000)"
2488
+ stroke-miterlimit="10"
2489
+ stroke-width="1"
2490
+ fill="var(--symbol-fill, #e0e0e0)"
2491
+ />
2492
+
2493
+ <!-- Bottom mounting flange -->
2494
+ <rect
2495
+ width="221.702"
2496
+ height="2.401"
2497
+ transform="translate(212.199 395.587)"
2498
+ stroke="var(--symbol-stroke, #000000)"
2499
+ stroke-miterlimit="10"
2500
+ stroke-width="1"
2501
+ fill="var(--symbol-fill, #e0e0e0)"
2502
+ />
2503
+ <rect
2504
+ width="221.702"
2505
+ height="6.269"
2506
+ transform="translate(212.199 397.988)"
2507
+ stroke="var(--symbol-stroke, #000000)"
2508
+ stroke-miterlimit="10"
2509
+ stroke-width="1"
2510
+ fill="var(--symbol-fill, #e0e0e0)"
2511
+ />
2512
+ <rect
2513
+ width="221.702"
2514
+ height="2.65"
2515
+ transform="translate(212.199 404.257)"
2516
+ stroke="var(--symbol-stroke, #000000)"
2517
+ stroke-miterlimit="10"
2518
+ stroke-width="1"
2519
+ fill="var(--symbol-fill, #e0e0e0)"
2520
+ />
2521
+
2522
+ <!-- Bottom flange -->
2523
+ <rect
2524
+ width="256"
2525
+ height="9.872"
2526
+ transform="translate(195.05 411.066)"
2527
+ stroke="var(--symbol-stroke, #000000)"
2528
+ stroke-miterlimit="10"
2529
+ stroke-width="1"
2530
+ fill="var(--symbol-fill, #eaeaea)"
2531
+ />
2532
+ <path
2533
+ d="M195.05,1148.286l17.149-4.156H433.9l17.149,4.156Z"
2534
+ transform="translate(0 -737.221)"
2535
+ fill="var(--symbol-fill, #e2e2e2)"
2536
+ stroke="var(--symbol-stroke, #000000)"
2537
+ stroke-miterlimit="10"
2538
+ stroke-width="1"
2539
+ />
2540
+
2541
+ <!-- Vessel top cone -->
2542
+ <path
2543
+ d="M451.05,328.356h-256l74.692-34.946H376.358Z"
2544
+ transform="translate(0 -82.72)"
2545
+ stroke="var(--symbol-stroke, #000000)"
2546
+ stroke-miterlimit="10"
2547
+ stroke-width="1"
2548
+ fill="var(--symbol-fill, #eaeaea)"
2549
+ />
2550
+
2551
+ <!-- Main vessel body -->
2552
+ <rect
2553
+ width="256"
2554
+ height="59.803"
2555
+ transform="translate(195.05 245.636)"
2556
+ stroke="var(--symbol-stroke, #000000)"
2557
+ stroke-miterlimit="10"
2558
+ stroke-width="1"
2559
+ fill="var(--symbol-fill, #eaeaea)"
2560
+ />
2561
+ <rect
2562
+ width="256"
2563
+ height="76.892"
2564
+ transform="translate(195.05 308.417)"
2565
+ stroke="var(--symbol-stroke, #000000)"
2566
+ stroke-miterlimit="10"
2567
+ stroke-width="1"
2568
+ fill="var(--symbol-fill, #eaeaea)"
2569
+ />
2570
+
2571
+ <!-- Vessel bottom transitions -->
2572
+ <path
2573
+ d="M449.265,705.7H196.773l-1.654-1.465a.014.014,0,0,1,0-.023H451.078Z"
2574
+ transform="translate(-0.049 -398.769)"
2575
+ stroke="var(--symbol-stroke, #000000)"
2576
+ stroke-miterlimit="10"
2577
+ stroke-width="1"
2578
+ fill="var(--symbol-fill, #e0e0e0)"
2579
+ />
2580
+ <path
2581
+ d="M449.278,710.66H196.793l-1.663,1.462v.016H451.086s.012,0,0-.016Z"
2582
+ transform="translate(-0.062 -403.731)"
2583
+ stroke="var(--symbol-stroke, #000000)"
2584
+ stroke-miterlimit="10"
2585
+ stroke-width="1"
2586
+ fill="var(--symbol-fill, #e0e0e0)"
2587
+ />
2588
+ <path
2589
+ d="M433.9,1060.758H212.2L195.05,1050.48h256Z"
2590
+ transform="translate(0 -665.171)"
2591
+ stroke="var(--symbol-stroke, #000000)"
2592
+ stroke-miterlimit="10"
2593
+ stroke-width="1"
2594
+ fill="var(--symbol-fill, #e0e0e0)"
2595
+ />
2596
+
2597
+ <!-- Agitator blades/impellers (simplified without gradients) -->
2598
+ <ellipse
2599
+ cx="15.454"
2600
+ cy="6.096"
2601
+ rx="15.454"
2602
+ ry="6.096"
2603
+ transform="translate(307.486 228.161)"
2604
+ stroke="var(--symbol-stroke, #000000)"
2605
+ stroke-miterlimit="10"
2606
+ stroke-width="1"
2607
+ fill="var(--symbol-fill, #c0c0c0)"
2608
+ />
2609
+ <ellipse
2610
+ cx="15.454"
2611
+ cy="5.3"
2612
+ rx="15.454"
2613
+ ry="5.3"
2614
+ transform="translate(307.486 229.753)"
2615
+ stroke="var(--symbol-stroke, #000000)"
2616
+ stroke-miterlimit="10"
2617
+ stroke-width="1"
2618
+ fill="var(--symbol-fill, #d0d0d0)"
2619
+ />
2620
+ <ellipse
2621
+ cx="5.174"
2622
+ cy="15.117"
2623
+ rx="5.174"
2624
+ ry="15.117"
2625
+ transform="translate(367.144 236.079) rotate(-77.61)"
2626
+ stroke="var(--symbol-stroke, #000000)"
2627
+ stroke-miterlimit="10"
2628
+ stroke-width="1"
2629
+ fill="var(--symbol-fill, #d0d0d0)"
2630
+ />
2631
+ <path
2632
+ d="M947,380.271s1.631-2.83,8.084-2.235c5.427.5,16.927,2.855,20.574,8.26"
2633
+ transform="translate(-578.512 -147.764)"
2634
+ fill="none"
2635
+ stroke="var(--symbol-stroke, #000000)"
2636
+ stroke-miterlimit="10"
2637
+ stroke-width="1"
2638
+ />
2639
+ <ellipse
2640
+ cx="15.117"
2641
+ cy="5.174"
2642
+ rx="15.117"
2643
+ ry="5.174"
2644
+ transform="translate(247 232.438) rotate(-12.39)"
2645
+ stroke="var(--symbol-stroke, #000000)"
2646
+ stroke-miterlimit="10"
2647
+ stroke-width="1"
2648
+ fill="var(--symbol-fill, #d0d0d0)"
2649
+ />
2650
+ <path
2651
+ d="M456.439,380.271s-1.631-2.83-8.073-2.235c-5.43.5-16.93,2.855-20.576,8.26"
2652
+ transform="translate(-179.058 -147.764)"
2653
+ fill="none"
2654
+ stroke="var(--symbol-stroke, #000000)"
2655
+ stroke-miterlimit="10"
2656
+ stroke-width="1"
2657
+ />
2658
+ <ellipse
2659
+ cx="1.651"
2660
+ cy="15.082"
2661
+ rx="1.651"
2662
+ ry="15.082"
2663
+ transform="translate(406.71 230.174) rotate(-68.13)"
2664
+ stroke="var(--symbol-stroke, #000000)"
2665
+ stroke-miterlimit="10"
2666
+ stroke-width="1"
2667
+ fill="var(--symbol-fill, #d0d0d0)"
2668
+ />
2669
+ <path
2670
+ d="M1119,375.62a119.876,119.876,0,0,1,26.193,10.419"
2671
+ transform="translate(-710.84 -145.969)"
2672
+ fill="none"
2673
+ stroke="var(--symbol-stroke, #000000)"
2674
+ stroke-miterlimit="10"
2675
+ stroke-width="1"
2676
+ />
2677
+ <ellipse
2678
+ cx="15.082"
2679
+ cy="1.651"
2680
+ rx="15.082"
2681
+ ry="1.651"
2682
+ transform="translate(210.225 238.342) rotate(-21.87)"
2683
+ stroke="var(--symbol-stroke, #000000)"
2684
+ stroke-miterlimit="10"
2685
+ stroke-width="1"
2686
+ fill="var(--symbol-fill, #d0d0d0)"
2687
+ />
2688
+ <path
2689
+ d="M293.943,375.62a119.829,119.829,0,0,0-26.193,10.419"
2690
+ transform="translate(-55.932 -145.969)"
2691
+ fill="none"
2692
+ stroke="var(--symbol-stroke, #000000)"
2693
+ stroke-miterlimit="10"
2694
+ stroke-width="1"
2695
+ />
2696
+ </g>
2697
+ `,
2698
+ metadata: {
2699
+ description: "Pixer mixer/agitator vessel with tapered hose barb connection at top",
2700
+ tags: ["vessel", "mixer", "agitator", "pixer", "tank", "hose-barb"]
2701
+ }
2702
+ },
2703
+ "vessel-balloon": {
2704
+ id: "vessel-balloon",
2705
+ name: "Vessel",
2706
+ category: "vessel",
2707
+ viewBox: { x: 0, y: 0, width: 50.625, height: 91 },
2708
+ ports: [
2709
+ { id: "top", type: "inlet", x: 25.3125, y: 0 },
2710
+ { id: "left", type: "inlet", x: 5, y: 39 },
2711
+ { id: "right", type: "outlet", x: 45.625, y: 39 },
2712
+ { id: "bottom", type: "outlet", x: 25.3125, y: 91 }
2713
+ ],
2714
+ svgContent: `
2715
+ <g transform="scale(0.196875) translate(-195, -100)">
2716
+ <!-- Top opening nozzle -->
2717
+ <rect
2718
+ width="65"
2719
+ height="19.5"
2720
+ transform="translate(290.5, 100)"
2721
+ stroke="var(--symbol-stroke, #000000)"
2722
+ stroke-miterlimit="10"
2723
+ stroke-width="1"
2724
+ fill="var(--symbol-fill, #e0e0e0)"
2725
+ />
2726
+
2727
+ <!-- Transition from nozzle to dome -->
2728
+ <path
2729
+ d="M 290.5,119.5 L 238,152 L 408,152 L 355.5,119.5 Z"
2730
+ stroke="var(--symbol-stroke, #000000)"
2731
+ stroke-miterlimit="10"
2732
+ stroke-width="1"
2733
+ fill="var(--symbol-fill, #e0e0e0)"
2734
+ />
2735
+
2736
+ <!-- Upper balloon dome - rounded bulge -->
2737
+ <path
2738
+ d="M 238,152 C 185,210 150,320 180,395 L 466,395 C 496,320 461,210 408,152 Z"
2739
+ stroke="var(--symbol-stroke, #000000)"
2740
+ stroke-miterlimit="10"
2741
+ stroke-width="1"
2742
+ fill="var(--symbol-fill, #eaeaea)"
2743
+ />
2744
+
2745
+ <!-- Tapered vessel body -->
2746
+ <path
2747
+ d="M 180,395 L 269.742,530 L 376.356,530 L 466,395 Z"
2748
+ stroke="var(--symbol-stroke, #000000)"
2749
+ stroke-miterlimit="10"
2750
+ stroke-width="1"
2751
+ fill="var(--symbol-fill, #eaeaea)"
2752
+ />
2753
+
2754
+ <!-- Bottom cone -->
2755
+ <path
2756
+ d="M 269.742,530 L 280,555 L 366,555 L 376.356,530 Z"
2757
+ stroke="var(--symbol-stroke, #000000)"
2758
+ stroke-miterlimit="10"
2759
+ stroke-width="1"
2760
+ fill="var(--symbol-fill, #e0e0e0)"
2761
+ />
2762
+
2763
+ <!-- Bottom flange matching pixer top dimensions -->
2764
+ <rect
2765
+ width="141.313"
2766
+ height="9.872"
2767
+ transform="translate(252.392, 555)"
2768
+ stroke="var(--symbol-stroke, #000000)"
2769
+ stroke-miterlimit="10"
2770
+ stroke-width="1"
2771
+ fill="var(--symbol-fill, #eaeaea)"
2772
+ />
2773
+ </g>
2774
+ `,
2775
+ metadata: {
2776
+ description: "Balloon-shaped vessel with rounded top and tapered bottom, designed to fit on standard pixer",
2777
+ tags: ["vessel", "storage", "tank", "balloon"]
2778
+ }
2779
+ },
2780
+ "psr-f": {
2781
+ id: "psr-f",
2782
+ name: "PSR F",
2783
+ category: "instrument",
2784
+ viewBox: { x: 0, y: 0, width: 57, height: 57 },
2785
+ ports: [
2786
+ { id: "top", type: "signal", x: 28.5, y: 14.25 },
2787
+ { id: "right", type: "signal", x: 42.75, y: 28.5 },
2788
+ { id: "bottom", type: "signal", x: 28.5, y: 42.75 },
2789
+ { id: "left", type: "signal", x: 14.25, y: 28.5 }
2790
+ ],
2791
+ svgContent: `
2792
+ <g transform="scale(0.5) translate(28.5, 28.5)">
2793
+ <!-- Circle background -->
2794
+ <ellipse
2795
+ cx="28.5"
2796
+ cy="28.5"
2797
+ rx="28.5"
2798
+ ry="28.5"
2799
+ stroke="var(--symbol-stroke, #000000)"
2800
+ stroke-width="1"
2801
+ fill="var(--symbol-fill, #ffffff)"
2802
+ />
2803
+
2804
+ <!-- Letter F -->
2805
+ <text
2806
+ x="28.5"
2807
+ y="38"
2808
+ text-anchor="middle"
2809
+ font-family="'Times New Roman', Georgia, serif"
2810
+ font-size="34"
2811
+ font-weight="normal"
2812
+ font-style="italic"
2813
+ fill="var(--symbol-stroke, #000000)"
2814
+ >F</text>
2815
+ </g>
2816
+ `,
2817
+ metadata: {
2818
+ description: "PSR F instrument symbol - Flow indicator",
2819
+ tags: ["instrument", "psr", "flow", "indicator"]
2820
+ }
2821
+ },
2822
+ "psr-p": {
2823
+ id: "psr-p",
2824
+ name: "PSR P",
2825
+ category: "instrument",
2826
+ viewBox: { x: 0, y: 0, width: 57, height: 57 },
2827
+ ports: [
2828
+ { id: "top", type: "signal", x: 28.5, y: 14.25 },
2829
+ { id: "right", type: "signal", x: 42.75, y: 28.5 },
2830
+ { id: "bottom", type: "signal", x: 28.5, y: 42.75 },
2831
+ { id: "left", type: "signal", x: 14.25, y: 28.5 }
2832
+ ],
2833
+ svgContent: `
2834
+ <g transform="scale(0.5) translate(28.5, 28.5)">
2835
+ <!-- Circle background -->
2836
+ <ellipse
2837
+ cx="28.5"
2838
+ cy="28.5"
2839
+ rx="28.5"
2840
+ ry="28.5"
2841
+ stroke="var(--symbol-stroke, #000000)"
2842
+ stroke-width="1"
2843
+ fill="var(--symbol-fill, #ffffff)"
2844
+ />
2845
+
2846
+ <!-- Letter P -->
2847
+ <text
2848
+ x="28.5"
2849
+ y="38"
2850
+ text-anchor="middle"
2851
+ font-family="'Times New Roman', Georgia, serif"
2852
+ font-size="34"
2853
+ font-weight="normal"
2854
+ font-style="italic"
2855
+ fill="var(--symbol-stroke, #000000)"
2856
+ >P</text>
2857
+ </g>
2858
+ `,
2859
+ metadata: {
2860
+ description: "PSR P instrument symbol - Pressure indicator",
2861
+ tags: ["instrument", "psr", "pressure", "indicator"]
1828
2862
  }
1829
2863
  }
1830
2864
  };
@@ -1904,6 +2938,24 @@ function NodeRenderer({
1904
2938
  `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
1905
2939
  );
1906
2940
  }
2941
+ const getStatusColor = (status) => {
2942
+ switch (status) {
2943
+ case "running":
2944
+ return "#22c55e";
2945
+ // Green
2946
+ case "alarm":
2947
+ return "#ef4444";
2948
+ // Red
2949
+ case "warning":
2950
+ return "#f59e0b";
2951
+ // Orange
2952
+ case "stopped":
2953
+ default:
2954
+ return "#edeeef";
2955
+ }
2956
+ };
2957
+ const fillColor = node.customColors?.fill ?? getStatusColor(node.status);
2958
+ const strokeColor = node.customColors?.stroke ?? "rgb(0, 0, 0)";
1907
2959
  return /* @__PURE__ */ jsxs9(
1908
2960
  "g",
1909
2961
  {
@@ -1912,6 +2964,11 @@ function NodeRenderer({
1912
2964
  "data-selected": isSelected,
1913
2965
  "data-dragging": isDragging,
1914
2966
  transform: transforms.join(" "),
2967
+ style: {
2968
+ "--symbol-fill": fillColor,
2969
+ "--symbol-stroke": strokeColor,
2970
+ cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
2971
+ },
1915
2972
  onClick: (e) => {
1916
2973
  e.stopPropagation();
1917
2974
  onNodeClick?.(node.id, e);
@@ -1931,9 +2988,6 @@ function NodeRenderer({
1931
2988
  onNodeDragStart?.(node.id, e);
1932
2989
  }
1933
2990
  },
1934
- style: {
1935
- cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
1936
- },
1937
2991
  children: [
1938
2992
  isSelected && /* @__PURE__ */ jsx13(
1939
2993
  "rect",
@@ -1986,6 +3040,9 @@ function PipeRenderer({
1986
3040
  onPipeClick,
1987
3041
  enableWaypointEditing = false,
1988
3042
  onWaypointDragStart,
3043
+ onWaypointClick,
3044
+ selectedWaypointPipeId,
3045
+ selectedWaypointIndex,
1989
3046
  editingPipeId,
1990
3047
  draggingWaypointIndex,
1991
3048
  waypointDragOffset,
@@ -2006,7 +3063,7 @@ function PipeRenderer({
2006
3063
  });
2007
3064
  const pointsString = displayPoints.map((point) => `${point.x},${point.y}`).join(" ");
2008
3065
  const pipeStyle = pipe.style || {};
2009
- const stroke = isSelected ? "#3b82f6" : pipeStyle.stroke || "#263238";
3066
+ const stroke = isSelected ? "#3b82f6" : pipeStyle.stroke || "#94a1b8";
2010
3067
  const strokeWidth = isSelected ? (pipeStyle.strokeWidth || 3) + 2 : pipeStyle.strokeWidth || 3;
2011
3068
  const strokeDasharray = pipeStyle.strokeDasharray;
2012
3069
  const opacity = pipeStyle.opacity !== void 0 ? pipeStyle.opacity : 1;
@@ -2055,10 +3112,11 @@ function PipeRenderer({
2055
3112
  ] }),
2056
3113
  displayPoints.map((point, idx) => {
2057
3114
  const isEndpoint = idx === 0 || idx === displayPoints.length - 1;
3115
+ const isSelectedWaypoint = selectedWaypointPipeId === pipe.id && selectedWaypointIndex === idx;
2058
3116
  const isDragging = isEditing && draggingWaypointIndex === idx;
2059
- const waypointSize = isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
2060
- const waypointOpacity = isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
2061
- const waypointColor = isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
3117
+ const waypointSize = isSelectedWaypoint ? 8 : isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
3118
+ const waypointOpacity = isSelectedWaypoint ? 1 : isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
3119
+ const waypointColor = isSelectedWaypoint ? "#3b82f6" : isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
2062
3120
  return /* @__PURE__ */ jsx14(
2063
3121
  "circle",
2064
3122
  {
@@ -2074,6 +3132,12 @@ function PipeRenderer({
2074
3132
  cursor: showEditingControls ? isDragging ? "grabbing" : "grab" : "default"
2075
3133
  },
2076
3134
  pointerEvents: showEditingControls ? "all" : "none",
3135
+ onClick: (e) => {
3136
+ if (showEditingControls) {
3137
+ e.stopPropagation();
3138
+ onWaypointClick?.(pipe.id, idx);
3139
+ }
3140
+ },
2077
3141
  onMouseDown: (e) => {
2078
3142
  if (showEditingControls) {
2079
3143
  e.stopPropagation();
@@ -2150,7 +3214,13 @@ function PipeRenderer({
2150
3214
 
2151
3215
  // src/components/PIDCanvas/DataOverlay.tsx
2152
3216
  import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
2153
- function DataOverlay({ nodeId, x, y, telemetry }) {
3217
+ function DataOverlay({
3218
+ nodeId,
3219
+ x,
3220
+ y,
3221
+ telemetry,
3222
+ scale = 1
3223
+ }) {
2154
3224
  const { value, display, history } = telemetry;
2155
3225
  const showValue = display?.showValue ?? true;
2156
3226
  const showStatus = display?.showStatus ?? true;
@@ -2190,9 +3260,9 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2190
3260
  const displayText = value.unit ? `${formattedValue} ${value.unit}` : formattedValue;
2191
3261
  const sparklinePath = (() => {
2192
3262
  if (!showSparkline || !history || history.length < 2) return "";
2193
- const width = 70;
2194
- const height = 20;
2195
- const padding = 5;
3263
+ const width = 70 * scale;
3264
+ const height = 20 * scale;
3265
+ const padding = 5 * scale;
2196
3266
  const effectiveWidth = width - padding * 2;
2197
3267
  const effectiveHeight = height - padding * 2;
2198
3268
  const min = Math.min(...history);
@@ -2206,21 +3276,22 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2206
3276
  return `M ${points.join(" L ")}`;
2207
3277
  })();
2208
3278
  const hasSparkline = showSparkline && sparklinePath;
2209
- const overlayHeight = label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22;
3279
+ const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale;
3280
+ const overlayWidth = 80 * scale;
2210
3281
  const displayX = x + offsetX;
2211
3282
  const displayY = y + offsetY;
2212
3283
  return /* @__PURE__ */ jsxs11("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
2213
3284
  /* @__PURE__ */ jsx15(
2214
3285
  "rect",
2215
3286
  {
2216
- x: displayX - 40,
2217
- y: displayY - 25,
2218
- width: "80",
3287
+ x: displayX - overlayWidth / 2,
3288
+ y: displayY - 25 * scale,
3289
+ width: overlayWidth,
2219
3290
  height: overlayHeight,
2220
- rx: "4",
3291
+ rx: 4 * scale,
2221
3292
  fill: colors.bg,
2222
3293
  stroke: colors.border,
2223
- strokeWidth: "1",
3294
+ strokeWidth: 1 * scale,
2224
3295
  opacity: "0.95",
2225
3296
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
2226
3297
  }
@@ -2229,9 +3300,9 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2229
3300
  "text",
2230
3301
  {
2231
3302
  x: displayX,
2232
- y: displayY - 15,
3303
+ y: displayY - 15 * scale,
2233
3304
  textAnchor: "middle",
2234
- fontSize: "8",
3305
+ fontSize: 8 * scale,
2235
3306
  fontWeight: "600",
2236
3307
  fill: finalTextColor,
2237
3308
  opacity: "0.9",
@@ -2242,31 +3313,41 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2242
3313
  "text",
2243
3314
  {
2244
3315
  x: displayX,
2245
- y: label ? displayY - 3 : displayY - 9,
3316
+ y: label ? displayY - 3 * scale : displayY - 9 * scale,
2246
3317
  textAnchor: "middle",
2247
- fontSize: "11",
3318
+ fontSize: 11 * scale,
2248
3319
  fontWeight: "700",
2249
3320
  fill: finalTextColor,
2250
3321
  letterSpacing: "0.02em",
2251
3322
  children: displayText
2252
3323
  }
2253
3324
  ),
2254
- showStatus && value.status !== "normal" && /* @__PURE__ */ jsx15("circle", { cx: displayX + 35, cy: displayY - 18, r: "3", fill: finalTextColor, opacity: "0.9", children: /* @__PURE__ */ jsx15(
2255
- "animate",
3325
+ showStatus && value.status !== "normal" && /* @__PURE__ */ jsx15(
3326
+ "circle",
2256
3327
  {
2257
- attributeName: "opacity",
2258
- values: "0.9;0.3;0.9",
2259
- dur: "1.5s",
2260
- repeatCount: "indefinite"
3328
+ cx: displayX + 35 * scale,
3329
+ cy: displayY - 18 * scale,
3330
+ r: 3 * scale,
3331
+ fill: finalTextColor,
3332
+ opacity: "0.9",
3333
+ children: /* @__PURE__ */ jsx15(
3334
+ "animate",
3335
+ {
3336
+ attributeName: "opacity",
3337
+ values: "0.9;0.3;0.9",
3338
+ dur: "1.5s",
3339
+ repeatCount: "indefinite"
3340
+ }
3341
+ )
2261
3342
  }
2262
- ) }),
3343
+ ),
2263
3344
  value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ jsxs11(
2264
3345
  "text",
2265
3346
  {
2266
3347
  x: displayX,
2267
- y: displayY + 5,
3348
+ y: displayY + 5 * scale,
2268
3349
  textAnchor: "middle",
2269
- fontSize: "7",
3350
+ fontSize: 7 * scale,
2270
3351
  fontWeight: "500",
2271
3352
  fill: finalTextColor,
2272
3353
  opacity: "0.7",
@@ -2277,13 +3358,13 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2277
3358
  ]
2278
3359
  }
2279
3360
  ),
2280
- hasSparkline && /* @__PURE__ */ jsx15("g", { transform: `translate(${displayX - 35}, ${displayY + (label ? 5 : 0)})`, children: /* @__PURE__ */ jsx15(
3361
+ hasSparkline && /* @__PURE__ */ jsx15("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ jsx15(
2281
3362
  "path",
2282
3363
  {
2283
3364
  d: sparklinePath,
2284
3365
  fill: "none",
2285
3366
  stroke: sparklineColor || finalTextColor,
2286
- strokeWidth: "1.5",
3367
+ strokeWidth: 1.5 * scale,
2287
3368
  strokeLinecap: "round",
2288
3369
  strokeLinejoin: "round",
2289
3370
  opacity: "0.9"
@@ -2292,8 +3373,145 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2292
3373
  ] });
2293
3374
  }
2294
3375
 
3376
+ // src/components/PIDCanvas/PositionPanel.tsx
3377
+ import { useState as useState7, useEffect as useEffect4 } from "react";
3378
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3379
+ function PositionPanel({
3380
+ selectedNode,
3381
+ selectedWaypoint,
3382
+ onPositionChange,
3383
+ onWaypointPositionChange,
3384
+ position = "top-right"
3385
+ }) {
3386
+ const [x, setX] = useState7("");
3387
+ const [y, setY] = useState7("");
3388
+ useEffect4(() => {
3389
+ if (selectedNode) {
3390
+ setX(selectedNode.transform.x.toFixed(2));
3391
+ setY(selectedNode.transform.y.toFixed(2));
3392
+ } else if (selectedWaypoint) {
3393
+ setX(selectedWaypoint.position.x.toFixed(2));
3394
+ setY(selectedWaypoint.position.y.toFixed(2));
3395
+ }
3396
+ }, [selectedNode, selectedWaypoint]);
3397
+ if (!selectedNode && !selectedWaypoint) {
3398
+ return null;
3399
+ }
3400
+ const handleXChange = (value) => {
3401
+ setX(value);
3402
+ const numValue = parseFloat(value);
3403
+ if (!isNaN(numValue)) {
3404
+ if (selectedNode && onPositionChange) {
3405
+ onPositionChange(selectedNode.id, numValue, selectedNode.transform.y);
3406
+ } else if (selectedWaypoint && onWaypointPositionChange) {
3407
+ onWaypointPositionChange(
3408
+ selectedWaypoint.pipeId,
3409
+ selectedWaypoint.pointIndex,
3410
+ numValue,
3411
+ selectedWaypoint.position.y
3412
+ );
3413
+ }
3414
+ }
3415
+ };
3416
+ const handleYChange = (value) => {
3417
+ setY(value);
3418
+ const numValue = parseFloat(value);
3419
+ if (!isNaN(numValue)) {
3420
+ if (selectedNode && onPositionChange) {
3421
+ onPositionChange(selectedNode.id, selectedNode.transform.x, numValue);
3422
+ } else if (selectedWaypoint && onWaypointPositionChange) {
3423
+ onWaypointPositionChange(
3424
+ selectedWaypoint.pipeId,
3425
+ selectedWaypoint.pointIndex,
3426
+ selectedWaypoint.position.x,
3427
+ numValue
3428
+ );
3429
+ }
3430
+ }
3431
+ };
3432
+ const handleBlur = () => {
3433
+ if (selectedNode) {
3434
+ setX(selectedNode.transform.x.toFixed(2));
3435
+ setY(selectedNode.transform.y.toFixed(2));
3436
+ } else if (selectedWaypoint) {
3437
+ setX(selectedWaypoint.position.x.toFixed(2));
3438
+ setY(selectedWaypoint.position.y.toFixed(2));
3439
+ }
3440
+ };
3441
+ const positionStyles = {
3442
+ "top-left": { top: 10, left: 10 },
3443
+ "top-right": { top: 10, right: 10 },
3444
+ "bottom-left": { bottom: 10, left: 10 },
3445
+ "bottom-right": { bottom: 10, right: 10 }
3446
+ };
3447
+ return /* @__PURE__ */ jsxs12(
3448
+ "div",
3449
+ {
3450
+ style: {
3451
+ position: "absolute",
3452
+ ...positionStyles[position],
3453
+ backgroundColor: "white",
3454
+ border: "1px solid #ccc",
3455
+ borderRadius: "4px",
3456
+ padding: "12px",
3457
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
3458
+ zIndex: 1e3,
3459
+ minWidth: "180px",
3460
+ fontFamily: "system-ui, -apple-system, sans-serif",
3461
+ fontSize: "13px"
3462
+ },
3463
+ children: [
3464
+ /* @__PURE__ */ jsx16("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
3465
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
3466
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3467
+ /* @__PURE__ */ jsx16("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
3468
+ /* @__PURE__ */ jsx16(
3469
+ "input",
3470
+ {
3471
+ type: "number",
3472
+ value: x,
3473
+ onChange: (e) => handleXChange(e.target.value),
3474
+ onBlur: handleBlur,
3475
+ style: {
3476
+ flex: 1,
3477
+ padding: "4px 8px",
3478
+ border: "1px solid #ddd",
3479
+ borderRadius: "3px",
3480
+ fontSize: "13px"
3481
+ },
3482
+ step: "1"
3483
+ }
3484
+ )
3485
+ ] }),
3486
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3487
+ /* @__PURE__ */ jsx16("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
3488
+ /* @__PURE__ */ jsx16(
3489
+ "input",
3490
+ {
3491
+ type: "number",
3492
+ value: y,
3493
+ onChange: (e) => handleYChange(e.target.value),
3494
+ onBlur: handleBlur,
3495
+ style: {
3496
+ flex: 1,
3497
+ padding: "4px 8px",
3498
+ border: "1px solid #ddd",
3499
+ borderRadius: "3px",
3500
+ fontSize: "13px"
3501
+ },
3502
+ step: "1"
3503
+ }
3504
+ )
3505
+ ] })
3506
+ ] }),
3507
+ /* @__PURE__ */ jsx16("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
3508
+ ]
3509
+ }
3510
+ );
3511
+ }
3512
+
2295
3513
  // src/diagram/hooks/useViewBox.ts
2296
- import { useState as useState7, useCallback as useCallback3, useRef as useRef2, useEffect as useEffect4 } from "react";
3514
+ import { useState as useState8, useCallback as useCallback3, useRef as useRef2, useEffect as useEffect5 } from "react";
2297
3515
  function useViewBox(options) {
2298
3516
  const {
2299
3517
  initialViewBox,
@@ -2304,7 +3522,7 @@ function useViewBox(options) {
2304
3522
  zoomSensitivity = 1e-3,
2305
3523
  allowLeftClickPan = true
2306
3524
  } = options;
2307
- const [viewBox, setViewBox] = useState7(initialViewBox);
3525
+ const [viewBox, setViewBox] = useState8(initialViewBox);
2308
3526
  const svgRef = useRef2(null);
2309
3527
  const isPanningRef = useRef2(false);
2310
3528
  const lastMousePosRef = useRef2({ x: 0, y: 0 });
@@ -2347,7 +3565,35 @@ function useViewBox(options) {
2347
3565
  };
2348
3566
  });
2349
3567
  }, [initialViewBox, maxZoom]);
2350
- useEffect4(() => {
3568
+ const fitToContent = useCallback3((bounds, padding = 50) => {
3569
+ const contentWidth = bounds.maxX - bounds.minX;
3570
+ const contentHeight = bounds.maxY - bounds.minY;
3571
+ if (contentWidth === 0 || contentHeight === 0) {
3572
+ return;
3573
+ }
3574
+ const paddedWidth = contentWidth + padding * 2;
3575
+ const paddedHeight = contentHeight + padding * 2;
3576
+ const aspectRatio = initialViewBox.width / initialViewBox.height;
3577
+ const contentAspectRatio = paddedWidth / paddedHeight;
3578
+ let newWidth;
3579
+ let newHeight;
3580
+ if (contentAspectRatio > aspectRatio) {
3581
+ newWidth = paddedWidth;
3582
+ newHeight = paddedWidth / aspectRatio;
3583
+ } else {
3584
+ newHeight = paddedHeight;
3585
+ newWidth = paddedHeight * aspectRatio;
3586
+ }
3587
+ const centerX = bounds.minX + contentWidth / 2;
3588
+ const centerY = bounds.minY + contentHeight / 2;
3589
+ setViewBox({
3590
+ x: centerX - newWidth / 2,
3591
+ y: centerY - newHeight / 2,
3592
+ width: newWidth,
3593
+ height: newHeight
3594
+ });
3595
+ }, [initialViewBox]);
3596
+ useEffect5(() => {
2351
3597
  if (!enableZoom || !svgRef.current) return;
2352
3598
  const svg = svgRef.current;
2353
3599
  const handleWheel = (e) => {
@@ -2380,7 +3626,7 @@ function useViewBox(options) {
2380
3626
  svg.addEventListener("wheel", handleWheel, { passive: false });
2381
3627
  return () => svg.removeEventListener("wheel", handleWheel);
2382
3628
  }, [enableZoom, zoomSensitivity, minZoom, initialViewBox]);
2383
- useEffect4(() => {
3629
+ useEffect5(() => {
2384
3630
  if (!enablePan || !svgRef.current) return;
2385
3631
  const svg = svgRef.current;
2386
3632
  const handleMouseDown = (e) => {
@@ -2456,7 +3702,7 @@ function useViewBox(options) {
2456
3702
  window.removeEventListener("keyup", handleKeyUp);
2457
3703
  };
2458
3704
  }, [enablePan, allowLeftClickPan]);
2459
- useEffect4(() => {
3705
+ useEffect5(() => {
2460
3706
  if (!svgRef.current) return;
2461
3707
  if (!enablePan && !enableZoom) return;
2462
3708
  const svg = svgRef.current;
@@ -2578,20 +3824,22 @@ function useViewBox(options) {
2578
3824
  resetViewBox,
2579
3825
  zoomIn,
2580
3826
  zoomOut,
3827
+ fitToContent,
3828
+ setViewBox,
2581
3829
  screenToWorld,
2582
3830
  svgRef
2583
3831
  };
2584
3832
  }
2585
3833
 
2586
3834
  // src/diagram/hooks/useSelection.ts
2587
- import { useState as useState8, useCallback as useCallback4 } from "react";
3835
+ import { useState as useState9, useCallback as useCallback4 } from "react";
2588
3836
  function useSelection(options = {}) {
2589
3837
  const {
2590
3838
  multiSelect = true,
2591
3839
  initialSelection = { selectedNodeIds: /* @__PURE__ */ new Set(), selectedPipeIds: /* @__PURE__ */ new Set() },
2592
3840
  onSelectionChange
2593
3841
  } = options;
2594
- const [selection, setSelection] = useState8(initialSelection);
3842
+ const [selection, setSelection] = useState9(initialSelection);
2595
3843
  const notifyChange = useCallback4(
2596
3844
  (newSelection) => {
2597
3845
  if (onSelectionChange) {
@@ -2689,14 +3937,14 @@ function useSelection(options = {}) {
2689
3937
  }
2690
3938
 
2691
3939
  // src/diagram/hooks/useTelemetry.ts
2692
- import { useState as useState9, useCallback as useCallback5, useEffect as useEffect5, useRef as useRef3 } from "react";
3940
+ import { useState as useState10, useCallback as useCallback5, useEffect as useEffect6, useRef as useRef3 } from "react";
2693
3941
  function useTelemetry(options = {}) {
2694
3942
  const {
2695
3943
  initialBindings = [],
2696
3944
  onTelemetryChange,
2697
3945
  refreshInterval = 0
2698
3946
  } = options;
2699
- const [telemetry, setTelemetry] = useState9(() => {
3947
+ const [telemetry, setTelemetry] = useState10(() => {
2700
3948
  const map = /* @__PURE__ */ new Map();
2701
3949
  initialBindings.forEach((binding) => {
2702
3950
  map.set(binding.nodeId, binding);
@@ -2817,7 +4065,7 @@ function useTelemetry(options = {}) {
2817
4065
  },
2818
4066
  [notifyChange]
2819
4067
  );
2820
- useEffect5(() => {
4068
+ useEffect6(() => {
2821
4069
  if (refreshInterval <= 0) return;
2822
4070
  const intervalId = setInterval(() => {
2823
4071
  notifyChange(telemetryRef.current);
@@ -2834,8 +4082,50 @@ function useTelemetry(options = {}) {
2834
4082
  };
2835
4083
  }
2836
4084
 
4085
+ // src/diagram/hooks/useTelemetryStatus.ts
4086
+ import { useEffect as useEffect7 } from "react";
4087
+ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
4088
+ const { enabled = true, mapStatus } = options;
4089
+ useEffect7(() => {
4090
+ if (!enabled) return;
4091
+ const defaultMapStatus = (telemetryStatus) => {
4092
+ switch (telemetryStatus) {
4093
+ case "normal":
4094
+ return "running";
4095
+ case "warning":
4096
+ return "warning";
4097
+ case "alarm":
4098
+ case "fault":
4099
+ return "alarm";
4100
+ case "off":
4101
+ return "stopped";
4102
+ default:
4103
+ return "stopped";
4104
+ }
4105
+ };
4106
+ const statusMapper = mapStatus || defaultMapStatus;
4107
+ let needsUpdate = false;
4108
+ const updatedNodes = diagram.nodes.map((node) => {
4109
+ const binding = telemetry.get(node.id);
4110
+ if (!binding) return node;
4111
+ const newStatus = statusMapper(binding.value.status);
4112
+ if (node.status !== newStatus) {
4113
+ needsUpdate = true;
4114
+ return { ...node, status: newStatus };
4115
+ }
4116
+ return node;
4117
+ });
4118
+ if (needsUpdate) {
4119
+ setDiagram({
4120
+ ...diagram,
4121
+ nodes: updatedNodes
4122
+ });
4123
+ }
4124
+ }, [telemetry, diagram, setDiagram, enabled, mapStatus]);
4125
+ }
4126
+
2837
4127
  // src/diagram/hooks/useDrag.ts
2838
- import { useState as useState10, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect6 } from "react";
4128
+ import { useState as useState11, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect8 } from "react";
2839
4129
  function useDrag(options = {}) {
2840
4130
  const {
2841
4131
  onDragStart,
@@ -2845,13 +4135,13 @@ function useDrag(options = {}) {
2845
4135
  dragThreshold = 3,
2846
4136
  screenToWorld = (x, y) => ({ x, y })
2847
4137
  } = options;
2848
- const [dragState, setDragState] = useState10({
4138
+ const [dragState, setDragState] = useState11({
2849
4139
  isDragging: false,
2850
4140
  startPosition: null,
2851
4141
  offset: { x: 0, y: 0 },
2852
4142
  draggedId: null
2853
4143
  });
2854
- const [isListening, setIsListening] = useState10(false);
4144
+ const [isListening, setIsListening] = useState11(false);
2855
4145
  const dragStartRef = useRef4(null);
2856
4146
  const hasMovedRef = useRef4(false);
2857
4147
  const applySnap = useCallback6(
@@ -3020,7 +4310,7 @@ function useDrag(options = {}) {
3020
4310
  },
3021
4311
  [screenToWorld, applySnap, onDragEnd]
3022
4312
  );
3023
- useEffect6(() => {
4313
+ useEffect8(() => {
3024
4314
  if (!isListening) return;
3025
4315
  document.addEventListener("mousemove", handleMouseMove);
3026
4316
  document.addEventListener("mouseup", handleMouseUp);
@@ -3089,7 +4379,7 @@ function useDrag(options = {}) {
3089
4379
  }
3090
4380
 
3091
4381
  // src/diagram/hooks/useKeyboardShortcuts.ts
3092
- import { useEffect as useEffect7 } from "react";
4382
+ import { useEffect as useEffect9 } from "react";
3093
4383
  function useKeyboardShortcuts(options) {
3094
4384
  const {
3095
4385
  onDelete,
@@ -3100,7 +4390,7 @@ function useKeyboardShortcuts(options) {
3100
4390
  onSelectAll,
3101
4391
  enabled = true
3102
4392
  } = options;
3103
- useEffect7(() => {
4393
+ useEffect9(() => {
3104
4394
  if (!enabled) return;
3105
4395
  const handleKeyDown = (event) => {
3106
4396
  const target = event.target;
@@ -3209,7 +4499,7 @@ function nodeHasPort(node, portId) {
3209
4499
  }
3210
4500
 
3211
4501
  // src/components/PIDCanvas/PIDCanvas.tsx
3212
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4502
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3213
4503
  function PIDCanvas({
3214
4504
  diagram,
3215
4505
  className = "",
@@ -3232,35 +4522,290 @@ function PIDCanvas({
3232
4522
  onPaste,
3233
4523
  onSymbolDrop,
3234
4524
  onPipeCreated,
4525
+ onMounted,
3235
4526
  telemetry,
3236
4527
  ...props
3237
4528
  }) {
3238
- const [localDiagram, setLocalDiagram] = useState11(diagram);
3239
- const [dragOverPosition, setDragOverPosition] = useState11(null);
3240
- const [isConnecting, setIsConnecting] = useState11(false);
3241
- const [connectionSource, setConnectionSource] = useState11(null);
3242
- const [connectionSourcePort, setConnectionSourcePort] = useState11(null);
3243
- const [connectionCursor, setConnectionCursor] = useState11(null);
3244
- const [hoveredNode, setHoveredNode] = useState11(null);
3245
- const [hoveredPort, setHoveredPort] = useState11(null);
3246
- useEffect8(() => {
3247
- setLocalDiagram(diagram);
4529
+ const [localDiagram, setLocalDiagram] = useState12(diagram);
4530
+ const [dragOverPosition, setDragOverPosition] = useState12(null);
4531
+ const [isConnecting, setIsConnecting] = useState12(false);
4532
+ const [connectionSource, setConnectionSource] = useState12(null);
4533
+ const [connectionSourcePort, setConnectionSourcePort] = useState12(null);
4534
+ const [connectionCursor, setConnectionCursor] = useState12(null);
4535
+ const [hoveredNode, setHoveredNode] = useState12(null);
4536
+ const [hoveredPort, setHoveredPort] = useState12(null);
4537
+ const [selectedWaypoint, setSelectedWaypoint] = useState12(null);
4538
+ const historyStack = useRef5([]);
4539
+ const redoStack = useRef5([]);
4540
+ const isUndoRedoAction = useRef5(false);
4541
+ const isInternalChange = useRef5(false);
4542
+ const lastInternalDiagram = useRef5(null);
4543
+ useEffect10(() => {
4544
+ const isExternal = !isInternalChange.current && diagram !== lastInternalDiagram.current;
4545
+ if (isExternal) {
4546
+ setLocalDiagram(diagram);
4547
+ if (!isUndoRedoAction.current) {
4548
+ historyStack.current = [];
4549
+ redoStack.current = [];
4550
+ }
4551
+ }
4552
+ isInternalChange.current = false;
3248
4553
  }, [diagram]);
3249
4554
  const {
3250
4555
  viewBox: controlledViewBox,
3251
4556
  svgRef,
3252
- screenToWorld
4557
+ screenToWorld,
4558
+ fitToContent: fitToContentViewBox,
4559
+ setViewBox
3253
4560
  } = useViewBox({
3254
4561
  initialViewBox: diagram.viewBox,
3255
4562
  enablePan: features.pan ?? true,
3256
4563
  enableZoom: features.zoom ?? true
3257
4564
  });
4565
+ const calculateContentBounds = useCallback7(() => {
4566
+ const nodes2 = localDiagram.nodes.filter((n) => n.visible !== false);
4567
+ if (nodes2.length === 0) {
4568
+ return null;
4569
+ }
4570
+ let minX = Infinity;
4571
+ let minY = Infinity;
4572
+ let maxX = -Infinity;
4573
+ let maxY = -Infinity;
4574
+ nodes2.forEach((node) => {
4575
+ const symbol = getSymbolDefinition(node.symbolId);
4576
+ if (!symbol) return;
4577
+ const nodeMinX = node.transform.x;
4578
+ const nodeMinY = node.transform.y;
4579
+ const nodeMaxX = node.transform.x + symbol.viewBox.width;
4580
+ const nodeMaxY = node.transform.y + symbol.viewBox.height;
4581
+ minX = Math.min(minX, nodeMinX);
4582
+ minY = Math.min(minY, nodeMinY);
4583
+ maxX = Math.max(maxX, nodeMaxX);
4584
+ maxY = Math.max(maxY, nodeMaxY);
4585
+ });
4586
+ return { minX, minY, maxX, maxY };
4587
+ }, [localDiagram.nodes]);
4588
+ useEffect10(() => {
4589
+ if (onMounted) {
4590
+ const controls = {
4591
+ fitToContent: (padding = 50) => {
4592
+ const bounds = calculateContentBounds();
4593
+ if (bounds) {
4594
+ fitToContentViewBox(bounds, padding);
4595
+ }
4596
+ },
4597
+ setZoom: (zoomLevel) => {
4598
+ const centerX = controlledViewBox.x + controlledViewBox.width / 2;
4599
+ const centerY = controlledViewBox.y + controlledViewBox.height / 2;
4600
+ const newWidth = diagram.viewBox.width / zoomLevel;
4601
+ const newHeight = diagram.viewBox.height / zoomLevel;
4602
+ setViewBox({
4603
+ x: centerX - newWidth / 2,
4604
+ y: centerY - newHeight / 2,
4605
+ width: newWidth,
4606
+ height: newHeight
4607
+ });
4608
+ },
4609
+ getZoom: () => {
4610
+ return diagram.viewBox.width / controlledViewBox.width;
4611
+ }
4612
+ };
4613
+ onMounted(controls);
4614
+ }
4615
+ }, [
4616
+ onMounted,
4617
+ calculateContentBounds,
4618
+ fitToContentViewBox,
4619
+ setViewBox,
4620
+ controlledViewBox,
4621
+ diagram.viewBox
4622
+ ]);
3258
4623
  const { selection, selectNode, selectPipe, clearSelection } = useSelection({
3259
4624
  multiSelect: features.multiSelect ?? true,
3260
4625
  onSelectionChange
3261
4626
  });
3262
4627
  const dragEnabled = features.dragNodes ?? false;
3263
4628
  const selectionEnabled = features.selection ?? false;
4629
+ const containerRef = useRef5(null);
4630
+ const pushToHistory = useCallback7((currentDiagram) => {
4631
+ if (isUndoRedoAction.current) return;
4632
+ const snapshot = JSON.parse(JSON.stringify(currentDiagram));
4633
+ historyStack.current.push(snapshot);
4634
+ if (historyStack.current.length > 15) {
4635
+ historyStack.current.shift();
4636
+ }
4637
+ redoStack.current = [];
4638
+ }, []);
4639
+ const undo = useCallback7(() => {
4640
+ if (historyStack.current.length === 0) return;
4641
+ isUndoRedoAction.current = true;
4642
+ const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
4643
+ redoStack.current.push(currentSnapshot);
4644
+ const previousState = historyStack.current.pop();
4645
+ isInternalChange.current = true;
4646
+ lastInternalDiagram.current = previousState;
4647
+ setLocalDiagram(previousState);
4648
+ onDiagramChange?.(previousState);
4649
+ setTimeout(() => {
4650
+ isUndoRedoAction.current = false;
4651
+ }, 0);
4652
+ }, [localDiagram, onDiagramChange]);
4653
+ const redo = useCallback7(() => {
4654
+ if (redoStack.current.length === 0) return;
4655
+ isUndoRedoAction.current = true;
4656
+ const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
4657
+ historyStack.current.push(currentSnapshot);
4658
+ const nextState = redoStack.current.pop();
4659
+ isInternalChange.current = true;
4660
+ lastInternalDiagram.current = nextState;
4661
+ setLocalDiagram(nextState);
4662
+ onDiagramChange?.(nextState);
4663
+ setTimeout(() => {
4664
+ isUndoRedoAction.current = false;
4665
+ }, 0);
4666
+ }, [localDiagram, onDiagramChange]);
4667
+ useEffect10(() => {
4668
+ const handleKeyDown = (e) => {
4669
+ const isCtrlOrCmd = e.ctrlKey || e.metaKey;
4670
+ if (isCtrlOrCmd && e.key === "z" && !e.shiftKey) {
4671
+ e.preventDefault();
4672
+ undo();
4673
+ } else if (isCtrlOrCmd && e.key === "z" && e.shiftKey) {
4674
+ e.preventDefault();
4675
+ redo();
4676
+ } else if (isCtrlOrCmd && e.key === "y") {
4677
+ e.preventDefault();
4678
+ redo();
4679
+ }
4680
+ };
4681
+ window.addEventListener("keydown", handleKeyDown);
4682
+ return () => window.removeEventListener("keydown", handleKeyDown);
4683
+ }, [undo, redo]);
4684
+ useEffect10(() => {
4685
+ if (!features.dragNodes) return;
4686
+ const handleKeyDown = (e) => {
4687
+ if (selection.selectedNodeIds.size === 0 && !selectedWaypoint) return;
4688
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
4689
+ return;
4690
+ }
4691
+ const nudgeAmount = e.shiftKey ? 10 : 1;
4692
+ let dx = 0;
4693
+ let dy = 0;
4694
+ switch (e.key) {
4695
+ case "ArrowLeft":
4696
+ dx = -nudgeAmount;
4697
+ e.preventDefault();
4698
+ break;
4699
+ case "ArrowRight":
4700
+ dx = nudgeAmount;
4701
+ e.preventDefault();
4702
+ break;
4703
+ case "ArrowUp":
4704
+ dy = -nudgeAmount;
4705
+ e.preventDefault();
4706
+ break;
4707
+ case "ArrowDown":
4708
+ dy = nudgeAmount;
4709
+ e.preventDefault();
4710
+ break;
4711
+ default:
4712
+ return;
4713
+ }
4714
+ if (selectedWaypoint) {
4715
+ pushToHistory(localDiagram);
4716
+ const updatedPipes = localDiagram.pipes.map((pipe) => {
4717
+ if (pipe.id === selectedWaypoint.pipeId) {
4718
+ const updatedRoutePoints = pipe.routePoints.map(
4719
+ (point, idx) => idx === selectedWaypoint.pointIndex ? { x: point.x + dx, y: point.y + dy } : point
4720
+ );
4721
+ return { ...pipe, routePoints: updatedRoutePoints };
4722
+ }
4723
+ return pipe;
4724
+ });
4725
+ const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4726
+ isInternalChange.current = true;
4727
+ lastInternalDiagram.current = updatedDiagram;
4728
+ setLocalDiagram(updatedDiagram);
4729
+ onDiagramChange?.(updatedDiagram);
4730
+ return;
4731
+ }
4732
+ if (selectionEnabled && selection.selectedNodeIds.size > 0) {
4733
+ pushToHistory(localDiagram);
4734
+ const updatedNodes = localDiagram.nodes.map(
4735
+ (node) => selection.selectedNodeIds.has(node.id) ? {
4736
+ ...node,
4737
+ transform: {
4738
+ ...node.transform,
4739
+ x: node.transform.x + dx,
4740
+ y: node.transform.y + dy
4741
+ }
4742
+ } : node
4743
+ );
4744
+ const updatedDiagram = { ...localDiagram, nodes: updatedNodes };
4745
+ isInternalChange.current = true;
4746
+ lastInternalDiagram.current = updatedDiagram;
4747
+ setLocalDiagram(updatedDiagram);
4748
+ onDiagramChange?.(updatedDiagram);
4749
+ selection.selectedNodeIds.forEach((nodeId) => {
4750
+ const node = updatedNodes.find((n) => n.id === nodeId);
4751
+ if (node) {
4752
+ onNodeMove?.(nodeId, node.transform.x, node.transform.y);
4753
+ }
4754
+ });
4755
+ }
4756
+ };
4757
+ window.addEventListener("keydown", handleKeyDown);
4758
+ return () => window.removeEventListener("keydown", handleKeyDown);
4759
+ }, [
4760
+ selectionEnabled,
4761
+ features.dragNodes,
4762
+ selection.selectedNodeIds,
4763
+ selectedWaypoint,
4764
+ localDiagram,
4765
+ onDiagramChange,
4766
+ onNodeMove,
4767
+ pushToHistory
4768
+ ]);
4769
+ const handlePositionChange = useCallback7(
4770
+ (nodeId, x, y) => {
4771
+ pushToHistory(localDiagram);
4772
+ const updatedNodes = localDiagram.nodes.map(
4773
+ (node) => node.id === nodeId ? {
4774
+ ...node,
4775
+ transform: {
4776
+ ...node.transform,
4777
+ x,
4778
+ y
4779
+ }
4780
+ } : node
4781
+ );
4782
+ const updatedDiagram = { ...localDiagram, nodes: updatedNodes };
4783
+ isInternalChange.current = true;
4784
+ lastInternalDiagram.current = updatedDiagram;
4785
+ setLocalDiagram(updatedDiagram);
4786
+ onDiagramChange?.(updatedDiagram);
4787
+ onNodeMove?.(nodeId, x, y);
4788
+ },
4789
+ [localDiagram, onDiagramChange, onNodeMove, pushToHistory]
4790
+ );
4791
+ const handleWaypointPositionChange = useCallback7(
4792
+ (pipeId, pointIndex, x, y) => {
4793
+ pushToHistory(localDiagram);
4794
+ const updatedPipes = localDiagram.pipes.map((pipe) => {
4795
+ if (pipe.id === pipeId) {
4796
+ const updatedRoutePoints = pipe.routePoints.map(
4797
+ (point, idx) => idx === pointIndex ? { x, y } : point
4798
+ );
4799
+ return { ...pipe, routePoints: updatedRoutePoints };
4800
+ }
4801
+ return pipe;
4802
+ });
4803
+ const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4804
+ setLocalDiagram(updatedDiagram);
4805
+ onDiagramChange?.(updatedDiagram);
4806
+ },
4807
+ [localDiagram, onDiagramChange, pushToHistory]
4808
+ );
3264
4809
  const handleDragEnd = useCallback7(
3265
4810
  (nodeId, offset, finalPosition) => {
3266
4811
  const dragDistance = Math.sqrt(offset.x ** 2 + offset.y ** 2);
@@ -3270,6 +4815,7 @@ function PIDCanvas({
3270
4815
  }
3271
4816
  return;
3272
4817
  }
4818
+ pushToHistory(localDiagram);
3273
4819
  const updatedNodes = localDiagram.nodes.map(
3274
4820
  (node) => node.id === nodeId ? {
3275
4821
  ...node,
@@ -3307,11 +4853,13 @@ function PIDCanvas({
3307
4853
  nodes: updatedNodes,
3308
4854
  pipes: updatedPipes
3309
4855
  };
4856
+ isInternalChange.current = true;
4857
+ lastInternalDiagram.current = updatedDiagram;
3310
4858
  setLocalDiagram(updatedDiagram);
3311
4859
  onDiagramChange?.(updatedDiagram);
3312
4860
  onNodeMove?.(nodeId, finalPosition.x, finalPosition.y);
3313
4861
  },
3314
- [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode]
4862
+ [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode, pushToHistory]
3315
4863
  );
3316
4864
  const { dragState, startDrag } = useDrag({
3317
4865
  snapToGrid: features.snapToGrid ?? 0,
@@ -3322,6 +4870,7 @@ function PIDCanvas({
3322
4870
  snapToGrid: features.snapToGrid ?? 0,
3323
4871
  screenToWorld,
3324
4872
  onDragEnd: (pipeId, _offset, finalPosition) => {
4873
+ pushToHistory(localDiagram);
3325
4874
  const [actualPipeId, pointIndex] = pipeId.split(":");
3326
4875
  const idx = parseInt(pointIndex, 10);
3327
4876
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -3332,6 +4881,8 @@ function PIDCanvas({
3332
4881
  return { ...pipe, routePoints: updatedRoutePoints };
3333
4882
  });
3334
4883
  const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4884
+ isInternalChange.current = true;
4885
+ lastInternalDiagram.current = updatedDiagram;
3335
4886
  setLocalDiagram(updatedDiagram);
3336
4887
  onDiagramChange?.(updatedDiagram);
3337
4888
  }
@@ -3344,6 +4895,15 @@ function PIDCanvas({
3344
4895
  },
3345
4896
  [localDiagram.nodes, startDrag]
3346
4897
  );
4898
+ const handleWaypointClick = useCallback7(
4899
+ (pipeId, pointIndex) => {
4900
+ setSelectedWaypoint({ pipeId, pointIndex });
4901
+ if (selectionEnabled) {
4902
+ clearSelection();
4903
+ }
4904
+ },
4905
+ [selectionEnabled, clearSelection]
4906
+ );
3347
4907
  const handleWaypointDragStart = useCallback7(
3348
4908
  (pipeId, pointIndex, event) => {
3349
4909
  const pipe = localDiagram.pipes.find((p) => p.id === pipeId);
@@ -3355,6 +4915,7 @@ function PIDCanvas({
3355
4915
  );
3356
4916
  const handlePipeSegmentClick = useCallback7(
3357
4917
  (pipeId, position, segmentIndex) => {
4918
+ pushToHistory(localDiagram);
3358
4919
  const updatedPipes = localDiagram.pipes.map((pipe) => {
3359
4920
  if (pipe.id !== pipeId) return pipe;
3360
4921
  const updatedRoutePoints = [
@@ -3368,13 +4929,14 @@ function PIDCanvas({
3368
4929
  setLocalDiagram(updatedDiagram);
3369
4930
  onDiagramChange?.(updatedDiagram);
3370
4931
  },
3371
- [localDiagram, onDiagramChange]
4932
+ [localDiagram, onDiagramChange, pushToHistory]
3372
4933
  );
3373
4934
  const telemetryEnabled = features.telemetry ?? false;
3374
4935
  const handleDelete = useCallback7(() => {
3375
4936
  if (!selectionEnabled) return;
3376
4937
  const { selectedNodeIds, selectedPipeIds } = selection;
3377
4938
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
4939
+ pushToHistory(localDiagram);
3378
4940
  const updatedNodes = localDiagram.nodes.filter((node) => !selectedNodeIds.has(node.id));
3379
4941
  const updatedPipes = localDiagram.pipes.filter(
3380
4942
  (pipe) => !selectedPipeIds.has(pipe.id) && !selectedNodeIds.has(pipe.fromNodeId) && !selectedNodeIds.has(pipe.toNodeId)
@@ -3384,11 +4946,21 @@ function PIDCanvas({
3384
4946
  nodes: updatedNodes,
3385
4947
  pipes: updatedPipes
3386
4948
  };
4949
+ isInternalChange.current = true;
4950
+ lastInternalDiagram.current = updatedDiagram;
3387
4951
  setLocalDiagram(updatedDiagram);
3388
4952
  clearSelection();
3389
4953
  onDiagramChange?.(updatedDiagram);
3390
4954
  onDelete?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3391
- }, [selection, localDiagram, selectionEnabled, clearSelection, onDiagramChange, onDelete]);
4955
+ }, [
4956
+ selection,
4957
+ localDiagram,
4958
+ selectionEnabled,
4959
+ clearSelection,
4960
+ onDiagramChange,
4961
+ onDelete,
4962
+ pushToHistory
4963
+ ]);
3392
4964
  const handleCopy = useCallback7(() => {
3393
4965
  if (!selectionEnabled) return;
3394
4966
  const { selectedNodeIds, selectedPipeIds } = selection;
@@ -3445,10 +5017,15 @@ function PIDCanvas({
3445
5017
  }
3446
5018
  onPipeClick?.(pipeId);
3447
5019
  };
3448
- const handleCanvasClick = () => {
5020
+ const handleCanvasClick = (e) => {
5021
+ const target = e.target;
5022
+ if (target.closest(".pipe-route-point")) {
5023
+ return;
5024
+ }
3449
5025
  if (selectionEnabled) {
3450
5026
  clearSelection();
3451
5027
  }
5028
+ setSelectedWaypoint(null);
3452
5029
  };
3453
5030
  const handleDragOver = useCallback7(
3454
5031
  (event) => {
@@ -3459,7 +5036,7 @@ function PIDCanvas({
3459
5036
  const worldPos = screenToWorld(event.clientX, event.clientY);
3460
5037
  setDragOverPosition(worldPos);
3461
5038
  },
3462
- [features.allowSymbolDrop, screenToWorld]
5039
+ [features.allowSymbolDrop, screenToWorld, svgRef]
3463
5040
  );
3464
5041
  const handleDrop = useCallback7(
3465
5042
  (event) => {
@@ -3472,7 +5049,7 @@ function PIDCanvas({
3472
5049
  const worldPos = screenToWorld(event.clientX, event.clientY);
3473
5050
  onSymbolDrop?.(symbolId, worldPos);
3474
5051
  },
3475
- [features.allowSymbolDrop, screenToWorld, onSymbolDrop]
5052
+ [features.allowSymbolDrop, screenToWorld, onSymbolDrop, svgRef]
3476
5053
  );
3477
5054
  const handleDragLeave = useCallback7(() => {
3478
5055
  setDragOverPosition(null);
@@ -3496,7 +5073,7 @@ function PIDCanvas({
3496
5073
  const distance = Math.sqrt(
3497
5074
  (worldPos.x - portPos.x) ** 2 + (worldPos.y - portPos.y) ** 2
3498
5075
  );
3499
- if (distance < 10) {
5076
+ if (distance < 15) {
3500
5077
  foundPort = port.id;
3501
5078
  foundNode = node.id;
3502
5079
  break;
@@ -3513,9 +5090,11 @@ function PIDCanvas({
3513
5090
  const { name } = diagram;
3514
5091
  const viewBox = controlledViewBox;
3515
5092
  const displayDiagram = localDiagram;
3516
- return /* @__PURE__ */ jsx16(
5093
+ const selectedNode = selectionEnabled && selection.selectedNodeIds.size === 1 ? localDiagram.nodes.find((n) => selection.selectedNodeIds.has(n.id)) || null : null;
5094
+ return /* @__PURE__ */ jsxs13(
3517
5095
  "div",
3518
5096
  {
5097
+ ref: containerRef,
3519
5098
  className: `pid-canvas ${className}`.trim(),
3520
5099
  style: {
3521
5100
  width: "100%",
@@ -3526,223 +5105,287 @@ function PIDCanvas({
3526
5105
  ...style
3527
5106
  },
3528
5107
  ...props,
3529
- children: /* @__PURE__ */ jsxs12(
3530
- "svg",
3531
- {
3532
- ref: svgRef,
3533
- width: "100%",
3534
- height: "100%",
3535
- viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
3536
- preserveAspectRatio: "xMidYMid meet",
3537
- style: {
3538
- display: "block"
3539
- },
3540
- "aria-label": `P&ID diagram: ${name}`,
3541
- role: "img",
3542
- onClick: handleCanvasClick,
3543
- onMouseMove: handleMouseMove,
3544
- onDragOver: handleDragOver,
3545
- onDrop: handleDrop,
3546
- onDragLeave: handleDragLeave,
3547
- children: [
3548
- features.showGrid !== false ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
3549
- /* @__PURE__ */ jsxs12("defs", { children: [
3550
- /* @__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" }) }),
3551
- /* @__PURE__ */ jsxs12("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
3552
- /* @__PURE__ */ jsx16("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
3553
- /* @__PURE__ */ jsx16("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
3554
- ] })
3555
- ] }),
3556
- /* @__PURE__ */ jsx16(
5108
+ children: [
5109
+ /* @__PURE__ */ jsxs13(
5110
+ "svg",
5111
+ {
5112
+ ref: svgRef,
5113
+ width: "100%",
5114
+ height: "100%",
5115
+ viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
5116
+ preserveAspectRatio: "xMidYMid meet",
5117
+ style: {
5118
+ display: "block"
5119
+ },
5120
+ "aria-label": `P&ID diagram: ${name}`,
5121
+ role: "img",
5122
+ onClick: handleCanvasClick,
5123
+ onMouseMove: handleMouseMove,
5124
+ onDragOver: handleDragOver,
5125
+ onDrop: handleDrop,
5126
+ onDragLeave: handleDragLeave,
5127
+ children: [
5128
+ features.showGrid !== false ? /* @__PURE__ */ jsxs13(Fragment5, { children: [
5129
+ /* @__PURE__ */ jsxs13("defs", { children: [
5130
+ /* @__PURE__ */ jsx17("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx17("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
5131
+ /* @__PURE__ */ jsxs13("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
5132
+ /* @__PURE__ */ jsx17("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
5133
+ /* @__PURE__ */ jsx17("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
5134
+ ] })
5135
+ ] }),
5136
+ /* @__PURE__ */ jsx17(
5137
+ "rect",
5138
+ {
5139
+ x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
5140
+ y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
5141
+ width: Math.ceil(viewBox.width * 3 / 25) * 25,
5142
+ height: Math.ceil(viewBox.height * 3 / 25) * 25,
5143
+ fill: "url(#grid-major)"
5144
+ }
5145
+ )
5146
+ ] }) : /* @__PURE__ */ jsx17(
3557
5147
  "rect",
3558
5148
  {
3559
5149
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3560
5150
  y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3561
5151
  width: Math.ceil(viewBox.width * 3 / 25) * 25,
3562
5152
  height: Math.ceil(viewBox.height * 3 / 25) * 25,
3563
- fill: "url(#grid-major)"
5153
+ fill: features.backgroundColor ?? "#ffffff"
3564
5154
  }
3565
- )
3566
- ] }) : /* @__PURE__ */ jsx16(
3567
- "rect",
3568
- {
3569
- x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3570
- y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3571
- width: Math.ceil(viewBox.width * 3 / 25) * 25,
3572
- height: Math.ceil(viewBox.height * 3 / 25) * 25,
3573
- fill: features.backgroundColor ?? "#ffffff"
3574
- }
3575
- ),
3576
- /* @__PURE__ */ jsx16(
3577
- PipeRenderer,
3578
- {
3579
- pipes: displayDiagram.pipes,
3580
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3581
- onPipeClick: selectionEnabled ? handlePipeClick : void 0,
3582
- enableWaypointEditing: false,
3583
- onWaypointDragStart: void 0,
3584
- editingPipeId: null,
3585
- draggingWaypointIndex: null,
3586
- waypointDragOffset: void 0,
3587
- onPipeSegmentClick: void 0
3588
- }
3589
- ),
3590
- /* @__PURE__ */ jsx16(
3591
- NodeRenderer,
3592
- {
3593
- nodes: displayDiagram.nodes,
3594
- selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
3595
- onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
3596
- onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
3597
- enableDrag: dragEnabled && !isConnecting,
3598
- onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
3599
- draggingNodeId: dragState.draggedId,
3600
- dragOffset: dragState.offset
3601
- }
3602
- ),
3603
- (features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx16(
3604
- PipeRenderer,
3605
- {
3606
- pipes: displayDiagram.pipes,
3607
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3608
- onPipeClick: void 0,
3609
- enableWaypointEditing: true,
3610
- onWaypointDragStart: handleWaypointDragStart,
3611
- editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
3612
- draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
3613
- waypointDragOffset: waypointDragState.offset,
3614
- onPipeSegmentClick: handlePipeSegmentClick
3615
- }
3616
- ),
3617
- telemetryEnabled && telemetry && /* @__PURE__ */ jsx16("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
3618
- const binding = telemetry.get(node.id);
3619
- if (!binding) return null;
3620
- const symbol = getSymbolDefinition(node.symbolId);
3621
- if (!symbol) return null;
3622
- const overlayY = node.transform.y - symbol.viewBox.height / 2 - 10;
3623
- return /* @__PURE__ */ jsx16(
3624
- DataOverlay,
5155
+ ),
5156
+ /* @__PURE__ */ jsx17(
5157
+ PipeRenderer,
3625
5158
  {
3626
- nodeId: node.id,
3627
- x: node.transform.x,
3628
- y: overlayY,
3629
- telemetry: binding
3630
- },
3631
- `telemetry-${node.id}`
3632
- );
3633
- }) }),
3634
- dragOverPosition && /* @__PURE__ */ jsx16(
3635
- "circle",
3636
- {
3637
- cx: dragOverPosition.x,
3638
- cy: dragOverPosition.y,
3639
- r: 20,
3640
- fill: "#3b82f6",
3641
- opacity: 0.3,
3642
- stroke: "#3b82f6",
3643
- strokeWidth: 2,
3644
- pointerEvents: "none"
3645
- }
3646
- ),
3647
- isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ jsx16("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
3648
- const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
3649
- if (!sourceNode) return null;
3650
- const symbol = getSymbolDefinition(sourceNode.symbolId);
3651
- let lineStartX = sourceNode.transform.x;
3652
- let lineStartY = sourceNode.transform.y;
3653
- if (connectionSourcePort && symbol?.ports) {
3654
- const port = symbol.ports.find((p) => p.id === connectionSourcePort);
3655
- if (port) {
3656
- const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
3657
- if (portPos) {
3658
- lineStartX = portPos.x;
3659
- lineStartY = portPos.y;
3660
- }
5159
+ pipes: displayDiagram.pipes,
5160
+ selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
5161
+ onPipeClick: selectionEnabled ? handlePipeClick : void 0,
5162
+ enableWaypointEditing: false,
5163
+ onWaypointDragStart: void 0,
5164
+ editingPipeId: null,
5165
+ draggingWaypointIndex: null,
5166
+ waypointDragOffset: void 0,
5167
+ onPipeSegmentClick: void 0
3661
5168
  }
3662
- }
3663
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
3664
- /* @__PURE__ */ jsx16(
3665
- "circle",
3666
- {
3667
- cx: sourceNode.transform.x,
3668
- cy: sourceNode.transform.y,
3669
- r: 30,
3670
- fill: "none",
3671
- stroke: "#10b981",
3672
- strokeWidth: 3,
3673
- opacity: 0.6
3674
- }
3675
- ),
3676
- /* @__PURE__ */ jsx16(
3677
- "line",
3678
- {
3679
- x1: lineStartX,
3680
- y1: lineStartY,
3681
- x2: connectionCursor.x,
3682
- y2: connectionCursor.y,
3683
- stroke: "#10b981",
3684
- strokeWidth: 2,
3685
- strokeDasharray: "5,5",
3686
- opacity: 0.8
3687
- }
3688
- ),
3689
- /* @__PURE__ */ jsx16(
3690
- "circle",
3691
- {
3692
- cx: connectionCursor.x,
3693
- cy: connectionCursor.y,
3694
- r: 8,
3695
- fill: "#10b981",
3696
- opacity: 0.6
3697
- }
3698
- )
3699
- ] });
3700
- })() }),
3701
- features.connectionMode && /* @__PURE__ */ jsx16("g", { id: "port-indicators", pointerEvents: "none", children: displayDiagram.nodes.map((node) => {
3702
- const symbol = getSymbolDefinition(node.symbolId);
3703
- if (!symbol?.ports) return null;
3704
- return symbol.ports.map((port) => {
3705
- const portPos = getPortWorldPosition(node, port.id);
3706
- if (!portPos) return null;
3707
- const isHovered = hoveredPort === port.id && hoveredNode === node.id;
3708
- const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
3709
- return /* @__PURE__ */ jsx16(
3710
- "circle",
5169
+ ),
5170
+ /* @__PURE__ */ jsx17(
5171
+ NodeRenderer,
5172
+ {
5173
+ nodes: displayDiagram.nodes,
5174
+ selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
5175
+ onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
5176
+ onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
5177
+ enableDrag: dragEnabled && !isConnecting,
5178
+ onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
5179
+ draggingNodeId: dragState.draggedId,
5180
+ dragOffset: dragState.offset
5181
+ }
5182
+ ),
5183
+ (features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx17(
5184
+ PipeRenderer,
5185
+ {
5186
+ pipes: displayDiagram.pipes,
5187
+ selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
5188
+ onPipeClick: void 0,
5189
+ enableWaypointEditing: true,
5190
+ onWaypointClick: handleWaypointClick,
5191
+ onWaypointDragStart: handleWaypointDragStart,
5192
+ selectedWaypointPipeId: selectedWaypoint?.pipeId || null,
5193
+ selectedWaypointIndex: selectedWaypoint?.pointIndex ?? null,
5194
+ editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
5195
+ draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
5196
+ waypointDragOffset: waypointDragState.offset,
5197
+ onPipeSegmentClick: handlePipeSegmentClick
5198
+ }
5199
+ ),
5200
+ telemetryEnabled && telemetry && /* @__PURE__ */ jsx17("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
5201
+ const binding = telemetry.get(node.id);
5202
+ if (!binding) return null;
5203
+ const symbol = getSymbolDefinition(node.symbolId);
5204
+ if (!symbol) return null;
5205
+ const overlayX = node.transform.x + symbol.viewBox.width / 2;
5206
+ const overlayY = node.transform.y + symbol.viewBox.height + 50;
5207
+ return /* @__PURE__ */ jsx17(
5208
+ DataOverlay,
3711
5209
  {
3712
- cx: portPos.x,
3713
- cy: portPos.y,
3714
- r: isHovered ? 8 : 5,
3715
- fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
3716
- stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
3717
- strokeWidth: isHovered || isSourcePort ? 2 : 1,
3718
- opacity: isHovered || isSourcePort ? 1 : 0.6
5210
+ nodeId: node.id,
5211
+ x: overlayX,
5212
+ y: overlayY,
5213
+ telemetry: binding,
5214
+ scale: features.telemetryScale ?? 1
3719
5215
  },
3720
- `${node.id}-${port.id}`
5216
+ `telemetry-${node.id}`
3721
5217
  );
3722
- });
3723
- }) })
3724
- ]
3725
- }
3726
- )
5218
+ }) }),
5219
+ dragOverPosition && /* @__PURE__ */ jsx17(
5220
+ "circle",
5221
+ {
5222
+ cx: dragOverPosition.x,
5223
+ cy: dragOverPosition.y,
5224
+ r: 20,
5225
+ fill: "#3b82f6",
5226
+ opacity: 0.3,
5227
+ stroke: "#3b82f6",
5228
+ strokeWidth: 2,
5229
+ pointerEvents: "none"
5230
+ }
5231
+ ),
5232
+ isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ jsx17("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
5233
+ const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
5234
+ if (!sourceNode) return null;
5235
+ const symbol = getSymbolDefinition(sourceNode.symbolId);
5236
+ let lineStartX = sourceNode.transform.x;
5237
+ let lineStartY = sourceNode.transform.y;
5238
+ if (connectionSourcePort && symbol?.ports) {
5239
+ const port = symbol.ports.find((p) => p.id === connectionSourcePort);
5240
+ if (port) {
5241
+ const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
5242
+ if (portPos) {
5243
+ lineStartX = portPos.x;
5244
+ lineStartY = portPos.y;
5245
+ }
5246
+ }
5247
+ }
5248
+ return /* @__PURE__ */ jsxs13(Fragment5, { children: [
5249
+ /* @__PURE__ */ jsx17(
5250
+ "circle",
5251
+ {
5252
+ cx: lineStartX,
5253
+ cy: lineStartY,
5254
+ r: 30,
5255
+ fill: "none",
5256
+ stroke: "#10b981",
5257
+ strokeWidth: 3,
5258
+ opacity: 0.6
5259
+ }
5260
+ ),
5261
+ /* @__PURE__ */ jsx17(
5262
+ "line",
5263
+ {
5264
+ x1: lineStartX,
5265
+ y1: lineStartY,
5266
+ x2: connectionCursor.x,
5267
+ y2: connectionCursor.y,
5268
+ stroke: "#10b981",
5269
+ strokeWidth: 2,
5270
+ strokeDasharray: "5,5",
5271
+ opacity: 0.8
5272
+ }
5273
+ ),
5274
+ /* @__PURE__ */ jsx17(
5275
+ "circle",
5276
+ {
5277
+ cx: connectionCursor.x,
5278
+ cy: connectionCursor.y,
5279
+ r: 8,
5280
+ fill: "#10b981",
5281
+ opacity: 0.6
5282
+ }
5283
+ )
5284
+ ] });
5285
+ })() }),
5286
+ features.connectionMode && /* @__PURE__ */ jsx17("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
5287
+ const symbol = getSymbolDefinition(node.symbolId);
5288
+ if (!symbol?.ports) return null;
5289
+ return symbol.ports.map((port) => {
5290
+ const portPos = getPortWorldPosition(node, port.id);
5291
+ if (!portPos) return null;
5292
+ const isHovered = hoveredPort === port.id && hoveredNode === node.id;
5293
+ const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
5294
+ return /* @__PURE__ */ jsxs13("g", { children: [
5295
+ /* @__PURE__ */ jsx17(
5296
+ "circle",
5297
+ {
5298
+ cx: portPos.x,
5299
+ cy: portPos.y,
5300
+ r: 15,
5301
+ fill: "transparent",
5302
+ style: { cursor: "pointer" },
5303
+ pointerEvents: "auto",
5304
+ onClick: (e) => {
5305
+ e.stopPropagation();
5306
+ if (isConnecting && connectionSource) {
5307
+ if (connectionSource !== node.id) {
5308
+ onPipeCreated?.(
5309
+ connectionSource,
5310
+ node.id,
5311
+ connectionSourcePort || void 0,
5312
+ port.id
5313
+ );
5314
+ setIsConnecting(false);
5315
+ setConnectionSource(null);
5316
+ setConnectionSourcePort(null);
5317
+ setConnectionCursor(null);
5318
+ setHoveredPort(null);
5319
+ }
5320
+ } else {
5321
+ setIsConnecting(true);
5322
+ setConnectionSource(node.id);
5323
+ setConnectionSourcePort(port.id);
5324
+ }
5325
+ }
5326
+ }
5327
+ ),
5328
+ /* @__PURE__ */ jsx17(
5329
+ "circle",
5330
+ {
5331
+ cx: portPos.x,
5332
+ cy: portPos.y,
5333
+ r: isHovered ? 8 : 5,
5334
+ fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
5335
+ stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
5336
+ strokeWidth: isHovered || isSourcePort ? 2 : 1,
5337
+ opacity: isHovered || isSourcePort ? 1 : 0.6,
5338
+ pointerEvents: "none"
5339
+ }
5340
+ )
5341
+ ] }, `${node.id}-${port.id}`);
5342
+ });
5343
+ }) })
5344
+ ]
5345
+ }
5346
+ ),
5347
+ features.dragNodes && /* @__PURE__ */ jsx17(
5348
+ PositionPanel,
5349
+ {
5350
+ selectedNode,
5351
+ selectedWaypoint: selectedWaypoint ? {
5352
+ pipeId: selectedWaypoint.pipeId,
5353
+ pointIndex: selectedWaypoint.pointIndex,
5354
+ position: localDiagram.pipes.find((p) => p.id === selectedWaypoint.pipeId)?.routePoints[selectedWaypoint.pointIndex] || { x: 0, y: 0 }
5355
+ } : null,
5356
+ onPositionChange: handlePositionChange,
5357
+ onWaypointPositionChange: handleWaypointPositionChange,
5358
+ position: "top-right"
5359
+ }
5360
+ )
5361
+ ]
3727
5362
  }
3728
5363
  );
3729
5364
  }
3730
5365
 
3731
5366
  // src/components/SymbolLibrary/SymbolLibrary.tsx
3732
- import { useState as useState12 } from "react";
3733
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
5367
+ import { useState as useState13 } from "react";
5368
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
3734
5369
  function SymbolLibrary({
3735
5370
  className = "",
3736
5371
  onSymbolDragStart,
3737
5372
  showCategories = true,
3738
5373
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
3739
5374
  }) {
3740
- const [selectedCategory, setSelectedCategory] = useState12("all");
3741
- const [searchQuery, setSearchQuery] = useState12("");
5375
+ const [selectedCategory, setSelectedCategory] = useState13("all");
5376
+ const [searchQuery, setSearchQuery] = useState13("");
3742
5377
  const symbolIds = getAvailableSymbols();
3743
5378
  const allSymbols = symbolIds.map((id) => getSymbolDefinition(id)).filter((symbol) => symbol !== void 0);
5379
+ const categoryMap = {
5380
+ Valves: "valve",
5381
+ Pumps: "pump",
5382
+ Tanks: "vessel",
5383
+ Instruments: "instrument",
5384
+ Displays: "display"
5385
+ };
3744
5386
  const filteredSymbols = allSymbols.filter((symbol) => {
3745
- const matchesCategory = selectedCategory === "all" || symbol.category === selectedCategory;
5387
+ const actualCategory = categoryMap[selectedCategory] || selectedCategory;
5388
+ const matchesCategory = selectedCategory === "all" || symbol.category === actualCategory;
3746
5389
  const matchesSearch = searchQuery === "" || symbol.name.toLowerCase().includes(searchQuery.toLowerCase()) || symbol.id.toLowerCase().includes(searchQuery.toLowerCase());
3747
5390
  return matchesCategory && matchesSearch;
3748
5391
  });
@@ -3751,9 +5394,9 @@ function SymbolLibrary({
3751
5394
  event.dataTransfer.effectAllowed = "copy";
3752
5395
  onSymbolDragStart?.(symbol.id, event);
3753
5396
  };
3754
- return /* @__PURE__ */ jsxs13("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
3755
- /* @__PURE__ */ jsx17("div", { style: styles.header, children: /* @__PURE__ */ jsx17("h3", { style: styles.title, children: "Symbol Library" }) }),
3756
- /* @__PURE__ */ jsx17("div", { style: styles.searchContainer, children: /* @__PURE__ */ jsx17(
5397
+ return /* @__PURE__ */ jsxs14("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
5398
+ /* @__PURE__ */ jsx18("div", { style: styles.header, children: /* @__PURE__ */ jsx18("h3", { style: styles.title, children: "Symbol Library" }) }),
5399
+ /* @__PURE__ */ jsx18("div", { style: styles.searchContainer, children: /* @__PURE__ */ jsx18(
3757
5400
  "input",
3758
5401
  {
3759
5402
  type: "text",
@@ -3763,8 +5406,8 @@ function SymbolLibrary({
3763
5406
  style: styles.searchInput
3764
5407
  }
3765
5408
  ) }),
3766
- showCategories && /* @__PURE__ */ jsxs13("div", { style: styles.categories, children: [
3767
- /* @__PURE__ */ jsx17(
5409
+ showCategories && /* @__PURE__ */ jsxs14("div", { style: styles.categories, children: [
5410
+ /* @__PURE__ */ jsx18(
3768
5411
  "button",
3769
5412
  {
3770
5413
  onClick: () => setSelectedCategory("all"),
@@ -3775,7 +5418,7 @@ function SymbolLibrary({
3775
5418
  children: "All"
3776
5419
  }
3777
5420
  ),
3778
- categories.map((category) => /* @__PURE__ */ jsx17(
5421
+ categories.map((category) => /* @__PURE__ */ jsx18(
3779
5422
  "button",
3780
5423
  {
3781
5424
  onClick: () => setSelectedCategory(category),
@@ -3788,7 +5431,7 @@ function SymbolLibrary({
3788
5431
  category
3789
5432
  ))
3790
5433
  ] }),
3791
- /* @__PURE__ */ jsx17("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ jsx17("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ jsxs13(
5434
+ /* @__PURE__ */ jsx18("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ jsx18("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ jsxs14(
3792
5435
  "div",
3793
5436
  {
3794
5437
  draggable: true,
@@ -3796,15 +5439,15 @@ function SymbolLibrary({
3796
5439
  style: styles.symbolCard,
3797
5440
  title: symbol.metadata?.description || symbol.name,
3798
5441
  children: [
3799
- /* @__PURE__ */ jsx17("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx17(
5442
+ /* @__PURE__ */ jsx18("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx18(
3800
5443
  "svg",
3801
5444
  {
3802
5445
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
3803
5446
  style: styles.symbolSvg,
3804
- children: /* @__PURE__ */ jsx17("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
5447
+ children: /* @__PURE__ */ jsx18("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
3805
5448
  }
3806
5449
  ) }),
3807
- /* @__PURE__ */ jsx17("div", { style: styles.symbolName, children: symbol.name })
5450
+ /* @__PURE__ */ jsx18("div", { style: styles.symbolName, children: symbol.name })
3808
5451
  ]
3809
5452
  },
3810
5453
  symbol.id
@@ -3913,7 +5556,7 @@ var styles = {
3913
5556
  };
3914
5557
 
3915
5558
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
3916
- import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
5559
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
3917
5560
  function NodeConfigPanel({
3918
5561
  node,
3919
5562
  binding,
@@ -3932,15 +5575,15 @@ function NodeConfigPanel({
3932
5575
  padding: "20px",
3933
5576
  ...style
3934
5577
  };
3935
- return /* @__PURE__ */ jsxs14("div", { className, style: defaultStyle, children: [
3936
- /* @__PURE__ */ jsxs14("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
5578
+ return /* @__PURE__ */ jsxs15("div", { className, style: defaultStyle, children: [
5579
+ /* @__PURE__ */ jsxs15("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
3937
5580
  "Configure: ",
3938
5581
  node.id
3939
5582
  ] }),
3940
- /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
3941
- /* @__PURE__ */ jsx18("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
3942
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
3943
- /* @__PURE__ */ jsx18(
5583
+ /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
5584
+ /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
5585
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
5586
+ /* @__PURE__ */ jsx19(
3944
5587
  "input",
3945
5588
  {
3946
5589
  type: "text",
@@ -3956,10 +5599,10 @@ function NodeConfigPanel({
3956
5599
  }
3957
5600
  }
3958
5601
  ),
3959
- /* @__PURE__ */ jsxs14("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
3960
- /* @__PURE__ */ jsxs14("div", { children: [
3961
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
3962
- /* @__PURE__ */ jsx18(
5602
+ /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5603
+ /* @__PURE__ */ jsxs15("div", { children: [
5604
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
5605
+ /* @__PURE__ */ jsx19(
3963
5606
  "input",
3964
5607
  {
3965
5608
  type: "number",
@@ -3980,9 +5623,9 @@ function NodeConfigPanel({
3980
5623
  }
3981
5624
  )
3982
5625
  ] }),
3983
- /* @__PURE__ */ jsxs14("div", { children: [
3984
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
3985
- /* @__PURE__ */ jsx18(
5626
+ /* @__PURE__ */ jsxs15("div", { children: [
5627
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
5628
+ /* @__PURE__ */ jsx19(
3986
5629
  "input",
3987
5630
  {
3988
5631
  type: "number",
@@ -4004,9 +5647,9 @@ function NodeConfigPanel({
4004
5647
  )
4005
5648
  ] })
4006
5649
  ] }),
4007
- /* @__PURE__ */ jsxs14("div", { children: [
4008
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
4009
- /* @__PURE__ */ jsx18(
5650
+ /* @__PURE__ */ jsxs15("div", { children: [
5651
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
5652
+ /* @__PURE__ */ jsx19(
4010
5653
  "input",
4011
5654
  {
4012
5655
  type: "number",
@@ -4026,9 +5669,9 @@ function NodeConfigPanel({
4026
5669
  }
4027
5670
  )
4028
5671
  ] }),
4029
- /* @__PURE__ */ jsxs14("div", { children: [
4030
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
4031
- /* @__PURE__ */ jsx18(
5672
+ /* @__PURE__ */ jsxs15("div", { children: [
5673
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
5674
+ /* @__PURE__ */ jsx19(
4032
5675
  "input",
4033
5676
  {
4034
5677
  type: "number",
@@ -4053,9 +5696,9 @@ function NodeConfigPanel({
4053
5696
  )
4054
5697
  ] })
4055
5698
  ] }),
4056
- /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4057
- /* @__PURE__ */ jsx18("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
4058
- !binding ? /* @__PURE__ */ jsx18(
5699
+ /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
5700
+ /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
5701
+ !binding ? /* @__PURE__ */ jsx19(
4059
5702
  "button",
4060
5703
  {
4061
5704
  onClick: () => {
@@ -4098,9 +5741,9 @@ function NodeConfigPanel({
4098
5741
  },
4099
5742
  children: "+ Add Telemetry"
4100
5743
  }
4101
- ) : /* @__PURE__ */ jsxs14(Fragment6, { children: [
4102
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
4103
- /* @__PURE__ */ jsx18(
5744
+ ) : /* @__PURE__ */ jsxs15(Fragment6, { children: [
5745
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
5746
+ /* @__PURE__ */ jsx19(
4104
5747
  "input",
4105
5748
  {
4106
5749
  type: "number",
@@ -4122,8 +5765,8 @@ function NodeConfigPanel({
4122
5765
  }
4123
5766
  }
4124
5767
  ),
4125
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
4126
- /* @__PURE__ */ jsx18(
5768
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
5769
+ /* @__PURE__ */ jsx19(
4127
5770
  "input",
4128
5771
  {
4129
5772
  type: "text",
@@ -4145,8 +5788,8 @@ function NodeConfigPanel({
4145
5788
  }
4146
5789
  }
4147
5790
  ),
4148
- /* @__PURE__ */ jsxs14("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4149
- /* @__PURE__ */ jsx18(
5791
+ /* @__PURE__ */ jsxs15("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
5792
+ /* @__PURE__ */ jsx19(
4150
5793
  "input",
4151
5794
  {
4152
5795
  type: "checkbox",
@@ -4168,8 +5811,8 @@ function NodeConfigPanel({
4168
5811
  ),
4169
5812
  "Show Sparkline"
4170
5813
  ] }),
4171
- /* @__PURE__ */ jsxs14("div", { style: { marginTop: "12px" }, children: [
4172
- /* @__PURE__ */ jsx18(
5814
+ /* @__PURE__ */ jsxs15("div", { style: { marginTop: "12px" }, children: [
5815
+ /* @__PURE__ */ jsx19(
4173
5816
  "label",
4174
5817
  {
4175
5818
  style: {
@@ -4181,10 +5824,10 @@ function NodeConfigPanel({
4181
5824
  children: "Telemetry Position Offset:"
4182
5825
  }
4183
5826
  ),
4184
- /* @__PURE__ */ jsxs14("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4185
- /* @__PURE__ */ jsxs14("div", { children: [
4186
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
4187
- /* @__PURE__ */ jsx18(
5827
+ /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5828
+ /* @__PURE__ */ jsxs15("div", { children: [
5829
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
5830
+ /* @__PURE__ */ jsx19(
4188
5831
  "input",
4189
5832
  {
4190
5833
  type: "number",
@@ -4211,9 +5854,9 @@ function NodeConfigPanel({
4211
5854
  }
4212
5855
  )
4213
5856
  ] }),
4214
- /* @__PURE__ */ jsxs14("div", { children: [
4215
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
4216
- /* @__PURE__ */ jsx18(
5857
+ /* @__PURE__ */ jsxs15("div", { children: [
5858
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
5859
+ /* @__PURE__ */ jsx19(
4217
5860
  "input",
4218
5861
  {
4219
5862
  type: "number",
@@ -4241,8 +5884,8 @@ function NodeConfigPanel({
4241
5884
  )
4242
5885
  ] })
4243
5886
  ] }),
4244
- /* @__PURE__ */ jsxs14("div", { style: { marginTop: "16px" }, children: [
4245
- /* @__PURE__ */ jsx18(
5887
+ /* @__PURE__ */ jsxs15("div", { style: { marginTop: "16px" }, children: [
5888
+ /* @__PURE__ */ jsx19(
4246
5889
  "label",
4247
5890
  {
4248
5891
  style: {
@@ -4254,10 +5897,10 @@ function NodeConfigPanel({
4254
5897
  children: "Display Colors:"
4255
5898
  }
4256
5899
  ),
4257
- /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4258
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
4259
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4260
- /* @__PURE__ */ jsx18(
5900
+ /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
5901
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
5902
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
5903
+ /* @__PURE__ */ jsx19(
4261
5904
  "input",
4262
5905
  {
4263
5906
  type: "color",
@@ -4283,7 +5926,7 @@ function NodeConfigPanel({
4283
5926
  }
4284
5927
  }
4285
5928
  ),
4286
- /* @__PURE__ */ jsx18(
5929
+ /* @__PURE__ */ jsx19(
4287
5930
  "input",
4288
5931
  {
4289
5932
  type: "text",
@@ -4313,10 +5956,10 @@ function NodeConfigPanel({
4313
5956
  )
4314
5957
  ] })
4315
5958
  ] }),
4316
- binding.display?.showSparkline && /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4317
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
4318
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4319
- /* @__PURE__ */ jsx18(
5959
+ binding.display?.showSparkline && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
5960
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
5961
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
5962
+ /* @__PURE__ */ jsx19(
4320
5963
  "input",
4321
5964
  {
4322
5965
  type: "color",
@@ -4342,7 +5985,7 @@ function NodeConfigPanel({
4342
5985
  }
4343
5986
  }
4344
5987
  ),
4345
- /* @__PURE__ */ jsx18(
5988
+ /* @__PURE__ */ jsx19(
4346
5989
  "input",
4347
5990
  {
4348
5991
  type: "text",
@@ -4372,9 +6015,9 @@ function NodeConfigPanel({
4372
6015
  )
4373
6016
  ] })
4374
6017
  ] }),
4375
- /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4376
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
4377
- /* @__PURE__ */ jsx18(
6018
+ /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6019
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
6020
+ /* @__PURE__ */ jsx19(
4378
6021
  "input",
4379
6022
  {
4380
6023
  type: "text",
@@ -4402,20 +6045,20 @@ function NodeConfigPanel({
4402
6045
  }
4403
6046
  }
4404
6047
  ),
4405
- /* @__PURE__ */ jsx18("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
6048
+ /* @__PURE__ */ jsx19("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
4406
6049
  ] })
4407
6050
  ] }),
4408
6051
  " "
4409
6052
  ] })
4410
6053
  ] })
4411
6054
  ] }),
4412
- binding && /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4413
- /* @__PURE__ */ jsx18("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
6055
+ binding && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
6056
+ /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
4414
6057
  ["highHigh", "high", "low", "lowLow"].map((key) => {
4415
6058
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
4416
- return /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4417
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
4418
- /* @__PURE__ */ jsx18(
6059
+ return /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6060
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
6061
+ /* @__PURE__ */ jsx19(
4419
6062
  "input",
4420
6063
  {
4421
6064
  type: "number",
@@ -4439,8 +6082,8 @@ function NodeConfigPanel({
4439
6082
  ] }, key);
4440
6083
  })
4441
6084
  ] }),
4442
- binding && /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4443
- /* @__PURE__ */ jsx18("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
6085
+ binding && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
6086
+ /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
4444
6087
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
4445
6088
  const defaultColors = {
4446
6089
  normal: "#10b981",
@@ -4450,13 +6093,13 @@ function NodeConfigPanel({
4450
6093
  off: "#6b7280"
4451
6094
  };
4452
6095
  const label = status === "normal" ? "Normal" : status === "warning" ? "Warning" : status === "alarm" ? "Alarm" : status === "fault" ? "Fault" : "Off";
4453
- return /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4454
- /* @__PURE__ */ jsxs14("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
6096
+ return /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6097
+ /* @__PURE__ */ jsxs15("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4455
6098
  label,
4456
6099
  ":"
4457
6100
  ] }),
4458
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4459
- /* @__PURE__ */ jsx18(
6101
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6102
+ /* @__PURE__ */ jsx19(
4460
6103
  "input",
4461
6104
  {
4462
6105
  type: "color",
@@ -4477,7 +6120,7 @@ function NodeConfigPanel({
4477
6120
  }
4478
6121
  }
4479
6122
  ),
4480
- /* @__PURE__ */ jsx18(
6123
+ /* @__PURE__ */ jsx19(
4481
6124
  "input",
4482
6125
  {
4483
6126
  type: "text",
@@ -4504,7 +6147,7 @@ function NodeConfigPanel({
4504
6147
  ] }, status);
4505
6148
  })
4506
6149
  ] }),
4507
- /* @__PURE__ */ jsx18("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ jsx18(
6150
+ /* @__PURE__ */ jsx19("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ jsx19(
4508
6151
  "button",
4509
6152
  {
4510
6153
  onClick: onRemove,
@@ -4744,7 +6387,7 @@ var DEFAULT_THRESHOLDS = {
4744
6387
  };
4745
6388
 
4746
6389
  // src/diagram/hooks/useSimulation.ts
4747
- import { useEffect as useEffect9 } from "react";
6390
+ import { useEffect as useEffect11 } from "react";
4748
6391
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4749
6392
  const {
4750
6393
  interval = 2e3,
@@ -4755,7 +6398,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4755
6398
  historyLength = 10,
4756
6399
  generateValue
4757
6400
  } = options;
4758
- useEffect9(() => {
6401
+ useEffect11(() => {
4759
6402
  if (!enabled) return;
4760
6403
  const intervalId = setInterval(() => {
4761
6404
  const updates = [];
@@ -4857,6 +6500,7 @@ export {
4857
6500
  useSelection,
4858
6501
  useSimulation,
4859
6502
  useTelemetry,
6503
+ useTelemetryStatus,
4860
6504
  useTheme,
4861
6505
  useViewBox,
4862
6506
  validateDiagram