@procaaso/alphinity-ui-components 1.0.4 → 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 },
1730
+ ports: [
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 },
1755
1778
  ports: [
1756
- { id: "inlet", x: 10, y: 30, type: "inlet", direction: "in" },
1757
- { id: "outlet", x: 70, y: 30, type: "outlet", direction: "out" }
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 },
1897
+ ports: [
1898
+ { id: "bottom-inlet", x: 0, y: 35, type: "control", direction: "in" },
1899
+ { id: "top-outlet", x: 48, y: 0, type: "control", direction: "out" }
1900
+ ],
1901
+ svgContent: `
1902
+ <g>
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
+
1941
+ </g>
1942
+ `,
1943
+ metadata: {
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 },
1812
2017
  ports: [
1813
- { id: "signal", x: 20, y: 40, type: "signal", direction: "bi" }
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" }
1814
2020
  ],
1815
2021
  svgContent: `
1816
2022
  <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"/>
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>
1823
2815
  </g>
1824
2816
  `,
1825
2817
  metadata: {
1826
- description: "Generic instrument indicator",
1827
- tags: ["instrument", "indicator", "measurement"]
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 });
@@ -2375,7 +3593,7 @@ function useViewBox(options) {
2375
3593
  height: newHeight
2376
3594
  });
2377
3595
  }, [initialViewBox]);
