@procaaso/alphinity-ui-components 1.0.4 → 1.0.6

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,145 +1694,1224 @@ 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",
1701
+ "valve-gate": {
1702
+ id: "valve-gate",
1703
+ name: "Gate Valve (Powered)",
1704
1704
  category: "valve",
1705
- viewBox: { x: 0, y: 0, width: 50, height: 50 },
1705
+ viewBox: { x: 0, y: 0, width: 26, height: 26 },
1706
1706
  ports: [
1707
- { id: "inlet", x: 0, y: 25, type: "inlet", direction: "in" },
1708
- { id: "outlet", x: 50, y: 25, type: "outlet", direction: "out" }
1707
+ { id: "inlet", x: 0, y: 18, type: "inlet", direction: "in" },
1708
+ { id: "outlet", x: 26, y: 18, type: "outlet", direction: "out" }
1709
1709
  ],
1710
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"/>
1711
+ <g transform="scale(0.5)">
1712
+ <!-- Actuator box -->
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"/>
1714
+ <!-- Actuator stem -->
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"/>
1716
+ <!-- Valve body (two triangles forming diamond) -->
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"/>
1719
1718
  </g>
1720
1719
  `,
1721
1720
  metadata: {
1722
- description: "Ball valve for flow control",
1723
- tags: ["valve", "control", "shutoff"]
1721
+ description: "Gate valve with powered actuator",
1722
+ tags: ["valve", "gate", "powered", "actuator"]
1724
1723
  }
1725
1724
  },
1726
- "valve-gate": {
1727
- id: "valve-gate",
1728
- name: "Gate Valve (Powered)",
1725
+ "pump-positive-displacement": {
1726
+ id: "pump-positive-displacement",
1727
+ name: "Positive Displacement Pump",
1728
+ category: "pump",
1729
+ viewBox: { x: 0, y: 0, width: 28.125, height: 28.125 },
1730
+ ports: [
1731
+ { id: "inlet", x: 0, y: 14.0625, type: "inlet", direction: "in" },
1732
+ { id: "outlet", x: 28.125, y: 14.0625, type: "outlet", direction: "out" }
1733
+ ],
1734
+ svgContent: `
1735
+ <g transform="scale(0.28125)">
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)",
1729
1776
  category: "valve",
1730
- viewBox: { x: 0, y: 0, width: 52, height: 52 },
1777
+ viewBox: { x: -0.25, y: -0.25, width: 44, height: 33.892578 },
1731
1778
  ports: [
1732
- { id: "inlet", x: 0, y: 36, type: "inlet", direction: "in" },
1733
- { id: "outlet", x: 52, y: 36, type: "outlet", direction: "out" }
1779
+ { id: "bottom-inlet", x: 0, y: 12.5, type: "control", direction: "in" },
1780
+ { id: "top-outlet", x: 24, y: 32.5, type: "control", direction: "out" }
1734
1781
  ],
1735
1782
  svgContent: `
1736
- <g>
1737
- <!-- Actuator box -->
1738
- <rect x="16.74" y="0" width="18.02" height="18.02" fill="#edeeef" stroke="#666666" pointer-events="all"/>
1739
- <!-- 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"/>
1741
- <!-- 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"/>
1783
+ <g transform="scale(0.5)">
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
+
1743
1830
  </g>
1744
1831
  `,
1745
1832
  metadata: {
1746
- description: "Gate valve with powered actuator",
1747
- tags: ["valve", "gate", "powered", "actuator"]
1833
+ description: "Vannefold heat exchanger block with bottom inlet configuration",
1834
+ tags: ["heat-exchanger", "vannefold", "fold", "bottom"]
1835
+ }
1836
+ },
1837
+ "vannefold-middle": {
1838
+ id: "vannefold-middle",
1839
+ name: "Vannefold Block (Middle)",
1840
+ category: "valve",
1841
+ viewBox: { x: -0.25, y: -0.25, width: 47.5, height: 26.548828 },
1842
+ ports: [
1843
+ { id: "left-inlet", x: 0, y: 13.65625, type: "control", direction: "in" },
1844
+ { id: "right-outlet", x: 47.5, y: 13.65625, type: "control", direction: "out" }
1845
+ ],
1846
+ svgContent: `
1847
+ <g transform="scale(0.5)">
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)" />
1885
+ </g>
1886
+ `,
1887
+ metadata: {
1888
+ description: "Vannefold heat exchanger block with side inlet configuration",
1889
+ tags: ["heat-exchanger", "vannefold", "fold", "middle"]
1890
+ }
1891
+ },
1892
+ "vannefold-top": {
1893
+ id: "vannefold-top",
1894
+ name: "Vannefold Block (Top)",
1895
+ category: "valve",
1896
+ viewBox: { x: -0.25, y: -0.25, width: 47.5, height: 30.392578 },
1897
+ ports: [
1898
+ { id: "bottom-inlet", x: 0, y: 17.5, type: "control", direction: "in" },
1899
+ { id: "top-outlet", x: 24, y: 0, type: "control", direction: "out" }
1900
+ ],
1901
+ svgContent: `
1902
+ <g transform="scale(0.5)">
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"]
1748
1946
  }
1749
1947
  },
1750
- "pump-centrifugal": {
1751
- id: "pump-centrifugal",
1752
- name: "Centrifugal Pump",
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: 44, height: 33.788002 },
1953
+ ports: [
1954
+ { id: "top-inlet", x: 23.5, y: 0, type: "control", direction: "in" },
1955
+ { id: "bottom-outlet", x: 0, y: 21, type: "control", direction: "out" }
1956
+ ],
1957
+ svgContent: `
1958
+ <g transform="scale(0.5)">
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: 33.892578, height: 44.25 },
2017
+ ports: [
2018
+ { id: "left-inlet", x: 0, y: 20.75, type: "control", direction: "in" },
2019
+ { id: "right-outlet", x: 21, y: 43.5, type: "control", direction: "out" }
2020
+ ],
2021
+ svgContent: `
2022
+ <g transform="scale(0.5)">
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: 33.892578, height: 44.25 },
2080
+ ports: [
2081
+ { id: "right-inlet", x: 33.892578, y: 20.75, type: "control", direction: "in" },
2082
+ { id: "left-outlet", x: 12.392578, y: 43.75, type: "control", direction: "out" }
2083
+ ],
2084
+ svgContent: `
2085
+ <g transform="scale(0.5)">
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",
1753
2141
  category: "pump",
1754
- viewBox: { x: 0, y: 0, width: 80, height: 60 },
2142
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 23.34375 },
1755
2143
  ports: [
1756
- { id: "inlet", x: 10, y: 30, type: "inlet", direction: "in" },
1757
- { id: "outlet", x: 70, y: 30, type: "outlet", direction: "out" }
2144
+ { id: "right-outlet", x: 25.3125, y: 15.46875, type: "control", direction: "out" },
2145
+ { id: "left-outlet", x: 0, y: 15.46875, type: "control", direction: "out" },
2146
+ { id: "left-inlet", x: 25.3125, y: 9.84375, type: "control", direction: "in" },
2147
+ { id: "right-inlet", x: 0, y: 9.84375, type: "control", direction: "in" },
2148
+ { id: "top-inlet", x: 12.65625, y: 0, type: "control", direction: "in" }
1758
2149
  ],
1759
2150
  svgContent: `
1760
- <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"/>
2151
+ <g transform="scale(0.0984375) 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
+ />
1772
2405
  </g>
1773
2406
  `,
1774
2407
  metadata: {
1775
- description: "Centrifugal pump for fluid transfer",
1776
- tags: ["pump", "centrifugal", "transfer"]
2408
+ description: "Pixer mixer/agitator vessel with top-mounted drive",
2409
+ tags: ["vessel", "mixer", "agitator", "pixer", "tank"]
1777
2410
  }
1778
2411
  },
