@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.cjs CHANGED
@@ -78,11 +78,14 @@ __export(index_exports, {
78
78
  mockSymbolLibrary: () => mockSymbolLibrary,
79
79
  nodeHasPort: () => nodeHasPort,
80
80
  overlayStyles: () => overlayStyles,
81
+ registerSymbol: () => registerSymbol,
82
+ registerSymbols: () => registerSymbols,
81
83
  useDrag: () => useDrag,
82
84
  useKeyboardShortcuts: () => useKeyboardShortcuts,
83
85
  useSelection: () => useSelection,
84
86
  useSimulation: () => useSimulation,
85
87
  useTelemetry: () => useTelemetry,
88
+ useTelemetryStatus: () => useTelemetryStatus,
86
89
  useTheme: () => useTheme,
87
90
  useViewBox: () => useViewBox,
88
91
  validateDiagram: () => validateDiagram
@@ -1785,145 +1788,1224 @@ var ThemeToggle = ({
1785
1788
  };
1786
1789
 
1787
1790
  // src/components/PIDCanvas/PIDCanvas.tsx
1788
- var import_react13 = require("react");
1791
+ var import_react15 = require("react");
1789
1792
 
1790
1793
  // src/diagram/symbols/mockSymbolLibrary.ts
1791
1794
  var mockSymbolLibrary = {
1792
- "valve-ball": {
1793
- id: "valve-ball",
1794
- name: "Ball Valve",
1795
+ "valve-gate": {
1796
+ id: "valve-gate",
1797
+ name: "Gate Valve (Powered)",
1795
1798
  category: "valve",
1796
- viewBox: { x: 0, y: 0, width: 50, height: 50 },
1799
+ viewBox: { x: 0, y: 0, width: 26, height: 26 },
1797
1800
  ports: [
1798
- { id: "inlet", x: 0, y: 25, type: "inlet", direction: "in" },
1799
- { id: "outlet", x: 50, y: 25, type: "outlet", direction: "out" }
1801
+ { id: "inlet", x: 0, y: 18, type: "inlet", direction: "in" },
1802
+ { id: "outlet", x: 26, y: 18, type: "outlet", direction: "out" }
1800
1803
  ],
1801
1804
  svgContent: `
1802
- <g>
1803
- <!-- Valve body -->
1804
- <rect x="15" y="10" width="20" height="30" fill="#546e7a" stroke="#263238" stroke-width="2" rx="2"/>
1805
- <!-- Ball indicator -->
1806
- <circle cx="25" cy="25" r="6" fill="#ffa726" stroke="#e65100" stroke-width="1.5"/>
1807
- <!-- Inlet/outlet lines -->
1808
- <line x1="0" y1="25" x2="15" y2="25" stroke="#263238" stroke-width="3" stroke-linecap="round"/>
1809
- <line x1="35" y1="25" x2="50" y2="25" stroke="#263238" stroke-width="3" stroke-linecap="round"/>
1805
+ <g transform="scale(0.5)">
1806
+ <!-- Actuator box -->
1807
+ <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"/>
1808
+ <!-- Actuator stem -->
1809
+ <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"/>
1810
+ <!-- Valve body (two triangles forming diamond) -->
1811
+ <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"/>
1810
1812
  </g>
1811
1813
  `,
1812
1814
  metadata: {
1813
- description: "Ball valve for flow control",
1814
- tags: ["valve", "control", "shutoff"]
1815
+ description: "Gate valve with powered actuator",
1816
+ tags: ["valve", "gate", "powered", "actuator"]
1815
1817
  }
1816
1818
  },
1817
- "valve-gate": {
1818
- id: "valve-gate",
1819
- name: "Gate Valve (Powered)",
1819
+ "pump-positive-displacement": {
1820
+ id: "pump-positive-displacement",
1821
+ name: "Positive Displacement Pump",
1822
+ category: "pump",
1823
+ viewBox: { x: 0, y: 0, width: 28.125, height: 28.125 },
1824
+ ports: [
1825
+ { id: "inlet", x: 0, y: 14.0625, type: "inlet", direction: "in" },
1826
+ { id: "outlet", x: 28.125, y: 14.0625, type: "outlet", direction: "out" }
1827
+ ],
1828
+ svgContent: `
1829
+ <g transform="scale(0.28125)">
1830
+ <!-- Outer circle -->
1831
+ <ellipse
1832
+ cx="50"
1833
+ cy="50"
1834
+ rx="50"
1835
+ ry="50"
1836
+ fill="var(--symbol-fill, #edeeef)"
1837
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1838
+ stroke-width="1"
1839
+ />
1840
+
1841
+ <!-- Diagonal line from top to bottom-right -->
1842
+ <path
1843
+ d="M 50 0 L 100 50 L 50 100"
1844
+ fill="none"
1845
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1846
+ stroke-width="1"
1847
+ stroke-miterlimit="10"
1848
+ />
1849
+
1850
+ <!-- Center square -->
1851
+ <rect
1852
+ x="35"
1853
+ y="35"
1854
+ width="30"
1855
+ height="30"
1856
+ fill="none"
1857
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1858
+ stroke-width="1"
1859
+ />
1860
+ </g>
1861
+ `,
1862
+ metadata: {
1863
+ description: "Positive displacement pump (ISO standard)",
1864
+ tags: ["pump", "positive-displacement", "iso"]
1865
+ }
1866
+ },
1867
+ "vannefold-bottom": {
1868
+ id: "vannefold-bottom",
1869
+ name: "Vannefold Block (Bottom)",
1820
1870
  category: "valve",
1821
- viewBox: { x: 0, y: 0, width: 52, height: 52 },
1871
+ viewBox: { x: -0.25, y: -0.25, width: 44, height: 33.892578 },
1822
1872
  ports: [
1823
- { id: "inlet", x: 0, y: 36, type: "inlet", direction: "in" },
1824
- { id: "outlet", x: 52, y: 36, type: "outlet", direction: "out" }
1873
+ { id: "bottom-inlet", x: 0, y: 12.5, type: "control", direction: "in" },
1874
+ { id: "top-outlet", x: 24, y: 32.5, type: "control", direction: "out" }
1825
1875
  ],
1826
1876
  svgContent: `
1827
- <g>
1828
- <!-- Actuator box -->
1829
- <rect x="16.74" y="0" width="18.02" height="18.02" fill="#edeeef" stroke="#666666" pointer-events="all"/>
1830
- <!-- Actuator stem -->
1831
- <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"/>
1832
- <!-- Valve body (two triangles forming diamond) -->
1833
- <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"/>
1877
+ <g transform="scale(0.5)">
1878
+ <!-- Bottom inlet pipe -->
1879
+ <path
1880
+ 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"
1881
+ fill="none"
1882
+ stroke="var(--symbol-stroke, #000000)"
1883
+ stroke-miterlimit="10"
1884
+ />
1885
+
1886
+ <!-- Top outlet pipe -->
1887
+ <path
1888
+ 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"
1889
+ fill="none"
1890
+ stroke="var(--symbol-stroke, #000000)"
1891
+ stroke-miterlimit="10"
1892
+ />
1893
+
1894
+ <!-- Main block outer -->
1895
+ <rect
1896
+ x="7"
1897
+ y="0"
1898
+ width="80"
1899
+ height="59.785156"
1900
+ fill="var(--symbol-fill, #eaeaea)"
1901
+ stroke="var(--symbol-stroke, #000000)"
1902
+ />
1903
+
1904
+ <!-- Main block inner -->
1905
+ <rect
1906
+ x="9"
1907
+ y="2"
1908
+ width="76"
1909
+ height="55.785156"
1910
+ fill="var(--symbol-fill, #eaeaea)"
1911
+ stroke="var(--symbol-stroke, #000000)"
1912
+ />
1913
+
1914
+ <!-- Bolt holes -->
1915
+ <ellipse cx="15" cy="7" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1916
+ <ellipse cx="15" cy="7" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1917
+ <ellipse cx="79" cy="7" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1918
+ <ellipse cx="79" cy="7" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1919
+ <ellipse cx="15" cy="51.785156" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1920
+ <ellipse cx="15" cy="51.785156" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1921
+ <ellipse cx="79" cy="51.785156" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1922
+ <ellipse cx="79" cy="51.785156" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1923
+
1834
1924
  </g>
1835
1925
  `,
1836
1926
  metadata: {
1837
- description: "Gate valve with powered actuator",
1838
- tags: ["valve", "gate", "powered", "actuator"]
1927
+ description: "Vannefold heat exchanger block with bottom inlet configuration",
1928
+ tags: ["heat-exchanger", "vannefold", "fold", "bottom"]
1929
+ }
1930
+ },
1931
+ "vannefold-middle": {
1932
+ id: "vannefold-middle",
1933
+ name: "Vannefold Block (Middle)",
1934
+ category: "valve",
1935
+ viewBox: { x: -0.25, y: -0.25, width: 47.5, height: 26.548828 },
1936
+ ports: [
1937
+ { id: "left-inlet", x: 0, y: 13.65625, type: "control", direction: "in" },
1938
+ { id: "right-outlet", x: 47.5, y: 13.65625, type: "control", direction: "out" }
1939
+ ],
1940
+ svgContent: `
1941
+ <g transform="scale(0.5)">
1942
+ <!-- Left inlet pipe -->
1943
+ <path
1944
+ 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"
1945
+ fill="none"
1946
+ stroke="var(--symbol-stroke, #000000)"
1947
+ stroke-miterlimit="10"
1948
+ />
1949
+
1950
+ <!-- Main block outer -->
1951
+ <rect
1952
+ x="7"
1953
+ y="0"
1954
+ width="80"
1955
+ height="52.097656"
1956
+ fill="var(--symbol-fill, #eaeaea)"
1957
+ stroke="var(--symbol-stroke, #000000)"
1958
+ />
1959
+
1960
+ <!-- Main block inner -->
1961
+ <rect
1962
+ x="9"
1963
+ y="0"
1964
+ width="76"
1965
+ height="52.097656"
1966
+ fill="var(--symbol-fill, #eaeaea)"
1967
+ stroke="var(--symbol-stroke, #000000)"
1968
+ />
1969
+
1970
+ <!-- Bolt holes -->
1971
+ <ellipse cx="15" cy="5.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1972
+ <ellipse cx="15" cy="5.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1973
+ <ellipse cx="79" cy="5.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1974
+ <ellipse cx="79" cy="5.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1975
+ <ellipse cx="15" cy="47.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1976
+ <ellipse cx="15" cy="47.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1977
+ <ellipse cx="79" cy="47.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1978
+ <ellipse cx="79" cy="47.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1979
+ </g>
1980
+ `,
1981
+ metadata: {
1982
+ description: "Vannefold heat exchanger block with side inlet configuration",
1983
+ tags: ["heat-exchanger", "vannefold", "fold", "middle"]
1984
+ }
1985
+ },
1986
+ "vannefold-top": {
1987
+ id: "vannefold-top",
1988
+ name: "Vannefold Block (Top)",
1989
+ category: "valve",
1990
+ viewBox: { x: -0.25, y: -0.25, width: 47.5, height: 30.392578 },
1991
+ ports: [
1992
+ { id: "bottom-inlet", x: 0, y: 17.5, type: "control", direction: "in" },
1993
+ { id: "top-outlet", x: 24, y: 0, type: "control", direction: "out" }
1994
+ ],
1995
+ svgContent: `
1996
+ <g transform="scale(0.5)">
1997
+ <!-- Bottom inlet pipe -->
1998
+ <path
1999
+ 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"
2000
+ fill="none"
2001
+ stroke="var(--symbol-stroke, #000000)"
2002
+ stroke-miterlimit="10"
2003
+ />
2004
+
2005
+ <!-- Main block outer -->
2006
+ <rect
2007
+ x="7"
2008
+ y="0"
2009
+ width="80"
2010
+ height="59.785156"
2011
+ fill="var(--symbol-fill, #eaeaea)"
2012
+ stroke="var(--symbol-stroke, #000000)"
2013
+ />
2014
+
2015
+ <!-- Main block inner -->
2016
+ <rect
2017
+ x="9"
2018
+ y="2"
2019
+ width="76"
2020
+ height="57.785156"
2021
+ fill="var(--symbol-fill, #eaeaea)"
2022
+ stroke="var(--symbol-stroke, #000000)"
2023
+ />
2024
+
2025
+ <!-- Bolt holes -->
2026
+ <ellipse cx="15" cy="8" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2027
+ <ellipse cx="15" cy="8" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2028
+ <ellipse cx="79" cy="8" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2029
+ <ellipse cx="79" cy="8" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2030
+ <ellipse cx="15" cy="55" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2031
+ <ellipse cx="15" cy="55" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2032
+ <ellipse cx="79" cy="55" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2033
+ <ellipse cx="79" cy="55" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2034
+
2035
+ </g>
2036
+ `,
2037
+ metadata: {
2038
+ description: "Vannefold heat exchanger block with bottom inlet and top outlet configuration",
2039
+ tags: ["heat-exchanger", "vannefold", "fold", "top"]
2040
+ }
2041
+ },
2042
+ "vannefold-top-top-inlet": {
2043
+ id: "vannefold-top-top-inlet",
2044
+ name: "Vannefold Block (Top, Top Inlet)",
2045
+ category: "valve",
2046
+ viewBox: { x: 0, y: 0, width: 44, height: 33.788002 },
2047
+ ports: [
2048
+ { id: "top-inlet", x: 23.5, y: 0, type: "control", direction: "in" },
2049
+ { id: "bottom-outlet", x: 0, y: 21, type: "control", direction: "out" }
2050
+ ],
2051
+ svgContent: `
2052
+ <g transform="scale(0.5)">
2053
+ <!-- Top inlet pipe flange -->
2054
+ <path
2055
+ 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"
2056
+ fill="none"
2057
+ stroke="var(--symbol-stroke, #000000)"
2058
+ stroke-miterlimit="10"
2059
+ />
2060
+
2061
+ <!-- Bottom outlet pipe flange -->
2062
+ <path
2063
+ 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"
2064
+ fill="none"
2065
+ stroke="var(--symbol-stroke, #000000)"
2066
+ stroke-miterlimit="10"
2067
+ />
2068
+
2069
+ <!-- Main block outer -->
2070
+ <rect
2071
+ x="7"
2072
+ y="6.7908498"
2073
+ width="80"
2074
+ height="59.785156"
2075
+ fill="var(--symbol-fill, #eaeaea)"
2076
+ stroke="var(--symbol-stroke, #000000)"
2077
+ />
2078
+
2079
+ <!-- Main block inner -->
2080
+ <rect
2081
+ x="9"
2082
+ y="8.7908498"
2083
+ width="76"
2084
+ height="57.785156"
2085
+ fill="var(--symbol-fill, #eaeaea)"
2086
+ stroke="var(--symbol-stroke, #000000)"
2087
+ />
2088
+
2089
+ <!-- Bolt holes -->
2090
+ <ellipse cx="15" cy="14.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2091
+ <ellipse cx="15" cy="14.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2092
+ <ellipse cx="15" cy="61.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2093
+ <ellipse cx="15" cy="61.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2094
+ <ellipse cx="79" cy="14.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2095
+ <ellipse cx="79" cy="14.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2096
+ <ellipse cx="79" cy="61.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2097
+ <ellipse cx="79" cy="61.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2098
+
2099
+ </g>
2100
+ `,
2101
+ metadata: {
2102
+ description: "Vannefold heat exchanger block with top inlet and bottom outlet configuration",
2103
+ tags: ["heat-exchanger", "vannefold", "fold", "top", "top-inlet"]
2104
+ }
2105
+ },
2106
+ "vannefold-left": {
2107
+ id: "vannefold-left",
2108
+ name: "Vannefold Block (Left)",
2109
+ category: "valve",
2110
+ viewBox: { x: 0, y: 0, width: 33.892578, height: 44.25 },
2111
+ ports: [
2112
+ { id: "left-inlet", x: 0, y: 20.75, type: "control", direction: "in" },
2113
+ { id: "right-outlet", x: 21, y: 43.5, type: "control", direction: "out" }
2114
+ ],
2115
+ svgContent: `
2116
+ <g transform="scale(0.5)">
2117
+ <!-- Left inlet pipe flange -->
2118
+ <path
2119
+ 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"
2120
+ fill="none"
2121
+ stroke="var(--symbol-stroke, #000000)"
2122
+ stroke-miterlimit="10"
2123
+ />
2124
+
2125
+ <!-- Right outlet pipe flange -->
2126
+ <path
2127
+ 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"
2128
+ fill="none"
2129
+ stroke="var(--symbol-stroke, #000000)"
2130
+ stroke-miterlimit="10"
2131
+ />
2132
+
2133
+ <!-- Main block outer -->
2134
+ <rect
2135
+ x="7"
2136
+ y="0.5"
2137
+ width="59.785156"
2138
+ height="80"
2139
+ fill="var(--symbol-fill, #eaeaea)"
2140
+ stroke="var(--symbol-stroke, #000000)"
2141
+ />
2142
+
2143
+ <!-- Main block inner -->
2144
+ <rect
2145
+ x="9"
2146
+ y="2.5"
2147
+ width="57.785156"
2148
+ height="76"
2149
+ fill="var(--symbol-fill, #eaeaea)"
2150
+ stroke="var(--symbol-stroke, #000000)"
2151
+ />
2152
+
2153
+ <!-- Bolt holes -->
2154
+ <ellipse cx="15" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2155
+ <ellipse cx="15" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2156
+ <ellipse cx="62" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2157
+ <ellipse cx="62" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2158
+ <ellipse cx="15" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2159
+ <ellipse cx="15" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2160
+ <ellipse cx="62" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2161
+ <ellipse cx="62" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2162
+
2163
+ `,
2164
+ metadata: {
2165
+ description: "Vannefold heat exchanger block with left inlet and right outlet, vertical orientation",
2166
+ tags: ["heat-exchanger", "vannefold", "fold", "left", "vertical"]
2167
+ }
2168
+ },
2169
+ "vannefold-right": {
2170
+ id: "vannefold-right",
2171
+ name: "Vannefold Block (Right)",
2172
+ category: "valve",
2173
+ viewBox: { x: 0, y: 0, width: 33.892578, height: 44.25 },
2174
+ ports: [
2175
+ { id: "right-inlet", x: 33.892578, y: 20.75, type: "control", direction: "in" },
2176
+ { id: "left-outlet", x: 12.392578, y: 43.75, type: "control", direction: "out" }
2177
+ ],
2178
+ svgContent: `
2179
+ <g transform="scale(0.5)">
2180
+ <!-- Right inlet pipe flange -->
2181
+ <path
2182
+ 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"
2183
+ fill="none"
2184
+ stroke="var(--symbol-stroke, #000000)"
2185
+ stroke-miterlimit="10"
2186
+ />
2187
+
2188
+ <!-- Left outlet pipe flange -->
2189
+ <path
2190
+ 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"
2191
+ fill="none"
2192
+ stroke="var(--symbol-stroke, #000000)"
2193
+ stroke-miterlimit="10"
2194
+ />
2195
+
2196
+ <!-- Main block outer -->
2197
+ <rect
2198
+ x="0"
2199
+ y="0.5"
2200
+ width="59.785156"
2201
+ height="80"
2202
+ fill="var(--symbol-fill, #eaeaea)"
2203
+ stroke="var(--symbol-stroke, #000000)"
2204
+ />
2205
+
2206
+ <!-- Main block inner -->
2207
+ <rect
2208
+ x="0"
2209
+ y="2.5"
2210
+ width="57.785156"
2211
+ height="76"
2212
+ fill="var(--symbol-fill, #eaeaea)"
2213
+ stroke="var(--symbol-stroke, #000000)"
2214
+ />
2215
+
2216
+ <!-- Bolt holes -->
2217
+ <ellipse cx="51.785156" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2218
+ <ellipse cx="51.785156" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2219
+ <ellipse cx="4.785156" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2220
+ <ellipse cx="4.785156" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2221
+ <ellipse cx="51.785156" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2222
+ <ellipse cx="51.785156" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2223
+ <ellipse cx="4.785156" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2224
+ <ellipse cx="4.785156" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2225
+
2226
+ `,
2227
+ metadata: {
2228
+ description: "Vannefold heat exchanger block with right inlet and left outlet, vertical orientation",
2229
+ tags: ["heat-exchanger", "vannefold", "fold", "right", "vertical"]
2230
+ }
2231
+ },
2232
+ "pixer": {
2233
+ id: "pixer",
2234
+ name: "Pixer",
2235
+ category: "pump",
2236
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 23.34375 },
2237
+ ports: [
2238
+ { id: "right-outlet", x: 25.3125, y: 15.46875, type: "control", direction: "out" },
2239
+ { id: "left-outlet", x: 0, y: 15.46875, type: "control", direction: "out" },
2240
+ { id: "left-inlet", x: 25.3125, y: 9.84375, type: "control", direction: "in" },
2241
+ { id: "right-inlet", x: 0, y: 9.84375, type: "control", direction: "in" },
2242
+ { id: "top-inlet", x: 12.65625, y: 0, type: "control", direction: "in" }
2243
+ ],
2244
+ svgContent: `
2245
+ <g transform="scale(0.0984375) translate(-195, -186)">
2246
+ <!-- Top mounting flange -->
2247
+ <rect
2248
+ width="141.313"
2249
+ height="9.872"
2250
+ transform="translate(252.392 185.89)"
2251
+ stroke="var(--symbol-stroke, #000000)"
2252
+ stroke-miterlimit="10"
2253
+ stroke-width="1"
2254
+ fill="var(--symbol-fill, #eaeaea)"
2255
+ />
2256
+
2257
+ <!-- Motor housing top -->
2258
+ <rect
2259
+ width="106.614"
2260
+ height="2.468"
2261
+ transform="translate(269.742 199.918)"
2262
+ stroke="var(--symbol-stroke, #000000)"
2263
+ stroke-miterlimit="10"
2264
+ stroke-width="1"
2265
+ fill="var(--symbol-fill, #e0e0e0)"
2266
+ />
2267
+ <rect
2268
+ width="106.614"
2269
+ height="5.835"
2270
+ transform="translate(269.742 202.384)"
2271
+ stroke="var(--symbol-stroke, #000000)"
2272
+ stroke-miterlimit="10"
2273
+ stroke-width="1"
2274
+ fill="var(--symbol-fill, #e0e0e0)"
2275
+ />
2276
+ <rect
2277
+ width="106.614"
2278
+ height="2.468"
2279
+ transform="translate(269.742 208.222)"
2280
+ stroke="var(--symbol-stroke, #000000)"
2281
+ stroke-miterlimit="10"
2282
+ stroke-width="1"
2283
+ fill="var(--symbol-fill, #e0e0e0)"
2284
+ />
2285
+
2286
+ <!-- Bottom mounting flange -->
2287
+ <rect
2288
+ width="221.702"
2289
+ height="2.401"
2290
+ transform="translate(212.199 395.587)"
2291
+ stroke="var(--symbol-stroke, #000000)"
2292
+ stroke-miterlimit="10"
2293
+ stroke-width="1"
2294
+ fill="var(--symbol-fill, #e0e0e0)"
2295
+ />
2296
+ <rect
2297
+ width="221.702"
2298
+ height="6.269"
2299
+ transform="translate(212.199 397.988)"
2300
+ stroke="var(--symbol-stroke, #000000)"
2301
+ stroke-miterlimit="10"
2302
+ stroke-width="1"
2303
+ fill="var(--symbol-fill, #e0e0e0)"
2304
+ />
2305
+ <rect
2306
+ width="221.702"
2307
+ height="2.65"
2308
+ transform="translate(212.199 404.257)"
2309
+ stroke="var(--symbol-stroke, #000000)"
2310
+ stroke-miterlimit="10"
2311
+ stroke-width="1"
2312
+ fill="var(--symbol-fill, #e0e0e0)"
2313
+ />
2314
+
2315
+ <!-- Motor housing transition -->
2316
+ <path
2317
+ 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"
2318
+ transform="translate(-192.059 -32.959)"
2319
+ stroke="var(--symbol-stroke, #000000)"
2320
+ stroke-miterlimit="10"
2321
+ stroke-width="1"
2322
+ fill="var(--symbol-fill, #e0e0e0)"
2323
+ />
2324
+
2325
+ <!-- Bottom flange -->
2326
+ <rect
2327
+ width="256"
2328
+ height="9.872"
2329
+ transform="translate(195.05 411.066)"
2330
+ stroke="var(--symbol-stroke, #000000)"
2331
+ stroke-miterlimit="10"
2332
+ stroke-width="1"
2333
+ fill="var(--symbol-fill, #eaeaea)"
2334
+ />
2335
+ <path
2336
+ d="M195.05,1148.286l17.149-4.156H433.9l17.149,4.156Z"
2337
+ transform="translate(0 -737.221)"
2338
+ fill="var(--symbol-fill, #e2e2e2)"
2339
+ stroke="var(--symbol-stroke, #000000)"
2340
+ stroke-miterlimit="10"
2341
+ stroke-width="1"
2342
+ />
2343
+
2344
+ <!-- Vessel top cone -->
2345
+ <path
2346
+ d="M451.05,328.356h-256l74.692-34.946H376.358Z"
2347
+ transform="translate(0 -82.72)"
2348
+ stroke="var(--symbol-stroke, #000000)"
2349
+ stroke-miterlimit="10"
2350
+ stroke-width="1"
2351
+ fill="var(--symbol-fill, #eaeaea)"
2352
+ />
2353
+
2354
+ <!-- Main vessel body -->
2355
+ <rect
2356
+ width="256"
2357
+ height="59.803"
2358
+ transform="translate(195.05 245.636)"
2359
+ stroke="var(--symbol-stroke, #000000)"
2360
+ stroke-miterlimit="10"
2361
+ stroke-width="1"
2362
+ fill="var(--symbol-fill, #eaeaea)"
2363
+ />
2364
+ <rect
2365
+ width="256"
2366
+ height="76.892"
2367
+ transform="translate(195.05 308.417)"
2368
+ stroke="var(--symbol-stroke, #000000)"
2369
+ stroke-miterlimit="10"
2370
+ stroke-width="1"
2371
+ fill="var(--symbol-fill, #eaeaea)"
2372
+ />
2373
+
2374
+ <!-- Vessel bottom transitions -->
2375
+ <path
2376
+ d="M449.265,705.7H196.773l-1.654-1.465a.014.014,0,0,1,0-.023H451.078Z"
2377
+ transform="translate(-0.049 -398.769)"
2378
+ stroke="var(--symbol-stroke, #000000)"
2379
+ stroke-miterlimit="10"
2380
+ stroke-width="1"
2381
+ fill="var(--symbol-fill, #e0e0e0)"
2382
+ />
2383
+ <path
2384
+ d="M449.278,710.66H196.793l-1.663,1.462v.016H451.086s.012,0,0-.016Z"
2385
+ transform="translate(-0.062 -403.731)"
2386
+ stroke="var(--symbol-stroke, #000000)"
2387
+ stroke-miterlimit="10"
2388
+ stroke-width="1"
2389
+ fill="var(--symbol-fill, #e0e0e0)"
2390
+ />
2391
+ <path
2392
+ d="M433.9,1060.758H212.2L195.05,1050.48h256Z"
2393
+ transform="translate(0 -665.171)"
2394
+ stroke="var(--symbol-stroke, #000000)"
2395
+ stroke-miterlimit="10"
2396
+ stroke-width="1"
2397
+ fill="var(--symbol-fill, #e0e0e0)"
2398
+ />
2399
+
2400
+ <!-- Agitator blades/impellers (simplified without gradients) -->
2401
+ <ellipse
2402
+ cx="15.454"
2403
+ cy="6.096"
2404
+ rx="15.454"
2405
+ ry="6.096"
2406
+ transform="translate(307.486 228.161)"
2407
+ stroke="var(--symbol-stroke, #000000)"
2408
+ stroke-miterlimit="10"
2409
+ stroke-width="1"
2410
+ fill="var(--symbol-fill, #c0c0c0)"
2411
+ />
2412
+ <ellipse
2413
+ cx="15.454"
2414
+ cy="5.3"
2415
+ rx="15.454"
2416
+ ry="5.3"
2417
+ transform="translate(307.486 229.753)"
2418
+ stroke="var(--symbol-stroke, #000000)"
2419
+ stroke-miterlimit="10"
2420
+ stroke-width="1"
2421
+ fill="var(--symbol-fill, #d0d0d0)"
2422
+ />
2423
+ <ellipse
2424
+ cx="5.174"
2425
+ cy="15.117"
2426
+ rx="5.174"
2427
+ ry="15.117"
2428
+ transform="translate(367.144 236.079) rotate(-77.61)"
2429
+ stroke="var(--symbol-stroke, #000000)"
2430
+ stroke-miterlimit="10"
2431
+ stroke-width="1"
2432
+ fill="var(--symbol-fill, #d0d0d0)"
2433
+ />
2434
+ <path
2435
+ d="M947,380.271s1.631-2.83,8.084-2.235c5.427.5,16.927,2.855,20.574,8.26"
2436
+ transform="translate(-578.512 -147.764)"
2437
+ fill="none"
2438
+ stroke="var(--symbol-stroke, #000000)"
2439
+ stroke-miterlimit="10"
2440
+ stroke-width="1"
2441
+ />
2442
+ <ellipse
2443
+ cx="15.117"
2444
+ cy="5.174"
2445
+ rx="15.117"
2446
+ ry="5.174"
2447
+ transform="translate(247 232.438) rotate(-12.39)"
2448
+ stroke="var(--symbol-stroke, #000000)"
2449
+ stroke-miterlimit="10"
2450
+ stroke-width="1"
2451
+ fill="var(--symbol-fill, #d0d0d0)"
2452
+ />
2453
+ <path
2454
+ d="M456.439,380.271s-1.631-2.83-8.073-2.235c-5.43.5-16.93,2.855-20.576,8.26"
2455
+ transform="translate(-179.058 -147.764)"
2456
+ fill="none"
2457
+ stroke="var(--symbol-stroke, #000000)"
2458
+ stroke-miterlimit="10"
2459
+ stroke-width="1"
2460
+ />
2461
+ <ellipse
2462
+ cx="1.651"
2463
+ cy="15.082"
2464
+ rx="1.651"
2465
+ ry="15.082"
2466
+ transform="translate(406.71 230.174) rotate(-68.13)"
2467
+ stroke="var(--symbol-stroke, #000000)"
2468
+ stroke-miterlimit="10"
2469
+ stroke-width="1"
2470
+ fill="var(--symbol-fill, #d0d0d0)"
2471
+ />
2472
+ <path
2473
+ d="M1119,375.62a119.876,119.876,0,0,1,26.193,10.419"
2474
+ transform="translate(-710.84 -145.969)"
2475
+ fill="none"
2476
+ stroke="var(--symbol-stroke, #000000)"
2477
+ stroke-miterlimit="10"
2478
+ stroke-width="1"
2479
+ />
2480
+ <ellipse
2481
+ cx="15.082"
2482
+ cy="1.651"
2483
+ rx="15.082"
2484
+ ry="1.651"
2485
+ transform="translate(210.225 238.342) rotate(-21.87)"
2486
+ stroke="var(--symbol-stroke, #000000)"
2487
+ stroke-miterlimit="10"
2488
+ stroke-width="1"
2489
+ fill="var(--symbol-fill, #d0d0d0)"
2490
+ />
2491
+ <path
2492
+ d="M293.943,375.62a119.829,119.829,0,0,0-26.193,10.419"
2493
+ transform="translate(-55.932 -145.969)"
2494
+ fill="none"
2495
+ stroke="var(--symbol-stroke, #000000)"
2496
+ stroke-miterlimit="10"
2497
+ stroke-width="1"
2498
+ />
2499
+ </g>
2500
+ `,
2501
+ metadata: {
2502
+ description: "Pixer mixer/agitator vessel with top-mounted drive",
2503
+ tags: ["vessel", "mixer", "agitator", "pixer", "tank"]
1839
2504
  }
1840
2505
  },
1841
- "pump-centrifugal": {
1842
- id: "pump-centrifugal",
1843
- name: "Centrifugal Pump",
2506
+ "pixer-barb": {
2507
+ id: "pixer-barb",
2508
+ name: "Pixer (Hose Barb)",
1844
2509
  category: "pump",
1845
- viewBox: { x: 0, y: 0, width: 80, height: 60 },
2510
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 23.34375 },
1846
2511
  ports: [
1847
- { id: "inlet", x: 10, y: 30, type: "inlet", direction: "in" },
1848
- { id: "outlet", x: 70, y: 30, type: "outlet", direction: "out" }
2512
+ { id: "right-outlet", x: 25.3125, y: 15.46875, type: "control", direction: "out" },
2513
+ { id: "left-outlet", x: 0, y: 15.46875, type: "control", direction: "out" },
2514
+ { id: "left-inlet", x: 25.3125, y: 9.84375, type: "control", direction: "in" },
2515
+ { id: "right-inlet", x: 0, y: 9.84375, type: "control", direction: "in" },
2516
+ { id: "top-inlet", x: 12.65625, y: 0, type: "control", direction: "in" }
1849
2517
  ],
1850
2518
  svgContent: `
1851
- <g>
1852
- <!-- Pump casing -->
1853
- <circle cx="40" cy="30" r="22" fill="#4fc3f7" stroke="#0277bd" stroke-width="2.5"/>
1854
- <!-- Impeller -->
1855
- <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"
1856
- stroke="#01579b" stroke-width="2.5" stroke-linecap="round"/>
1857
- <!-- Center hub -->
1858
- <circle cx="40" cy="30" r="5" fill="#0277bd" stroke="#01579b" stroke-width="1.5"/>
1859
- <!-- Inlet pipe -->
1860
- <line x1="0" y1="30" x2="18" y2="30" stroke="#0277bd" stroke-width="4" stroke-linecap="round"/>
1861
- <!-- Outlet pipe -->
1862
- <line x1="62" y1="30" x2="80" y2="30" stroke="#0277bd" stroke-width="4" stroke-linecap="round"/>
2519
+ <g transform="scale(0.0984375) translate(-195, -186)">
2520
+ <!-- Hose barb ridges at top -->
2521
+ <rect
2522
+ width="20"
2523
+ height="2"
2524
+ transform="translate(313 173)"
2525
+ stroke="var(--symbol-stroke, #000000)"
2526
+ stroke-miterlimit="10"
2527
+ stroke-width="0.5"
2528
+ fill="var(--symbol-fill, #d0d0d0)"
2529
+ />
2530
+ <rect
2531
+ width="20"
2532
+ height="2"
2533
+ transform="translate(313 177)"
2534
+ stroke="var(--symbol-stroke, #000000)"
2535
+ stroke-miterlimit="10"
2536
+ stroke-width="0.5"
2537
+ fill="var(--symbol-fill, #d0d0d0)"
2538
+ />
2539
+ <rect
2540
+ width="20"
2541
+ height="2"
2542
+ transform="translate(313 181)"
2543
+ stroke="var(--symbol-stroke, #000000)"
2544
+ stroke-miterlimit="10"
2545
+ stroke-width="0.5"
2546
+ fill="var(--symbol-fill, #d0d0d0)"
2547
+ />
2548
+
2549
+ <!-- Taper from barb to motor housing -->
2550
+ <path
2551
+ d="M 313,183 L 269.742,199.918 L 376.356,199.918 L 333,183 Z"
2552
+ stroke="var(--symbol-stroke, #000000)"
2553
+ stroke-miterlimit="10"
2554
+ stroke-width="1"
2555
+ fill="var(--symbol-fill, #d0d0d0)"
2556
+ />
2557
+
2558
+ <!-- Motor housing top -->
2559
+ <rect
2560
+ width="106.614"
2561
+ height="2.468"
2562
+ transform="translate(269.742 199.918)"
2563
+ stroke="var(--symbol-stroke, #000000)"
2564
+ stroke-miterlimit="10"
2565
+ stroke-width="1"
2566
+ fill="var(--symbol-fill, #e0e0e0)"
2567
+ />
2568
+ <rect
2569
+ width="106.614"
2570
+ height="5.835"
2571
+ transform="translate(269.742 202.384)"
2572
+ stroke="var(--symbol-stroke, #000000)"
2573
+ stroke-miterlimit="10"
2574
+ stroke-width="1"
2575
+ fill="var(--symbol-fill, #e0e0e0)"
2576
+ />
2577
+ <rect
2578
+ width="106.614"
2579
+ height="2.468"
2580
+ transform="translate(269.742 208.222)"
2581
+ stroke="var(--symbol-stroke, #000000)"
2582
+ stroke-miterlimit="10"
2583
+ stroke-width="1"
2584
+ fill="var(--symbol-fill, #e0e0e0)"
2585
+ />
2586
+
2587
+ <!-- Bottom mounting flange -->
2588
+ <rect
2589
+ width="221.702"
2590
+ height="2.401"
2591
+ transform="translate(212.199 395.587)"
2592
+ stroke="var(--symbol-stroke, #000000)"
2593
+ stroke-miterlimit="10"
2594
+ stroke-width="1"
2595
+ fill="var(--symbol-fill, #e0e0e0)"
2596
+ />
2597
+ <rect
2598
+ width="221.702"
2599
+ height="6.269"
2600
+ transform="translate(212.199 397.988)"
2601
+ stroke="var(--symbol-stroke, #000000)"
2602
+ stroke-miterlimit="10"
2603
+ stroke-width="1"
2604
+ fill="var(--symbol-fill, #e0e0e0)"
2605
+ />
2606
+ <rect
2607
+ width="221.702"
2608
+ height="2.65"
2609
+ transform="translate(212.199 404.257)"
2610
+ stroke="var(--symbol-stroke, #000000)"
2611
+ stroke-miterlimit="10"
2612
+ stroke-width="1"
2613
+ fill="var(--symbol-fill, #e0e0e0)"
2614
+ />
2615
+
2616
+ <!-- Bottom flange -->
2617
+ <rect
2618
+ width="256"
2619
+ height="9.872"
2620
+ transform="translate(195.05 411.066)"
2621
+ stroke="var(--symbol-stroke, #000000)"
2622
+ stroke-miterlimit="10"
2623
+ stroke-width="1"
2624
+ fill="var(--symbol-fill, #eaeaea)"
2625
+ />
2626
+ <path
2627
+ d="M195.05,1148.286l17.149-4.156H433.9l17.149,4.156Z"
2628
+ transform="translate(0 -737.221)"
2629
+ fill="var(--symbol-fill, #e2e2e2)"
2630
+ stroke="var(--symbol-stroke, #000000)"
2631
+ stroke-miterlimit="10"
2632
+ stroke-width="1"
2633
+ />
2634
+
2635
+ <!-- Vessel top cone -->
2636
+ <path
2637
+ d="M451.05,328.356h-256l74.692-34.946H376.358Z"
2638
+ transform="translate(0 -82.72)"
2639
+ stroke="var(--symbol-stroke, #000000)"
2640
+ stroke-miterlimit="10"
2641
+ stroke-width="1"
2642
+ fill="var(--symbol-fill, #eaeaea)"
2643
+ />
2644
+
2645
+ <!-- Main vessel body -->
2646
+ <rect
2647
+ width="256"
2648
+ height="59.803"
2649
+ transform="translate(195.05 245.636)"
2650
+ stroke="var(--symbol-stroke, #000000)"
2651
+ stroke-miterlimit="10"
2652
+ stroke-width="1"
2653
+ fill="var(--symbol-fill, #eaeaea)"
2654
+ />
2655
+ <rect
2656
+ width="256"
2657
+ height="76.892"
2658
+ transform="translate(195.05 308.417)"
2659
+ stroke="var(--symbol-stroke, #000000)"
2660
+ stroke-miterlimit="10"
2661
+ stroke-width="1"
2662
+ fill="var(--symbol-fill, #eaeaea)"
2663
+ />
2664
+
2665
+ <!-- Vessel bottom transitions -->
2666
+ <path
2667
+ d="M449.265,705.7H196.773l-1.654-1.465a.014.014,0,0,1,0-.023H451.078Z"
2668
+ transform="translate(-0.049 -398.769)"
2669
+ stroke="var(--symbol-stroke, #000000)"
2670
+ stroke-miterlimit="10"
2671
+ stroke-width="1"
2672
+ fill="var(--symbol-fill, #e0e0e0)"
2673
+ />
2674
+ <path
2675
+ d="M449.278,710.66H196.793l-1.663,1.462v.016H451.086s.012,0,0-.016Z"
2676
+ transform="translate(-0.062 -403.731)"
2677
+ stroke="var(--symbol-stroke, #000000)"
2678
+ stroke-miterlimit="10"
2679
+ stroke-width="1"
2680
+ fill="var(--symbol-fill, #e0e0e0)"
2681
+ />
2682
+ <path
2683
+ d="M433.9,1060.758H212.2L195.05,1050.48h256Z"
2684
+ transform="translate(0 -665.171)"
2685
+ stroke="var(--symbol-stroke, #000000)"
2686
+ stroke-miterlimit="10"
2687
+ stroke-width="1"
2688
+ fill="var(--symbol-fill, #e0e0e0)"
2689
+ />
2690
+
2691
+ <!-- Agitator blades/impellers (simplified without gradients) -->
2692
+ <ellipse
2693
+ cx="15.454"
2694
+ cy="6.096"
2695
+ rx="15.454"
2696
+ ry="6.096"
2697
+ transform="translate(307.486 228.161)"
2698
+ stroke="var(--symbol-stroke, #000000)"
2699
+ stroke-miterlimit="10"
2700
+ stroke-width="1"
2701
+ fill="var(--symbol-fill, #c0c0c0)"
2702
+ />
2703
+ <ellipse
2704
+ cx="15.454"
2705
+ cy="5.3"
2706
+ rx="15.454"
2707
+ ry="5.3"
2708
+ transform="translate(307.486 229.753)"
2709
+ stroke="var(--symbol-stroke, #000000)"
2710
+ stroke-miterlimit="10"
2711
+ stroke-width="1"
2712
+ fill="var(--symbol-fill, #d0d0d0)"
2713
+ />
2714
+ <ellipse
2715
+ cx="5.174"
2716
+ cy="15.117"
2717
+ rx="5.174"
2718
+ ry="15.117"
2719
+ transform="translate(367.144 236.079) rotate(-77.61)"
2720
+ stroke="var(--symbol-stroke, #000000)"
2721
+ stroke-miterlimit="10"
2722
+ stroke-width="1"
2723
+ fill="var(--symbol-fill, #d0d0d0)"
2724
+ />
2725
+ <path
2726
+ d="M947,380.271s1.631-2.83,8.084-2.235c5.427.5,16.927,2.855,20.574,8.26"
2727
+ transform="translate(-578.512 -147.764)"
2728
+ fill="none"
2729
+ stroke="var(--symbol-stroke, #000000)"
2730
+ stroke-miterlimit="10"
2731
+ stroke-width="1"
2732
+ />
2733
+ <ellipse
2734
+ cx="15.117"
2735
+ cy="5.174"
2736
+ rx="15.117"
2737
+ ry="5.174"
2738
+ transform="translate(247 232.438) rotate(-12.39)"
2739
+ stroke="var(--symbol-stroke, #000000)"
2740
+ stroke-miterlimit="10"
2741
+ stroke-width="1"
2742
+ fill="var(--symbol-fill, #d0d0d0)"
2743
+ />
2744
+ <path
2745
+ d="M456.439,380.271s-1.631-2.83-8.073-2.235c-5.43.5-16.93,2.855-20.576,8.26"
2746
+ transform="translate(-179.058 -147.764)"
2747
+ fill="none"
2748
+ stroke="var(--symbol-stroke, #000000)"
2749
+ stroke-miterlimit="10"
2750
+ stroke-width="1"
2751
+ />
2752
+ <ellipse
2753
+ cx="1.651"
2754
+ cy="15.082"
2755
+ rx="1.651"
2756
+ ry="15.082"
2757
+ transform="translate(406.71 230.174) rotate(-68.13)"
2758
+ stroke="var(--symbol-stroke, #000000)"
2759
+ stroke-miterlimit="10"
2760
+ stroke-width="1"
2761
+ fill="var(--symbol-fill, #d0d0d0)"
2762
+ />
2763
+ <path
2764
+ d="M1119,375.62a119.876,119.876,0,0,1,26.193,10.419"
2765
+ transform="translate(-710.84 -145.969)"
2766
+ fill="none"
2767
+ stroke="var(--symbol-stroke, #000000)"
2768
+ stroke-miterlimit="10"
2769
+ stroke-width="1"
2770
+ />
2771
+ <ellipse
2772
+ cx="15.082"
2773
+ cy="1.651"
2774
+ rx="15.082"
2775
+ ry="1.651"
2776
+ transform="translate(210.225 238.342) rotate(-21.87)"
2777
+ stroke="var(--symbol-stroke, #000000)"
2778
+ stroke-miterlimit="10"
2779
+ stroke-width="1"
2780
+ fill="var(--symbol-fill, #d0d0d0)"
2781
+ />
2782
+ <path
2783
+ d="M293.943,375.62a119.829,119.829,0,0,0-26.193,10.419"
2784
+ transform="translate(-55.932 -145.969)"
2785
+ fill="none"
2786
+ stroke="var(--symbol-stroke, #000000)"
2787
+ stroke-miterlimit="10"
2788
+ stroke-width="1"
2789
+ />
1863
2790
  </g>
1864
2791
  `,
1865
2792
  metadata: {
1866
- description: "Centrifugal pump for fluid transfer",
1867
- tags: ["pump", "centrifugal", "transfer"]
2793
+ description: "Pixer mixer/agitator vessel with tapered hose barb connection at top",
2794
+ tags: ["vessel", "mixer", "agitator", "pixer", "tank", "hose-barb"]
1868
2795
  }
1869
2796
  },
1870
- "vessel-tank": {
1871
- id: "vessel-tank",
1872
- name: "Storage Tank",
2797
+ "vessel-balloon": {
2798
+ id: "vessel-balloon",
2799
+ name: "Vessel",
1873
2800
  category: "vessel",
1874
- viewBox: { x: 0, y: 0, width: 60, height: 80 },
2801
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 45.5 },
1875
2802
  ports: [
1876
- { id: "inlet", x: 30, y: 10, type: "inlet", direction: "in" },
1877
- { id: "outlet", x: 30, y: 70, type: "outlet", direction: "out" }
2803
+ { id: "top", type: "inlet", x: 12.65625, y: 0 },
2804
+ { id: "left", type: "inlet", x: 2.5, y: 19.5 },
2805
+ { id: "right", type: "outlet", x: 22.8125, y: 19.5 },
2806
+ { id: "bottom", type: "outlet", x: 12.65625, y: 45.5 }
1878
2807
  ],
1879
2808
  svgContent: `
1880
- <g>
1881
- <!-- Tank body -->
1882
- <rect x="10" y="15" width="40" height="50" fill="#66bb6a" stroke="#2e7d32" stroke-width="2.5" rx="3"/>
1883
- <!-- Tank bottom (rounded) -->
1884
- <ellipse cx="30" cy="65" rx="20" ry="8" fill="#66bb6a" stroke="#2e7d32" stroke-width="2.5"/>
1885
- <!-- Liquid level indicator -->
1886
- <rect x="12" y="35" width="36" height="28" fill="#4dd0e1" opacity="0.6"/>
1887
- <!-- Inlet pipe -->
1888
- <line x1="30" y1="0" x2="30" y2="15" stroke="#2e7d32" stroke-width="3" stroke-linecap="round"/>
1889
- <!-- Outlet pipe -->
1890
- <line x1="30" y1="65" x2="30" y2="80" stroke="#2e7d32" stroke-width="3" stroke-linecap="round"/>
2809
+ <g transform="scale(0.0984375) translate(-195, -100)">
2810
+ <!-- Top opening nozzle -->
2811
+ <rect
2812
+ width="65"
2813
+ height="19.5"
2814
+ transform="translate(290.5, 100)"
2815
+ stroke="var(--symbol-stroke, #000000)"
2816
+ stroke-miterlimit="10"
2817
+ stroke-width="1"
2818
+ fill="var(--symbol-fill, #e0e0e0)"
2819
+ />
2820
+
2821
+ <!-- Transition from nozzle to dome -->
2822
+ <path
2823
+ d="M 290.5,119.5 L 238,152 L 408,152 L 355.5,119.5 Z"
2824
+ stroke="var(--symbol-stroke, #000000)"
2825
+ stroke-miterlimit="10"
2826
+ stroke-width="1"
2827
+ fill="var(--symbol-fill, #e0e0e0)"
2828
+ />
2829
+
2830
+ <!-- Upper balloon dome - rounded bulge -->
2831
+ <path
2832
+ d="M 238,152 C 185,210 150,320 180,395 L 466,395 C 496,320 461,210 408,152 Z"
2833
+ stroke="var(--symbol-stroke, #000000)"
2834
+ stroke-miterlimit="10"
2835
+ stroke-width="1"
2836
+ fill="var(--symbol-fill, #eaeaea)"
2837
+ />
2838
+
2839
+ <!-- Tapered vessel body -->
2840
+ <path
2841
+ d="M 180,395 L 269.742,530 L 376.356,530 L 466,395 Z"
2842
+ stroke="var(--symbol-stroke, #000000)"
2843
+ stroke-miterlimit="10"
2844
+ stroke-width="1"
2845
+ fill="var(--symbol-fill, #eaeaea)"
2846
+ />
2847
+
2848
+ <!-- Bottom cone -->
2849
+ <path
2850
+ d="M 269.742,530 L 280,555 L 366,555 L 376.356,530 Z"
2851
+ stroke="var(--symbol-stroke, #000000)"
2852
+ stroke-miterlimit="10"
2853
+ stroke-width="1"
2854
+ fill="var(--symbol-fill, #e0e0e0)"
2855
+ />
2856
+
2857
+ <!-- Bottom flange matching pixer top dimensions -->
2858
+ <rect
2859
+ width="141.313"
2860
+ height="9.872"
2861
+ transform="translate(252.392, 555)"
2862
+ stroke="var(--symbol-stroke, #000000)"
2863
+ stroke-miterlimit="10"
2864
+ stroke-width="1"
2865
+ fill="var(--symbol-fill, #eaeaea)"
2866
+ />
1891
2867
  </g>
1892
2868
  `,
1893
2869
  metadata: {
1894
- description: "Storage vessel for liquids",
1895
- tags: ["vessel", "tank", "storage"]
2870
+ description: "Balloon-shaped vessel with rounded top and tapered bottom, designed to fit on standard pixer",
2871
+ tags: ["vessel", "storage", "tank", "balloon"]
1896
2872
  }
1897
2873
  },
1898
- "instrument-indicator": {
1899
- id: "instrument-indicator",
1900
- name: "Instrument Indicator",
2874
+ "hollow-fiber": {
2875
+ id: "hollow-fiber",
2876
+ name: "Hollow Fiber",
2877
+ category: "other",
2878
+ viewBox: { x: 0, y: 0, width: 30, height: 109 },
2879
+ ports: [
2880
+ { id: "top-left", type: "outlet", x: 10, y: 2.75 },
2881
+ { id: "top-right", type: "outlet", x: 24.5, y: 22.25 },
2882
+ { id: "bottom-left", type: "inlet", x: 10, y: 106.25 },
2883
+ { id: "bottom-right", type: "inlet", x: 24.5, y: 87.25 }
2884
+ ],
2885
+ svgContent: `
2886
+ <g transform="scale(0.5)">
2887
+ <!-- Top left flange -->
2888
+ <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)" />
2889
+
2890
+ <!-- Top right flange -->
2891
+ <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)" />
2892
+
2893
+ <!-- Bottom left flange -->
2894
+ <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)" />
2895
+
2896
+ <!-- Bottom right flange -->
2897
+ <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)" />
2898
+
2899
+ <!-- Main vessel body -->
2900
+ <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)" />
2901
+ </g>
2902
+ `,
2903
+ metadata: {
2904
+ description: "Hollow fiber filter module with multiple inlet/outlet connections",
2905
+ tags: ["filter", "hollow-fiber", "filtration", "separation", "membrane"]
2906
+ }
2907
+ },
2908
+ "psr-f": {
2909
+ id: "psr-f",
2910
+ name: "PSR F",
1901
2911
  category: "instrument",
1902
- viewBox: { x: 0, y: 0, width: 40, height: 40 },
2912
+ viewBox: { x: 0, y: 0, width: 28.5, height: 28.5 },
1903
2913
  ports: [
1904
- { id: "signal", x: 20, y: 40, type: "signal", direction: "bi" }
2914
+ { id: "top", type: "signal", x: 14.25, y: 7.125 },
2915
+ { id: "right", type: "signal", x: 21.375, y: 14.25 },
2916
+ { id: "bottom", type: "signal", x: 14.25, y: 21.375 },
2917
+ { id: "left", type: "signal", x: 7.125, y: 14.25 }
1905
2918
  ],
1906
2919
  svgContent: `
1907
- <g>
1908
- <!-- Instrument circle -->
1909
- <circle cx="20" cy="20" r="15" fill="#fff" stroke="#616161" stroke-width="2"/>
1910
- <!-- Inner circle -->
1911
- <circle cx="20" cy="20" r="12" fill="none" stroke="#616161" stroke-width="1"/>
1912
- <!-- Signal line -->
1913
- <line x1="20" y1="35" x2="20" y2="40" stroke="#616161" stroke-width="2" stroke-linecap="round"/>
2920
+ <g transform="scale(0.25) translate(28.5, 28.5)">
2921
+ <!-- Circle background -->
2922
+ <ellipse
2923
+ cx="28.5"
2924
+ cy="28.5"
2925
+ rx="28.5"
2926
+ ry="28.5"
2927
+ stroke="var(--symbol-stroke, #000000)"
2928
+ stroke-width="1"
2929
+ fill="var(--symbol-fill, #ffffff)"
2930
+ />
2931
+
2932
+ <!-- Letter F -->
2933
+ <text
2934
+ x="28.5"
2935
+ y="38"
2936
+ text-anchor="middle"
2937
+ font-family="'Times New Roman', Georgia, serif"
2938
+ font-size="34"
2939
+ font-weight="normal"
2940
+ font-style="italic"
2941
+ fill="var(--symbol-stroke, #000000)"
2942
+ >F</text>
1914
2943
  </g>
1915
2944
  `,
1916
2945
  metadata: {
1917
- description: "Generic instrument indicator",
1918
- tags: ["instrument", "indicator", "measurement"]
2946
+ description: "PSR F instrument symbol - Flow indicator",
2947
+ tags: ["instrument", "psr", "flow", "indicator"]
2948
+ }
2949
+ },
2950
+ "psr-p": {
2951
+ id: "psr-p",
2952
+ name: "PSR P",
2953
+ category: "instrument",
2954
+ viewBox: { x: 0, y: 0, width: 28.5, height: 28.5 },
2955
+ ports: [
2956
+ { id: "top", type: "signal", x: 14.25, y: 7.125 },
2957
+ { id: "right", type: "signal", x: 21.375, y: 14.25 },
2958
+ { id: "bottom", type: "signal", x: 14.25, y: 21.375 },
2959
+ { id: "left", type: "signal", x: 7.125, y: 14.25 }
2960
+ ],
2961
+ svgContent: `
2962
+ <g transform="scale(0.25) translate(28.5, 28.5)">
2963
+ <!-- Circle background -->
2964
+ <ellipse
2965
+ cx="28.5"
2966
+ cy="28.5"
2967
+ rx="28.5"
2968
+ ry="28.5"
2969
+ stroke="var(--symbol-stroke, #000000)"
2970
+ stroke-width="1"
2971
+ fill="var(--symbol-fill, #ffffff)"
2972
+ />
2973
+
2974
+ <!-- Letter P -->
2975
+ <text
2976
+ x="28.5"
2977
+ y="38"
2978
+ text-anchor="middle"
2979
+ font-family="'Times New Roman', Georgia, serif"
2980
+ font-size="34"
2981
+ font-weight="normal"
2982
+ font-style="italic"
2983
+ fill="var(--symbol-stroke, #000000)"
2984
+ >P</text>
2985
+ </g>
2986
+ `,
2987
+ metadata: {
2988
+ description: "PSR P instrument symbol - Pressure indicator",
2989
+ tags: ["instrument", "psr", "pressure", "indicator"]
1919
2990
  }
1920
2991
  }
1921
2992
  };
2993
+ var customSymbols = {};
2994
+ function registerSymbol(symbol) {
2995
+ customSymbols[symbol.id] = symbol;
2996
+ }
2997
+ function registerSymbols(symbols) {
2998
+ if (Array.isArray(symbols)) {
2999
+ symbols.forEach((symbol) => registerSymbol(symbol));
3000
+ } else {
3001
+ Object.values(symbols).forEach((symbol) => registerSymbol(symbol));
3002
+ }
3003
+ }
1922
3004
  function getSymbolDefinition(symbolId) {
1923
- return mockSymbolLibrary[symbolId];
3005
+ return customSymbols[symbolId] ?? mockSymbolLibrary[symbolId];
1924
3006
  }
1925
3007
  function getAvailableSymbols() {
1926
- return Object.keys(mockSymbolLibrary);
3008
+ return [...Object.keys(customSymbols), ...Object.keys(mockSymbolLibrary)];
1927
3009
  }
1928
3010
 
1929
3011
  // src/components/PIDCanvas/NodeRenderer.tsx
@@ -1995,6 +3077,24 @@ function NodeRenderer({
1995
3077
  `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
1996
3078
  );
1997
3079
  }
3080
+ const getStatusColor = (status) => {
3081
+ switch (status) {
3082
+ case "running":
3083
+ return "#22c55e";
3084
+ // Green
3085
+ case "alarm":
3086
+ return "#ef4444";
3087
+ // Red
3088
+ case "warning":
3089
+ return "#f59e0b";
3090
+ // Orange
3091
+ case "stopped":
3092
+ default:
3093
+ return "#edeeef";
3094
+ }
3095
+ };
3096
+ const fillColor = node.customColors?.fill ?? getStatusColor(node.status);
3097
+ const strokeColor = node.customColors?.stroke ?? "rgb(0, 0, 0)";
1998
3098
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1999
3099
  "g",
2000
3100
  {
@@ -2003,6 +3103,11 @@ function NodeRenderer({
2003
3103
  "data-selected": isSelected,
2004
3104
  "data-dragging": isDragging,
2005
3105
  transform: transforms.join(" "),
3106
+ style: {
3107
+ "--symbol-fill": fillColor,
3108
+ "--symbol-stroke": strokeColor,
3109
+ cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
3110
+ },
2006
3111
  onClick: (e) => {
2007
3112
  e.stopPropagation();
2008
3113
  onNodeClick?.(node.id, e);
@@ -2022,9 +3127,6 @@ function NodeRenderer({
2022
3127
  onNodeDragStart?.(node.id, e);
2023
3128
  }
2024
3129
  },
2025
- style: {
2026
- cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
2027
- },
2028
3130
  children: [
2029
3131
  isSelected && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2030
3132
  "rect",
@@ -2077,6 +3179,9 @@ function PipeRenderer({
2077
3179
  onPipeClick,
2078
3180
  enableWaypointEditing = false,
2079
3181
  onWaypointDragStart,
3182
+ onWaypointClick,
3183
+ selectedWaypointPipeId,
3184
+ selectedWaypointIndex,
2080
3185
  editingPipeId,
2081
3186
  draggingWaypointIndex,
2082
3187
  waypointDragOffset,
@@ -2097,7 +3202,7 @@ function PipeRenderer({
2097
3202
  });
2098
3203
  const pointsString = displayPoints.map((point) => `${point.x},${point.y}`).join(" ");
2099
3204
  const pipeStyle = pipe.style || {};
2100
- const stroke = isSelected ? "#3b82f6" : pipeStyle.stroke || "#263238";
3205
+ const stroke = isSelected ? "#3b82f6" : pipeStyle.stroke || "#94a1b8";
2101
3206
  const strokeWidth = isSelected ? (pipeStyle.strokeWidth || 3) + 2 : pipeStyle.strokeWidth || 3;
2102
3207
  const strokeDasharray = pipeStyle.strokeDasharray;
2103
3208
  const opacity = pipeStyle.opacity !== void 0 ? pipeStyle.opacity : 1;
@@ -2146,10 +3251,11 @@ function PipeRenderer({
2146
3251
  ] }),
2147
3252
  displayPoints.map((point, idx) => {
2148
3253
  const isEndpoint = idx === 0 || idx === displayPoints.length - 1;
3254
+ const isSelectedWaypoint = selectedWaypointPipeId === pipe.id && selectedWaypointIndex === idx;
2149
3255
  const isDragging = isEditing && draggingWaypointIndex === idx;
2150
- const waypointSize = isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
2151
- const waypointOpacity = isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
2152
- const waypointColor = isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
3256
+ const waypointSize = isSelectedWaypoint ? 8 : isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
3257
+ const waypointOpacity = isSelectedWaypoint ? 1 : isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
3258
+ const waypointColor = isSelectedWaypoint ? "#3b82f6" : isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
2153
3259
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2154
3260
  "circle",
2155
3261
  {
@@ -2165,6 +3271,12 @@ function PipeRenderer({
2165
3271
  cursor: showEditingControls ? isDragging ? "grabbing" : "grab" : "default"
2166
3272
  },
2167
3273
  pointerEvents: showEditingControls ? "all" : "none",
3274
+ onClick: (e) => {
3275
+ if (showEditingControls) {
3276
+ e.stopPropagation();
3277
+ onWaypointClick?.(pipe.id, idx);
3278
+ }
3279
+ },
2168
3280
  onMouseDown: (e) => {
2169
3281
  if (showEditingControls) {
2170
3282
  e.stopPropagation();
@@ -2241,7 +3353,13 @@ function PipeRenderer({
2241
3353
 
2242
3354
  // src/components/PIDCanvas/DataOverlay.tsx
2243
3355
  var import_jsx_runtime15 = require("react/jsx-runtime");
2244
- function DataOverlay({ nodeId, x, y, telemetry }) {
3356
+ function DataOverlay({
3357
+ nodeId,
3358
+ x,
3359
+ y,
3360
+ telemetry,
3361
+ scale = 1
3362
+ }) {
2245
3363
  const { value, display, history } = telemetry;
2246
3364
  const showValue = display?.showValue ?? true;
2247
3365
  const showStatus = display?.showStatus ?? true;
@@ -2281,9 +3399,9 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2281
3399
  const displayText = value.unit ? `${formattedValue} ${value.unit}` : formattedValue;
2282
3400
  const sparklinePath = (() => {
2283
3401
  if (!showSparkline || !history || history.length < 2) return "";
2284
- const width = 70;
2285
- const height = 20;
2286
- const padding = 5;
3402
+ const width = 70 * scale;
3403
+ const height = 20 * scale;
3404
+ const padding = 5 * scale;
2287
3405
  const effectiveWidth = width - padding * 2;
2288
3406
  const effectiveHeight = height - padding * 2;
2289
3407
  const min = Math.min(...history);
@@ -2297,21 +3415,22 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2297
3415
  return `M ${points.join(" L ")}`;
2298
3416
  })();
2299
3417
  const hasSparkline = showSparkline && sparklinePath;
2300
- const overlayHeight = label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22;
3418
+ const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale;
3419
+ const overlayWidth = 80 * scale;
2301
3420
  const displayX = x + offsetX;
2302
3421
  const displayY = y + offsetY;
2303
3422
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
2304
3423
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2305
3424
  "rect",
2306
3425
  {
2307
- x: displayX - 40,
2308
- y: displayY - 25,
2309
- width: "80",
3426
+ x: displayX - overlayWidth / 2,
3427
+ y: displayY - 25 * scale,
3428
+ width: overlayWidth,
2310
3429
  height: overlayHeight,
2311
- rx: "4",
3430
+ rx: 4 * scale,
2312
3431
  fill: colors.bg,
2313
3432
  stroke: colors.border,
2314
- strokeWidth: "1",
3433
+ strokeWidth: 1 * scale,
2315
3434
  opacity: "0.95",
2316
3435
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
2317
3436
  }
@@ -2320,9 +3439,9 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2320
3439
  "text",
2321
3440
  {
2322
3441
  x: displayX,
2323
- y: displayY - 15,
3442
+ y: displayY - 15 * scale,
2324
3443
  textAnchor: "middle",
2325
- fontSize: "8",
3444
+ fontSize: 8 * scale,
2326
3445
  fontWeight: "600",
2327
3446
  fill: finalTextColor,
2328
3447
  opacity: "0.9",
@@ -2333,31 +3452,41 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2333
3452
  "text",
2334
3453
  {
2335
3454
  x: displayX,
2336
- y: label ? displayY - 3 : displayY - 9,
3455
+ y: label ? displayY - 3 * scale : displayY - 9 * scale,
2337
3456
  textAnchor: "middle",
2338
- fontSize: "11",
3457
+ fontSize: 11 * scale,
2339
3458
  fontWeight: "700",
2340
3459
  fill: finalTextColor,
2341
3460
  letterSpacing: "0.02em",
2342
3461
  children: displayText
2343
3462
  }
2344
3463
  ),
2345
- showStatus && value.status !== "normal" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("circle", { cx: displayX + 35, cy: displayY - 18, r: "3", fill: finalTextColor, opacity: "0.9", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2346
- "animate",
3464
+ showStatus && value.status !== "normal" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3465
+ "circle",
2347
3466
  {
2348
- attributeName: "opacity",
2349
- values: "0.9;0.3;0.9",
2350
- dur: "1.5s",
2351
- repeatCount: "indefinite"
3467
+ cx: displayX + 35 * scale,
3468
+ cy: displayY - 18 * scale,
3469
+ r: 3 * scale,
3470
+ fill: finalTextColor,
3471
+ opacity: "0.9",
3472
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3473
+ "animate",
3474
+ {
3475
+ attributeName: "opacity",
3476
+ values: "0.9;0.3;0.9",
3477
+ dur: "1.5s",
3478
+ repeatCount: "indefinite"
3479
+ }
3480
+ )
2352
3481
  }
2353
- ) }),
3482
+ ),
2354
3483
  value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2355
3484
  "text",
2356
3485
  {
2357
3486
  x: displayX,
2358
- y: displayY + 5,
3487
+ y: displayY + 5 * scale,
2359
3488
  textAnchor: "middle",
2360
- fontSize: "7",
3489
+ fontSize: 7 * scale,
2361
3490
  fontWeight: "500",
2362
3491
  fill: finalTextColor,
2363
3492
  opacity: "0.7",
@@ -2368,13 +3497,13 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2368
3497
  ]
2369
3498
  }
2370
3499
  ),
2371
- hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("g", { transform: `translate(${displayX - 35}, ${displayY + (label ? 5 : 0)})`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3500
+ hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2372
3501
  "path",
2373
3502
  {
2374
3503
  d: sparklinePath,
2375
3504
  fill: "none",
2376
3505
  stroke: sparklineColor || finalTextColor,
2377
- strokeWidth: "1.5",
3506
+ strokeWidth: 1.5 * scale,
2378
3507
  strokeLinecap: "round",
2379
3508
  strokeLinejoin: "round",
2380
3509
  opacity: "0.9"
@@ -2383,8 +3512,145 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2383
3512
  ] });
2384
3513
  }
2385
3514
 
2386
- // src/diagram/hooks/useViewBox.ts
3515
+ // src/components/PIDCanvas/PositionPanel.tsx
2387
3516
  var import_react8 = require("react");
3517
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3518
+ function PositionPanel({
3519
+ selectedNode,
3520
+ selectedWaypoint,
3521
+ onPositionChange,
3522
+ onWaypointPositionChange,
3523
+ position = "top-right"
3524
+ }) {
3525
+ const [x, setX] = (0, import_react8.useState)("");
3526
+ const [y, setY] = (0, import_react8.useState)("");
3527
+ (0, import_react8.useEffect)(() => {
3528
+ if (selectedNode) {
3529
+ setX(selectedNode.transform.x.toFixed(2));
3530
+ setY(selectedNode.transform.y.toFixed(2));
3531
+ } else if (selectedWaypoint) {
3532
+ setX(selectedWaypoint.position.x.toFixed(2));
3533
+ setY(selectedWaypoint.position.y.toFixed(2));
3534
+ }
3535
+ }, [selectedNode, selectedWaypoint]);
3536
+ if (!selectedNode && !selectedWaypoint) {
3537
+ return null;
3538
+ }
3539
+ const handleXChange = (value) => {
3540
+ setX(value);
3541
+ const numValue = parseFloat(value);
3542
+ if (!isNaN(numValue)) {
3543
+ if (selectedNode && onPositionChange) {
3544
+ onPositionChange(selectedNode.id, numValue, selectedNode.transform.y);
3545
+ } else if (selectedWaypoint && onWaypointPositionChange) {
3546
+ onWaypointPositionChange(
3547
+ selectedWaypoint.pipeId,
3548
+ selectedWaypoint.pointIndex,
3549
+ numValue,
3550
+ selectedWaypoint.position.y
3551
+ );
3552
+ }
3553
+ }
3554
+ };
3555
+ const handleYChange = (value) => {
3556
+ setY(value);
3557
+ const numValue = parseFloat(value);
3558
+ if (!isNaN(numValue)) {
3559
+ if (selectedNode && onPositionChange) {
3560
+ onPositionChange(selectedNode.id, selectedNode.transform.x, numValue);
3561
+ } else if (selectedWaypoint && onWaypointPositionChange) {
3562
+ onWaypointPositionChange(
3563
+ selectedWaypoint.pipeId,
3564
+ selectedWaypoint.pointIndex,
3565
+ selectedWaypoint.position.x,
3566
+ numValue
3567
+ );
3568
+ }
3569
+ }
3570
+ };
3571
+ const handleBlur = () => {
3572
+ if (selectedNode) {
3573
+ setX(selectedNode.transform.x.toFixed(2));
3574
+ setY(selectedNode.transform.y.toFixed(2));
3575
+ } else if (selectedWaypoint) {
3576
+ setX(selectedWaypoint.position.x.toFixed(2));
3577
+ setY(selectedWaypoint.position.y.toFixed(2));
3578
+ }
3579
+ };
3580
+ const positionStyles = {
3581
+ "top-left": { top: 10, left: 10 },
3582
+ "top-right": { top: 10, right: 10 },
3583
+ "bottom-left": { bottom: 10, left: 10 },
3584
+ "bottom-right": { bottom: 10, right: 10 }
3585
+ };
3586
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3587
+ "div",
3588
+ {
3589
+ style: {
3590
+ position: "absolute",
3591
+ ...positionStyles[position],
3592
+ backgroundColor: "white",
3593
+ border: "1px solid #ccc",
3594
+ borderRadius: "4px",
3595
+ padding: "12px",
3596
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
3597
+ zIndex: 1e3,
3598
+ minWidth: "180px",
3599
+ fontFamily: "system-ui, -apple-system, sans-serif",
3600
+ fontSize: "13px"
3601
+ },
3602
+ children: [
3603
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
3604
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
3605
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3606
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
3607
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3608
+ "input",
3609
+ {
3610
+ type: "number",
3611
+ value: x,
3612
+ onChange: (e) => handleXChange(e.target.value),
3613
+ onBlur: handleBlur,
3614
+ style: {
3615
+ flex: 1,
3616
+ padding: "4px 8px",
3617
+ border: "1px solid #ddd",
3618
+ borderRadius: "3px",
3619
+ fontSize: "13px"
3620
+ },
3621
+ step: "1"
3622
+ }
3623
+ )
3624
+ ] }),
3625
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3626
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
3627
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3628
+ "input",
3629
+ {
3630
+ type: "number",
3631
+ value: y,
3632
+ onChange: (e) => handleYChange(e.target.value),
3633
+ onBlur: handleBlur,
3634
+ style: {
3635
+ flex: 1,
3636
+ padding: "4px 8px",
3637
+ border: "1px solid #ddd",
3638
+ borderRadius: "3px",
3639
+ fontSize: "13px"
3640
+ },
3641
+ step: "1"
3642
+ }
3643
+ )
3644
+ ] })
3645
+ ] }),
3646
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
3647
+ ]
3648
+ }
3649
+ );
3650
+ }
3651
+
3652
+ // src/diagram/hooks/useViewBox.ts
3653
+ var import_react9 = require("react");
2388
3654
  function useViewBox(options) {
2389
3655
  const {
2390
3656
  initialViewBox,
@@ -2395,16 +3661,16 @@ function useViewBox(options) {
2395
3661
  zoomSensitivity = 1e-3,
2396
3662
  allowLeftClickPan = true
2397
3663
  } = options;
2398
- const [viewBox, setViewBox] = (0, import_react8.useState)(initialViewBox);
2399
- const svgRef = (0, import_react8.useRef)(null);
2400
- const isPanningRef = (0, import_react8.useRef)(false);
2401
- const lastMousePosRef = (0, import_react8.useRef)({ x: 0, y: 0 });
2402
- const spaceKeyDownRef = (0, import_react8.useRef)(false);
2403
- const lastTouchDistanceRef = (0, import_react8.useRef)(null);
2404
- const resetViewBox = (0, import_react8.useCallback)(() => {
3664
+ const [viewBox, setViewBox] = (0, import_react9.useState)(initialViewBox);
3665
+ const svgRef = (0, import_react9.useRef)(null);
3666
+ const isPanningRef = (0, import_react9.useRef)(false);
3667
+ const lastMousePosRef = (0, import_react9.useRef)({ x: 0, y: 0 });
3668
+ const spaceKeyDownRef = (0, import_react9.useRef)(false);
3669
+ const lastTouchDistanceRef = (0, import_react9.useRef)(null);
3670
+ const resetViewBox = (0, import_react9.useCallback)(() => {
2405
3671
  setViewBox(initialViewBox);
2406
3672
  }, [initialViewBox]);
2407
- const zoomIn = (0, import_react8.useCallback)(() => {
3673
+ const zoomIn = (0, import_react9.useCallback)(() => {
2408
3674
  setViewBox((prev) => {
2409
3675
  const currentZoom = initialViewBox.width / prev.width;
2410
3676
  const newZoom = Math.min(1 / minZoom, currentZoom * 1.25);
@@ -2421,7 +3687,7 @@ function useViewBox(options) {
2421
3687
  };
2422
3688
  });
2423
3689
  }, [initialViewBox, minZoom]);
2424
- const zoomOut = (0, import_react8.useCallback)(() => {
3690
+ const zoomOut = (0, import_react9.useCallback)(() => {
2425
3691
  setViewBox((prev) => {
2426
3692
  const currentZoom = initialViewBox.width / prev.width;
2427
3693
  const newZoom = Math.max(1 / maxZoom, currentZoom * 0.8);
@@ -2438,7 +3704,7 @@ function useViewBox(options) {
2438
3704
  };
2439
3705
  });
2440
3706
  }, [initialViewBox, maxZoom]);
2441
- const fitToContent = (0, import_react8.useCallback)((bounds, padding = 50) => {
3707
+ const fitToContent = (0, import_react9.useCallback)((bounds, padding = 50) => {
2442
3708
  const contentWidth = bounds.maxX - bounds.minX;
2443
3709
  const contentHeight = bounds.maxY - bounds.minY;
2444
3710
  if (contentWidth === 0 || contentHeight === 0) {
@@ -2466,7 +3732,7 @@ function useViewBox(options) {
2466
3732
  height: newHeight
2467
3733
  });
2468
3734
  }, [initialViewBox]);
2469
- (0, import_react8.useEffect)(() => {
3735
+ (0, import_react9.useEffect)(() => {
2470
3736
  if (!enableZoom || !svgRef.current) return;
2471
3737
  const svg = svgRef.current;
2472
3738
  const handleWheel = (e) => {
@@ -2499,7 +3765,7 @@ function useViewBox(options) {
2499
3765
  svg.addEventListener("wheel", handleWheel, { passive: false });
2500
3766
  return () => svg.removeEventListener("wheel", handleWheel);
2501
3767
  }, [enableZoom, zoomSensitivity, minZoom, initialViewBox]);
2502
- (0, import_react8.useEffect)(() => {
3768
+ (0, import_react9.useEffect)(() => {
2503
3769
  if (!enablePan || !svgRef.current) return;
2504
3770
  const svg = svgRef.current;
2505
3771
  const handleMouseDown = (e) => {
@@ -2575,7 +3841,7 @@ function useViewBox(options) {
2575
3841
  window.removeEventListener("keyup", handleKeyUp);
2576
3842
  };
2577
3843
  }, [enablePan, allowLeftClickPan]);
2578
- (0, import_react8.useEffect)(() => {
3844
+ (0, import_react9.useEffect)(() => {
2579
3845
  if (!svgRef.current) return;
2580
3846
  if (!enablePan && !enableZoom) return;
2581
3847
  const svg = svgRef.current;
@@ -2666,7 +3932,7 @@ function useViewBox(options) {
2666
3932
  svg.removeEventListener("touchend", handleTouchEnd);
2667
3933
  };
2668
3934
  }, [enablePan, enableZoom, minZoom, initialViewBox]);
2669
- const screenToWorld = (0, import_react8.useCallback)(
3935
+ const screenToWorld = (0, import_react9.useCallback)(
2670
3936
  (screenX, screenY) => {
2671
3937
  const svg = svgRef.current;
2672
3938
  if (!svg) return { x: screenX, y: screenY };
@@ -2705,15 +3971,15 @@ function useViewBox(options) {
2705
3971
  }
2706
3972
 
2707
3973
  // src/diagram/hooks/useSelection.ts
2708
- var import_react9 = require("react");
3974
+ var import_react10 = require("react");
2709
3975
  function useSelection(options = {}) {
2710
3976
  const {
2711
3977
  multiSelect = true,
2712
3978
  initialSelection = { selectedNodeIds: /* @__PURE__ */ new Set(), selectedPipeIds: /* @__PURE__ */ new Set() },
2713
3979
  onSelectionChange
2714
3980
  } = options;
2715
- const [selection, setSelection] = (0, import_react9.useState)(initialSelection);
2716
- const notifyChange = (0, import_react9.useCallback)(
3981
+ const [selection, setSelection] = (0, import_react10.useState)(initialSelection);
3982
+ const notifyChange = (0, import_react10.useCallback)(
2717
3983
  (newSelection) => {
2718
3984
  if (onSelectionChange) {
2719
3985
  onSelectionChange(newSelection);
@@ -2721,15 +3987,15 @@ function useSelection(options = {}) {
2721
3987
  },
2722
3988
  [onSelectionChange]
2723
3989
  );
2724
- const isNodeSelected = (0, import_react9.useCallback)(
3990
+ const isNodeSelected = (0, import_react10.useCallback)(
2725
3991
  (nodeId) => selection.selectedNodeIds.has(nodeId),
2726
3992
  [selection.selectedNodeIds]
2727
3993
  );
2728
- const isPipeSelected = (0, import_react9.useCallback)(
3994
+ const isPipeSelected = (0, import_react10.useCallback)(
2729
3995
  (pipeId) => selection.selectedPipeIds.has(pipeId),
2730
3996
  [selection.selectedPipeIds]
2731
3997
  );
2732
- const selectNode = (0, import_react9.useCallback)(
3998
+ const selectNode = (0, import_react10.useCallback)(
2733
3999
  (nodeId, isMultiSelect = false) => {
2734
4000
  setSelection((prev) => {
2735
4001
  const newNodeIds = new Set(isMultiSelect && multiSelect ? prev.selectedNodeIds : []);
@@ -2748,7 +4014,7 @@ function useSelection(options = {}) {
2748
4014
  },
2749
4015
  [multiSelect, notifyChange]
2750
4016
  );
2751
- const selectPipe = (0, import_react9.useCallback)(
4017
+ const selectPipe = (0, import_react10.useCallback)(
2752
4018
  (pipeId, isMultiSelect = false) => {
2753
4019
  setSelection((prev) => {
2754
4020
  const newPipeIds = new Set(isMultiSelect && multiSelect ? prev.selectedPipeIds : []);
@@ -2767,7 +4033,7 @@ function useSelection(options = {}) {
2767
4033
  },
2768
4034
  [multiSelect, notifyChange]
2769
4035
  );
2770
- const clearSelection = (0, import_react9.useCallback)(() => {
4036
+ const clearSelection = (0, import_react10.useCallback)(() => {
2771
4037
  const newSelection = {
2772
4038
  selectedNodeIds: /* @__PURE__ */ new Set(),
2773
4039
  selectedPipeIds: /* @__PURE__ */ new Set()
@@ -2775,7 +4041,7 @@ function useSelection(options = {}) {
2775
4041
  setSelection(newSelection);
2776
4042
  notifyChange(newSelection);
2777
4043
  }, [notifyChange]);
2778
- const selectNodes = (0, import_react9.useCallback)(
4044
+ const selectNodes = (0, import_react10.useCallback)(
2779
4045
  (nodeIds) => {
2780
4046
  const newSelection = {
2781
4047
  selectedNodeIds: new Set(nodeIds),
@@ -2786,7 +4052,7 @@ function useSelection(options = {}) {
2786
4052
  },
2787
4053
  [notifyChange]
2788
4054
  );
2789
- const selectPipes = (0, import_react9.useCallback)(
4055
+ const selectPipes = (0, import_react10.useCallback)(
2790
4056
  (pipeIds) => {
2791
4057
  const newSelection = {
2792
4058
  selectedNodeIds: /* @__PURE__ */ new Set(),
@@ -2810,23 +4076,23 @@ function useSelection(options = {}) {
2810
4076
  }
2811
4077
 
2812
4078
  // src/diagram/hooks/useTelemetry.ts
2813
- var import_react10 = require("react");
4079
+ var import_react11 = require("react");
2814
4080
  function useTelemetry(options = {}) {
2815
4081
  const {
2816
4082
  initialBindings = [],
2817
4083
  onTelemetryChange,
2818
4084
  refreshInterval = 0
2819
4085
  } = options;
2820
- const [telemetry, setTelemetry] = (0, import_react10.useState)(() => {
4086
+ const [telemetry, setTelemetry] = (0, import_react11.useState)(() => {
2821
4087
  const map = /* @__PURE__ */ new Map();
2822
4088
  initialBindings.forEach((binding) => {
2823
4089
  map.set(binding.nodeId, binding);
2824
4090
  });
2825
4091
  return map;
2826
4092
  });
2827
- const telemetryRef = (0, import_react10.useRef)(telemetry);
4093
+ const telemetryRef = (0, import_react11.useRef)(telemetry);
2828
4094
  telemetryRef.current = telemetry;
2829
- const notifyChange = (0, import_react10.useCallback)(
4095
+ const notifyChange = (0, import_react11.useCallback)(
2830
4096
  (newTelemetry) => {
2831
4097
  if (onTelemetryChange) {
2832
4098
  onTelemetryChange(newTelemetry);
@@ -2834,7 +4100,7 @@ function useTelemetry(options = {}) {
2834
4100
  },
2835
4101
  [onTelemetryChange]
2836
4102
  );
2837
- const updateTelemetry = (0, import_react10.useCallback)(
4103
+ const updateTelemetry = (0, import_react11.useCallback)(
2838
4104
  (nodeId, value) => {
2839
4105
  setTelemetry((prev) => {
2840
4106
  const newMap = new Map(prev);
@@ -2864,7 +4130,7 @@ function useTelemetry(options = {}) {
2864
4130
  },
2865
4131
  [notifyChange]
2866
4132
  );
2867
- const updateTelemetryBatch = (0, import_react10.useCallback)(
4133
+ const updateTelemetryBatch = (0, import_react11.useCallback)(
2868
4134
  (updates) => {
2869
4135
  setTelemetry((prev) => {
2870
4136
  const newMap = new Map(prev);
@@ -2918,16 +4184,16 @@ function useTelemetry(options = {}) {
2918
4184
  },
2919
4185
  [notifyChange]
2920
4186
  );
2921
- const getTelemetry = (0, import_react10.useCallback)(
4187
+ const getTelemetry = (0, import_react11.useCallback)(
2922
4188
  (nodeId) => telemetryRef.current.get(nodeId),
2923
4189
  []
2924
4190
  );
2925
- const clearTelemetry = (0, import_react10.useCallback)(() => {
4191
+ const clearTelemetry = (0, import_react11.useCallback)(() => {
2926
4192
  const newMap = /* @__PURE__ */ new Map();
2927
4193
  setTelemetry(newMap);
2928
4194
  notifyChange(newMap);
2929
4195
  }, [notifyChange]);
2930
- const setBindings = (0, import_react10.useCallback)(
4196
+ const setBindings = (0, import_react11.useCallback)(
2931
4197
  (bindings) => {
2932
4198
  const newMap = /* @__PURE__ */ new Map();
2933
4199
  bindings.forEach((binding) => {
@@ -2938,7 +4204,7 @@ function useTelemetry(options = {}) {
2938
4204
  },
2939
4205
  [notifyChange]
2940
4206
  );
2941
- (0, import_react10.useEffect)(() => {
4207
+ (0, import_react11.useEffect)(() => {
2942
4208
  if (refreshInterval <= 0) return;
2943
4209
  const intervalId = setInterval(() => {
2944
4210
  notifyChange(telemetryRef.current);
@@ -2955,8 +4221,50 @@ function useTelemetry(options = {}) {
2955
4221
  };
2956
4222
  }
2957
4223
 
4224
+ // src/diagram/hooks/useTelemetryStatus.ts
4225
+ var import_react12 = require("react");
4226
+ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
4227
+ const { enabled = true, mapStatus } = options;
4228
+ (0, import_react12.useEffect)(() => {
4229
+ if (!enabled) return;
4230
+ const defaultMapStatus = (telemetryStatus) => {
4231
+ switch (telemetryStatus) {
4232
+ case "normal":
4233
+ return "running";
4234
+ case "warning":
4235
+ return "warning";
4236
+ case "alarm":
4237
+ case "fault":
4238
+ return "alarm";
4239
+ case "off":
4240
+ return "stopped";
4241
+ default:
4242
+ return "stopped";
4243
+ }
4244
+ };
4245
+ const statusMapper = mapStatus || defaultMapStatus;
4246
+ let needsUpdate = false;
4247
+ const updatedNodes = diagram.nodes.map((node) => {
4248
+ const binding = telemetry.get(node.id);
4249
+ if (!binding) return node;
4250
+ const newStatus = statusMapper(binding.value.status);
4251
+ if (node.status !== newStatus) {
4252
+ needsUpdate = true;
4253
+ return { ...node, status: newStatus };
4254
+ }
4255
+ return node;
4256
+ });
4257
+ if (needsUpdate) {
4258
+ setDiagram({
4259
+ ...diagram,
4260
+ nodes: updatedNodes
4261
+ });
4262
+ }
4263
+ }, [telemetry, diagram, setDiagram, enabled, mapStatus]);
4264
+ }
4265
+
2958
4266
  // src/diagram/hooks/useDrag.ts
2959
- var import_react11 = require("react");
4267
+ var import_react13 = require("react");
2960
4268
  function useDrag(options = {}) {
2961
4269
  const {
2962
4270
  onDragStart,
@@ -2966,23 +4274,23 @@ function useDrag(options = {}) {
2966
4274
  dragThreshold = 3,
2967
4275
  screenToWorld = (x, y) => ({ x, y })
2968
4276
  } = options;
2969
- const [dragState, setDragState] = (0, import_react11.useState)({
4277
+ const [dragState, setDragState] = (0, import_react13.useState)({
2970
4278
  isDragging: false,
2971
4279
  startPosition: null,
2972
4280
  offset: { x: 0, y: 0 },
2973
4281
  draggedId: null
2974
4282
  });
2975
- const [isListening, setIsListening] = (0, import_react11.useState)(false);
2976
- const dragStartRef = (0, import_react11.useRef)(null);
2977
- const hasMovedRef = (0, import_react11.useRef)(false);
2978
- const applySnap = (0, import_react11.useCallback)(
4283
+ const [isListening, setIsListening] = (0, import_react13.useState)(false);
4284
+ const dragStartRef = (0, import_react13.useRef)(null);
4285
+ const hasMovedRef = (0, import_react13.useRef)(false);
4286
+ const applySnap = (0, import_react13.useCallback)(
2979
4287
  (value) => {
2980
4288
  if (snapToGrid <= 0) return value;
2981
4289
  return Math.round(value / snapToGrid) * snapToGrid;
2982
4290
  },
2983
4291
  [snapToGrid]
2984
4292
  );
2985
- const handleMouseMove = (0, import_react11.useCallback)(
4293
+ const handleMouseMove = (0, import_react13.useCallback)(
2986
4294
  (event) => {
2987
4295
  if (!dragStartRef.current) return;
2988
4296
  const { id, screenStart: _screenStart, itemPosition, clickOffset } = dragStartRef.current;
@@ -3023,7 +4331,7 @@ function useDrag(options = {}) {
3023
4331
  },
3024
4332
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
3025
4333
  );
3026
- const handleMouseUp = (0, import_react11.useCallback)(
4334
+ const handleMouseUp = (0, import_react13.useCallback)(
3027
4335
  (event) => {
3028
4336
  if (!dragStartRef.current || !hasMovedRef.current) {
3029
4337
  dragStartRef.current = null;
@@ -3060,7 +4368,7 @@ function useDrag(options = {}) {
3060
4368
  },
3061
4369
  [screenToWorld, applySnap, onDragEnd]
3062
4370
  );
3063
- const handleTouchMove = (0, import_react11.useCallback)(
4371
+ const handleTouchMove = (0, import_react13.useCallback)(
3064
4372
  (event) => {
3065
4373
  if (!dragStartRef.current || event.touches.length !== 1) return;
3066
4374
  const touch = event.touches[0];
@@ -3103,7 +4411,7 @@ function useDrag(options = {}) {
3103
4411
  },
3104
4412
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
3105
4413
  );
3106
- const handleTouchEnd = (0, import_react11.useCallback)(
4414
+ const handleTouchEnd = (0, import_react13.useCallback)(
3107
4415
  (event) => {
3108
4416
  if (!dragStartRef.current || !hasMovedRef.current) {
3109
4417
  dragStartRef.current = null;
@@ -3141,7 +4449,7 @@ function useDrag(options = {}) {
3141
4449
  },
3142
4450
  [screenToWorld, applySnap, onDragEnd]
3143
4451
  );
3144
- (0, import_react11.useEffect)(() => {
4452
+ (0, import_react13.useEffect)(() => {
3145
4453
  if (!isListening) return;
3146
4454
  document.addEventListener("mousemove", handleMouseMove);
3147
4455
  document.addEventListener("mouseup", handleMouseUp);
@@ -3156,7 +4464,7 @@ function useDrag(options = {}) {
3156
4464
  document.removeEventListener("touchcancel", handleTouchEnd);
3157
4465
  };
3158
4466
  }, [isListening, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
3159
- const startDrag = (0, import_react11.useCallback)(
4467
+ const startDrag = (0, import_react13.useCallback)(
3160
4468
  (id, event, itemPosition) => {
3161
4469
  event.stopPropagation();
3162
4470
  let clientX;
@@ -3186,7 +4494,7 @@ function useDrag(options = {}) {
3186
4494
  },
3187
4495
  [screenToWorld]
3188
4496
  );
3189
- const cancelDrag = (0, import_react11.useCallback)(() => {
4497
+ const cancelDrag = (0, import_react13.useCallback)(() => {
3190
4498
  dragStartRef.current = null;
3191
4499
  hasMovedRef.current = false;
3192
4500
  setIsListening(false);
@@ -3197,7 +4505,7 @@ function useDrag(options = {}) {
3197
4505
  draggedId: null
3198
4506
  });
3199
4507
  }, []);
3200
- const isDraggingItem = (0, import_react11.useCallback)(
4508
+ const isDraggingItem = (0, import_react13.useCallback)(
3201
4509
  (id) => dragState.isDragging && dragState.draggedId === id,
3202
4510
  [dragState.isDragging, dragState.draggedId]
3203
4511
  );
@@ -3210,7 +4518,7 @@ function useDrag(options = {}) {
3210
4518
  }
3211
4519
 
3212
4520
  // src/diagram/hooks/useKeyboardShortcuts.ts
3213
- var import_react12 = require("react");
4521
+ var import_react14 = require("react");
3214
4522
  function useKeyboardShortcuts(options) {
3215
4523
  const {
3216
4524
  onDelete,
@@ -3221,7 +4529,7 @@ function useKeyboardShortcuts(options) {
3221
4529
  onSelectAll,
3222
4530
  enabled = true
3223
4531
  } = options;
3224
- (0, import_react12.useEffect)(() => {
4532
+ (0, import_react14.useEffect)(() => {
3225
4533
  if (!enabled) return;
3226
4534
  const handleKeyDown = (event) => {
3227
4535
  const target = event.target;
@@ -3330,7 +4638,7 @@ function nodeHasPort(node, portId) {
3330
4638
  }
3331
4639
 
3332
4640
  // src/components/PIDCanvas/PIDCanvas.tsx
3333
- var import_jsx_runtime16 = require("react/jsx-runtime");
4641
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3334
4642
  function PIDCanvas({
3335
4643
  diagram,
3336
4644
  className = "",
@@ -3357,16 +4665,30 @@ function PIDCanvas({
3357
4665
  telemetry,
3358
4666
  ...props
3359
4667
  }) {
3360
- const [localDiagram, setLocalDiagram] = (0, import_react13.useState)(diagram);
3361
- const [dragOverPosition, setDragOverPosition] = (0, import_react13.useState)(null);
3362
- const [isConnecting, setIsConnecting] = (0, import_react13.useState)(false);
3363
- const [connectionSource, setConnectionSource] = (0, import_react13.useState)(null);
3364
- const [connectionSourcePort, setConnectionSourcePort] = (0, import_react13.useState)(null);
3365
- const [connectionCursor, setConnectionCursor] = (0, import_react13.useState)(null);
3366
- const [hoveredNode, setHoveredNode] = (0, import_react13.useState)(null);
3367
- const [hoveredPort, setHoveredPort] = (0, import_react13.useState)(null);
3368
- (0, import_react13.useEffect)(() => {
3369
- setLocalDiagram(diagram);
4668
+ const [localDiagram, setLocalDiagram] = (0, import_react15.useState)(diagram);
4669
+ const [dragOverPosition, setDragOverPosition] = (0, import_react15.useState)(null);
4670
+ const [isConnecting, setIsConnecting] = (0, import_react15.useState)(false);
4671
+ const [connectionSource, setConnectionSource] = (0, import_react15.useState)(null);
4672
+ const [connectionSourcePort, setConnectionSourcePort] = (0, import_react15.useState)(null);
4673
+ const [connectionCursor, setConnectionCursor] = (0, import_react15.useState)(null);
4674
+ const [hoveredNode, setHoveredNode] = (0, import_react15.useState)(null);
4675
+ const [hoveredPort, setHoveredPort] = (0, import_react15.useState)(null);
4676
+ const [selectedWaypoint, setSelectedWaypoint] = (0, import_react15.useState)(null);
4677
+ const historyStack = (0, import_react15.useRef)([]);
4678
+ const redoStack = (0, import_react15.useRef)([]);
4679
+ const isUndoRedoAction = (0, import_react15.useRef)(false);
4680
+ const isInternalChange = (0, import_react15.useRef)(false);
4681
+ const lastInternalDiagram = (0, import_react15.useRef)(null);
4682
+ (0, import_react15.useEffect)(() => {
4683
+ const isExternal = !isInternalChange.current && diagram !== lastInternalDiagram.current;
4684
+ if (isExternal) {
4685
+ setLocalDiagram(diagram);
4686
+ if (!isUndoRedoAction.current) {
4687
+ historyStack.current = [];
4688
+ redoStack.current = [];
4689
+ }
4690
+ }
4691
+ isInternalChange.current = false;
3370
4692
  }, [diagram]);
3371
4693
  const {
3372
4694
  viewBox: controlledViewBox,
@@ -3379,7 +4701,7 @@ function PIDCanvas({
3379
4701
  enablePan: features.pan ?? true,
3380
4702
  enableZoom: features.zoom ?? true
3381
4703
  });
3382
- const calculateContentBounds = (0, import_react13.useCallback)(() => {
4704
+ const calculateContentBounds = (0, import_react15.useCallback)(() => {
3383
4705
  const nodes2 = localDiagram.nodes.filter((n) => n.visible !== false);
3384
4706
  if (nodes2.length === 0) {
3385
4707
  return null;
@@ -3402,7 +4724,7 @@ function PIDCanvas({
3402
4724
  });
3403
4725
  return { minX, minY, maxX, maxY };
3404
4726
  }, [localDiagram.nodes]);
3405
- (0, import_react13.useEffect)(() => {
4727
+ (0, import_react15.useEffect)(() => {
3406
4728
  if (onMounted) {
3407
4729
  const controls = {
3408
4730
  fitToContent: (padding = 50) => {
@@ -3443,7 +4765,187 @@ function PIDCanvas({
3443
4765
  });
3444
4766
  const dragEnabled = features.dragNodes ?? false;
3445
4767
  const selectionEnabled = features.selection ?? false;
3446
- const handleDragEnd = (0, import_react13.useCallback)(
4768
+ const containerRef = (0, import_react15.useRef)(null);
4769
+ const pushToHistory = (0, import_react15.useCallback)((currentDiagram) => {
4770
+ if (isUndoRedoAction.current) return;
4771
+ const snapshot = JSON.parse(JSON.stringify(currentDiagram));
4772
+ historyStack.current.push(snapshot);
4773
+ if (historyStack.current.length > 15) {
4774
+ historyStack.current.shift();
4775
+ }
4776
+ redoStack.current = [];
4777
+ }, []);
4778
+ const undo = (0, import_react15.useCallback)(() => {
4779
+ if (historyStack.current.length === 0) return;
4780
+ isUndoRedoAction.current = true;
4781
+ const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
4782
+ redoStack.current.push(currentSnapshot);
4783
+ const previousState = historyStack.current.pop();
4784
+ isInternalChange.current = true;
4785
+ lastInternalDiagram.current = previousState;
4786
+ setLocalDiagram(previousState);
4787
+ onDiagramChange?.(previousState);
4788
+ setTimeout(() => {
4789
+ isUndoRedoAction.current = false;
4790
+ }, 0);
4791
+ }, [localDiagram, onDiagramChange]);
4792
+ const redo = (0, import_react15.useCallback)(() => {
4793
+ if (redoStack.current.length === 0) return;
4794
+ isUndoRedoAction.current = true;
4795
+ const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
4796
+ historyStack.current.push(currentSnapshot);
4797
+ const nextState = redoStack.current.pop();
4798
+ isInternalChange.current = true;
4799
+ lastInternalDiagram.current = nextState;
4800
+ setLocalDiagram(nextState);
4801
+ onDiagramChange?.(nextState);
4802
+ setTimeout(() => {
4803
+ isUndoRedoAction.current = false;
4804
+ }, 0);
4805
+ }, [localDiagram, onDiagramChange]);
4806
+ (0, import_react15.useEffect)(() => {
4807
+ const handleKeyDown = (e) => {
4808
+ const isCtrlOrCmd = e.ctrlKey || e.metaKey;
4809
+ if (isCtrlOrCmd && e.key === "z" && !e.shiftKey) {
4810
+ e.preventDefault();
4811
+ undo();
4812
+ } else if (isCtrlOrCmd && e.key === "z" && e.shiftKey) {
4813
+ e.preventDefault();
4814
+ redo();
4815
+ } else if (isCtrlOrCmd && e.key === "y") {
4816
+ e.preventDefault();
4817
+ redo();
4818
+ }
4819
+ };
4820
+ window.addEventListener("keydown", handleKeyDown);
4821
+ return () => window.removeEventListener("keydown", handleKeyDown);
4822
+ }, [undo, redo]);
4823
+ (0, import_react15.useEffect)(() => {
4824
+ if (!features.dragNodes) return;
4825
+ const handleKeyDown = (e) => {
4826
+ if (selection.selectedNodeIds.size === 0 && !selectedWaypoint) return;
4827
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
4828
+ return;
4829
+ }
4830
+ const nudgeAmount = e.shiftKey ? 10 : 1;
4831
+ let dx = 0;
4832
+ let dy = 0;
4833
+ switch (e.key) {
4834
+ case "ArrowLeft":
4835
+ dx = -nudgeAmount;
4836
+ e.preventDefault();
4837
+ break;
4838
+ case "ArrowRight":
4839
+ dx = nudgeAmount;
4840
+ e.preventDefault();
4841
+ break;
4842
+ case "ArrowUp":
4843
+ dy = -nudgeAmount;
4844
+ e.preventDefault();
4845
+ break;
4846
+ case "ArrowDown":
4847
+ dy = nudgeAmount;
4848
+ e.preventDefault();
4849
+ break;
4850
+ default:
4851
+ return;
4852
+ }
4853
+ if (selectedWaypoint) {
4854
+ pushToHistory(localDiagram);
4855
+ const updatedPipes = localDiagram.pipes.map((pipe) => {
4856
+ if (pipe.id === selectedWaypoint.pipeId) {
4857
+ const updatedRoutePoints = pipe.routePoints.map(
4858
+ (point, idx) => idx === selectedWaypoint.pointIndex ? { x: point.x + dx, y: point.y + dy } : point
4859
+ );
4860
+ return { ...pipe, routePoints: updatedRoutePoints };
4861
+ }
4862
+ return pipe;
4863
+ });
4864
+ const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4865
+ isInternalChange.current = true;
4866
+ lastInternalDiagram.current = updatedDiagram;
4867
+ setLocalDiagram(updatedDiagram);
4868
+ onDiagramChange?.(updatedDiagram);
4869
+ return;
4870
+ }
4871
+ if (selectionEnabled && selection.selectedNodeIds.size > 0) {
4872
+ pushToHistory(localDiagram);
4873
+ const updatedNodes = localDiagram.nodes.map(
4874
+ (node) => selection.selectedNodeIds.has(node.id) ? {
4875
+ ...node,
4876
+ transform: {
4877
+ ...node.transform,
4878
+ x: node.transform.x + dx,
4879
+ y: node.transform.y + dy
4880
+ }
4881
+ } : node
4882
+ );
4883
+ const updatedDiagram = { ...localDiagram, nodes: updatedNodes };
4884
+ isInternalChange.current = true;
4885
+ lastInternalDiagram.current = updatedDiagram;
4886
+ setLocalDiagram(updatedDiagram);
4887
+ onDiagramChange?.(updatedDiagram);
4888
+ selection.selectedNodeIds.forEach((nodeId) => {
4889
+ const node = updatedNodes.find((n) => n.id === nodeId);
4890
+ if (node) {
4891
+ onNodeMove?.(nodeId, node.transform.x, node.transform.y);
4892
+ }
4893
+ });
4894
+ }
4895
+ };
4896
+ window.addEventListener("keydown", handleKeyDown);
4897
+ return () => window.removeEventListener("keydown", handleKeyDown);
4898
+ }, [
4899
+ selectionEnabled,
4900
+ features.dragNodes,
4901
+ selection.selectedNodeIds,
4902
+ selectedWaypoint,
4903
+ localDiagram,
4904
+ onDiagramChange,
4905
+ onNodeMove,
4906
+ pushToHistory
4907
+ ]);
4908
+ const handlePositionChange = (0, import_react15.useCallback)(
4909
+ (nodeId, x, y) => {
4910
+ pushToHistory(localDiagram);
4911
+ const updatedNodes = localDiagram.nodes.map(
4912
+ (node) => node.id === nodeId ? {
4913
+ ...node,
4914
+ transform: {
4915
+ ...node.transform,
4916
+ x,
4917
+ y
4918
+ }
4919
+ } : node
4920
+ );
4921
+ const updatedDiagram = { ...localDiagram, nodes: updatedNodes };
4922
+ isInternalChange.current = true;
4923
+ lastInternalDiagram.current = updatedDiagram;
4924
+ setLocalDiagram(updatedDiagram);
4925
+ onDiagramChange?.(updatedDiagram);
4926
+ onNodeMove?.(nodeId, x, y);
4927
+ },
4928
+ [localDiagram, onDiagramChange, onNodeMove, pushToHistory]
4929
+ );
4930
+ const handleWaypointPositionChange = (0, import_react15.useCallback)(
4931
+ (pipeId, pointIndex, x, y) => {
4932
+ pushToHistory(localDiagram);
4933
+ const updatedPipes = localDiagram.pipes.map((pipe) => {
4934
+ if (pipe.id === pipeId) {
4935
+ const updatedRoutePoints = pipe.routePoints.map(
4936
+ (point, idx) => idx === pointIndex ? { x, y } : point
4937
+ );
4938
+ return { ...pipe, routePoints: updatedRoutePoints };
4939
+ }
4940
+ return pipe;
4941
+ });
4942
+ const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4943
+ setLocalDiagram(updatedDiagram);
4944
+ onDiagramChange?.(updatedDiagram);
4945
+ },
4946
+ [localDiagram, onDiagramChange, pushToHistory]
4947
+ );
4948
+ const handleDragEnd = (0, import_react15.useCallback)(
3447
4949
  (nodeId, offset, finalPosition) => {
3448
4950
  const dragDistance = Math.sqrt(offset.x ** 2 + offset.y ** 2);
3449
4951
  if (dragDistance < 2) {
@@ -3452,6 +4954,7 @@ function PIDCanvas({
3452
4954
  }
3453
4955
  return;
3454
4956
  }
4957
+ pushToHistory(localDiagram);
3455
4958
  const updatedNodes = localDiagram.nodes.map(
3456
4959
  (node) => node.id === nodeId ? {
3457
4960
  ...node,
@@ -3489,11 +4992,13 @@ function PIDCanvas({
3489
4992
  nodes: updatedNodes,
3490
4993
  pipes: updatedPipes
3491
4994
  };
4995
+ isInternalChange.current = true;
4996
+ lastInternalDiagram.current = updatedDiagram;
3492
4997
  setLocalDiagram(updatedDiagram);
3493
4998
  onDiagramChange?.(updatedDiagram);
3494
4999
  onNodeMove?.(nodeId, finalPosition.x, finalPosition.y);
3495
5000
  },
3496
- [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode]
5001
+ [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode, pushToHistory]
3497
5002
  );
3498
5003
  const { dragState, startDrag } = useDrag({
3499
5004
  snapToGrid: features.snapToGrid ?? 0,
@@ -3504,6 +5009,7 @@ function PIDCanvas({
3504
5009
  snapToGrid: features.snapToGrid ?? 0,
3505
5010
  screenToWorld,
3506
5011
  onDragEnd: (pipeId, _offset, finalPosition) => {
5012
+ pushToHistory(localDiagram);
3507
5013
  const [actualPipeId, pointIndex] = pipeId.split(":");
3508
5014
  const idx = parseInt(pointIndex, 10);
3509
5015
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -3514,11 +5020,13 @@ function PIDCanvas({
3514
5020
  return { ...pipe, routePoints: updatedRoutePoints };
3515
5021
  });
3516
5022
  const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
5023
+ isInternalChange.current = true;
5024
+ lastInternalDiagram.current = updatedDiagram;
3517
5025
  setLocalDiagram(updatedDiagram);
3518
5026
  onDiagramChange?.(updatedDiagram);
3519
5027
  }
3520
5028
  });
3521
- const handleNodeDragStart = (0, import_react13.useCallback)(
5029
+ const handleNodeDragStart = (0, import_react15.useCallback)(
3522
5030
  (nodeId, event) => {
3523
5031
  const node = localDiagram.nodes.find((n) => n.id === nodeId);
3524
5032
  if (!node) return;
@@ -3526,7 +5034,16 @@ function PIDCanvas({
3526
5034
  },
3527
5035
  [localDiagram.nodes, startDrag]
3528
5036
  );
3529
- const handleWaypointDragStart = (0, import_react13.useCallback)(
5037
+ const handleWaypointClick = (0, import_react15.useCallback)(
5038
+ (pipeId, pointIndex) => {
5039
+ setSelectedWaypoint({ pipeId, pointIndex });
5040
+ if (selectionEnabled) {
5041
+ clearSelection();
5042
+ }
5043
+ },
5044
+ [selectionEnabled, clearSelection]
5045
+ );
5046
+ const handleWaypointDragStart = (0, import_react15.useCallback)(
3530
5047
  (pipeId, pointIndex, event) => {
3531
5048
  const pipe = localDiagram.pipes.find((p) => p.id === pipeId);
3532
5049
  if (!pipe || pointIndex >= pipe.routePoints.length) return;
@@ -3535,8 +5052,9 @@ function PIDCanvas({
3535
5052
  },
3536
5053
  [localDiagram.pipes, startWaypointDrag]
3537
5054
  );
3538
- const handlePipeSegmentClick = (0, import_react13.useCallback)(
5055
+ const handlePipeSegmentClick = (0, import_react15.useCallback)(
3539
5056
  (pipeId, position, segmentIndex) => {
5057
+ pushToHistory(localDiagram);
3540
5058
  const updatedPipes = localDiagram.pipes.map((pipe) => {
3541
5059
  if (pipe.id !== pipeId) return pipe;
3542
5060
  const updatedRoutePoints = [
@@ -3550,13 +5068,14 @@ function PIDCanvas({
3550
5068
  setLocalDiagram(updatedDiagram);
3551
5069
  onDiagramChange?.(updatedDiagram);
3552
5070
  },
3553
- [localDiagram, onDiagramChange]
5071
+ [localDiagram, onDiagramChange, pushToHistory]
3554
5072
  );
3555
5073
  const telemetryEnabled = features.telemetry ?? false;
3556
- const handleDelete = (0, import_react13.useCallback)(() => {
5074
+ const handleDelete = (0, import_react15.useCallback)(() => {
3557
5075
  if (!selectionEnabled) return;
3558
5076
  const { selectedNodeIds, selectedPipeIds } = selection;
3559
5077
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
5078
+ pushToHistory(localDiagram);
3560
5079
  const updatedNodes = localDiagram.nodes.filter((node) => !selectedNodeIds.has(node.id));
3561
5080
  const updatedPipes = localDiagram.pipes.filter(
3562
5081
  (pipe) => !selectedPipeIds.has(pipe.id) && !selectedNodeIds.has(pipe.fromNodeId) && !selectedNodeIds.has(pipe.toNodeId)
@@ -3566,18 +5085,28 @@ function PIDCanvas({
3566
5085
  nodes: updatedNodes,
3567
5086
  pipes: updatedPipes
3568
5087
  };
5088
+ isInternalChange.current = true;
5089
+ lastInternalDiagram.current = updatedDiagram;
3569
5090
  setLocalDiagram(updatedDiagram);
3570
5091
  clearSelection();
3571
5092
  onDiagramChange?.(updatedDiagram);
3572
5093
  onDelete?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3573
- }, [selection, localDiagram, selectionEnabled, clearSelection, onDiagramChange, onDelete]);
3574
- const handleCopy = (0, import_react13.useCallback)(() => {
5094
+ }, [
5095
+ selection,
5096
+ localDiagram,
5097
+ selectionEnabled,
5098
+ clearSelection,
5099
+ onDiagramChange,
5100
+ onDelete,
5101
+ pushToHistory
5102
+ ]);
5103
+ const handleCopy = (0, import_react15.useCallback)(() => {
3575
5104
  if (!selectionEnabled) return;
3576
5105
  const { selectedNodeIds, selectedPipeIds } = selection;
3577
5106
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
3578
5107
  onItemsCopied?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3579
5108
  }, [selection, selectionEnabled, onItemsCopied]);
3580
- const handlePaste = (0, import_react13.useCallback)(() => {
5109
+ const handlePaste = (0, import_react15.useCallback)(() => {
3581
5110
  if (!selectionEnabled) return;
3582
5111
  onPaste?.();
3583
5112
  }, [selectionEnabled, onPaste]);
@@ -3627,12 +5156,17 @@ function PIDCanvas({
3627
5156
  }
3628
5157
  onPipeClick?.(pipeId);
3629
5158
  };
3630
- const handleCanvasClick = () => {
5159
+ const handleCanvasClick = (e) => {
5160
+ const target = e.target;
5161
+ if (target.closest(".pipe-route-point")) {
5162
+ return;
5163
+ }
3631
5164
  if (selectionEnabled) {
3632
5165
  clearSelection();
3633
5166
  }
5167
+ setSelectedWaypoint(null);
3634
5168
  };
3635
- const handleDragOver = (0, import_react13.useCallback)(
5169
+ const handleDragOver = (0, import_react15.useCallback)(
3636
5170
  (event) => {
3637
5171
  if (!features.allowSymbolDrop) return;
3638
5172
  event.preventDefault();
@@ -3641,9 +5175,9 @@ function PIDCanvas({
3641
5175
  const worldPos = screenToWorld(event.clientX, event.clientY);
3642
5176
  setDragOverPosition(worldPos);
3643
5177
  },
3644
- [features.allowSymbolDrop, screenToWorld]
5178
+ [features.allowSymbolDrop, screenToWorld, svgRef]
3645
5179
  );
3646
- const handleDrop = (0, import_react13.useCallback)(
5180
+ const handleDrop = (0, import_react15.useCallback)(
3647
5181
  (event) => {
3648
5182
  if (!features.allowSymbolDrop) return;
3649
5183
  event.preventDefault();
@@ -3654,12 +5188,12 @@ function PIDCanvas({
3654
5188
  const worldPos = screenToWorld(event.clientX, event.clientY);
3655
5189
  onSymbolDrop?.(symbolId, worldPos);
3656
5190
  },
3657
- [features.allowSymbolDrop, screenToWorld, onSymbolDrop]
5191
+ [features.allowSymbolDrop, screenToWorld, onSymbolDrop, svgRef]
3658
5192
  );
3659
- const handleDragLeave = (0, import_react13.useCallback)(() => {
5193
+ const handleDragLeave = (0, import_react15.useCallback)(() => {
3660
5194
  setDragOverPosition(null);
3661
5195
  }, []);
3662
- const handleMouseMove = (0, import_react13.useCallback)(
5196
+ const handleMouseMove = (0, import_react15.useCallback)(
3663
5197
  (event) => {
3664
5198
  const connectionModeEnabled = features.connectionMode ?? false;
3665
5199
  if (connectionModeEnabled) {
@@ -3678,7 +5212,7 @@ function PIDCanvas({
3678
5212
  const distance = Math.sqrt(
3679
5213
  (worldPos.x - portPos.x) ** 2 + (worldPos.y - portPos.y) ** 2
3680
5214
  );
3681
- if (distance < 10) {
5215
+ if (distance < 15) {
3682
5216
  foundPort = port.id;
3683
5217
  foundNode = node.id;
3684
5218
  break;
@@ -3695,9 +5229,11 @@ function PIDCanvas({
3695
5229
  const { name } = diagram;
3696
5230
  const viewBox = controlledViewBox;
3697
5231
  const displayDiagram = localDiagram;
3698
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5232
+ const selectedNode = selectionEnabled && selection.selectedNodeIds.size === 1 ? localDiagram.nodes.find((n) => selection.selectedNodeIds.has(n.id)) || null : null;
5233
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3699
5234
  "div",
3700
5235
  {
5236
+ ref: containerRef,
3701
5237
  className: `pid-canvas ${className}`.trim(),
3702
5238
  style: {
3703
5239
  width: "100%",
@@ -3708,223 +5244,287 @@ function PIDCanvas({
3708
5244
  ...style
3709
5245
  },
3710
5246
  ...props,
3711
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3712
- "svg",
3713
- {
3714
- ref: svgRef,
3715
- width: "100%",
3716
- height: "100%",
3717
- viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
3718
- preserveAspectRatio: "xMidYMid meet",
3719
- style: {
3720
- display: "block"
3721
- },
3722
- "aria-label": `P&ID diagram: ${name}`,
3723
- role: "img",
3724
- onClick: handleCanvasClick,
3725
- onMouseMove: handleMouseMove,
3726
- onDragOver: handleDragOver,
3727
- onDrop: handleDrop,
3728
- onDragLeave: handleDragLeave,
3729
- children: [
3730
- features.showGrid !== false ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3731
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("defs", { children: [
3732
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
3733
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
3734
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
3735
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
3736
- ] })
3737
- ] }),
3738
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5247
+ children: [
5248
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5249
+ "svg",
5250
+ {
5251
+ ref: svgRef,
5252
+ width: "100%",
5253
+ height: "100%",
5254
+ viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
5255
+ preserveAspectRatio: "xMidYMid meet",
5256
+ style: {
5257
+ display: "block"
5258
+ },
5259
+ "aria-label": `P&ID diagram: ${name}`,
5260
+ role: "img",
5261
+ onClick: handleCanvasClick,
5262
+ onMouseMove: handleMouseMove,
5263
+ onDragOver: handleDragOver,
5264
+ onDrop: handleDrop,
5265
+ onDragLeave: handleDragLeave,
5266
+ children: [
5267
+ features.showGrid !== false ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
5268
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("defs", { children: [
5269
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
5270
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
5271
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
5272
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
5273
+ ] })
5274
+ ] }),
5275
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5276
+ "rect",
5277
+ {
5278
+ x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
5279
+ y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
5280
+ width: Math.ceil(viewBox.width * 3 / 25) * 25,
5281
+ height: Math.ceil(viewBox.height * 3 / 25) * 25,
5282
+ fill: "url(#grid-major)"
5283
+ }
5284
+ )
5285
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3739
5286
  "rect",
3740
5287
  {
3741
5288
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3742
5289
  y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3743
5290
  width: Math.ceil(viewBox.width * 3 / 25) * 25,
3744
5291
  height: Math.ceil(viewBox.height * 3 / 25) * 25,
3745
- fill: "url(#grid-major)"
5292
+ fill: features.backgroundColor ?? "#ffffff"
3746
5293
  }
3747
- )
3748
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3749
- "rect",
3750
- {
3751
- x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3752
- y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3753
- width: Math.ceil(viewBox.width * 3 / 25) * 25,
3754
- height: Math.ceil(viewBox.height * 3 / 25) * 25,
3755
- fill: features.backgroundColor ?? "#ffffff"
3756
- }
3757
- ),
3758
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3759
- PipeRenderer,
3760
- {
3761
- pipes: displayDiagram.pipes,
3762
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3763
- onPipeClick: selectionEnabled ? handlePipeClick : void 0,
3764
- enableWaypointEditing: false,
3765
- onWaypointDragStart: void 0,
3766
- editingPipeId: null,
3767
- draggingWaypointIndex: null,
3768
- waypointDragOffset: void 0,
3769
- onPipeSegmentClick: void 0
3770
- }
3771
- ),
3772
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3773
- NodeRenderer,
3774
- {
3775
- nodes: displayDiagram.nodes,
3776
- selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
3777
- onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
3778
- onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
3779
- enableDrag: dragEnabled && !isConnecting,
3780
- onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
3781
- draggingNodeId: dragState.draggedId,
3782
- dragOffset: dragState.offset
3783
- }
3784
- ),
3785
- (features.editPipeRoutes ?? false) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3786
- PipeRenderer,
3787
- {
3788
- pipes: displayDiagram.pipes,
3789
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3790
- onPipeClick: void 0,
3791
- enableWaypointEditing: true,
3792
- onWaypointDragStart: handleWaypointDragStart,
3793
- editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
3794
- draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
3795
- waypointDragOffset: waypointDragState.offset,
3796
- onPipeSegmentClick: handlePipeSegmentClick
3797
- }
3798
- ),
3799
- telemetryEnabled && telemetry && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
3800
- const binding = telemetry.get(node.id);
3801
- if (!binding) return null;
3802
- const symbol = getSymbolDefinition(node.symbolId);
3803
- if (!symbol) return null;
3804
- const overlayY = node.transform.y - symbol.viewBox.height / 2 - 10;
3805
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3806
- DataOverlay,
5294
+ ),
5295
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5296
+ PipeRenderer,
3807
5297
  {
3808
- nodeId: node.id,
3809
- x: node.transform.x,
3810
- y: overlayY,
3811
- telemetry: binding
3812
- },
3813
- `telemetry-${node.id}`
3814
- );
3815
- }) }),
3816
- dragOverPosition && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3817
- "circle",
3818
- {
3819
- cx: dragOverPosition.x,
3820
- cy: dragOverPosition.y,
3821
- r: 20,
3822
- fill: "#3b82f6",
3823
- opacity: 0.3,
3824
- stroke: "#3b82f6",
3825
- strokeWidth: 2,
3826
- pointerEvents: "none"
3827
- }
3828
- ),
3829
- isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
3830
- const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
3831
- if (!sourceNode) return null;
3832
- const symbol = getSymbolDefinition(sourceNode.symbolId);
3833
- let lineStartX = sourceNode.transform.x;
3834
- let lineStartY = sourceNode.transform.y;
3835
- if (connectionSourcePort && symbol?.ports) {
3836
- const port = symbol.ports.find((p) => p.id === connectionSourcePort);
3837
- if (port) {
3838
- const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
3839
- if (portPos) {
3840
- lineStartX = portPos.x;
3841
- lineStartY = portPos.y;
3842
- }
5298
+ pipes: displayDiagram.pipes,
5299
+ selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
5300
+ onPipeClick: selectionEnabled ? handlePipeClick : void 0,
5301
+ enableWaypointEditing: false,
5302
+ onWaypointDragStart: void 0,
5303
+ editingPipeId: null,
5304
+ draggingWaypointIndex: null,
5305
+ waypointDragOffset: void 0,
5306
+ onPipeSegmentClick: void 0
3843
5307
  }
3844
- }
3845
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3846
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3847
- "circle",
3848
- {
3849
- cx: sourceNode.transform.x,
3850
- cy: sourceNode.transform.y,
3851
- r: 30,
3852
- fill: "none",
3853
- stroke: "#10b981",
3854
- strokeWidth: 3,
3855
- opacity: 0.6
3856
- }
3857
- ),
3858
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3859
- "line",
3860
- {
3861
- x1: lineStartX,
3862
- y1: lineStartY,
3863
- x2: connectionCursor.x,
3864
- y2: connectionCursor.y,
3865
- stroke: "#10b981",
3866
- strokeWidth: 2,
3867
- strokeDasharray: "5,5",
3868
- opacity: 0.8
3869
- }
3870
- ),
3871
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3872
- "circle",
3873
- {
3874
- cx: connectionCursor.x,
3875
- cy: connectionCursor.y,
3876
- r: 8,
3877
- fill: "#10b981",
3878
- opacity: 0.6
3879
- }
3880
- )
3881
- ] });
3882
- })() }),
3883
- features.connectionMode && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "port-indicators", pointerEvents: "none", children: displayDiagram.nodes.map((node) => {
3884
- const symbol = getSymbolDefinition(node.symbolId);
3885
- if (!symbol?.ports) return null;
3886
- return symbol.ports.map((port) => {
3887
- const portPos = getPortWorldPosition(node, port.id);
3888
- if (!portPos) return null;
3889
- const isHovered = hoveredPort === port.id && hoveredNode === node.id;
3890
- const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
3891
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3892
- "circle",
5308
+ ),
5309
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5310
+ NodeRenderer,
5311
+ {
5312
+ nodes: displayDiagram.nodes,
5313
+ selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
5314
+ onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
5315
+ onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
5316
+ enableDrag: dragEnabled && !isConnecting,
5317
+ onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
5318
+ draggingNodeId: dragState.draggedId,
5319
+ dragOffset: dragState.offset
5320
+ }
5321
+ ),
5322
+ (features.editPipeRoutes ?? false) && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5323
+ PipeRenderer,
5324
+ {
5325
+ pipes: displayDiagram.pipes,
5326
+ selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
5327
+ onPipeClick: void 0,
5328
+ enableWaypointEditing: true,
5329
+ onWaypointClick: handleWaypointClick,
5330
+ onWaypointDragStart: handleWaypointDragStart,
5331
+ selectedWaypointPipeId: selectedWaypoint?.pipeId || null,
5332
+ selectedWaypointIndex: selectedWaypoint?.pointIndex ?? null,
5333
+ editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
5334
+ draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
5335
+ waypointDragOffset: waypointDragState.offset,
5336
+ onPipeSegmentClick: handlePipeSegmentClick
5337
+ }
5338
+ ),
5339
+ telemetryEnabled && telemetry && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
5340
+ const binding = telemetry.get(node.id);
5341
+ if (!binding) return null;
5342
+ const symbol = getSymbolDefinition(node.symbolId);
5343
+ if (!symbol) return null;
5344
+ const overlayX = node.transform.x + symbol.viewBox.width / 2;
5345
+ const overlayY = node.transform.y + symbol.viewBox.height + 50;
5346
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5347
+ DataOverlay,
3893
5348
  {
3894
- cx: portPos.x,
3895
- cy: portPos.y,
3896
- r: isHovered ? 8 : 5,
3897
- fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
3898
- stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
3899
- strokeWidth: isHovered || isSourcePort ? 2 : 1,
3900
- opacity: isHovered || isSourcePort ? 1 : 0.6
5349
+ nodeId: node.id,
5350
+ x: overlayX,
5351
+ y: overlayY,
5352
+ telemetry: binding,
5353
+ scale: features.telemetryScale ?? 1
3901
5354
  },
3902
- `${node.id}-${port.id}`
5355
+ `telemetry-${node.id}`
3903
5356
  );
3904
- });
3905
- }) })
3906
- ]
3907
- }
3908
- )
5357
+ }) }),
5358
+ dragOverPosition && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5359
+ "circle",
5360
+ {
5361
+ cx: dragOverPosition.x,
5362
+ cy: dragOverPosition.y,
5363
+ r: 20,
5364
+ fill: "#3b82f6",
5365
+ opacity: 0.3,
5366
+ stroke: "#3b82f6",
5367
+ strokeWidth: 2,
5368
+ pointerEvents: "none"
5369
+ }
5370
+ ),
5371
+ isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
5372
+ const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
5373
+ if (!sourceNode) return null;
5374
+ const symbol = getSymbolDefinition(sourceNode.symbolId);
5375
+ let lineStartX = sourceNode.transform.x;
5376
+ let lineStartY = sourceNode.transform.y;
5377
+ if (connectionSourcePort && symbol?.ports) {
5378
+ const port = symbol.ports.find((p) => p.id === connectionSourcePort);
5379
+ if (port) {
5380
+ const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
5381
+ if (portPos) {
5382
+ lineStartX = portPos.x;
5383
+ lineStartY = portPos.y;
5384
+ }
5385
+ }
5386
+ }
5387
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
5388
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5389
+ "circle",
5390
+ {
5391
+ cx: lineStartX,
5392
+ cy: lineStartY,
5393
+ r: 30,
5394
+ fill: "none",
5395
+ stroke: "#10b981",
5396
+ strokeWidth: 3,
5397
+ opacity: 0.6
5398
+ }
5399
+ ),
5400
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5401
+ "line",
5402
+ {
5403
+ x1: lineStartX,
5404
+ y1: lineStartY,
5405
+ x2: connectionCursor.x,
5406
+ y2: connectionCursor.y,
5407
+ stroke: "#10b981",
5408
+ strokeWidth: 2,
5409
+ strokeDasharray: "5,5",
5410
+ opacity: 0.8
5411
+ }
5412
+ ),
5413
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5414
+ "circle",
5415
+ {
5416
+ cx: connectionCursor.x,
5417
+ cy: connectionCursor.y,
5418
+ r: 8,
5419
+ fill: "#10b981",
5420
+ opacity: 0.6
5421
+ }
5422
+ )
5423
+ ] });
5424
+ })() }),
5425
+ features.connectionMode && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
5426
+ const symbol = getSymbolDefinition(node.symbolId);
5427
+ if (!symbol?.ports) return null;
5428
+ return symbol.ports.map((port) => {
5429
+ const portPos = getPortWorldPosition(node, port.id);
5430
+ if (!portPos) return null;
5431
+ const isHovered = hoveredPort === port.id && hoveredNode === node.id;
5432
+ const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
5433
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("g", { children: [
5434
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5435
+ "circle",
5436
+ {
5437
+ cx: portPos.x,
5438
+ cy: portPos.y,
5439
+ r: 15,
5440
+ fill: "transparent",
5441
+ style: { cursor: "pointer" },
5442
+ pointerEvents: "auto",
5443
+ onClick: (e) => {
5444
+ e.stopPropagation();
5445
+ if (isConnecting && connectionSource) {
5446
+ if (connectionSource !== node.id) {
5447
+ onPipeCreated?.(
5448
+ connectionSource,
5449
+ node.id,
5450
+ connectionSourcePort || void 0,
5451
+ port.id
5452
+ );
5453
+ setIsConnecting(false);
5454
+ setConnectionSource(null);
5455
+ setConnectionSourcePort(null);
5456
+ setConnectionCursor(null);
5457
+ setHoveredPort(null);
5458
+ }
5459
+ } else {
5460
+ setIsConnecting(true);
5461
+ setConnectionSource(node.id);
5462
+ setConnectionSourcePort(port.id);
5463
+ }
5464
+ }
5465
+ }
5466
+ ),
5467
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5468
+ "circle",
5469
+ {
5470
+ cx: portPos.x,
5471
+ cy: portPos.y,
5472
+ r: isHovered ? 8 : 5,
5473
+ fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
5474
+ stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
5475
+ strokeWidth: isHovered || isSourcePort ? 2 : 1,
5476
+ opacity: isHovered || isSourcePort ? 1 : 0.6,
5477
+ pointerEvents: "none"
5478
+ }
5479
+ )
5480
+ ] }, `${node.id}-${port.id}`);
5481
+ });
5482
+ }) })
5483
+ ]
5484
+ }
5485
+ ),
5486
+ features.dragNodes && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5487
+ PositionPanel,
5488
+ {
5489
+ selectedNode,
5490
+ selectedWaypoint: selectedWaypoint ? {
5491
+ pipeId: selectedWaypoint.pipeId,
5492
+ pointIndex: selectedWaypoint.pointIndex,
5493
+ position: localDiagram.pipes.find((p) => p.id === selectedWaypoint.pipeId)?.routePoints[selectedWaypoint.pointIndex] || { x: 0, y: 0 }
5494
+ } : null,
5495
+ onPositionChange: handlePositionChange,
5496
+ onWaypointPositionChange: handleWaypointPositionChange,
5497
+ position: "top-right"
5498
+ }
5499
+ )
5500
+ ]
3909
5501
  }
3910
5502
  );
3911
5503
  }
3912
5504
 
3913
5505
  // src/components/SymbolLibrary/SymbolLibrary.tsx
3914
- var import_react14 = require("react");
3915
- var import_jsx_runtime17 = require("react/jsx-runtime");
5506
+ var import_react16 = require("react");
5507
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3916
5508
  function SymbolLibrary({
3917
5509
  className = "",
3918
5510
  onSymbolDragStart,
3919
5511
  showCategories = true,
3920
5512
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
3921
5513
  }) {
3922
- const [selectedCategory, setSelectedCategory] = (0, import_react14.useState)("all");
3923
- const [searchQuery, setSearchQuery] = (0, import_react14.useState)("");
5514
+ const [selectedCategory, setSelectedCategory] = (0, import_react16.useState)("all");
5515
+ const [searchQuery, setSearchQuery] = (0, import_react16.useState)("");
3924
5516
  const symbolIds = getAvailableSymbols();
3925
5517
  const allSymbols = symbolIds.map((id) => getSymbolDefinition(id)).filter((symbol) => symbol !== void 0);
5518
+ const categoryMap = {
5519
+ Valves: "valve",
5520
+ Pumps: "pump",
5521
+ Tanks: "vessel",
5522
+ Instruments: "instrument",
5523
+ Displays: "display"
5524
+ };
3926
5525
  const filteredSymbols = allSymbols.filter((symbol) => {
3927
- const matchesCategory = selectedCategory === "all" || symbol.category === selectedCategory;
5526
+ const actualCategory = categoryMap[selectedCategory] || selectedCategory;
5527
+ const matchesCategory = selectedCategory === "all" || symbol.category === actualCategory;
3928
5528
  const matchesSearch = searchQuery === "" || symbol.name.toLowerCase().includes(searchQuery.toLowerCase()) || symbol.id.toLowerCase().includes(searchQuery.toLowerCase());
3929
5529
  return matchesCategory && matchesSearch;
3930
5530
  });
@@ -3933,9 +5533,9 @@ function SymbolLibrary({
3933
5533
  event.dataTransfer.effectAllowed = "copy";
3934
5534
  onSymbolDragStart?.(symbol.id, event);
3935
5535
  };
3936
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
3937
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h3", { style: styles.title, children: "Symbol Library" }) }),
3938
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.searchContainer, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5536
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
5537
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: styles.title, children: "Symbol Library" }) }),
5538
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.searchContainer, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3939
5539
  "input",
3940
5540
  {
3941
5541
  type: "text",
@@ -3945,8 +5545,8 @@ function SymbolLibrary({
3945
5545
  style: styles.searchInput
3946
5546
  }
3947
5547
  ) }),
3948
- showCategories && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: styles.categories, children: [
3949
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5548
+ showCategories && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: styles.categories, children: [
5549
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3950
5550
  "button",
3951
5551
  {
3952
5552
  onClick: () => setSelectedCategory("all"),
@@ -3957,7 +5557,7 @@ function SymbolLibrary({
3957
5557
  children: "All"
3958
5558
  }
3959
5559
  ),
3960
- categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5560
+ categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3961
5561
  "button",
3962
5562
  {
3963
5563
  onClick: () => setSelectedCategory(category),
@@ -3970,7 +5570,7 @@ function SymbolLibrary({
3970
5570
  category
3971
5571
  ))
3972
5572
  ] }),
3973
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5573
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3974
5574
  "div",
3975
5575
  {
3976
5576
  draggable: true,
@@ -3978,15 +5578,15 @@ function SymbolLibrary({
3978
5578
  style: styles.symbolCard,
3979
5579
  title: symbol.metadata?.description || symbol.name,
3980
5580
  children: [
3981
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.symbolPreview, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5581
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.symbolPreview, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3982
5582
  "svg",
3983
5583
  {
3984
5584
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
3985
5585
  style: styles.symbolSvg,
3986
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
5586
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
3987
5587
  }
3988
5588
  ) }),
3989
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.symbolName, children: symbol.name })
5589
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.symbolName, children: symbol.name })
3990
5590
  ]
3991
5591
  },
3992
5592
  symbol.id
@@ -4095,7 +5695,7 @@ var styles = {
4095
5695
  };
4096
5696
 
4097
5697
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
4098
- var import_jsx_runtime18 = require("react/jsx-runtime");
5698
+ var import_jsx_runtime19 = require("react/jsx-runtime");
4099
5699
  function NodeConfigPanel({
4100
5700
  node,
4101
5701
  binding,
@@ -4114,15 +5714,15 @@ function NodeConfigPanel({
4114
5714
  padding: "20px",
4115
5715
  ...style
4116
5716
  };
4117
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className, style: defaultStyle, children: [
4118
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
5717
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className, style: defaultStyle, children: [
5718
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
4119
5719
  "Configure: ",
4120
5720
  node.id
4121
5721
  ] }),
4122
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4123
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
4124
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
4125
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5722
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "20px" }, children: [
5723
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
5724
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
5725
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4126
5726
  "input",
4127
5727
  {
4128
5728
  type: "text",
@@ -4138,10 +5738,10 @@ function NodeConfigPanel({
4138
5738
  }
4139
5739
  }
4140
5740
  ),
4141
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4142
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4143
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
4144
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5741
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5742
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5743
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
5744
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4145
5745
  "input",
4146
5746
  {
4147
5747
  type: "number",
@@ -4162,9 +5762,9 @@ function NodeConfigPanel({
4162
5762
  }
4163
5763
  )
4164
5764
  ] }),
4165
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4166
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
4167
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5765
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5766
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
5767
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4168
5768
  "input",
4169
5769
  {
4170
5770
  type: "number",
@@ -4186,9 +5786,9 @@ function NodeConfigPanel({
4186
5786
  )
4187
5787
  ] })
4188
5788
  ] }),
4189
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4190
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
4191
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5789
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5790
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
5791
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4192
5792
  "input",
4193
5793
  {
4194
5794
  type: "number",
@@ -4208,9 +5808,9 @@ function NodeConfigPanel({
4208
5808
  }
4209
5809
  )
4210
5810
  ] }),
4211
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4212
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
4213
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5811
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5812
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
5813
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4214
5814
  "input",
4215
5815
  {
4216
5816
  type: "number",
@@ -4235,9 +5835,9 @@ function NodeConfigPanel({
4235
5835
  )
4236
5836
  ] })
4237
5837
  ] }),
4238
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4239
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
4240
- !binding ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5838
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "20px" }, children: [
5839
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
5840
+ !binding ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4241
5841
  "button",
4242
5842
  {
4243
5843
  onClick: () => {
@@ -4280,9 +5880,9 @@ function NodeConfigPanel({
4280
5880
  },
4281
5881
  children: "+ Add Telemetry"
4282
5882
  }
4283
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
4284
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
4285
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5883
+ ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
5884
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
5885
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4286
5886
  "input",
4287
5887
  {
4288
5888
  type: "number",
@@ -4304,8 +5904,8 @@ function NodeConfigPanel({
4304
5904
  }
4305
5905
  }
4306
5906
  ),
4307
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
4308
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5907
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
5908
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4309
5909
  "input",
4310
5910
  {
4311
5911
  type: "text",
@@ -4327,8 +5927,8 @@ function NodeConfigPanel({
4327
5927
  }
4328
5928
  }
4329
5929
  ),
4330
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4331
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5930
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
5931
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4332
5932
  "input",
4333
5933
  {
4334
5934
  type: "checkbox",
@@ -4350,8 +5950,8 @@ function NodeConfigPanel({
4350
5950
  ),
4351
5951
  "Show Sparkline"
4352
5952
  ] }),
4353
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginTop: "12px" }, children: [
4354
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5953
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginTop: "12px" }, children: [
5954
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4355
5955
  "label",
4356
5956
  {
4357
5957
  style: {
@@ -4363,10 +5963,10 @@ function NodeConfigPanel({
4363
5963
  children: "Telemetry Position Offset:"
4364
5964
  }
4365
5965
  ),
4366
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4367
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4368
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
4369
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5966
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5967
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5968
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
5969
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4370
5970
  "input",
4371
5971
  {
4372
5972
  type: "number",
@@ -4393,9 +5993,9 @@ function NodeConfigPanel({
4393
5993
  }
4394
5994
  )
4395
5995
  ] }),
4396
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4397
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
4398
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5996
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5997
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
5998
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4399
5999
  "input",
4400
6000
  {
4401
6001
  type: "number",
@@ -4423,8 +6023,8 @@ function NodeConfigPanel({
4423
6023
  )
4424
6024
  ] })
4425
6025
  ] }),
4426
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginTop: "16px" }, children: [
4427
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6026
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginTop: "16px" }, children: [
6027
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4428
6028
  "label",
4429
6029
  {
4430
6030
  style: {
@@ -4436,10 +6036,10 @@ function NodeConfigPanel({
4436
6036
  children: "Display Colors:"
4437
6037
  }
4438
6038
  ),
4439
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4440
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
4441
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4442
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6039
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6040
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
6041
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6042
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4443
6043
  "input",
4444
6044
  {
4445
6045
  type: "color",
@@ -4465,7 +6065,7 @@ function NodeConfigPanel({
4465
6065
  }
4466
6066
  }
4467
6067
  ),
4468
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6068
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4469
6069
  "input",
4470
6070
  {
4471
6071
  type: "text",
@@ -4495,10 +6095,10 @@ function NodeConfigPanel({
4495
6095
  )
4496
6096
  ] })
4497
6097
  ] }),
4498
- binding.display?.showSparkline && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4499
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
4500
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4501
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6098
+ binding.display?.showSparkline && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6099
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
6100
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6101
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4502
6102
  "input",
4503
6103
  {
4504
6104
  type: "color",
@@ -4524,7 +6124,7 @@ function NodeConfigPanel({
4524
6124
  }
4525
6125
  }
4526
6126
  ),
4527
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6127
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4528
6128
  "input",
4529
6129
  {
4530
6130
  type: "text",
@@ -4554,9 +6154,9 @@ function NodeConfigPanel({
4554
6154
  )
4555
6155
  ] })
4556
6156
  ] }),
4557
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4558
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
4559
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6157
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6158
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
6159
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4560
6160
  "input",
4561
6161
  {
4562
6162
  type: "text",
@@ -4584,20 +6184,20 @@ function NodeConfigPanel({
4584
6184
  }
4585
6185
  }
4586
6186
  ),
4587
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
6187
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
4588
6188
  ] })
4589
6189
  ] }),
4590
6190
  " "
4591
6191
  ] })
4592
6192
  ] })
4593
6193
  ] }),
4594
- binding && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4595
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
6194
+ binding && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "20px" }, children: [
6195
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
4596
6196
  ["highHigh", "high", "low", "lowLow"].map((key) => {
4597
6197
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
4598
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4599
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
4600
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6198
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6199
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
6200
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4601
6201
  "input",
4602
6202
  {
4603
6203
  type: "number",
@@ -4621,8 +6221,8 @@ function NodeConfigPanel({
4621
6221
  ] }, key);
4622
6222
  })
4623
6223
  ] }),
4624
- binding && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4625
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
6224
+ binding && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "20px" }, children: [
6225
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
4626
6226
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
4627
6227
  const defaultColors = {
4628
6228
  normal: "#10b981",
@@ -4632,13 +6232,13 @@ function NodeConfigPanel({
4632
6232
  off: "#6b7280"
4633
6233
  };
4634
6234
  const label = status === "normal" ? "Normal" : status === "warning" ? "Warning" : status === "alarm" ? "Alarm" : status === "fault" ? "Fault" : "Off";
4635
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4636
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
6235
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6236
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4637
6237
  label,
4638
6238
  ":"
4639
6239
  ] }),
4640
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4641
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6240
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6241
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4642
6242
  "input",
4643
6243
  {
4644
6244
  type: "color",
@@ -4659,7 +6259,7 @@ function NodeConfigPanel({
4659
6259
  }
4660
6260
  }
4661
6261
  ),
4662
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6262
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4663
6263
  "input",
4664
6264
  {
4665
6265
  type: "text",
@@ -4686,7 +6286,7 @@ function NodeConfigPanel({
4686
6286
  ] }, status);
4687
6287
  })
4688
6288
  ] }),
4689
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6289
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4690
6290
  "button",
4691
6291
  {
4692
6292
  onClick: onRemove,
@@ -4926,7 +6526,7 @@ var DEFAULT_THRESHOLDS = {
4926
6526
  };
4927
6527
 
4928
6528
  // src/diagram/hooks/useSimulation.ts
4929
- var import_react15 = require("react");
6529
+ var import_react17 = require("react");
4930
6530
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4931
6531
  const {
4932
6532
  interval = 2e3,
@@ -4937,7 +6537,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4937
6537
  historyLength = 10,
4938
6538
  generateValue
4939
6539
  } = options;
4940
- (0, import_react15.useEffect)(() => {
6540
+ (0, import_react17.useEffect)(() => {
4941
6541
  if (!enabled) return;
4942
6542
  const intervalId = setInterval(() => {
4943
6543
  const updates = [];
@@ -5035,11 +6635,14 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
5035
6635
  mockSymbolLibrary,
5036
6636
  nodeHasPort,
5037
6637
  overlayStyles,
6638
+ registerSymbol,
6639
+ registerSymbols,
5038
6640
  useDrag,
5039
6641
  useKeyboardShortcuts,
5040
6642
  useSelection,
5041
6643
  useSimulation,
5042
6644
  useTelemetry,
6645
+ useTelemetryStatus,
5043
6646
  useTheme,
5044
6647
  useViewBox,
5045
6648
  validateDiagram