2378
- useEffect4(() => {
3596
+ useEffect5(() => {
2379
3597
  if (!enableZoom || !svgRef.current) return;
2380
3598
  const svg = svgRef.current;
2381
3599
  const handleWheel = (e) => {
@@ -2408,7 +3626,7 @@ function useViewBox(options) {
2408
3626
  svg.addEventListener("wheel", handleWheel, { passive: false });
2409
3627
  return () => svg.removeEventListener("wheel", handleWheel);
2410
3628
  }, [enableZoom, zoomSensitivity, minZoom, initialViewBox]);
2411
- useEffect4(() => {
3629
+ useEffect5(() => {
2412
3630
  if (!enablePan || !svgRef.current) return;
2413
3631
  const svg = svgRef.current;
2414
3632
  const handleMouseDown = (e) => {
@@ -2484,7 +3702,7 @@ function useViewBox(options) {
2484
3702
  window.removeEventListener("keyup", handleKeyUp);
2485
3703
  };
2486
3704
  }, [enablePan, allowLeftClickPan]);
2487
- useEffect4(() => {
3705
+ useEffect5(() => {
2488
3706
  if (!svgRef.current) return;
2489
3707
  if (!enablePan && !enableZoom) return;
2490
3708
  const svg = svgRef.current;
@@ -2614,14 +3832,14 @@ function useViewBox(options) {
2614
3832
  }
2615
3833
 
2616
3834
  // src/diagram/hooks/useSelection.ts
2617
- import { useState as useState8, useCallback as useCallback4 } from "react";
3835
+ import { useState as useState9, useCallback as useCallback4 } from "react";
2618
3836
  function useSelection(options = {}) {
2619
3837
  const {
2620
3838
  multiSelect = true,
2621
3839
  initialSelection = { selectedNodeIds: /* @__PURE__ */ new Set(), selectedPipeIds: /* @__PURE__ */ new Set() },
2622
3840
  onSelectionChange
2623
3841
  } = options;
2624
- const [selection, setSelection] = useState8(initialSelection);
3842
+ const [selection, setSelection] = useState9(initialSelection);
2625
3843
  const notifyChange = useCallback4(
2626
3844
  (newSelection) => {
2627
3845
  if (onSelectionChange) {
@@ -2719,14 +3937,14 @@ function useSelection(options = {}) {
2719
3937
  }
2720
3938
 
2721
3939
  // src/diagram/hooks/useTelemetry.ts
2722
- 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";
2723
3941
  function useTelemetry(options = {}) {
2724
3942
  const {
2725
3943
  initialBindings = [],
2726
3944
  onTelemetryChange,
2727
3945
  refreshInterval = 0
2728
3946
  } = options;
2729
- const [telemetry, setTelemetry] = useState9(() => {
3947
+ const [telemetry, setTelemetry] = useState10(() => {
2730
3948
  const map = /* @__PURE__ */ new Map();
2731
3949
  initialBindings.forEach((binding) => {
2732
3950
  map.set(binding.nodeId, binding);
@@ -2847,7 +4065,7 @@ function useTelemetry(options = {}) {
2847
4065
  },
2848
4066
  [notifyChange]
2849
4067
  );
2850
- useEffect5(() => {
4068
+ useEffect6(() => {
2851
4069
  if (refreshInterval <= 0) return;
2852
4070
  const intervalId = setInterval(() => {
2853
4071
  notifyChange(telemetryRef.current);
@@ -2864,8 +4082,50 @@ function useTelemetry(options = {}) {
2864
4082
  };
2865
4083
  }
2866
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
+
2867
4127
  // src/diagram/hooks/useDrag.ts
2868
- 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";
2869
4129
  function useDrag(options = {}) {
2870
4130
  const {
2871
4131
  onDragStart,
@@ -2875,13 +4135,13 @@ function useDrag(options = {}) {
2875
4135
  dragThreshold = 3,
2876
4136
  screenToWorld = (x, y) => ({ x, y })
2877
4137
  } = options;
2878
- const [dragState, setDragState] = useState10({
4138
+ const [dragState, setDragState] = useState11({
2879
4139
  isDragging: false,
2880
4140
  startPosition: null,
2881
4141
  offset: { x: 0, y: 0 },
2882
4142
  draggedId: null
2883
4143
  });
2884
- const [isListening, setIsListening] = useState10(false);
4144
+ const [isListening, setIsListening] = useState11(false);
2885
4145
  const dragStartRef = useRef4(null);
2886
4146
  const hasMovedRef = useRef4(false);
2887
4147
  const applySnap = useCallback6(
@@ -3050,7 +4310,7 @@ function useDrag(options = {}) {
3050
4310
  },
3051
4311
  [screenToWorld, applySnap, onDragEnd]
3052
4312
  );
3053
- useEffect6(() => {
4313
+ useEffect8(() => {
3054
4314
  if (!isListening) return;
3055
4315
  document.addEventListener("mousemove", handleMouseMove);
3056
4316
  document.addEventListener("mouseup", handleMouseUp);
@@ -3119,7 +4379,7 @@ function useDrag(options = {}) {
3119
4379
  }
3120
4380
 
3121
4381
  // src/diagram/hooks/useKeyboardShortcuts.ts
3122
- import { useEffect as useEffect7 } from "react";
4382
+ import { useEffect as useEffect9 } from "react";
3123
4383
  function useKeyboardShortcuts(options) {
3124
4384
  const {
3125
4385
  onDelete,
@@ -3130,7 +4390,7 @@ function useKeyboardShortcuts(options) {
3130
4390
  onSelectAll,
3131
4391
  enabled = true
3132
4392
  } = options;
3133
- useEffect7(() => {
4393
+ useEffect9(() => {
3134
4394
  if (!enabled) return;
3135
4395
  const handleKeyDown = (event) => {
3136
4396
  const target = event.target;
@@ -3239,7 +4499,7 @@ function nodeHasPort(node, portId) {
3239
4499
  }
3240
4500
 
3241
4501
  // src/components/PIDCanvas/PIDCanvas.tsx
3242
- 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";
3243
4503
  function PIDCanvas({
3244
4504
  diagram,
3245
4505
  className = "",
@@ -3266,16 +4526,30 @@ function PIDCanvas({
3266
4526
  telemetry,
3267
4527
  ...props
3268
4528
  }) {
3269
- const [localDiagram, setLocalDiagram] = useState11(diagram);
3270
- const [dragOverPosition, setDragOverPosition] = useState11(null);
3271
- const [isConnecting, setIsConnecting] = useState11(false);
3272
- const [connectionSource, setConnectionSource] = useState11(null);
3273
- const [connectionSourcePort, setConnectionSourcePort] = useState11(null);
3274
- const [connectionCursor, setConnectionCursor] = useState11(null);
3275
- const [hoveredNode, setHoveredNode] = useState11(null);
3276
- const [hoveredPort, setHoveredPort] = useState11(null);
3277
- useEffect8(() => {
3278
- 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;
3279
4553
  }, [diagram]);
3280
4554
  const {
3281
4555
  viewBox: controlledViewBox,
@@ -3311,7 +4585,7 @@ function PIDCanvas({
3311
4585
  });
3312
4586
  return { minX, minY, maxX, maxY };
3313
4587
  }, [localDiagram.nodes]);
3314
- useEffect8(() => {
4588
+ useEffect10(() => {
3315
4589
  if (onMounted) {
3316
4590
  const controls = {
3317
4591
  fitToContent: (padding = 50) => {
@@ -3352,6 +4626,186 @@ function PIDCanvas({
3352
4626
  });
3353
4627
  const dragEnabled = features.dragNodes ?? false;
3354
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
+ );
3355
4809
  const handleDragEnd = useCallback7(
3356
4810
  (nodeId, offset, finalPosition) => {
3357
4811
  const dragDistance = Math.sqrt(offset.x ** 2 + offset.y ** 2);
@@ -3361,6 +4815,7 @@ function PIDCanvas({
3361
4815
  }
3362
4816
  return;
3363
4817
  }
4818
+ pushToHistory(localDiagram);
3364
4819
  const updatedNodes = localDiagram.nodes.map(
3365
4820
  (node) => node.id === nodeId ? {
3366
4821
  ...node,
@@ -3398,11 +4853,13 @@ function PIDCanvas({
3398
4853
  nodes: updatedNodes,
3399
4854
  pipes: updatedPipes
3400
4855
  };
4856
+ isInternalChange.current = true;
4857
+ lastInternalDiagram.current = updatedDiagram;
3401
4858
  setLocalDiagram(updatedDiagram);
3402
4859
  onDiagramChange?.(updatedDiagram);
3403
4860
  onNodeMove?.(nodeId, finalPosition.x, finalPosition.y);
3404
4861
  },
3405
- [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode]
4862
+ [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode, pushToHistory]
3406
4863
  );
3407
4864
  const { dragState, startDrag } = useDrag({
3408
4865
  snapToGrid: features.snapToGrid ?? 0,
@@ -3413,6 +4870,7 @@ function PIDCanvas({
3413
4870
  snapToGrid: features.snapToGrid ?? 0,
3414
4871
  screenToWorld,
3415
4872
  onDragEnd: (pipeId, _offset, finalPosition) => {
4873
+ pushToHistory(localDiagram);
3416
4874
  const [actualPipeId, pointIndex] = pipeId.split(":");
3417
4875
  const idx = parseInt(pointIndex, 10);
3418
4876
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -3423,6 +4881,8 @@ function PIDCanvas({
3423
4881
  return { ...pipe, routePoints: updatedRoutePoints };
3424
4882
  });
3425
4883
  const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4884
+ isInternalChange.current = true;
4885
+ lastInternalDiagram.current = updatedDiagram;
3426
4886
  setLocalDiagram(updatedDiagram);
3427
4887
  onDiagramChange?.(updatedDiagram);
3428
4888
  }
@@ -3435,6 +4895,15 @@ function PIDCanvas({
3435
4895
  },
3436
4896
  [localDiagram.nodes, startDrag]
3437
4897
  );
4898
+ const handleWaypointClick = useCallback7(
4899
+ (pipeId, pointIndex) => {
4900
+ setSelectedWaypoint({ pipeId, pointIndex });
4901
+ if (selectionEnabled) {
4902
+ clearSelection();
4903
+ }
4904
+ },
4905
+ [selectionEnabled, clearSelection]
4906
+ );
3438
4907
  const handleWaypointDragStart = useCallback7(
3439
4908
  (pipeId, pointIndex, event) => {
3440
4909
  const pipe = localDiagram.pipes.find((p) => p.id === pipeId);
@@ -3446,6 +4915,7 @@ function PIDCanvas({
3446
4915
  );
3447
4916
  const handlePipeSegmentClick = useCallback7(
3448
4917
  (pipeId, position, segmentIndex) => {
4918
+ pushToHistory(localDiagram);
3449
4919
  const updatedPipes = localDiagram.pipes.map((pipe) => {
3450
4920
  if (pipe.id !== pipeId) return pipe;
3451
4921
  const updatedRoutePoints = [
@@ -3459,13 +4929,14 @@ function PIDCanvas({
3459
4929
  setLocalDiagram(updatedDiagram);
3460
4930
  onDiagramChange?.(updatedDiagram);
3461
4931
  },
3462
- [localDiagram, onDiagramChange]
4932
+ [localDiagram, onDiagramChange, pushToHistory]
3463
4933
  );
3464
4934
  const telemetryEnabled = features.telemetry ?? false;
3465
4935
  const handleDelete = useCallback7(() => {
3466
4936
  if (!selectionEnabled) return;
3467
4937
  const { selectedNodeIds, selectedPipeIds } = selection;
3468
4938
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
4939
+ pushToHistory(localDiagram);
3469
4940
  const updatedNodes = localDiagram.nodes.filter((node) => !selectedNodeIds.has(node.id));
3470
4941
  const updatedPipes = localDiagram.pipes.filter(
3471
4942
  (pipe) => !selectedPipeIds.has(pipe.id) && !selectedNodeIds.has(pipe.fromNodeId) && !selectedNodeIds.has(pipe.toNodeId)
@@ -3475,11 +4946,21 @@ function PIDCanvas({
3475
4946
  nodes: updatedNodes,
3476
4947
  pipes: updatedPipes
3477
4948
  };
4949
+ isInternalChange.current = true;
4950
+ lastInternalDiagram.current = updatedDiagram;
3478
4951
  setLocalDiagram(updatedDiagram);
3479
4952
  clearSelection();
3480
4953
  onDiagramChange?.(updatedDiagram);
3481
4954
  onDelete?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3482
- }, [selection, localDiagram, selectionEnabled, clearSelection, onDiagramChange, onDelete]);
4955
+ }, [
4956
+ selection,
4957
+ localDiagram,
4958
+ selectionEnabled,
4959
+ clearSelection,
4960
+ onDiagramChange,
4961
+ onDelete,
4962
+ pushToHistory
4963
+ ]);
3483
4964
  const handleCopy = useCallback7(() => {
3484
4965
  if (!selectionEnabled) return;
3485
4966
  const { selectedNodeIds, selectedPipeIds } = selection;
@@ -3536,10 +5017,15 @@ function PIDCanvas({
3536
5017
  }
3537
5018
  onPipeClick?.(pipeId);
3538
5019
  };
3539
- const handleCanvasClick = () => {
5020
+ const handleCanvasClick = (e) => {
5021
+ const target = e.target;
5022
+ if (target.closest(".pipe-route-point")) {
5023
+ return;
5024
+ }
3540
5025
  if (selectionEnabled) {
3541
5026
  clearSelection();
3542
5027
  }
5028
+ setSelectedWaypoint(null);
3543
5029
  };
3544
5030
  const handleDragOver = useCallback7(
3545
5031
  (event) => {
@@ -3550,7 +5036,7 @@ function PIDCanvas({
3550
5036
  const worldPos = screenToWorld(event.clientX, event.clientY);
3551
5037
  setDragOverPosition(worldPos);
3552
5038
  },
3553
- [features.allowSymbolDrop, screenToWorld]
5039
+ [features.allowSymbolDrop, screenToWorld, svgRef]
3554
5040
  );
3555
5041
  const handleDrop = useCallback7(
3556
5042
  (event) => {
@@ -3563,7 +5049,7 @@ function PIDCanvas({
3563
5049
  const worldPos = screenToWorld(event.clientX, event.clientY);
3564
5050
  onSymbolDrop?.(symbolId, worldPos);
3565
5051
  },
3566
- [features.allowSymbolDrop, screenToWorld, onSymbolDrop]
5052
+ [features.allowSymbolDrop, screenToWorld, onSymbolDrop, svgRef]
3567
5053
  );
3568
5054
  const handleDragLeave = useCallback7(() => {
3569
5055
  setDragOverPosition(null);
@@ -3587,7 +5073,7 @@ function PIDCanvas({
3587
5073
  const distance = Math.sqrt(
3588
5074
  (worldPos.x - portPos.x) ** 2 + (worldPos.y - portPos.y) ** 2
3589
5075
  );
3590
- if (distance < 10) {
5076
+ if (distance < 15) {
3591
5077
  foundPort = port.id;
3592
5078
  foundNode = node.id;
3593
5079
  break;
@@ -3604,9 +5090,11 @@ function PIDCanvas({
3604
5090
  const { name } = diagram;
3605
5091
  const viewBox = controlledViewBox;
3606
5092
  const displayDiagram = localDiagram;
3607
- 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(
3608
5095
  "div",
3609
5096
  {
5097
+ ref: containerRef,
3610
5098
  className: `pid-canvas ${className}`.trim(),
3611
5099
  style: {
3612
5100
  width: "100%",
@@ -3617,223 +5105,287 @@ function PIDCanvas({
3617
5105
  ...style
3618
5106
  },
3619
5107
  ...props,
3620
- children: /* @__PURE__ */ jsxs12(
3621
- "svg",
3622
- {
3623
- ref: svgRef,
3624
- width: "100%",
3625
- height: "100%",
3626
- viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
3627
- preserveAspectRatio: "xMidYMid meet",
3628
- style: {
3629
- display: "block"
3630
- },
3631
- "aria-label": `P&ID diagram: ${name}`,
3632
- role: "img",
3633
- onClick: handleCanvasClick,
3634
- onMouseMove: handleMouseMove,
3635
- onDragOver: handleDragOver,
3636
- onDrop: handleDrop,
3637
- onDragLeave: handleDragLeave,
3638
- children: [
3639
- features.showGrid !== false ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
3640
- /* @__PURE__ */ jsxs12("defs", { children: [
3641
- /* @__PURE__ */ jsx16("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx16("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
3642
- /* @__PURE__ */ jsxs12("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
3643
- /* @__PURE__ */ jsx16("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
3644
- /* @__PURE__ */ jsx16("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
3645
- ] })
3646
- ] }),
3647
- /* @__PURE__ */ jsx16(
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(
3648
5147
  "rect",
3649
5148
  {
3650
5149
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3651
5150
  y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3652
5151
  width: Math.ceil(viewBox.width * 3 / 25) * 25,
3653
5152
  height: Math.ceil(viewBox.height * 3 / 25) * 25,
3654
- fill: "url(#grid-major)"
5153
+ fill: features.backgroundColor ?? "#ffffff"
3655
5154
  }
3656
- )
3657
- ] }) : /* @__PURE__ */ jsx16(
3658
- "rect",
3659
- {
3660
- x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3661
- y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3662
- width: Math.ceil(viewBox.width * 3 / 25) * 25,
3663
- height: Math.ceil(viewBox.height * 3 / 25) * 25,
3664
- fill: features.backgroundColor ?? "#ffffff"
3665
- }
3666
- ),
3667
- /* @__PURE__ */ jsx16(
3668
- PipeRenderer,
3669
- {
3670
- pipes: displayDiagram.pipes,
3671
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3672
- onPipeClick: selectionEnabled ? handlePipeClick : void 0,
3673
- enableWaypointEditing: false,
3674
- onWaypointDragStart: void 0,
3675
- editingPipeId: null,
3676
- draggingWaypointIndex: null,
3677
- waypointDragOffset: void 0,
3678
- onPipeSegmentClick: void 0
3679
- }
3680
- ),
3681
- /* @__PURE__ */ jsx16(
3682
- NodeRenderer,
3683
- {
3684
- nodes: displayDiagram.nodes,
3685
- selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
3686
- onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
3687
- onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
3688
- enableDrag: dragEnabled && !isConnecting,
3689
- onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
3690
- draggingNodeId: dragState.draggedId,
3691
- dragOffset: dragState.offset
3692
- }
3693
- ),
3694
- (features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx16(
3695
- PipeRenderer,
3696
- {
3697
- pipes: displayDiagram.pipes,
3698
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3699
- onPipeClick: void 0,
3700
- enableWaypointEditing: true,
3701
- onWaypointDragStart: handleWaypointDragStart,
3702
- editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
3703
- draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
3704
- waypointDragOffset: waypointDragState.offset,
3705
- onPipeSegmentClick: handlePipeSegmentClick
3706
- }
3707
- ),
3708
- telemetryEnabled && telemetry && /* @__PURE__ */ jsx16("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
3709
- const binding = telemetry.get(node.id);
3710
- if (!binding) return null;
3711
- const symbol = getSymbolDefinition(node.symbolId);
3712
- if (!symbol) return null;
3713
- const overlayY = node.transform.y - symbol.viewBox.height / 2 - 10;
3714
- return /* @__PURE__ */ jsx16(
3715
- DataOverlay,
5155
+ ),
5156
+ /* @__PURE__ */ jsx17(
5157
+ PipeRenderer,
3716
5158
  {
3717
- nodeId: node.id,
3718
- x: node.transform.x,
3719
- y: overlayY,
3720
- telemetry: binding
3721
- },
3722
- `telemetry-${node.id}`
3723
- );
3724
- }) }),
3725
- dragOverPosition && /* @__PURE__ */ jsx16(
3726
- "circle",
3727
- {
3728
- cx: dragOverPosition.x,
3729
- cy: dragOverPosition.y,
3730
- r: 20,
3731
- fill: "#3b82f6",
3732
- opacity: 0.3,
3733
- stroke: "#3b82f6",
3734
- strokeWidth: 2,
3735
- pointerEvents: "none"
3736
- }
3737
- ),
3738
- isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ jsx16("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
3739
- const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
3740
- if (!sourceNode) return null;
3741
- const symbol = getSymbolDefinition(sourceNode.symbolId);
3742
- let lineStartX = sourceNode.transform.x;
3743
- let lineStartY = sourceNode.transform.y;
3744
- if (connectionSourcePort && symbol?.ports) {
3745
- const port = symbol.ports.find((p) => p.id === connectionSourcePort);
3746
- if (port) {
3747
- const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
3748
- if (portPos) {
3749
- lineStartX = portPos.x;
3750
- lineStartY = portPos.y;
3751
- }
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
3752
5168
  }
3753
- }
3754
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
3755
- /* @__PURE__ */ jsx16(
3756
- "circle",
3757
- {
3758
- cx: sourceNode.transform.x,
3759
- cy: sourceNode.transform.y,
3760
- r: 30,
3761
- fill: "none",
3762
- stroke: "#10b981",
3763
- strokeWidth: 3,
3764
- opacity: 0.6
3765
- }
3766
- ),
3767
- /* @__PURE__ */ jsx16(
3768
- "line",
3769
- {
3770
- x1: lineStartX,
3771
- y1: lineStartY,
3772
- x2: connectionCursor.x,
3773
- y2: connectionCursor.y,
3774
- stroke: "#10b981",
3775
- strokeWidth: 2,
3776
- strokeDasharray: "5,5",
3777
- opacity: 0.8
3778
- }
3779
- ),
3780
- /* @__PURE__ */ jsx16(
3781
- "circle",
3782
- {
3783
- cx: connectionCursor.x,
3784
- cy: connectionCursor.y,
3785
- r: 8,
3786
- fill: "#10b981",
3787
- opacity: 0.6
3788
- }
3789
- )
3790
- ] });
3791
- })() }),
3792
- features.connectionMode && /* @__PURE__ */ jsx16("g", { id: "port-indicators", pointerEvents: "none", children: displayDiagram.nodes.map((node) => {
3793
- const symbol = getSymbolDefinition(node.symbolId);
3794
- if (!symbol?.ports) return null;
3795
- return symbol.ports.map((port) => {
3796
- const portPos = getPortWorldPosition(node, port.id);
3797
- if (!portPos) return null;
3798
- const isHovered = hoveredPort === port.id && hoveredNode === node.id;
3799
- const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
3800
- return /* @__PURE__ */ jsx16(
3801
- "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,
3802
5209
  {
3803
- cx: portPos.x,
3804
- cy: portPos.y,
3805
- r: isHovered ? 8 : 5,
3806
- fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
3807
- stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
3808
- strokeWidth: isHovered || isSourcePort ? 2 : 1,
3809
- opacity: isHovered || isSourcePort ? 1 : 0.6
5210
+ nodeId: node.id,
5211
+ x: overlayX,
5212
+ y: overlayY,
5213
+ telemetry: binding,
5214
+ scale: features.telemetryScale ?? 1
3810
5215
  },
3811
- `${node.id}-${port.id}`
5216
+ `telemetry-${node.id}`
3812
5217
  );
3813
- });
3814
- }) })
3815
- ]
3816
- }
3817
- )
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
+ ]
3818
5362
  }
3819
5363
  );
3820
5364
  }
3821
5365
 
3822
5366
  // src/components/SymbolLibrary/SymbolLibrary.tsx
3823
- import { useState as useState12 } from "react";
3824
- 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";
3825
5369
  function SymbolLibrary({
3826
5370
  className = "",
3827
5371
  onSymbolDragStart,
3828
5372
  showCategories = true,
3829
5373
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
3830
5374
  }) {
3831
- const [selectedCategory, setSelectedCategory] = useState12("all");
3832
- const [searchQuery, setSearchQuery] = useState12("");
5375
+ const [selectedCategory, setSelectedCategory] = useState13("all");
5376
+ const [searchQuery, setSearchQuery] = useState13("");
3833
5377
  const symbolIds = getAvailableSymbols();
3834
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
+ };
3835
5386
  const filteredSymbols = allSymbols.filter((symbol) => {
3836
- const matchesCategory = selectedCategory === "all" || symbol.category === selectedCategory;
5387
+ const actualCategory = categoryMap[selectedCategory] || selectedCategory;
5388
+ const matchesCategory = selectedCategory === "all" || symbol.category === actualCategory;
3837
5389
  const matchesSearch = searchQuery === "" || symbol.name.toLowerCase().includes(searchQuery.toLowerCase()) || symbol.id.toLowerCase().includes(searchQuery.toLowerCase());
3838
5390
  return matchesCategory && matchesSearch;
3839
5391
  });
@@ -3842,9 +5394,9 @@ function SymbolLibrary({
3842
5394
  event.dataTransfer.effectAllowed = "copy";
3843
5395
  onSymbolDragStart?.(symbol.id, event);
3844
5396
  };
3845
- return /* @__PURE__ */ jsxs13("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
3846
- /* @__PURE__ */ jsx17("div", { style: styles.header, children: /* @__PURE__ */ jsx17("h3", { style: styles.title, children: "Symbol Library" }) }),
3847
- /* @__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(
3848
5400
  "input",
3849
5401
  {
3850
5402
  type: "text",
@@ -3854,8 +5406,8 @@ function SymbolLibrary({
3854
5406
  style: styles.searchInput
3855
5407
  }
3856
5408
  ) }),
3857
- showCategories && /* @__PURE__ */ jsxs13("div", { style: styles.categories, children: [
3858
- /* @__PURE__ */ jsx17(
5409
+ showCategories && /* @__PURE__ */ jsxs14("div", { style: styles.categories, children: [
5410
+ /* @__PURE__ */ jsx18(
3859
5411
  "button",
3860
5412
  {
3861
5413
  onClick: () => setSelectedCategory("all"),
@@ -3866,7 +5418,7 @@ function SymbolLibrary({
3866
5418
  children: "All"
3867
5419
  }
3868
5420
  ),
3869
- categories.map((category) => /* @__PURE__ */ jsx17(
5421
+ categories.map((category) => /* @__PURE__ */ jsx18(
3870
5422
  "button",
3871
5423
  {
3872
5424
  onClick: () => setSelectedCategory(category),
@@ -3879,7 +5431,7 @@ function SymbolLibrary({
3879
5431
  category
3880
5432
  ))
3881
5433
  ] }),
3882
- /* @__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(
3883
5435
  "div",
3884
5436
  {
3885
5437
  draggable: true,
@@ -3887,15 +5439,15 @@ function SymbolLibrary({
3887
5439
  style: styles.symbolCard,
3888
5440
  title: symbol.metadata?.description || symbol.name,
3889
5441
  children: [
3890
- /* @__PURE__ */ jsx17("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx17(
5442
+ /* @__PURE__ */ jsx18("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx18(
3891
5443
  "svg",
3892
5444
  {
3893
5445
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
3894
5446
  style: styles.symbolSvg,
3895
- children: /* @__PURE__ */ jsx17("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
5447
+ children: /* @__PURE__ */ jsx18("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
3896
5448
  }
3897
5449
  ) }),
3898
- /* @__PURE__ */ jsx17("div", { style: styles.symbolName, children: symbol.name })
5450
+ /* @__PURE__ */ jsx18("div", { style: styles.symbolName, children: symbol.name })
3899
5451
  ]
3900
5452
  },
3901
5453
  symbol.id
@@ -4004,7 +5556,7 @@ var styles = {
4004
5556
  };
4005
5557
 
4006
5558
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
4007
- 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";
4008
5560
  function NodeConfigPanel({
4009
5561
  node,
4010
5562
  binding,
@@ -4023,15 +5575,15 @@ function NodeConfigPanel({
4023
5575
  padding: "20px",
4024
5576
  ...style
4025
5577
  };
4026
- return /* @__PURE__ */ jsxs14("div", { className, style: defaultStyle, children: [
4027
- /* @__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: [
4028
5580
  "Configure: ",
4029
5581
  node.id
4030
5582
  ] }),
4031
- /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4032
- /* @__PURE__ */ jsx18("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
4033
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
4034
- /* @__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(
4035
5587
  "input",
4036
5588
  {
4037
5589
  type: "text",
@@ -4047,10 +5599,10 @@ function NodeConfigPanel({
4047
5599
  }
4048
5600
  }
4049
5601
  ),
4050
- /* @__PURE__ */ jsxs14("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4051
- /* @__PURE__ */ jsxs14("div", { children: [
4052
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
4053
- /* @__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(
4054
5606
  "input",
4055
5607
  {
4056
5608
  type: "number",
@@ -4071,9 +5623,9 @@ function NodeConfigPanel({
4071
5623
  }
4072
5624
  )
4073
5625
  ] }),
4074
- /* @__PURE__ */ jsxs14("div", { children: [
4075
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
4076
- /* @__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(
4077
5629
  "input",
4078
5630
  {
4079
5631
  type: "number",
@@ -4095,9 +5647,9 @@ function NodeConfigPanel({
4095
5647
  )
4096
5648
  ] })
4097
5649
  ] }),
4098
- /* @__PURE__ */ jsxs14("div", { children: [
4099
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
4100
- /* @__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(
4101
5653
  "input",
4102
5654
  {
4103
5655
  type: "number",
@@ -4117,9 +5669,9 @@ function NodeConfigPanel({
4117
5669
  }
4118
5670
  )
4119
5671
  ] }),
4120
- /* @__PURE__ */ jsxs14("div", { children: [
4121
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
4122
- /* @__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(
4123
5675
  "input",
4124
5676
  {
4125
5677
  type: "number",
@@ -4144,9 +5696,9 @@ function NodeConfigPanel({
4144
5696
  )
4145
5697
  ] })
4146
5698
  ] }),
4147
- /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4148
- /* @__PURE__ */ jsx18("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
4149
- !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(
4150
5702
  "button",
4151
5703
  {
4152
5704
  onClick: () => {
@@ -4189,9 +5741,9 @@ function NodeConfigPanel({
4189
5741
  },
4190
5742
  children: "+ Add Telemetry"
4191
5743
  }
4192
- ) : /* @__PURE__ */ jsxs14(Fragment6, { children: [
4193
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
4194
- /* @__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(
4195
5747
  "input",
4196
5748
  {
4197
5749
  type: "number",
@@ -4213,8 +5765,8 @@ function NodeConfigPanel({
4213
5765
  }
4214
5766
  }
4215
5767
  ),
4216
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
4217
- /* @__PURE__ */ jsx18(
5768
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
5769
+ /* @__PURE__ */ jsx19(
4218
5770
  "input",
4219
5771
  {
4220
5772
  type: "text",
@@ -4236,8 +5788,8 @@ function NodeConfigPanel({
4236
5788
  }
4237
5789
  }
4238
5790
  ),
4239
- /* @__PURE__ */ jsxs14("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4240
- /* @__PURE__ */ jsx18(
5791
+ /* @__PURE__ */ jsxs15("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
5792
+ /* @__PURE__ */ jsx19(
4241
5793
  "input",
4242
5794
  {
4243
5795
  type: "checkbox",
@@ -4259,8 +5811,8 @@ function NodeConfigPanel({
4259
5811
  ),
4260
5812
  "Show Sparkline"
4261
5813
  ] }),
4262
- /* @__PURE__ */ jsxs14("div", { style: { marginTop: "12px" }, children: [
4263
- /* @__PURE__ */ jsx18(
5814
+ /* @__PURE__ */ jsxs15("div", { style: { marginTop: "12px" }, children: [
5815
+ /* @__PURE__ */ jsx19(
4264
5816
  "label",
4265
5817
  {
4266
5818
  style: {
@@ -4272,10 +5824,10 @@ function NodeConfigPanel({
4272
5824
  children: "Telemetry Position Offset:"
4273
5825
  }
4274
5826
  ),
4275
- /* @__PURE__ */ jsxs14("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4276
- /* @__PURE__ */ jsxs14("div", { children: [
4277
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
4278
- /* @__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(
4279
5831
  "input",
4280
5832
  {
4281
5833
  type: "number",
@@ -4302,9 +5854,9 @@ function NodeConfigPanel({
4302
5854
  }
4303
5855
  )
4304
5856
  ] }),
4305
- /* @__PURE__ */ jsxs14("div", { children: [
4306
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
4307
- /* @__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(
4308
5860
  "input",
4309
5861
  {
4310
5862
  type: "number",
@@ -4332,8 +5884,8 @@ function NodeConfigPanel({
4332
5884
  )
4333
5885
  ] })
4334
5886
  ] }),
4335
- /* @__PURE__ */ jsxs14("div", { style: { marginTop: "16px" }, children: [
4336
- /* @__PURE__ */ jsx18(
5887
+ /* @__PURE__ */ jsxs15("div", { style: { marginTop: "16px" }, children: [
5888
+ /* @__PURE__ */ jsx19(
4337
5889
  "label",
4338
5890
  {
4339
5891
  style: {
@@ -4345,10 +5897,10 @@ function NodeConfigPanel({
4345
5897
  children: "Display Colors:"
4346
5898
  }
4347
5899
  ),
4348
- /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4349
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
4350
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4351
- /* @__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(
4352
5904
  "input",
4353
5905
  {
4354
5906
  type: "color",
@@ -4374,7 +5926,7 @@ function NodeConfigPanel({
4374
5926
  }
4375
5927
  }
4376
5928
  ),
4377
- /* @__PURE__ */ jsx18(
5929
+ /* @__PURE__ */ jsx19(
4378
5930
  "input",
4379
5931
  {
4380
5932
  type: "text",
@@ -4404,10 +5956,10 @@ function NodeConfigPanel({
4404
5956
  )
4405
5957
  ] })
4406
5958
  ] }),
4407
- binding.display?.showSparkline && /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4408
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
4409
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4410
- /* @__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(
4411
5963
  "input",
4412
5964
  {
4413
5965
  type: "color",
@@ -4433,7 +5985,7 @@ function NodeConfigPanel({
4433
5985
  }
4434
5986
  }
4435
5987
  ),
4436
- /* @__PURE__ */ jsx18(
5988
+ /* @__PURE__ */ jsx19(
4437
5989
  "input",
4438
5990
  {
4439
5991
  type: "text",
@@ -4463,9 +6015,9 @@ function NodeConfigPanel({
4463
6015
  )
4464
6016
  ] })
4465
6017
  ] }),
4466
- /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4467
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
4468
- /* @__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(
4469
6021
  "input",
4470
6022
  {
4471
6023
  type: "text",
@@ -4493,20 +6045,20 @@ function NodeConfigPanel({
4493
6045
  }
4494
6046
  }
4495
6047
  ),
4496
- /* @__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" })
4497
6049
  ] })
4498
6050
  ] }),
4499
6051
  " "
4500
6052
  ] })
4501
6053
  ] })
4502
6054
  ] }),
4503
- binding && /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4504
- /* @__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" }),
4505
6057
  ["highHigh", "high", "low", "lowLow"].map((key) => {
4506
6058
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
4507
- return /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4508
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
4509
- /* @__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(
4510
6062
  "input",
4511
6063
  {
4512
6064
  type: "number",
@@ -4530,8 +6082,8 @@ function NodeConfigPanel({
4530
6082
  ] }, key);
4531
6083
  })
4532
6084
  ] }),
4533
- binding && /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4534
- /* @__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" }),
4535
6087
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
4536
6088
  const defaultColors = {
4537
6089
  normal: "#10b981",
@@ -4541,13 +6093,13 @@ function NodeConfigPanel({
4541
6093
  off: "#6b7280"
4542
6094
  };
4543
6095
  const label = status === "normal" ? "Normal" : status === "warning" ? "Warning" : status === "alarm" ? "Alarm" : status === "fault" ? "Fault" : "Off";
4544
- return /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "8px" }, children: [
4545
- /* @__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: [
4546
6098
  label,
4547
6099
  ":"
4548
6100
  ] }),
4549
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4550
- /* @__PURE__ */ jsx18(
6101
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6102
+ /* @__PURE__ */ jsx19(
4551
6103
  "input",
4552
6104
  {
4553
6105
  type: "color",
@@ -4568,7 +6120,7 @@ function NodeConfigPanel({
4568
6120
  }
4569
6121
  }
4570
6122
  ),
4571
- /* @__PURE__ */ jsx18(
6123
+ /* @__PURE__ */ jsx19(
4572
6124
  "input",
4573
6125
  {
4574
6126
  type: "text",
@@ -4595,7 +6147,7 @@ function NodeConfigPanel({
4595
6147
  ] }, status);
4596
6148
  })
4597
6149
  ] }),
4598
- /* @__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(
4599
6151
  "button",
4600
6152
  {
4601
6153
  onClick: onRemove,
@@ -4835,7 +6387,7 @@ var DEFAULT_THRESHOLDS = {
4835
6387
  };
4836
6388
 
4837
6389
  // src/diagram/hooks/useSimulation.ts
4838
- import { useEffect as useEffect9 } from "react";
6390
+ import { useEffect as useEffect11 } from "react";
4839
6391
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4840
6392
  const {
4841
6393
  interval = 2e3,
@@ -4846,7 +6398,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4846
6398
  historyLength = 10,
4847
6399
  generateValue
4848
6400
  } = options;
4849
- useEffect9(() => {
6401
+ useEffect11(() => {
4850
6402
  if (!enabled) return;
4851
6403
  const intervalId = setInterval(() => {
4852
6404
  const updates = [];
@@ -4948,6 +6500,7 @@ export {
4948
6500
  useSelection,
4949
6501
  useSimulation,
4950
6502
  useTelemetry,
6503
+ useTelemetryStatus,
4951
6504
  useTheme,
4952
6505
  useViewBox,
4953
6506
  validateDiagram