1779
- "vessel-tank": {
1780
- id: "vessel-tank",
1781
- name: "Storage Tank",
2412
+ "pixer-barb": {
2413
+ id: "pixer-barb",
2414
+ name: "Pixer (Hose Barb)",
2415
+ category: "pump",
2416
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 23.34375 },
2417
+ ports: [
2418
+ { id: "right-outlet", x: 25.3125, y: 15.46875, type: "control", direction: "out" },
2419
+ { id: "left-outlet", x: 0, y: 15.46875, type: "control", direction: "out" },
2420
+ { id: "left-inlet", x: 25.3125, y: 9.84375, type: "control", direction: "in" },
2421
+ { id: "right-inlet", x: 0, y: 9.84375, type: "control", direction: "in" },
2422
+ { id: "top-inlet", x: 12.65625, y: 0, type: "control", direction: "in" }
2423
+ ],
2424
+ svgContent: `
2425
+ <g transform="scale(0.0984375) 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",
1782
2706
  category: "vessel",
1783
- viewBox: { x: 0, y: 0, width: 60, height: 80 },
2707
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 45.5 },
2708
+ ports: [
2709
+ { id: "top", type: "inlet", x: 12.65625, y: 0 },
2710
+ { id: "left", type: "inlet", x: 2.5, y: 19.5 },
2711
+ { id: "right", type: "outlet", x: 22.8125, y: 19.5 },
2712
+ { id: "bottom", type: "outlet", x: 12.65625, y: 45.5 }
2713
+ ],
2714
+ svgContent: `
2715
+ <g transform="scale(0.0984375) 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
+ "hollow-fiber": {
2781
+ id: "hollow-fiber",
2782
+ name: "Hollow Fiber",
2783
+ category: "other",
2784
+ viewBox: { x: 0, y: 0, width: 30, height: 109 },
2785
+ ports: [
2786
+ { id: "top-left", type: "outlet", x: 10, y: 2.75 },
2787
+ { id: "top-right", type: "outlet", x: 24.5, y: 22.25 },
2788
+ { id: "bottom-left", type: "inlet", x: 10, y: 106.25 },
2789
+ { id: "bottom-right", type: "inlet", x: 24.5, y: 87.25 }
2790
+ ],
2791
+ svgContent: `
2792
+ <g transform="scale(0.5)">
2793
+ <!-- Top left flange -->
2794
+ <rect x="10.5" y="0.5" width="20" height="10" rx="1.5" ry="1.5" fill="var(--symbol-fill, #eaeaea)" stroke="var(--symbol-stroke, #000000)" />
2795
+
2796
+ <!-- Top right flange -->
2797
+ <rect x="39.5" y="39.5" width="20" height="10" rx="1.5" ry="1.5" fill="var(--symbol-fill, #eaeaea)" stroke="var(--symbol-stroke, #000000)" />
2798
+
2799
+ <!-- Bottom left flange -->
2800
+ <rect x="10.5" y="207.5" width="20" height="10" rx="1.5" ry="1.5" fill="var(--symbol-fill, #eaeaea)" stroke="var(--symbol-stroke, #000000)" />
2801
+
2802
+ <!-- Bottom right flange -->
2803
+ <rect x="39.5" y="169.5" width="20" height="10" rx="1.5" ry="1.5" fill="var(--symbol-fill, #eaeaea)" stroke="var(--symbol-stroke, #000000)" />
2804
+
2805
+ <!-- Main vessel body -->
2806
+ <rect x="0.5" y="9.5" width="40" height="199.5" rx="6" ry="6" fill="var(--symbol-fill, #eaeaea)" stroke="var(--symbol-stroke, #000000)" />
2807
+ </g>
2808
+ `,
2809
+ metadata: {
2810
+ description: "Hollow fiber filter module with multiple inlet/outlet connections",
2811
+ tags: ["filter", "hollow-fiber", "filtration", "separation", "membrane"]
2812
+ }
2813
+ },
2814
+ "psr-f": {
2815
+ id: "psr-f",
2816
+ name: "PSR F",
2817
+ category: "instrument",
2818
+ viewBox: { x: 0, y: 0, width: 28.5, height: 28.5 },
1784
2819
  ports: [
1785
- { id: "inlet", x: 30, y: 10, type: "inlet", direction: "in" },
1786
- { id: "outlet", x: 30, y: 70, type: "outlet", direction: "out" }
2820
+ { id: "top", type: "signal", x: 14.25, y: 7.125 },
2821
+ { id: "right", type: "signal", x: 21.375, y: 14.25 },
2822
+ { id: "bottom", type: "signal", x: 14.25, y: 21.375 },
2823
+ { id: "left", type: "signal", x: 7.125, y: 14.25 }
1787
2824
  ],
1788
2825
  svgContent: `
1789
- <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"/>
2826
+ <g transform="scale(0.25) translate(28.5, 28.5)">
2827
+ <!-- Circle background -->
2828
+ <ellipse
2829
+ cx="28.5"
2830
+ cy="28.5"
2831
+ rx="28.5"
2832
+ ry="28.5"
2833
+ stroke="var(--symbol-stroke, #000000)"
2834
+ stroke-width="1"
2835
+ fill="var(--symbol-fill, #ffffff)"
2836
+ />
2837
+
2838
+ <!-- Letter F -->
2839
+ <text
2840
+ x="28.5"
2841
+ y="38"
2842
+ text-anchor="middle"
2843
+ font-family="'Times New Roman', Georgia, serif"
2844
+ font-size="34"
2845
+ font-weight="normal"
2846
+ font-style="italic"
2847
+ fill="var(--symbol-stroke, #000000)"
2848
+ >F</text>
1800
2849
  </g>
1801
2850
  `,
1802
2851
  metadata: {
1803
- description: "Storage vessel for liquids",
1804
- tags: ["vessel", "tank", "storage"]
2852
+ description: "PSR F instrument symbol - Flow indicator",
2853
+ tags: ["instrument", "psr", "flow", "indicator"]
1805
2854
  }
1806
2855
  },
1807
- "instrument-indicator": {
1808
- id: "instrument-indicator",
1809
- name: "Instrument Indicator",
2856
+ "psr-p": {
2857
+ id: "psr-p",
2858
+ name: "PSR P",
1810
2859
  category: "instrument",
1811
- viewBox: { x: 0, y: 0, width: 40, height: 40 },
2860
+ viewBox: { x: 0, y: 0, width: 28.5, height: 28.5 },
1812
2861
  ports: [
1813
- { id: "signal", x: 20, y: 40, type: "signal", direction: "bi" }
2862
+ { id: "top", type: "signal", x: 14.25, y: 7.125 },
2863
+ { id: "right", type: "signal", x: 21.375, y: 14.25 },
2864
+ { id: "bottom", type: "signal", x: 14.25, y: 21.375 },
2865
+ { id: "left", type: "signal", x: 7.125, y: 14.25 }
1814
2866
  ],
1815
2867
  svgContent: `
1816
- <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"/>
2868
+ <g transform="scale(0.25) translate(28.5, 28.5)">
2869
+ <!-- Circle background -->
2870
+ <ellipse
2871
+ cx="28.5"
2872
+ cy="28.5"
2873
+ rx="28.5"
2874
+ ry="28.5"
2875
+ stroke="var(--symbol-stroke, #000000)"
2876
+ stroke-width="1"
2877
+ fill="var(--symbol-fill, #ffffff)"
2878
+ />
2879
+
2880
+ <!-- Letter P -->
2881
+ <text
2882
+ x="28.5"
2883
+ y="38"
2884
+ text-anchor="middle"
2885
+ font-family="'Times New Roman', Georgia, serif"
2886
+ font-size="34"
2887
+ font-weight="normal"
2888
+ font-style="italic"
2889
+ fill="var(--symbol-stroke, #000000)"
2890
+ >P</text>
1823
2891
  </g>
1824
2892
  `,
1825
2893
  metadata: {
1826
- description: "Generic instrument indicator",
1827
- tags: ["instrument", "indicator", "measurement"]
2894
+ description: "PSR P instrument symbol - Pressure indicator",
2895
+ tags: ["instrument", "psr", "pressure", "indicator"]
1828
2896
  }
1829
2897
  }
1830
2898
  };
2899
+ var customSymbols = {};
2900
+ function registerSymbol(symbol) {
2901
+ customSymbols[symbol.id] = symbol;
2902
+ }
2903
+ function registerSymbols(symbols) {
2904
+ if (Array.isArray(symbols)) {
2905
+ symbols.forEach((symbol) => registerSymbol(symbol));
2906
+ } else {
2907
+ Object.values(symbols).forEach((symbol) => registerSymbol(symbol));
2908
+ }
2909
+ }
1831
2910
  function getSymbolDefinition(symbolId) {
1832
- return mockSymbolLibrary[symbolId];
2911
+ return customSymbols[symbolId] ?? mockSymbolLibrary[symbolId];
1833
2912
  }
1834
2913
  function getAvailableSymbols() {
1835
- return Object.keys(mockSymbolLibrary);
2914
+ return [...Object.keys(customSymbols), ...Object.keys(mockSymbolLibrary)];
1836
2915
  }
1837
2916
 
1838
2917
  // src/components/PIDCanvas/NodeRenderer.tsx
@@ -1904,6 +2983,24 @@ function NodeRenderer({
1904
2983
  `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
1905
2984
  );
1906
2985
  }
2986
+ const getStatusColor = (status) => {
2987
+ switch (status) {
2988
+ case "running":
2989
+ return "#22c55e";
2990
+ // Green
2991
+ case "alarm":
2992
+ return "#ef4444";
2993
+ // Red
2994
+ case "warning":
2995
+ return "#f59e0b";
2996
+ // Orange
2997
+ case "stopped":
2998
+ default:
2999
+ return "#edeeef";
3000
+ }
3001
+ };
3002
+ const fillColor = node.customColors?.fill ?? getStatusColor(node.status);
3003
+ const strokeColor = node.customColors?.stroke ?? "rgb(0, 0, 0)";
1907
3004
  return /* @__PURE__ */ jsxs9(
1908
3005
  "g",
1909
3006
  {
@@ -1912,6 +3009,11 @@ function NodeRenderer({
1912
3009
  "data-selected": isSelected,
1913
3010
  "data-dragging": isDragging,
1914
3011
  transform: transforms.join(" "),
3012
+ style: {
3013
+ "--symbol-fill": fillColor,
3014
+ "--symbol-stroke": strokeColor,
3015
+ cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
3016
+ },
1915
3017
  onClick: (e) => {
1916
3018
  e.stopPropagation();
1917
3019
  onNodeClick?.(node.id, e);
@@ -1931,9 +3033,6 @@ function NodeRenderer({
1931
3033
  onNodeDragStart?.(node.id, e);
1932
3034
  }
1933
3035
  },
1934
- style: {
1935
- cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
1936
- },
1937
3036
  children: [
1938
3037
  isSelected && /* @__PURE__ */ jsx13(
1939
3038
  "rect",
@@ -1986,6 +3085,9 @@ function PipeRenderer({
1986
3085
  onPipeClick,
1987
3086
  enableWaypointEditing = false,
1988
3087
  onWaypointDragStart,
3088
+ onWaypointClick,
3089
+ selectedWaypointPipeId,
3090
+ selectedWaypointIndex,
1989
3091
  editingPipeId,
1990
3092
  draggingWaypointIndex,
1991
3093
  waypointDragOffset,
@@ -2006,7 +3108,7 @@ function PipeRenderer({
2006
3108
  });
2007
3109
  const pointsString = displayPoints.map((point) => `${point.x},${point.y}`).join(" ");
2008
3110
  const pipeStyle = pipe.style || {};
2009
- const stroke = isSelected ? "#3b82f6" : pipeStyle.stroke || "#263238";
3111
+ const stroke = isSelected ? "#3b82f6" : pipeStyle.stroke || "#94a1b8";
2010
3112
  const strokeWidth = isSelected ? (pipeStyle.strokeWidth || 3) + 2 : pipeStyle.strokeWidth || 3;
2011
3113
  const strokeDasharray = pipeStyle.strokeDasharray;
2012
3114
  const opacity = pipeStyle.opacity !== void 0 ? pipeStyle.opacity : 1;
@@ -2055,10 +3157,11 @@ function PipeRenderer({
2055
3157
  ] }),
2056
3158
  displayPoints.map((point, idx) => {
2057
3159
  const isEndpoint = idx === 0 || idx === displayPoints.length - 1;
3160
+ const isSelectedWaypoint = selectedWaypointPipeId === pipe.id && selectedWaypointIndex === idx;
2058
3161
  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;
3162
+ const waypointSize = isSelectedWaypoint ? 8 : isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
3163
+ const waypointOpacity = isSelectedWaypoint ? 1 : isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
3164
+ const waypointColor = isSelectedWaypoint ? "#3b82f6" : isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
2062
3165
  return /* @__PURE__ */ jsx14(
2063
3166
  "circle",
2064
3167
  {
@@ -2074,6 +3177,12 @@ function PipeRenderer({
2074
3177
  cursor: showEditingControls ? isDragging ? "grabbing" : "grab" : "default"
2075
3178
  },
2076
3179
  pointerEvents: showEditingControls ? "all" : "none",
3180
+ onClick: (e) => {
3181
+ if (showEditingControls) {
3182
+ e.stopPropagation();
3183
+ onWaypointClick?.(pipe.id, idx);
3184
+ }
3185
+ },
2077
3186
  onMouseDown: (e) => {
2078
3187
  if (showEditingControls) {
2079
3188
  e.stopPropagation();
@@ -2150,7 +3259,13 @@ function PipeRenderer({
2150
3259
 
2151
3260
  // src/components/PIDCanvas/DataOverlay.tsx
2152
3261
  import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
2153
- function DataOverlay({ nodeId, x, y, telemetry }) {
3262
+ function DataOverlay({
3263
+ nodeId,
3264
+ x,
3265
+ y,
3266
+ telemetry,
3267
+ scale = 1
3268
+ }) {
2154
3269
  const { value, display, history } = telemetry;
2155
3270
  const showValue = display?.showValue ?? true;
2156
3271
  const showStatus = display?.showStatus ?? true;
@@ -2190,9 +3305,9 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2190
3305
  const displayText = value.unit ? `${formattedValue} ${value.unit}` : formattedValue;
2191
3306
  const sparklinePath = (() => {
2192
3307
  if (!showSparkline || !history || history.length < 2) return "";
2193
- const width = 70;
2194
- const height = 20;
2195
- const padding = 5;
3308
+ const width = 70 * scale;
3309
+ const height = 20 * scale;
3310
+ const padding = 5 * scale;
2196
3311
  const effectiveWidth = width - padding * 2;
2197
3312
  const effectiveHeight = height - padding * 2;
2198
3313
  const min = Math.min(...history);
@@ -2206,21 +3321,22 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2206
3321
  return `M ${points.join(" L ")}`;
2207
3322
  })();
2208
3323
  const hasSparkline = showSparkline && sparklinePath;
2209
- const overlayHeight = label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22;
3324
+ const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale;
3325
+ const overlayWidth = 80 * scale;
2210
3326
  const displayX = x + offsetX;
2211
3327
  const displayY = y + offsetY;
2212
3328
  return /* @__PURE__ */ jsxs11("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
2213
3329
  /* @__PURE__ */ jsx15(
2214
3330
  "rect",
2215
3331
  {
2216
- x: displayX - 40,
2217
- y: displayY - 25,
2218
- width: "80",
3332
+ x: displayX - overlayWidth / 2,
3333
+ y: displayY - 25 * scale,
3334
+ width: overlayWidth,
2219
3335
  height: overlayHeight,
2220
- rx: "4",
3336
+ rx: 4 * scale,
2221
3337
  fill: colors.bg,
2222
3338
  stroke: colors.border,
2223
- strokeWidth: "1",
3339
+ strokeWidth: 1 * scale,
2224
3340
  opacity: "0.95",
2225
3341
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
2226
3342
  }
@@ -2229,9 +3345,9 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2229
3345
  "text",
2230
3346
  {
2231
3347
  x: displayX,
2232
- y: displayY - 15,
3348
+ y: displayY - 15 * scale,
2233
3349
  textAnchor: "middle",
2234
- fontSize: "8",
3350
+ fontSize: 8 * scale,
2235
3351
  fontWeight: "600",
2236
3352
  fill: finalTextColor,
2237
3353
  opacity: "0.9",
@@ -2242,31 +3358,41 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2242
3358
  "text",
2243
3359
  {
2244
3360
  x: displayX,
2245
- y: label ? displayY - 3 : displayY - 9,
3361
+ y: label ? displayY - 3 * scale : displayY - 9 * scale,
2246
3362
  textAnchor: "middle",
2247
- fontSize: "11",
3363
+ fontSize: 11 * scale,
2248
3364
  fontWeight: "700",
2249
3365
  fill: finalTextColor,
2250
3366
  letterSpacing: "0.02em",
2251
3367
  children: displayText
2252
3368
  }
2253
3369
  ),
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",
3370
+ showStatus && value.status !== "normal" && /* @__PURE__ */ jsx15(
3371
+ "circle",
2256
3372
  {
2257
- attributeName: "opacity",
2258
- values: "0.9;0.3;0.9",
2259
- dur: "1.5s",
2260
- repeatCount: "indefinite"
3373
+ cx: displayX + 35 * scale,
3374
+ cy: displayY - 18 * scale,
3375
+ r: 3 * scale,
3376
+ fill: finalTextColor,
3377
+ opacity: "0.9",
3378
+ children: /* @__PURE__ */ jsx15(
3379
+ "animate",
3380
+ {
3381
+ attributeName: "opacity",
3382
+ values: "0.9;0.3;0.9",
3383
+ dur: "1.5s",
3384
+ repeatCount: "indefinite"
3385
+ }
3386
+ )
2261
3387
  }
2262
- ) }),
3388
+ ),
2263
3389
  value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ jsxs11(
2264
3390
  "text",
2265
3391
  {
2266
3392
  x: displayX,
2267
- y: displayY + 5,
3393
+ y: displayY + 5 * scale,
2268
3394
  textAnchor: "middle",
2269
- fontSize: "7",
3395
+ fontSize: 7 * scale,
2270
3396
  fontWeight: "500",
2271
3397
  fill: finalTextColor,
2272
3398
  opacity: "0.7",
@@ -2277,13 +3403,13 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2277
3403
  ]
2278
3404
  }
2279
3405
  ),
2280
- hasSparkline && /* @__PURE__ */ jsx15("g", { transform: `translate(${displayX - 35}, ${displayY + (label ? 5 : 0)})`, children: /* @__PURE__ */ jsx15(
3406
+ hasSparkline && /* @__PURE__ */ jsx15("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ jsx15(
2281
3407
  "path",
2282
3408
  {
2283
3409
  d: sparklinePath,
2284
3410
  fill: "none",
2285
3411
  stroke: sparklineColor || finalTextColor,
2286
- strokeWidth: "1.5",
3412
+ strokeWidth: 1.5 * scale,
2287
3413
  strokeLinecap: "round",
2288
3414
  strokeLinejoin: "round",
2289
3415
  opacity: "0.9"
@@ -2292,8 +3418,145 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2292
3418
  ] });
2293
3419
  }
2294
3420
 
3421
+ // src/components/PIDCanvas/PositionPanel.tsx
3422
+ import { useState as useState7, useEffect as useEffect4 } from "react";
3423
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3424
+ function PositionPanel({
3425
+ selectedNode,
3426
+ selectedWaypoint,
3427
+ onPositionChange,
3428
+ onWaypointPositionChange,
3429
+ position = "top-right"
3430
+ }) {
3431
+ const [x, setX] = useState7("");
3432
+ const [y, setY] = useState7("");
3433
+ useEffect4(() => {
3434
+ if (selectedNode) {
3435
+ setX(selectedNode.transform.x.toFixed(2));
3436
+ setY(selectedNode.transform.y.toFixed(2));
3437
+ } else if (selectedWaypoint) {
3438
+ setX(selectedWaypoint.position.x.toFixed(2));
3439
+ setY(selectedWaypoint.position.y.toFixed(2));
3440
+ }
3441
+ }, [selectedNode, selectedWaypoint]);
3442
+ if (!selectedNode && !selectedWaypoint) {
3443
+ return null;
3444
+ }
3445
+ const handleXChange = (value) => {
3446
+ setX(value);
3447
+ const numValue = parseFloat(value);
3448
+ if (!isNaN(numValue)) {
3449
+ if (selectedNode && onPositionChange) {
3450
+ onPositionChange(selectedNode.id, numValue, selectedNode.transform.y);
3451
+ } else if (selectedWaypoint && onWaypointPositionChange) {
3452
+ onWaypointPositionChange(
3453
+ selectedWaypoint.pipeId,
3454
+ selectedWaypoint.pointIndex,
3455
+ numValue,
3456
+ selectedWaypoint.position.y
3457
+ );
3458
+ }
3459
+ }
3460
+ };
3461
+ const handleYChange = (value) => {
3462
+ setY(value);
3463
+ const numValue = parseFloat(value);
3464
+ if (!isNaN(numValue)) {
3465
+ if (selectedNode && onPositionChange) {
3466
+ onPositionChange(selectedNode.id, selectedNode.transform.x, numValue);
3467
+ } else if (selectedWaypoint && onWaypointPositionChange) {
3468
+ onWaypointPositionChange(
3469
+ selectedWaypoint.pipeId,
3470
+ selectedWaypoint.pointIndex,
3471
+ selectedWaypoint.position.x,
3472
+ numValue
3473
+ );
3474
+ }
3475
+ }
3476
+ };
3477
+ const handleBlur = () => {
3478
+ if (selectedNode) {
3479
+ setX(selectedNode.transform.x.toFixed(2));
3480
+ setY(selectedNode.transform.y.toFixed(2));
3481
+ } else if (selectedWaypoint) {
3482
+ setX(selectedWaypoint.position.x.toFixed(2));
3483
+ setY(selectedWaypoint.position.y.toFixed(2));
3484
+ }
3485
+ };
3486
+ const positionStyles = {
3487
+ "top-left": { top: 10, left: 10 },
3488
+ "top-right": { top: 10, right: 10 },
3489
+ "bottom-left": { bottom: 10, left: 10 },
3490
+ "bottom-right": { bottom: 10, right: 10 }
3491
+ };
3492
+ return /* @__PURE__ */ jsxs12(
3493
+ "div",
3494
+ {
3495
+ style: {
3496
+ position: "absolute",
3497
+ ...positionStyles[position],
3498
+ backgroundColor: "white",
3499
+ border: "1px solid #ccc",
3500
+ borderRadius: "4px",
3501
+ padding: "12px",
3502
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
3503
+ zIndex: 1e3,
3504
+ minWidth: "180px",
3505
+ fontFamily: "system-ui, -apple-system, sans-serif",
3506
+ fontSize: "13px"
3507
+ },
3508
+ children: [
3509
+ /* @__PURE__ */ jsx16("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
3510
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
3511
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3512
+ /* @__PURE__ */ jsx16("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
3513
+ /* @__PURE__ */ jsx16(
3514
+ "input",
3515
+ {
3516
+ type: "number",
3517
+ value: x,
3518
+ onChange: (e) => handleXChange(e.target.value),
3519
+ onBlur: handleBlur,
3520
+ style: {
3521
+ flex: 1,
3522
+ padding: "4px 8px",
3523
+ border: "1px solid #ddd",
3524
+ borderRadius: "3px",
3525
+ fontSize: "13px"
3526
+ },
3527
+ step: "1"
3528
+ }
3529
+ )
3530
+ ] }),
3531
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3532
+ /* @__PURE__ */ jsx16("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
3533
+ /* @__PURE__ */ jsx16(
3534
+ "input",
3535
+ {
3536
+ type: "number",
3537
+ value: y,
3538
+ onChange: (e) => handleYChange(e.target.value),
3539
+ onBlur: handleBlur,
3540
+ style: {
3541
+ flex: 1,
3542
+ padding: "4px 8px",
3543
+ border: "1px solid #ddd",
3544
+ borderRadius: "3px",
3545
+ fontSize: "13px"
3546
+ },
3547
+ step: "1"
3548
+ }
3549
+ )
3550
+ ] })
3551
+ ] }),
3552
+ /* @__PURE__ */ jsx16("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
3553
+ ]
3554
+ }
3555
+ );
3556
+ }
3557
+
2295
3558
  // src/diagram/hooks/useViewBox.ts
2296
- import { useState as useState7, useCallback as useCallback3, useRef as useRef2, useEffect as useEffect4 } from "react";
3559
+ import { useState as useState8, useCallback as useCallback3, useRef as useRef2, useEffect as useEffect5 } from "react";
2297
3560
  function useViewBox(options) {
2298
3561
  const {
2299
3562
  initialViewBox,
@@ -2304,7 +3567,7 @@ function useViewBox(options) {
2304
3567
  zoomSensitivity = 1e-3,
2305
3568
  allowLeftClickPan = true
2306
3569
  } = options;
2307
- const [viewBox, setViewBox] = useState7(initialViewBox);
3570
+ const [viewBox, setViewBox] = useState8(initialViewBox);
2308
3571
  const svgRef = useRef2(null);
2309
3572
  const isPanningRef = useRef2(false);
2310
3573
  const lastMousePosRef = useRef2({ x: 0, y: 0 });
@@ -2375,7 +3638,7 @@ function useViewBox(options) {
2375
3638
  height: newHeight
2376
3639
  });
2377
3640
  }, [initialViewBox]);
2378
- useEffect4(() => {
3641
+ useEffect5(() => {
2379
3642
  if (!enableZoom || !svgRef.current) return;
2380
3643
  const svg = svgRef.current;
2381
3644
  const handleWheel = (e) => {
@@ -2408,7 +3671,7 @@ function useViewBox(options) {
2408
3671
  svg.addEventListener("wheel", handleWheel, { passive: false });
2409
3672
  return () => svg.removeEventListener("wheel", handleWheel);
2410
3673
  }, [enableZoom, zoomSensitivity, minZoom, initialViewBox]);
2411
- useEffect4(() => {
3674
+ useEffect5(() => {
2412
3675
  if (!enablePan || !svgRef.current) return;
2413
3676
  const svg = svgRef.current;
2414
3677
  const handleMouseDown = (e) => {
@@ -2484,7 +3747,7 @@ function useViewBox(options) {
2484
3747
  window.removeEventListener("keyup", handleKeyUp);
2485
3748
  };
2486
3749
  }, [enablePan, allowLeftClickPan]);
2487
- useEffect4(() => {
3750
+ useEffect5(() => {
2488
3751
  if (!svgRef.current) return;
2489
3752
  if (!enablePan && !enableZoom) return;
2490
3753
  const svg = svgRef.current;
@@ -2614,14 +3877,14 @@ function useViewBox(options) {
2614
3877
  }
2615
3878
 
2616
3879
  // src/diagram/hooks/useSelection.ts
2617
- import { useState as useState8, useCallback as useCallback4 } from "react";
3880
+ import { useState as useState9, useCallback as useCallback4 } from "react";
2618
3881
  function useSelection(options = {}) {
2619
3882
  const {
2620
3883
  multiSelect = true,
2621
3884
  initialSelection = { selectedNodeIds: /* @__PURE__ */ new Set(), selectedPipeIds: /* @__PURE__ */ new Set() },
2622
3885
  onSelectionChange
2623
3886
  } = options;
2624
- const [selection, setSelection] = useState8(initialSelection);
3887
+ const [selection, setSelection] = useState9(initialSelection);
2625
3888
  const notifyChange = useCallback4(
2626
3889
  (newSelection) => {
2627
3890
  if (onSelectionChange) {
@@ -2719,14 +3982,14 @@ function useSelection(options = {}) {
2719
3982
  }
2720
3983
 
2721
3984
  // src/diagram/hooks/useTelemetry.ts
2722
- import { useState as useState9, useCallback as useCallback5, useEffect as useEffect5, useRef as useRef3 } from "react";
3985
+ import { useState as useState10, useCallback as useCallback5, useEffect as useEffect6, useRef as useRef3 } from "react";
2723
3986
  function useTelemetry(options = {}) {
2724
3987
  const {
2725
3988
  initialBindings = [],
2726
3989
  onTelemetryChange,
2727
3990
  refreshInterval = 0
2728
3991
  } = options;
2729
- const [telemetry, setTelemetry] = useState9(() => {
3992
+ const [telemetry, setTelemetry] = useState10(() => {
2730
3993
  const map = /* @__PURE__ */ new Map();
2731
3994
  initialBindings.forEach((binding) => {
2732
3995
  map.set(binding.nodeId, binding);
@@ -2847,7 +4110,7 @@ function useTelemetry(options = {}) {
2847
4110
  },
2848
4111
  [notifyChange]
2849
4112
  );
2850
- useEffect5(() => {
4113
+ useEffect6(() => {
2851
4114
  if (refreshInterval <= 0) return;
2852
4115
  const intervalId = setInterval(() => {
2853
4116
  notifyChange(telemetryRef.current);
@@ -2864,8 +4127,50 @@ function useTelemetry(options = {}) {
2864
4127
  };
2865
4128
  }
2866
4129
 
4130
+ // src/diagram/hooks/useTelemetryStatus.ts
4131
+ import { useEffect as useEffect7 } from "react";
4132
+ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
4133
+ const { enabled = true, mapStatus } = options;
4134
+ useEffect7(() => {
4135
+ if (!enabled) return;
4136
+ const defaultMapStatus = (telemetryStatus) => {
4137
+ switch (telemetryStatus) {
4138
+ case "normal":
4139
+ return "running";
4140
+ case "warning":
4141
+ return "warning";
4142
+ case "alarm":
4143
+ case "fault":
4144
+ return "alarm";
4145
+ case "off":
4146
+ return "stopped";
4147
+ default:
4148
+ return "stopped";
4149
+ }
4150
+ };
4151
+ const statusMapper = mapStatus || defaultMapStatus;
4152
+ let needsUpdate = false;
4153
+ const updatedNodes = diagram.nodes.map((node) => {
4154
+ const binding = telemetry.get(node.id);
4155
+ if (!binding) return node;
4156
+ const newStatus = statusMapper(binding.value.status);
4157
+ if (node.status !== newStatus) {
4158
+ needsUpdate = true;
4159
+ return { ...node, status: newStatus };
4160
+ }
4161
+ return node;
4162
+ });
4163
+ if (needsUpdate) {
4164
+ setDiagram({
4165
+ ...diagram,
4166
+ nodes: updatedNodes
4167
+ });
4168
+ }
4169
+ }, [telemetry, diagram, setDiagram, enabled, mapStatus]);
4170
+ }
4171
+
2867
4172
  // src/diagram/hooks/useDrag.ts
2868
- import { useState as useState10, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect6 } from "react";
4173
+ import { useState as useState11, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect8 } from "react";
2869
4174
  function useDrag(options = {}) {
2870
4175
  const {
2871
4176
  onDragStart,
@@ -2875,13 +4180,13 @@ function useDrag(options = {}) {
2875
4180
  dragThreshold = 3,
2876
4181
  screenToWorld = (x, y) => ({ x, y })
2877
4182
  } = options;
2878
- const [dragState, setDragState] = useState10({
4183
+ const [dragState, setDragState] = useState11({
2879
4184
  isDragging: false,
2880
4185
  startPosition: null,
2881
4186
  offset: { x: 0, y: 0 },
2882
4187
  draggedId: null
2883
4188
  });
2884
- const [isListening, setIsListening] = useState10(false);
4189
+ const [isListening, setIsListening] = useState11(false);
2885
4190
  const dragStartRef = useRef4(null);
2886
4191
  const hasMovedRef = useRef4(false);
2887
4192
  const applySnap = useCallback6(
@@ -3050,7 +4355,7 @@ function useDrag(options = {}) {
3050
4355
  },
3051
4356
  [screenToWorld, applySnap, onDragEnd]
3052
4357
  );
3053
- useEffect6(() => {
4358
+ useEffect8(() => {
3054
4359
  if (!isListening) return;
3055
4360
  document.addEventListener("mousemove", handleMouseMove);
3056
4361
  document.addEventListener("mouseup", handleMouseUp);
@@ -3119,7 +4424,7 @@ function useDrag(options = {}) {
3119
4424
  }
3120
4425
 
3121
4426
  // src/diagram/hooks/useKeyboardShortcuts.ts
3122
- import { useEffect as useEffect7 } from "react";
4427
+ import { useEffect as useEffect9 } from "react";
3123
4428
  function useKeyboardShortcuts(options) {
3124
4429
  const {
3125
4430
  onDelete,
@@ -3130,7 +4435,7 @@ function useKeyboardShortcuts(options) {
3130
4435
  onSelectAll,
3131
4436
  enabled = true
3132
4437
  } = options;
3133
- useEffect7(() => {
4438
+ useEffect9(() => {
3134
4439
  if (!enabled) return;
3135
4440
  const handleKeyDown = (event) => {
3136
4441
  const target = event.target;
@@ -3239,7 +4544,7 @@ function nodeHasPort(node, portId) {
3239
4544
  }
3240
4545
 
3241
4546
  // src/components/PIDCanvas/PIDCanvas.tsx
3242
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4547
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3243
4548
  function PIDCanvas({
3244
4549
  diagram,
3245
4550
  className = "",
@@ -3266,16 +4571,30 @@ function PIDCanvas({
3266
4571
  telemetry,
3267
4572
  ...props
3268
4573
  }) {
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);
4574
+ const [localDiagram, setLocalDiagram] = useState12(diagram);
4575
+ const [dragOverPosition, setDragOverPosition] = useState12(null);
4576
+ const [isConnecting, setIsConnecting] = useState12(false);
4577
+ const [connectionSource, setConnectionSource] = useState12(null);
4578
+ const [connectionSourcePort, setConnectionSourcePort] = useState12(null);
4579
+ const [connectionCursor, setConnectionCursor] = useState12(null);
4580
+ const [hoveredNode, setHoveredNode] = useState12(null);
4581
+ const [hoveredPort, setHoveredPort] = useState12(null);
4582
+ const [selectedWaypoint, setSelectedWaypoint] = useState12(null);
4583
+ const historyStack = useRef5([]);
4584
+ const redoStack = useRef5([]);
4585
+ const isUndoRedoAction = useRef5(false);
4586
+ const isInternalChange = useRef5(false);
4587
+ const lastInternalDiagram = useRef5(null);
4588
+ useEffect10(() => {
4589
+ const isExternal = !isInternalChange.current && diagram !== lastInternalDiagram.current;
4590
+ if (isExternal) {
4591
+ setLocalDiagram(diagram);
4592
+ if (!isUndoRedoAction.current) {
4593
+ historyStack.current = [];
4594
+ redoStack.current = [];
4595
+ }
4596
+ }
4597
+ isInternalChange.current = false;
3279
4598
  }, [diagram]);
3280
4599
  const {
3281
4600
  viewBox: controlledViewBox,
@@ -3311,7 +4630,7 @@ function PIDCanvas({
3311
4630
  });
3312
4631
  return { minX, minY, maxX, maxY };
3313
4632
  }, [localDiagram.nodes]);
3314
- useEffect8(() => {
4633
+ useEffect10(() => {
3315
4634
  if (onMounted) {
3316
4635
  const controls = {
3317
4636
  fitToContent: (padding = 50) => {
@@ -3352,6 +4671,186 @@ function PIDCanvas({
3352
4671
  });
3353
4672
  const dragEnabled = features.dragNodes ?? false;
3354
4673
  const selectionEnabled = features.selection ?? false;
4674
+ const containerRef = useRef5(null);
4675
+ const pushToHistory = useCallback7((currentDiagram) => {
4676
+ if (isUndoRedoAction.current) return;
4677
+ const snapshot = JSON.parse(JSON.stringify(currentDiagram));
4678
+ historyStack.current.push(snapshot);
4679
+ if (historyStack.current.length > 15) {
4680
+ historyStack.current.shift();
4681
+ }
4682
+ redoStack.current = [];
4683
+ }, []);
4684
+ const undo = useCallback7(() => {
4685
+ if (historyStack.current.length === 0) return;
4686
+ isUndoRedoAction.current = true;
4687
+ const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
4688
+ redoStack.current.push(currentSnapshot);
4689
+ const previousState = historyStack.current.pop();
4690
+ isInternalChange.current = true;
4691
+ lastInternalDiagram.current = previousState;
4692
+ setLocalDiagram(previousState);
4693
+ onDiagramChange?.(previousState);
4694
+ setTimeout(() => {
4695
+ isUndoRedoAction.current = false;
4696
+ }, 0);
4697
+ }, [localDiagram, onDiagramChange]);
4698
+ const redo = useCallback7(() => {
4699
+ if (redoStack.current.length === 0) return;
4700
+ isUndoRedoAction.current = true;
4701
+ const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
4702
+ historyStack.current.push(currentSnapshot);
4703
+ const nextState = redoStack.current.pop();
4704
+ isInternalChange.current = true;
4705
+ lastInternalDiagram.current = nextState;
4706
+ setLocalDiagram(nextState);
4707
+ onDiagramChange?.(nextState);
4708
+ setTimeout(() => {
4709
+ isUndoRedoAction.current = false;
4710
+ }, 0);
4711
+ }, [localDiagram, onDiagramChange]);
4712
+ useEffect10(() => {
4713
+ const handleKeyDown = (e) => {
4714
+ const isCtrlOrCmd = e.ctrlKey || e.metaKey;
4715
+ if (isCtrlOrCmd && e.key === "z" && !e.shiftKey) {
4716
+ e.preventDefault();
4717
+ undo();
4718
+ } else if (isCtrlOrCmd && e.key === "z" && e.shiftKey) {
4719
+ e.preventDefault();
4720
+ redo();
4721
+ } else if (isCtrlOrCmd && e.key === "y") {
4722
+ e.preventDefault();
4723
+ redo();
4724
+ }
4725
+ };
4726
+ window.addEventListener("keydown", handleKeyDown);
4727
+ return () => window.removeEventListener("keydown", handleKeyDown);
4728
+ }, [undo, redo]);
4729
+ useEffect10(() => {
4730
+ if (!features.dragNodes) return;
4731
+ const handleKeyDown = (e) => {
4732
+ if (selection.selectedNodeIds.size === 0 && !selectedWaypoint) return;
4733
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
4734
+ return;
4735
+ }
4736
+ const nudgeAmount = e.shiftKey ? 10 : 1;
4737
+ let dx = 0;
4738
+ let dy = 0;
4739
+ switch (e.key) {
4740
+ case "ArrowLeft":
4741
+ dx = -nudgeAmount;
4742
+ e.preventDefault();
4743
+ break;
4744
+ case "ArrowRight":
4745
+ dx = nudgeAmount;
4746
+ e.preventDefault();
4747
+ break;
4748
+ case "ArrowUp":
4749
+ dy = -nudgeAmount;
4750
+ e.preventDefault();
4751
+ break;
4752
+ case "ArrowDown":
4753
+ dy = nudgeAmount;
4754
+ e.preventDefault();
4755
+ break;
4756
+ default:
4757
+ return;
4758
+ }
4759
+ if (selectedWaypoint) {
4760
+ pushToHistory(localDiagram);
4761
+ const updatedPipes = localDiagram.pipes.map((pipe) => {
4762
+ if (pipe.id === selectedWaypoint.pipeId) {
4763
+ const updatedRoutePoints = pipe.routePoints.map(
4764
+ (point, idx) => idx === selectedWaypoint.pointIndex ? { x: point.x + dx, y: point.y + dy } : point
4765
+ );
4766
+ return { ...pipe, routePoints: updatedRoutePoints };
4767
+ }
4768
+ return pipe;
4769
+ });
4770
+ const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4771
+ isInternalChange.current = true;
4772
+ lastInternalDiagram.current = updatedDiagram;
4773
+ setLocalDiagram(updatedDiagram);
4774
+ onDiagramChange?.(updatedDiagram);
4775
+ return;
4776
+ }
4777
+ if (selectionEnabled && selection.selectedNodeIds.size > 0) {
4778
+ pushToHistory(localDiagram);
4779
+ const updatedNodes = localDiagram.nodes.map(
4780
+ (node) => selection.selectedNodeIds.has(node.id) ? {
4781
+ ...node,
4782
+ transform: {
4783
+ ...node.transform,
4784
+ x: node.transform.x + dx,
4785
+ y: node.transform.y + dy
4786
+ }
4787
+ } : node
4788
+ );
4789
+ const updatedDiagram = { ...localDiagram, nodes: updatedNodes };
4790
+ isInternalChange.current = true;
4791
+ lastInternalDiagram.current = updatedDiagram;
4792
+ setLocalDiagram(updatedDiagram);
4793
+ onDiagramChange?.(updatedDiagram);
4794
+ selection.selectedNodeIds.forEach((nodeId) => {
4795
+ const node = updatedNodes.find((n) => n.id === nodeId);
4796
+ if (node) {
4797
+ onNodeMove?.(nodeId, node.transform.x, node.transform.y);
4798
+ }
4799
+ });
4800
+ }
4801
+ };
4802
+ window.addEventListener("keydown", handleKeyDown);
4803
+ return () => window.removeEventListener("keydown", handleKeyDown);
4804
+ }, [
4805
+ selectionEnabled,
4806
+ features.dragNodes,
4807
+ selection.selectedNodeIds,
4808
+ selectedWaypoint,
4809
+ localDiagram,
4810
+ onDiagramChange,
4811
+ onNodeMove,
4812
+ pushToHistory
4813
+ ]);
4814
+ const handlePositionChange = useCallback7(
4815
+ (nodeId, x, y) => {
4816
+ pushToHistory(localDiagram);
4817
+ const updatedNodes = localDiagram.nodes.map(
4818
+ (node) => node.id === nodeId ? {
4819
+ ...node,
4820
+ transform: {
4821
+ ...node.transform,
4822
+ x,
4823
+ y
4824
+ }
4825
+ } : node
4826
+ );
4827
+ const updatedDiagram = { ...localDiagram, nodes: updatedNodes };
4828
+ isInternalChange.current = true;
4829
+ lastInternalDiagram.current = updatedDiagram;
4830
+ setLocalDiagram(updatedDiagram);
4831
+ onDiagramChange?.(updatedDiagram);
4832
+ onNodeMove?.(nodeId, x, y);
4833
+ },
4834
+ [localDiagram, onDiagramChange, onNodeMove, pushToHistory]
4835
+ );
4836
+ const handleWaypointPositionChange = useCallback7(
4837
+ (pipeId, pointIndex, x, y) => {
4838
+ pushToHistory(localDiagram);
4839
+ const updatedPipes = localDiagram.pipes.map((pipe) => {
4840
+ if (pipe.id === pipeId) {
4841
+ const updatedRoutePoints = pipe.routePoints.map(
4842
+ (point, idx) => idx === pointIndex ? { x, y } : point
4843
+ );
4844
+ return { ...pipe, routePoints: updatedRoutePoints };
4845
+ }
4846
+ return pipe;
4847
+ });
4848
+ const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4849
+ setLocalDiagram(updatedDiagram);
4850
+ onDiagramChange?.(updatedDiagram);
4851
+ },
4852
+ [localDiagram, onDiagramChange, pushToHistory]
4853
+ );
3355
4854
  const handleDragEnd = useCallback7(
3356
4855
  (nodeId, offset, finalPosition) => {
3357
4856
  const dragDistance = Math.sqrt(offset.x ** 2 + offset.y ** 2);
@@ -3361,6 +4860,7 @@ function PIDCanvas({
3361
4860
  }
3362
4861
  return;
3363
4862
  }
4863
+ pushToHistory(localDiagram);
3364
4864
  const updatedNodes = localDiagram.nodes.map(
3365
4865
  (node) => node.id === nodeId ? {
3366
4866
  ...node,
@@ -3398,11 +4898,13 @@ function PIDCanvas({
3398
4898
  nodes: updatedNodes,
3399
4899
  pipes: updatedPipes
3400
4900
  };
4901
+ isInternalChange.current = true;
4902
+ lastInternalDiagram.current = updatedDiagram;
3401
4903
  setLocalDiagram(updatedDiagram);
3402
4904
  onDiagramChange?.(updatedDiagram);
3403
4905
  onNodeMove?.(nodeId, finalPosition.x, finalPosition.y);
3404
4906
  },
3405
- [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode]
4907
+ [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode, pushToHistory]
3406
4908
  );
3407
4909
  const { dragState, startDrag } = useDrag({
3408
4910
  snapToGrid: features.snapToGrid ?? 0,
@@ -3413,6 +4915,7 @@ function PIDCanvas({
3413
4915
  snapToGrid: features.snapToGrid ?? 0,
3414
4916
  screenToWorld,
3415
4917
  onDragEnd: (pipeId, _offset, finalPosition) => {
4918
+ pushToHistory(localDiagram);
3416
4919
  const [actualPipeId, pointIndex] = pipeId.split(":");
3417
4920
  const idx = parseInt(pointIndex, 10);
3418
4921
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -3423,6 +4926,8 @@ function PIDCanvas({
3423
4926
  return { ...pipe, routePoints: updatedRoutePoints };
3424
4927
  });
3425
4928
  const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4929
+ isInternalChange.current = true;
4930
+ lastInternalDiagram.current = updatedDiagram;
3426
4931
  setLocalDiagram(updatedDiagram);
3427
4932
  onDiagramChange?.(updatedDiagram);
3428
4933
  }
@@ -3435,6 +4940,15 @@ function PIDCanvas({
3435
4940
  },
3436
4941
  [localDiagram.nodes, startDrag]
3437
4942
  );
4943
+ const handleWaypointClick = useCallback7(
4944
+ (pipeId, pointIndex) => {
4945
+ setSelectedWaypoint({ pipeId, pointIndex });
4946
+ if (selectionEnabled) {
4947
+ clearSelection();
4948
+ }
4949
+ },
4950
+ [selectionEnabled, clearSelection]
4951
+ );
3438
4952
  const handleWaypointDragStart = useCallback7(
3439
4953
  (pipeId, pointIndex, event) => {
3440
4954
  const pipe = localDiagram.pipes.find((p) => p.id === pipeId);
@@ -3446,6 +4960,7 @@ function PIDCanvas({
3446
4960
  );
3447
4961
  const handlePipeSegmentClick = useCallback7(
3448
4962
  (pipeId, position, segmentIndex) => {
4963
+ pushToHistory(localDiagram);
3449
4964
  const updatedPipes = localDiagram.pipes.map((pipe) => {
3450
4965
  if (pipe.id !== pipeId) return pipe;
3451
4966
  const updatedRoutePoints = [
@@ -3459,13 +4974,14 @@ function PIDCanvas({
3459
4974
  setLocalDiagram(updatedDiagram);
3460
4975
  onDiagramChange?.(updatedDiagram);
3461
4976
  },
3462
- [localDiagram, onDiagramChange]
4977
+ [localDiagram, onDiagramChange, pushToHistory]
3463
4978
  );
3464
4979
  const telemetryEnabled = features.telemetry ?? false;
3465
4980
  const handleDelete = useCallback7(() => {
3466
4981
  if (!selectionEnabled) return;
3467
4982
  const { selectedNodeIds, selectedPipeIds } = selection;
3468
4983
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
4984
+ pushToHistory(localDiagram);
3469
4985
  const updatedNodes = localDiagram.nodes.filter((node) => !selectedNodeIds.has(node.id));
3470
4986
  const updatedPipes = localDiagram.pipes.filter(
3471
4987
  (pipe) => !selectedPipeIds.has(pipe.id) && !selectedNodeIds.has(pipe.fromNodeId) && !selectedNodeIds.has(pipe.toNodeId)
@@ -3475,11 +4991,21 @@ function PIDCanvas({
3475
4991
  nodes: updatedNodes,
3476
4992
  pipes: updatedPipes
3477
4993
  };
4994
+ isInternalChange.current = true;
4995
+ lastInternalDiagram.current = updatedDiagram;
3478
4996
  setLocalDiagram(updatedDiagram);
3479
4997
  clearSelection();
3480
4998
  onDiagramChange?.(updatedDiagram);
3481
4999
  onDelete?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3482
- }, [selection, localDiagram, selectionEnabled, clearSelection, onDiagramChange, onDelete]);
5000
+ }, [
5001
+ selection,
5002
+ localDiagram,
5003
+ selectionEnabled,
5004
+ clearSelection,
5005
+ onDiagramChange,
5006
+ onDelete,
5007
+ pushToHistory
5008
+ ]);
3483
5009
  const handleCopy = useCallback7(() => {
3484
5010
  if (!selectionEnabled) return;
3485
5011
  const { selectedNodeIds, selectedPipeIds } = selection;
@@ -3536,10 +5062,15 @@ function PIDCanvas({
3536
5062
  }
3537
5063
  onPipeClick?.(pipeId);
3538
5064
  };
3539
- const handleCanvasClick = () => {
5065
+ const handleCanvasClick = (e) => {
5066
+ const target = e.target;
5067
+ if (target.closest(".pipe-route-point")) {
5068
+ return;
5069
+ }
3540
5070
  if (selectionEnabled) {
3541
5071
  clearSelection();
3542
5072
  }
5073
+ setSelectedWaypoint(null);
3543
5074
  };
3544
5075
  const handleDragOver = useCallback7(
3545
5076
  (event) => {
@@ -3550,7 +5081,7 @@ function PIDCanvas({
3550
5081
  const worldPos = screenToWorld(event.clientX, event.clientY);
3551
5082
  setDragOverPosition(worldPos);
3552
5083
  },
3553
- [features.allowSymbolDrop, screenToWorld]
5084
+ [features.allowSymbolDrop, screenToWorld, svgRef]
3554
5085
  );
3555
5086
  const handleDrop = useCallback7(
3556
5087
  (event) => {
@@ -3563,7 +5094,7 @@ function PIDCanvas({
3563
5094
  const worldPos = screenToWorld(event.clientX, event.clientY);
3564
5095
  onSymbolDrop?.(symbolId, worldPos);
3565
5096
  },
3566
- [features.allowSymbolDrop, screenToWorld, onSymbolDrop]
5097
+ [features.allowSymbolDrop, screenToWorld, onSymbolDrop, svgRef]
3567
5098
  );
3568
5099
  const handleDragLeave = useCallback7(() => {
3569
5100
  setDragOverPosition(null);
@@ -3587,7 +5118,7 @@ function PIDCanvas({
3587
5118
  const distance = Math.sqrt(
3588
5119
  (worldPos.x - portPos.x) ** 2 + (worldPos.y - portPos.y) ** 2
3589
5120
  );
3590
- if (distance < 10) {
5121
+ if (distance < 15) {
3591
5122
  foundPort = port.id;
3592
5123
  foundNode = node.id;
3593
5124
  break;
@@ -3604,9 +5135,11 @@ function PIDCanvas({
3604
5135
  const { name } = diagram;
3605
5136
  const viewBox = controlledViewBox;
3606
5137
  const displayDiagram = localDiagram;
3607
- return /* @__PURE__ */ jsx16(
5138
+ const selectedNode = selectionEnabled && selection.selectedNodeIds.size === 1 ? localDiagram.nodes.find((n) => selection.selectedNodeIds.has(n.id)) || null : null;
5139
+ return /* @__PURE__ */ jsxs13(
3608
5140
  "div",
3609
5141
  {
5142
+ ref: containerRef,
3610
5143
  className: `pid-canvas ${className}`.trim(),
3611
5144
  style: {
3612
5145
  width: "100%",
@@ -3617,223 +5150,287 @@ function PIDCanvas({
3617
5150
  ...style
3618
5151
  },
3619
5152
  ...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(
5153
+ children: [
5154
+ /* @__PURE__ */ jsxs13(
5155
+ "svg",
5156
+ {
5157
+ ref: svgRef,
5158
+ width: "100%",
5159
+ height: "100%",
5160
+ viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
5161
+ preserveAspectRatio: "xMidYMid meet",
5162
+ style: {
5163
+ display: "block"
5164
+ },
5165
+ "aria-label": `P&ID diagram: ${name}`,
5166
+ role: "img",
5167
+ onClick: handleCanvasClick,
5168
+ onMouseMove: handleMouseMove,
5169
+ onDragOver: handleDragOver,
5170
+ onDrop: handleDrop,
5171
+ onDragLeave: handleDragLeave,
5172
+ children: [
5173
+ features.showGrid !== false ? /* @__PURE__ */ jsxs13(Fragment5, { children: [
5174
+ /* @__PURE__ */ jsxs13("defs", { children: [
5175
+ /* @__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" }) }),
5176
+ /* @__PURE__ */ jsxs13("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
5177
+ /* @__PURE__ */ jsx17("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
5178
+ /* @__PURE__ */ jsx17("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
5179
+ ] })
5180
+ ] }),
5181
+ /* @__PURE__ */ jsx17(
5182
+ "rect",
5183
+ {
5184
+ x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
5185
+ y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
5186
+ width: Math.ceil(viewBox.width * 3 / 25) * 25,
5187
+ height: Math.ceil(viewBox.height * 3 / 25) * 25,
5188
+ fill: "url(#grid-major)"
5189
+ }
5190
+ )
5191
+ ] }) : /* @__PURE__ */ jsx17(
3648
5192
  "rect",
3649
5193
  {
3650
5194
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3651
5195
  y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3652
5196
  width: Math.ceil(viewBox.width * 3 / 25) * 25,
3653
5197
  height: Math.ceil(viewBox.height * 3 / 25) * 25,
3654
- fill: "url(#grid-major)"
5198
+ fill: features.backgroundColor ?? "#ffffff"
3655
5199
  }
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,
5200
+ ),
5201
+ /* @__PURE__ */ jsx17(
5202
+ PipeRenderer,
3716
5203
  {
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
- }
5204
+ pipes: displayDiagram.pipes,
5205
+ selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
5206
+ onPipeClick: selectionEnabled ? handlePipeClick : void 0,
5207
+ enableWaypointEditing: false,
5208
+ onWaypointDragStart: void 0,
5209
+ editingPipeId: null,
5210
+ draggingWaypointIndex: null,
5211
+ waypointDragOffset: void 0,
5212
+ onPipeSegmentClick: void 0
3752
5213
  }
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",
5214
+ ),
5215
+ /* @__PURE__ */ jsx17(
5216
+ NodeRenderer,
5217
+ {
5218
+ nodes: displayDiagram.nodes,
5219
+ selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
5220
+ onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
5221
+ onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
5222
+ enableDrag: dragEnabled && !isConnecting,
5223
+ onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
5224
+ draggingNodeId: dragState.draggedId,
5225
+ dragOffset: dragState.offset
5226
+ }
5227
+ ),
5228
+ (features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx17(
5229
+ PipeRenderer,
5230
+ {
5231
+ pipes: displayDiagram.pipes,
5232
+ selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
5233
+ onPipeClick: void 0,
5234
+ enableWaypointEditing: true,
5235
+ onWaypointClick: handleWaypointClick,
5236
+ onWaypointDragStart: handleWaypointDragStart,
5237
+ selectedWaypointPipeId: selectedWaypoint?.pipeId || null,
5238
+ selectedWaypointIndex: selectedWaypoint?.pointIndex ?? null,
5239
+ editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
5240
+ draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
5241
+ waypointDragOffset: waypointDragState.offset,
5242
+ onPipeSegmentClick: handlePipeSegmentClick
5243
+ }
5244
+ ),
5245
+ telemetryEnabled && telemetry && /* @__PURE__ */ jsx17("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
5246
+ const binding = telemetry.get(node.id);
5247
+ if (!binding) return null;
5248
+ const symbol = getSymbolDefinition(node.symbolId);
5249
+ if (!symbol) return null;
5250
+ const overlayX = node.transform.x + symbol.viewBox.width / 2;
5251
+ const overlayY = node.transform.y + symbol.viewBox.height + 50;
5252
+ return /* @__PURE__ */ jsx17(
5253
+ DataOverlay,
3802
5254
  {
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
5255
+ nodeId: node.id,
5256
+ x: overlayX,
5257
+ y: overlayY,
5258
+ telemetry: binding,
5259
+ scale: features.telemetryScale ?? 1
3810
5260
  },
3811
- `${node.id}-${port.id}`
5261
+ `telemetry-${node.id}`
3812
5262
  );
3813
- });
3814
- }) })
3815
- ]
3816
- }
3817
- )
5263
+ }) }),
5264
+ dragOverPosition && /* @__PURE__ */ jsx17(
5265
+ "circle",
5266
+ {
5267
+ cx: dragOverPosition.x,
5268
+ cy: dragOverPosition.y,
5269
+ r: 20,
5270
+ fill: "#3b82f6",
5271
+ opacity: 0.3,
5272
+ stroke: "#3b82f6",
5273
+ strokeWidth: 2,
5274
+ pointerEvents: "none"
5275
+ }
5276
+ ),
5277
+ isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ jsx17("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
5278
+ const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
5279
+ if (!sourceNode) return null;
5280
+ const symbol = getSymbolDefinition(sourceNode.symbolId);
5281
+ let lineStartX = sourceNode.transform.x;
5282
+ let lineStartY = sourceNode.transform.y;
5283
+ if (connectionSourcePort && symbol?.ports) {
5284
+ const port = symbol.ports.find((p) => p.id === connectionSourcePort);
5285
+ if (port) {
5286
+ const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
5287
+ if (portPos) {
5288
+ lineStartX = portPos.x;
5289
+ lineStartY = portPos.y;
5290
+ }
5291
+ }
5292
+ }
5293
+ return /* @__PURE__ */ jsxs13(Fragment5, { children: [
5294
+ /* @__PURE__ */ jsx17(
5295
+ "circle",
5296
+ {
5297
+ cx: lineStartX,
5298
+ cy: lineStartY,
5299
+ r: 30,
5300
+ fill: "none",
5301
+ stroke: "#10b981",
5302
+ strokeWidth: 3,
5303
+ opacity: 0.6
5304
+ }
5305
+ ),
5306
+ /* @__PURE__ */ jsx17(
5307
+ "line",
5308
+ {
5309
+ x1: lineStartX,
5310
+ y1: lineStartY,
5311
+ x2: connectionCursor.x,
5312
+ y2: connectionCursor.y,
5313
+ stroke: "#10b981",
5314
+ strokeWidth: 2,
5315
+ strokeDasharray: "5,5",
5316
+ opacity: 0.8
5317
+ }
5318
+ ),
5319
+ /* @__PURE__ */ jsx17(
5320
+ "circle",
5321
+ {
5322
+ cx: connectionCursor.x,
5323
+ cy: connectionCursor.y,
5324
+ r: 8,
5325
+ fill: "#10b981",
5326
+ opacity: 0.6
5327
+ }
5328
+ )
5329
+ ] });
5330
+ })() }),
5331
+ features.connectionMode && /* @__PURE__ */ jsx17("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
5332
+ const symbol = getSymbolDefinition(node.symbolId);
5333
+ if (!symbol?.ports) return null;
5334
+ return symbol.ports.map((port) => {
5335
+ const portPos = getPortWorldPosition(node, port.id);
5336
+ if (!portPos) return null;
5337
+ const isHovered = hoveredPort === port.id && hoveredNode === node.id;
5338
+ const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
5339
+ return /* @__PURE__ */ jsxs13("g", { children: [
5340
+ /* @__PURE__ */ jsx17(
5341
+ "circle",
5342
+ {
5343
+ cx: portPos.x,
5344
+ cy: portPos.y,
5345
+ r: 15,
5346
+ fill: "transparent",
5347
+ style: { cursor: "pointer" },
5348
+ pointerEvents: "auto",
5349
+ onClick: (e) => {
5350
+ e.stopPropagation();
5351
+ if (isConnecting && connectionSource) {
5352
+ if (connectionSource !== node.id) {
5353
+ onPipeCreated?.(
5354
+ connectionSource,
5355
+ node.id,
5356
+ connectionSourcePort || void 0,
5357
+ port.id
5358
+ );
5359
+ setIsConnecting(false);
5360
+ setConnectionSource(null);
5361
+ setConnectionSourcePort(null);
5362
+ setConnectionCursor(null);
5363
+ setHoveredPort(null);
5364
+ }
5365
+ } else {
5366
+ setIsConnecting(true);
5367
+ setConnectionSource(node.id);
5368
+ setConnectionSourcePort(port.id);
5369
+ }
5370
+ }
5371
+ }
5372
+ ),
5373
+ /* @__PURE__ */ jsx17(
5374
+ "circle",
5375
+ {
5376
+ cx: portPos.x,
5377
+ cy: portPos.y,
5378
+ r: isHovered ? 8 : 5,
5379
+ fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
5380
+ stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
5381
+ strokeWidth: isHovered || isSourcePort ? 2 : 1,
5382
+ opacity: isHovered || isSourcePort ? 1 : 0.6,
5383
+ pointerEvents: "none"
5384
+ }
5385
+ )
5386
+ ] }, `${node.id}-${port.id}`);
5387
+ });
5388
+ }) })
5389
+ ]
5390
+ }
5391
+ ),
5392
+ features.dragNodes && /* @__PURE__ */ jsx17(
5393
+ PositionPanel,
5394
+ {
5395
+ selectedNode,
5396
+ selectedWaypoint: selectedWaypoint ? {
5397
+ pipeId: selectedWaypoint.pipeId,
5398
+ pointIndex: selectedWaypoint.pointIndex,
5399
+ position: localDiagram.pipes.find((p) => p.id === selectedWaypoint.pipeId)?.routePoints[selectedWaypoint.pointIndex] || { x: 0, y: 0 }
5400
+ } : null,
5401
+ onPositionChange: handlePositionChange,
5402
+ onWaypointPositionChange: handleWaypointPositionChange,
5403
+ position: "top-right"
5404
+ }
5405
+ )
5406
+ ]
3818
5407
  }
3819
5408
  );
3820
5409
  }
3821
5410
 
3822
5411
  // src/components/SymbolLibrary/SymbolLibrary.tsx
3823
- import { useState as useState12 } from "react";
3824
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
5412
+ import { useState as useState13 } from "react";
5413
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
3825
5414
  function SymbolLibrary({
3826
5415
  className = "",
3827
5416
  onSymbolDragStart,
3828
5417
  showCategories = true,
3829
5418
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
3830
5419
  }) {
3831
- const [selectedCategory, setSelectedCategory] = useState12("all");
3832
- const [searchQuery, setSearchQuery] = useState12("");
5420
+ const [selectedCategory, setSelectedCategory] = useState13("all");
5421
+ const [searchQuery, setSearchQuery] = useState13("");
3833
5422
  const symbolIds = getAvailableSymbols();
3834
5423
  const allSymbols = symbolIds.map((id) => getSymbolDefinition(id)).filter((symbol) => symbol !== void 0);
5424
+ const categoryMap = {
5425
+ Valves: "valve",
5426
+ Pumps: "pump",
5427
+ Tanks: "vessel",
5428
+ Instruments: "instrument",
5429
+ Displays: "display"
5430
+ };
3835
5431
  const filteredSymbols = allSymbols.filter((symbol) => {
3836
- const matchesCategory = selectedCategory === "all" || symbol.category === selectedCategory;
5432
+ const actualCategory = categoryMap[selectedCategory] || selectedCategory;
5433
+ const matchesCategory = selectedCategory === "all" || symbol.category === actualCategory;
3837
5434
  const matchesSearch = searchQuery === "" || symbol.name.toLowerCase().includes(searchQuery.toLowerCase()) || symbol.id.toLowerCase().includes(searchQuery.toLowerCase());
3838
5435
  return matchesCategory && matchesSearch;
3839
5436
  });
@@ -3842,9 +5439,9 @@ function SymbolLibrary({
3842
5439
  event.dataTransfer.effectAllowed = "copy";
3843
5440
  onSymbolDragStart?.(symbol.id, event);
3844
5441
  };
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(
5442
+ return /* @__PURE__ */ jsxs14("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
5443
+ /* @__PURE__ */ jsx18("div", { style: styles.header, children: /* @__PURE__ */ jsx18("h3", { style: styles.title, children: "Symbol Library" }) }),
5444
+ /* @__PURE__ */ jsx18("div", { style: styles.searchContainer, children: /* @__PURE__ */ jsx18(
3848
5445
  "input",
3849
5446
  {
3850
5447
  type: "text",
@@ -3854,8 +5451,8 @@ function SymbolLibrary({
3854
5451
  style: styles.searchInput
3855
5452
  }
3856
5453
  ) }),
3857
- showCategories && /* @__PURE__ */ jsxs13("div", { style: styles.categories, children: [
3858
- /* @__PURE__ */ jsx17(
5454
+ showCategories && /* @__PURE__ */ jsxs14("div", { style: styles.categories, children: [
5455
+ /* @__PURE__ */ jsx18(
3859
5456
  "button",
3860
5457
  {
3861
5458
  onClick: () => setSelectedCategory("all"),
@@ -3866,7 +5463,7 @@ function SymbolLibrary({
3866
5463
  children: "All"
3867
5464
  }
3868
5465
  ),
3869
- categories.map((category) => /* @__PURE__ */ jsx17(
5466
+ categories.map((category) => /* @__PURE__ */ jsx18(
3870
5467
  "button",
3871
5468
  {
3872
5469
  onClick: () => setSelectedCategory(category),
@@ -3879,7 +5476,7 @@ function SymbolLibrary({
3879
5476
  category
3880
5477
  ))
3881
5478
  ] }),
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(
5479
+ /* @__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
5480
  "div",
3884
5481
  {
3885
5482
  draggable: true,
@@ -3887,15 +5484,15 @@ function SymbolLibrary({
3887
5484
  style: styles.symbolCard,
3888
5485
  title: symbol.metadata?.description || symbol.name,
3889
5486
  children: [
3890
- /* @__PURE__ */ jsx17("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx17(
5487
+ /* @__PURE__ */ jsx18("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx18(
3891
5488
  "svg",
3892
5489
  {
3893
5490
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
3894
5491
  style: styles.symbolSvg,
3895
- children: /* @__PURE__ */ jsx17("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
5492
+ children: /* @__PURE__ */ jsx18("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
3896
5493
  }
3897
5494
  ) }),
3898
- /* @__PURE__ */ jsx17("div", { style: styles.symbolName, children: symbol.name })
5495
+ /* @__PURE__ */ jsx18("div", { style: styles.symbolName, children: symbol.name })
3899
5496
  ]
3900
5497
  },
3901
5498
  symbol.id
@@ -4004,7 +5601,7 @@ var styles = {
4004
5601
  };
4005
5602
 
4006
5603
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
4007
- import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
5604
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
4008
5605
  function NodeConfigPanel({
4009
5606
  node,
4010
5607
  binding,
@@ -4023,15 +5620,15 @@ function NodeConfigPanel({
4023
5620
  padding: "20px",
4024
5621
  ...style
4025
5622
  };
4026
- return /* @__PURE__ */ jsxs14("div", { className, style: defaultStyle, children: [
4027
- /* @__PURE__ */ jsxs14("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
5623
+ return /* @__PURE__ */ jsxs15("div", { className, style: defaultStyle, children: [
5624
+ /* @__PURE__ */ jsxs15("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
4028
5625
  "Configure: ",
4029
5626
  node.id
4030
5627
  ] }),
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(
5628
+ /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
5629
+ /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
5630
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
5631
+ /* @__PURE__ */ jsx19(
4035
5632
  "input",
4036
5633
  {
4037
5634
  type: "text",
@@ -4047,10 +5644,10 @@ function NodeConfigPanel({
4047
5644
  }
4048
5645
  }
4049
5646
  ),
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(
5647
+ /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5648
+ /* @__PURE__ */ jsxs15("div", { children: [
5649
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
5650
+ /* @__PURE__ */ jsx19(
4054
5651
  "input",
4055
5652
  {
4056
5653
  type: "number",
@@ -4071,9 +5668,9 @@ function NodeConfigPanel({
4071
5668
  }
4072
5669
  )
4073
5670
  ] }),
4074
- /* @__PURE__ */ jsxs14("div", { children: [
4075
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
4076
- /* @__PURE__ */ jsx18(
5671
+ /* @__PURE__ */ jsxs15("div", { children: [
5672
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
5673
+ /* @__PURE__ */ jsx19(
4077
5674
  "input",
4078
5675
  {
4079
5676
  type: "number",
@@ -4095,9 +5692,9 @@ function NodeConfigPanel({
4095
5692
  )
4096
5693
  ] })
4097
5694
  ] }),
4098
- /* @__PURE__ */ jsxs14("div", { children: [
4099
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
4100
- /* @__PURE__ */ jsx18(
5695
+ /* @__PURE__ */ jsxs15("div", { children: [
5696
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
5697
+ /* @__PURE__ */ jsx19(
4101
5698
  "input",
4102
5699
  {
4103
5700
  type: "number",
@@ -4117,9 +5714,9 @@ function NodeConfigPanel({
4117
5714
  }
4118
5715
  )
4119
5716
  ] }),
4120
- /* @__PURE__ */ jsxs14("div", { children: [
4121
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
4122
- /* @__PURE__ */ jsx18(
5717
+ /* @__PURE__ */ jsxs15("div", { children: [
5718
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
5719
+ /* @__PURE__ */ jsx19(
4123
5720
  "input",
4124
5721
  {
4125
5722
  type: "number",
@@ -4144,9 +5741,9 @@ function NodeConfigPanel({
4144
5741
  )
4145
5742
  ] })
4146
5743
  ] }),
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(
5744
+ /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
5745
+ /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
5746
+ !binding ? /* @__PURE__ */ jsx19(
4150
5747
  "button",
4151
5748
  {
4152
5749
  onClick: () => {
@@ -4189,9 +5786,9 @@ function NodeConfigPanel({
4189
5786
  },
4190
5787
  children: "+ Add Telemetry"
4191
5788
  }
4192
- ) : /* @__PURE__ */ jsxs14(Fragment6, { children: [
4193
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
4194
- /* @__PURE__ */ jsx18(
5789
+ ) : /* @__PURE__ */ jsxs15(Fragment6, { children: [
5790
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
5791
+ /* @__PURE__ */ jsx19(
4195
5792
  "input",
4196
5793
  {
4197
5794
  type: "number",
@@ -4213,8 +5810,8 @@ function NodeConfigPanel({
4213
5810
  }
4214
5811
  }
4215
5812
  ),
4216
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
4217
- /* @__PURE__ */ jsx18(
5813
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
5814
+ /* @__PURE__ */ jsx19(
4218
5815
  "input",
4219
5816
  {
4220
5817
  type: "text",
@@ -4236,8 +5833,8 @@ function NodeConfigPanel({
4236
5833
  }
4237
5834
  }
4238
5835
  ),
4239
- /* @__PURE__ */ jsxs14("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4240
- /* @__PURE__ */ jsx18(
5836
+ /* @__PURE__ */ jsxs15("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
5837
+ /* @__PURE__ */ jsx19(
4241
5838
  "input",
4242
5839
  {
4243
5840
  type: "checkbox",
@@ -4259,8 +5856,8 @@ function NodeConfigPanel({
4259
5856
  ),
4260
5857
  "Show Sparkline"
4261
5858
  ] }),
4262
- /* @__PURE__ */ jsxs14("div", { style: { marginTop: "12px" }, children: [
4263
- /* @__PURE__ */ jsx18(
5859
+ /* @__PURE__ */ jsxs15("div", { style: { marginTop: "12px" }, children: [
5860
+ /* @__PURE__ */ jsx19(
4264
5861
  "label",
4265
5862
  {
4266
5863
  style: {
@@ -4272,10 +5869,10 @@ function NodeConfigPanel({
4272
5869
  children: "Telemetry Position Offset:"
4273
5870
  }
4274
5871
  ),
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(
5872
+ /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5873
+ /* @__PURE__ */ jsxs15("div", { children: [
5874
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
5875
+ /* @__PURE__ */ jsx19(
4279
5876
  "input",
4280
5877
  {
4281
5878
  type: "number",
@@ -4302,9 +5899,9 @@ function NodeConfigPanel({
4302
5899
  }
4303
5900
  )
4304
5901
  ] }),
4305
- /* @__PURE__ */ jsxs14("div", { children: [
4306
- /* @__PURE__ */ jsx18("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
4307
- /* @__PURE__ */ jsx18(
5902
+ /* @__PURE__ */ jsxs15("div", { children: [
5903
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
5904
+ /* @__PURE__ */ jsx19(
4308
5905
  "input",
4309
5906
  {
4310
5907
  type: "number",
@@ -4332,8 +5929,8 @@ function NodeConfigPanel({
4332
5929
  )
4333
5930
  ] })
4334
5931
  ] }),
4335
- /* @__PURE__ */ jsxs14("div", { style: { marginTop: "16px" }, children: [
4336
- /* @__PURE__ */ jsx18(
5932
+ /* @__PURE__ */ jsxs15("div", { style: { marginTop: "16px" }, children: [
5933
+ /* @__PURE__ */ jsx19(
4337
5934
  "label",
4338
5935
  {
4339
5936
  style: {
@@ -4345,10 +5942,10 @@ function NodeConfigPanel({
4345
5942
  children: "Display Colors:"
4346
5943
  }
4347
5944
  ),
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(
5945
+ /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
5946
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
5947
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
5948
+ /* @__PURE__ */ jsx19(
4352
5949
  "input",
4353
5950
  {
4354
5951
  type: "color",
@@ -4374,7 +5971,7 @@ function NodeConfigPanel({
4374
5971
  }
4375
5972
  }
4376
5973
  ),
4377
- /* @__PURE__ */ jsx18(
5974
+ /* @__PURE__ */ jsx19(
4378
5975
  "input",
4379
5976
  {
4380
5977
  type: "text",
@@ -4404,10 +6001,10 @@ function NodeConfigPanel({
4404
6001
  )
4405
6002
  ] })
4406
6003
  ] }),
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(
6004
+ binding.display?.showSparkline && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6005
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
6006
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6007
+ /* @__PURE__ */ jsx19(
4411
6008
  "input",
4412
6009
  {
4413
6010
  type: "color",
@@ -4433,7 +6030,7 @@ function NodeConfigPanel({
4433
6030
  }
4434
6031
  }
4435
6032
  ),
4436
- /* @__PURE__ */ jsx18(
6033
+ /* @__PURE__ */ jsx19(
4437
6034
  "input",
4438
6035
  {
4439
6036
  type: "text",
@@ -4463,9 +6060,9 @@ function NodeConfigPanel({
4463
6060
  )
4464
6061
  ] })
4465
6062
  ] }),
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(
6063
+ /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6064
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
6065
+ /* @__PURE__ */ jsx19(
4469
6066
  "input",
4470
6067
  {
4471
6068
  type: "text",
@@ -4493,20 +6090,20 @@ function NodeConfigPanel({
4493
6090
  }
4494
6091
  }
4495
6092
  ),
4496
- /* @__PURE__ */ jsx18("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
6093
+ /* @__PURE__ */ jsx19("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
4497
6094
  ] })
4498
6095
  ] }),
4499
6096
  " "
4500
6097
  ] })
4501
6098
  ] })
4502
6099
  ] }),
4503
- binding && /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4504
- /* @__PURE__ */ jsx18("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
6100
+ binding && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
6101
+ /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
4505
6102
  ["highHigh", "high", "low", "lowLow"].map((key) => {
4506
6103
  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(
6104
+ return /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6105
+ /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
6106
+ /* @__PURE__ */ jsx19(
4510
6107
  "input",
4511
6108
  {
4512
6109
  type: "number",
@@ -4530,8 +6127,8 @@ function NodeConfigPanel({
4530
6127
  ] }, key);
4531
6128
  })
4532
6129
  ] }),
4533
- binding && /* @__PURE__ */ jsxs14("div", { style: { marginBottom: "20px" }, children: [
4534
- /* @__PURE__ */ jsx18("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
6130
+ binding && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
6131
+ /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
4535
6132
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
4536
6133
  const defaultColors = {
4537
6134
  normal: "#10b981",
@@ -4541,13 +6138,13 @@ function NodeConfigPanel({
4541
6138
  off: "#6b7280"
4542
6139
  };
4543
6140
  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: [
6141
+ return /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6142
+ /* @__PURE__ */ jsxs15("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4546
6143
  label,
4547
6144
  ":"
4548
6145
  ] }),
4549
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4550
- /* @__PURE__ */ jsx18(
6146
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6147
+ /* @__PURE__ */ jsx19(
4551
6148
  "input",
4552
6149
  {
4553
6150
  type: "color",
@@ -4568,7 +6165,7 @@ function NodeConfigPanel({
4568
6165
  }
4569
6166
  }
4570
6167
  ),
4571
- /* @__PURE__ */ jsx18(
6168
+ /* @__PURE__ */ jsx19(
4572
6169
  "input",
4573
6170
  {
4574
6171
  type: "text",
@@ -4595,7 +6192,7 @@ function NodeConfigPanel({
4595
6192
  ] }, status);
4596
6193
  })
4597
6194
  ] }),
4598
- /* @__PURE__ */ jsx18("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ jsx18(
6195
+ /* @__PURE__ */ jsx19("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ jsx19(
4599
6196
  "button",
4600
6197
  {
4601
6198
  onClick: onRemove,
@@ -4835,7 +6432,7 @@ var DEFAULT_THRESHOLDS = {
4835
6432
  };
4836
6433
 
4837
6434
  // src/diagram/hooks/useSimulation.ts
4838
- import { useEffect as useEffect9 } from "react";
6435
+ import { useEffect as useEffect11 } from "react";
4839
6436
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4840
6437
  const {
4841
6438
  interval = 2e3,
@@ -4846,7 +6443,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4846
6443
  historyLength = 10,
4847
6444
  generateValue
4848
6445
  } = options;
4849
- useEffect9(() => {
6446
+ useEffect11(() => {
4850
6447
  if (!enabled) return;
4851
6448
  const intervalId = setInterval(() => {
4852
6449
  const updates = [];
@@ -4943,11 +6540,14 @@ export {
4943
6540
  mockSymbolLibrary,
4944
6541
  nodeHasPort,
4945
6542
  overlayStyles,
6543
+ registerSymbol,
6544
+ registerSymbols,
4946
6545
  useDrag,
4947
6546
  useKeyboardShortcuts,
4948
6547
  useSelection,
4949
6548
  useSimulation,
4950
6549
  useTelemetry,
6550
+ useTelemetryStatus,
4951
6551
  useTheme,
4952
6552
  useViewBox,
4953
6553
  validateDiagram