@procaaso/alphinity-ui-components 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -707,14 +707,721 @@ var StatusIndicator = ({
707
707
  ] });
708
708
  };
709
709
 
710
+ // src/components/NodeControlsPanel/NodeControlsPanel.tsx
711
+ import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
712
+ var NodeControlsPanel = ({
713
+ config,
714
+ onClose,
715
+ position = "bottom",
716
+ touchOptimized = true,
717
+ className = "",
718
+ style,
719
+ ...props
720
+ }) => {
721
+ if (!config) return null;
722
+ const isBottom = position === "bottom";
723
+ const touchClass = touchOptimized ? "touch-optimized" : "";
724
+ const containerStyle = {
725
+ position: "fixed",
726
+ zIndex: 9999,
727
+ background: "white",
728
+ boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.15)",
729
+ borderRadius: isBottom ? "16px 16px 0 0" : "0",
730
+ animation: isBottom ? "slideUp 0.3s ease-out" : "slideInRight 0.3s ease-out",
731
+ display: "flex",
732
+ flexDirection: "column",
733
+ ...isBottom ? {
734
+ left: 0,
735
+ right: 0,
736
+ bottom: 0,
737
+ maxHeight: "70vh",
738
+ borderTop: "1px solid #e5e7eb"
739
+ } : {
740
+ right: 0,
741
+ top: 0,
742
+ bottom: 0,
743
+ width: touchOptimized ? "400px" : "320px",
744
+ borderLeft: "1px solid #e5e7eb"
745
+ },
746
+ ...style
747
+ };
748
+ const headerStyle = {
749
+ display: "flex",
750
+ alignItems: "center",
751
+ justifyContent: "space-between",
752
+ padding: touchOptimized ? "1.25rem 1.5rem" : "1rem",
753
+ borderBottom: "2px solid #e5e7eb",
754
+ background: "#f9fafb",
755
+ flexShrink: 0
756
+ };
757
+ const titleStyle = {
758
+ margin: 0,
759
+ fontSize: touchOptimized ? "1.5rem" : "1.25rem",
760
+ fontWeight: 600,
761
+ color: "#111827"
762
+ };
763
+ const closeButtonStyle = {
764
+ background: "#ef4444",
765
+ color: "white",
766
+ border: "none",
767
+ borderRadius: "8px",
768
+ width: touchOptimized ? "56px" : "44px",
769
+ height: touchOptimized ? "56px" : "44px",
770
+ fontSize: touchOptimized ? "1.75rem" : "1.5rem",
771
+ fontWeight: "bold",
772
+ cursor: "pointer",
773
+ display: "flex",
774
+ alignItems: "center",
775
+ justifyContent: "center",
776
+ transition: "all 0.2s ease",
777
+ flexShrink: 0
778
+ };
779
+ const contentStyle = {
780
+ padding: touchOptimized ? "1.5rem" : "1rem",
781
+ overflowY: "auto",
782
+ flex: 1
783
+ };
784
+ return /* @__PURE__ */ jsxs6(Fragment, { children: [
785
+ /* @__PURE__ */ jsx7(
786
+ "div",
787
+ {
788
+ style: {
789
+ position: "fixed",
790
+ inset: 0,
791
+ background: "rgba(0, 0, 0, 0.3)",
792
+ zIndex: 9998,
793
+ animation: "fadeIn 0.3s ease-out"
794
+ },
795
+ onClick: onClose
796
+ }
797
+ ),
798
+ /* @__PURE__ */ jsxs6(
799
+ "div",
800
+ {
801
+ className: `node-controls-panel ${touchClass} ${className}`.trim(),
802
+ style: containerStyle,
803
+ ...props,
804
+ children: [
805
+ /* @__PURE__ */ jsxs6("div", { style: headerStyle, children: [
806
+ /* @__PURE__ */ jsx7("h2", { style: titleStyle, children: config.title }),
807
+ /* @__PURE__ */ jsx7(
808
+ "button",
809
+ {
810
+ style: closeButtonStyle,
811
+ onClick: onClose,
812
+ onMouseEnter: (e) => {
813
+ e.currentTarget.style.background = "#dc2626";
814
+ e.currentTarget.style.transform = "scale(1.05)";
815
+ },
816
+ onMouseLeave: (e) => {
817
+ e.currentTarget.style.background = "#ef4444";
818
+ e.currentTarget.style.transform = "scale(1)";
819
+ },
820
+ "aria-label": "Close",
821
+ children: "\u2715"
822
+ }
823
+ )
824
+ ] }),
825
+ /* @__PURE__ */ jsx7("div", { style: contentStyle, children: /* @__PURE__ */ jsx7(
826
+ ControlPanel,
827
+ {
828
+ title: config.nodeId,
829
+ selectedMode: config.currentMode,
830
+ modeOptions: config.modes,
831
+ onModeChange: config.onModeChange,
832
+ setpointValue: config.setpoint,
833
+ onSetpointChange: config.onSetpointChange,
834
+ secondSetpointValue: config.secondSetpoint,
835
+ onSecondSetpointChange: config.onSecondSetpointChange,
836
+ modeConfigs: config.modeConfigs,
837
+ status: config.status,
838
+ setpointReadOnly: config.readOnly,
839
+ showStartStopButtons: !!(config.onStart || config.onStop),
840
+ isRunning: config.isRunning,
841
+ onStart: config.onStart,
842
+ onStop: config.onStop,
843
+ startButtonText: config.startButtonText,
844
+ stopButtonText: config.stopButtonText,
845
+ size: touchOptimized ? "large" : "medium"
846
+ }
847
+ ) })
848
+ ]
849
+ }
850
+ ),
851
+ /* @__PURE__ */ jsx7("style", { children: `
852
+ @keyframes slideUp {
853
+ from {
854
+ transform: translateY(100%);
855
+ opacity: 0;
856
+ }
857
+ to {
858
+ transform: translateY(0);
859
+ opacity: 1;
860
+ }
861
+ }
862
+
863
+ @keyframes slideInRight {
864
+ from {
865
+ transform: translateX(100%);
866
+ opacity: 0;
867
+ }
868
+ to {
869
+ transform: translateX(0);
870
+ opacity: 1;
871
+ }
872
+ }
873
+
874
+ @keyframes fadeIn {
875
+ from {
876
+ opacity: 0;
877
+ }
878
+ to {
879
+ opacity: 1;
880
+ }
881
+ }
882
+
883
+ .node-controls-panel.touch-optimized button {
884
+ min-height: 56px;
885
+ font-size: 1.125rem;
886
+ }
887
+
888
+ .node-controls-panel.touch-optimized input {
889
+ min-height: 56px;
890
+ font-size: 1.125rem;
891
+ }
892
+ ` })
893
+ ] });
894
+ };
895
+
896
+ // src/components/DeviceControlPanel/DeviceControlPanel.tsx
897
+ import { useState as useState5, useEffect as useEffect2 } from "react";
898
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
899
+ function DeviceControlPanel({
900
+ binding,
901
+ onClose,
902
+ position = "bottom",
903
+ touchOptimized = true,
904
+ onModeChange,
905
+ onParameterChange,
906
+ onStart,
907
+ onStop,
908
+ onCustomAction
909
+ }) {
910
+ const [localParameterValues, setLocalParameterValues] = useState5({});
911
+ useEffect2(() => {
912
+ if (binding) {
913
+ const currentParams2 = binding.parameters[binding.state.currentMode] || [];
914
+ const values = {};
915
+ currentParams2.forEach((param) => {
916
+ values[param.id] = param.value;
917
+ });
918
+ setLocalParameterValues(values);
919
+ }
920
+ }, [binding, binding?.state.currentMode]);
921
+ if (!binding) return null;
922
+ const currentMode = binding.state.currentMode;
923
+ const currentParams = binding.parameters[currentMode] || [];
924
+ const touchSize = touchOptimized ? 56 : 40;
925
+ const backgroundColor = binding.theme?.backgroundColor || "#edeeef";
926
+ const surfaceColor = "#f5f6f7";
927
+ const borderColor = "#d2d2d2";
928
+ const textColor = binding.theme?.textColor || "#000000";
929
+ const mutedTextColor = "#666666";
930
+ const selectedColor = "#b4d0fe";
931
+ const statusColors = {
932
+ normal: binding.theme?.statusColors?.normal || "#888888",
933
+ // Gray (no alarm)
934
+ warning: binding.theme?.statusColors?.warning || "#FFD700",
935
+ // Yellow
936
+ alarm: binding.theme?.statusColors?.alarm || "#FF0000",
937
+ // Red
938
+ fault: binding.theme?.statusColors?.fault || "#FF0000",
939
+ // Red
940
+ off: binding.theme?.statusColors?.off || "#666666",
941
+ // Dark gray
942
+ starting: binding.theme?.statusColors?.starting || "#90EE90",
943
+ // Light green
944
+ stopping: binding.theme?.statusColors?.stopping || "#FFD700"
945
+ // Yellow
946
+ };
947
+ const statusColor = statusColors[binding.state.status || "normal"];
948
+ const handleModeChange = (mode) => {
949
+ onModeChange?.(mode);
950
+ };
951
+ const handleParameterCommit = (parameterId, value) => {
952
+ onParameterChange?.(parameterId, value);
953
+ };
954
+ const handleParameterInput = (parameterId, value) => {
955
+ setLocalParameterValues((prev) => ({ ...prev, [parameterId]: value }));
956
+ };
957
+ const panelStyle = {
958
+ position: "fixed",
959
+ ...position === "bottom" ? {
960
+ bottom: 0,
961
+ left: 0,
962
+ right: 0,
963
+ animation: "slideUp 0.3s ease-out"
964
+ } : {
965
+ right: 0,
966
+ top: 0,
967
+ bottom: 0,
968
+ width: "400px",
969
+ animation: "slideIn 0.3s ease-out"
970
+ },
971
+ backgroundColor,
972
+ color: textColor,
973
+ borderRadius: position === "bottom" ? "8px 8px 0 0" : "8px 0 0 8px",
974
+ boxShadow: "none",
975
+ border: `2px solid ${borderColor}`,
976
+ padding: "20px",
977
+ zIndex: 9999,
978
+ maxHeight: position === "bottom" ? "70vh" : "100vh",
979
+ overflowY: "auto",
980
+ ...binding.display?.style
981
+ };
982
+ const titleText = binding.display?.title || binding.deviceName || binding.tag || binding.nodeId;
983
+ return /* @__PURE__ */ jsx8(Fragment2, { children: /* @__PURE__ */ jsxs7("div", { style: panelStyle, className: binding.display?.className, children: [
984
+ /* @__PURE__ */ jsxs7(
985
+ "div",
986
+ {
987
+ style: {
988
+ display: "flex",
989
+ justifyContent: "space-between",
990
+ alignItems: "center",
991
+ marginBottom: "16px",
992
+ paddingBottom: "12px",
993
+ borderBottom: `2px solid ${borderColor}`
994
+ },
995
+ children: [
996
+ /* @__PURE__ */ jsxs7("div", { children: [
997
+ /* @__PURE__ */ jsx8(
998
+ "h3",
999
+ {
1000
+ style: {
1001
+ margin: 0,
1002
+ fontSize: "14px",
1003
+ fontWeight: 600,
1004
+ fontFamily: "Arial, sans-serif",
1005
+ letterSpacing: "0.5px",
1006
+ textTransform: "uppercase",
1007
+ color: textColor
1008
+ },
1009
+ children: titleText
1010
+ }
1011
+ ),
1012
+ binding.deviceType && /* @__PURE__ */ jsx8(
1013
+ "div",
1014
+ {
1015
+ style: {
1016
+ fontSize: "11px",
1017
+ color: mutedTextColor,
1018
+ marginTop: "4px",
1019
+ fontFamily: "Arial, sans-serif"
1020
+ },
1021
+ children: binding.deviceType
1022
+ }
1023
+ )
1024
+ ] }),
1025
+ /* @__PURE__ */ jsx8(
1026
+ "button",
1027
+ {
1028
+ onClick: onClose,
1029
+ style: {
1030
+ width: touchSize * 0.7,
1031
+ height: touchSize * 0.7,
1032
+ backgroundColor: surfaceColor,
1033
+ color: textColor,
1034
+ border: `2px solid ${borderColor}`,
1035
+ borderRadius: "2px",
1036
+ fontSize: "18px",
1037
+ cursor: "pointer",
1038
+ fontWeight: "bold",
1039
+ fontFamily: "Arial, sans-serif"
1040
+ },
1041
+ children: "\xD7"
1042
+ }
1043
+ )
1044
+ ]
1045
+ }
1046
+ ),
1047
+ binding.state.status && /* @__PURE__ */ jsxs7(
1048
+ "div",
1049
+ {
1050
+ style: {
1051
+ display: "flex",
1052
+ alignItems: "center",
1053
+ gap: "12px",
1054
+ padding: "10px 12px",
1055
+ backgroundColor: surfaceColor,
1056
+ border: `1px solid ${borderColor}`,
1057
+ borderRadius: "6px",
1058
+ marginBottom: "16px"
1059
+ },
1060
+ children: [
1061
+ /* @__PURE__ */ jsx8(
1062
+ "div",
1063
+ {
1064
+ style: {
1065
+ width: "10px",
1066
+ height: "10px",
1067
+ borderRadius: "50%",
1068
+ backgroundColor: statusColor,
1069
+ border: "1px solid #333"
1070
+ }
1071
+ }
1072
+ ),
1073
+ /* @__PURE__ */ jsxs7("div", { style: { flex: 1 }, children: [
1074
+ /* @__PURE__ */ jsx8(
1075
+ "div",
1076
+ {
1077
+ style: {
1078
+ fontWeight: 600,
1079
+ textTransform: "uppercase",
1080
+ fontSize: "11px",
1081
+ fontFamily: "Arial, sans-serif",
1082
+ letterSpacing: "0.5px",
1083
+ color: textColor
1084
+ },
1085
+ children: binding.state.status
1086
+ }
1087
+ ),
1088
+ binding.state.statusMessage && /* @__PURE__ */ jsx8(
1089
+ "div",
1090
+ {
1091
+ style: {
1092
+ fontSize: "10px",
1093
+ color: mutedTextColor,
1094
+ marginTop: "2px",
1095
+ fontFamily: "Arial, sans-serif"
1096
+ },
1097
+ children: binding.state.statusMessage
1098
+ }
1099
+ )
1100
+ ] }),
1101
+ binding.state.isRunning !== void 0 && /* @__PURE__ */ jsx8(
1102
+ "div",
1103
+ {
1104
+ style: {
1105
+ padding: "4px 10px",
1106
+ backgroundColor: binding.state.isRunning ? selectedColor : surfaceColor,
1107
+ border: `1px solid ${borderColor}`,
1108
+ borderRadius: "2px",
1109
+ fontSize: "10px",
1110
+ fontWeight: 600,
1111
+ fontFamily: "Arial, sans-serif",
1112
+ letterSpacing: "0.5px",
1113
+ color: textColor
1114
+ },
1115
+ children: binding.state.isRunning ? "RUNNING" : "STOPPED"
1116
+ }
1117
+ )
1118
+ ]
1119
+ }
1120
+ ),
1121
+ binding.display?.showModeSelector !== false && binding.modes.length > 0 && /* @__PURE__ */ jsxs7("div", { style: { marginBottom: "16px" }, children: [
1122
+ /* @__PURE__ */ jsx8(
1123
+ "label",
1124
+ {
1125
+ style: {
1126
+ display: "block",
1127
+ marginBottom: "8px",
1128
+ fontSize: "11px",
1129
+ fontWeight: 600,
1130
+ textTransform: "uppercase",
1131
+ letterSpacing: "0.5px",
1132
+ fontFamily: "Arial, sans-serif",
1133
+ color: mutedTextColor
1134
+ },
1135
+ children: "Mode"
1136
+ }
1137
+ ),
1138
+ /* @__PURE__ */ jsx8("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: binding.modes.map((mode) => {
1139
+ const isSelected = mode.value === currentMode;
1140
+ const isEnabled = mode.enabled !== false;
1141
+ return /* @__PURE__ */ jsx8(
1142
+ "button",
1143
+ {
1144
+ onClick: () => isEnabled && handleModeChange(mode.value),
1145
+ disabled: !isEnabled,
1146
+ style: {
1147
+ flex: 1,
1148
+ minWidth: "80px",
1149
+ height: touchSize,
1150
+ backgroundColor: isSelected ? selectedColor : surfaceColor,
1151
+ color: isSelected ? "#000000" : textColor,
1152
+ border: `1px solid ${isSelected ? "#b4d0fe" : borderColor}`,
1153
+ borderRadius: "6px",
1154
+ fontSize: "11px",
1155
+ fontWeight: 600,
1156
+ fontFamily: "Arial, sans-serif",
1157
+ letterSpacing: "0.5px",
1158
+ textTransform: "uppercase",
1159
+ cursor: isEnabled ? "pointer" : "not-allowed",
1160
+ opacity: isEnabled ? 1 : 0.4
1161
+ },
1162
+ children: mode.label
1163
+ },
1164
+ mode.value
1165
+ );
1166
+ }) })
1167
+ ] }),
1168
+ currentParams.length > 0 && /* @__PURE__ */ jsxs7("div", { style: { marginBottom: "16px" }, children: [
1169
+ /* @__PURE__ */ jsx8(
1170
+ "label",
1171
+ {
1172
+ style: {
1173
+ display: "block",
1174
+ marginBottom: "8px",
1175
+ fontSize: "11px",
1176
+ fontWeight: 600,
1177
+ textTransform: "uppercase",
1178
+ letterSpacing: "0.5px",
1179
+ fontFamily: "Arial, sans-serif",
1180
+ color: mutedTextColor
1181
+ },
1182
+ children: "Parameters"
1183
+ }
1184
+ ),
1185
+ currentParams.map((param) => /* @__PURE__ */ jsxs7(
1186
+ "div",
1187
+ {
1188
+ style: {
1189
+ marginBottom: "10px",
1190
+ padding: "10px",
1191
+ backgroundColor: surfaceColor,
1192
+ border: `1px solid ${borderColor}`,
1193
+ borderRadius: "6px"
1194
+ },
1195
+ children: [
1196
+ /* @__PURE__ */ jsxs7(
1197
+ "div",
1198
+ {
1199
+ style: { display: "flex", justifyContent: "space-between", marginBottom: "8px" },
1200
+ children: [
1201
+ /* @__PURE__ */ jsx8(
1202
+ "span",
1203
+ {
1204
+ style: {
1205
+ fontSize: "11px",
1206
+ fontWeight: 600,
1207
+ textTransform: "uppercase",
1208
+ letterSpacing: "0.5px",
1209
+ fontFamily: "Arial, sans-serif",
1210
+ color: mutedTextColor
1211
+ },
1212
+ children: param.label
1213
+ }
1214
+ ),
1215
+ /* @__PURE__ */ jsxs7(
1216
+ "span",
1217
+ {
1218
+ style: {
1219
+ fontSize: "13px",
1220
+ fontWeight: 700,
1221
+ fontFamily: "Arial, sans-serif",
1222
+ color: textColor
1223
+ },
1224
+ children: [
1225
+ localParameterValues[param.id] ?? param.value,
1226
+ " ",
1227
+ param.unit && /* @__PURE__ */ jsx8("span", { style: { color: mutedTextColor, fontSize: "10px", fontWeight: 600 }, children: param.unit })
1228
+ ]
1229
+ }
1230
+ )
1231
+ ]
1232
+ }
1233
+ ),
1234
+ param.type === "boolean" ? /* @__PURE__ */ jsx8(
1235
+ "button",
1236
+ {
1237
+ onClick: () => {
1238
+ const newValue = !localParameterValues[param.id];
1239
+ handleParameterInput(param.id, newValue);
1240
+ handleParameterCommit(param.id, newValue);
1241
+ },
1242
+ disabled: param.readOnly,
1243
+ style: {
1244
+ width: "100%",
1245
+ height: touchSize,
1246
+ backgroundColor: localParameterValues[param.id] ? "#3f3f46" : surfaceColor,
1247
+ color: localParameterValues[param.id] ? textColor : mutedTextColor,
1248
+ border: `1px solid ${localParameterValues[param.id] ? "#52525b" : borderColor}`,
1249
+ borderRadius: "4px",
1250
+ fontSize: "12px",
1251
+ fontWeight: 600,
1252
+ fontFamily: "monospace",
1253
+ letterSpacing: "0.5px",
1254
+ cursor: param.readOnly ? "not-allowed" : "pointer",
1255
+ transition: "all 0.15s"
1256
+ },
1257
+ children: localParameterValues[param.id] ? "ON" : "OFF"
1258
+ }
1259
+ ) : param.type === "select" ? /* @__PURE__ */ jsx8(
1260
+ "select",
1261
+ {
1262
+ value: localParameterValues[param.id] || param.value,
1263
+ onChange: (e) => {
1264
+ handleParameterInput(param.id, e.target.value);
1265
+ handleParameterCommit(param.id, e.target.value);
1266
+ },
1267
+ disabled: param.readOnly,
1268
+ style: {
1269
+ width: "100%",
1270
+ height: touchSize,
1271
+ backgroundColor: surfaceColor,
1272
+ color: textColor,
1273
+ border: `1px solid ${borderColor}`,
1274
+ borderRadius: "4px",
1275
+ padding: "0 12px",
1276
+ fontSize: "12px",
1277
+ fontFamily: "monospace",
1278
+ cursor: "pointer"
1279
+ },
1280
+ children: param.options?.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
1281
+ }
1282
+ ) : /* @__PURE__ */ jsx8(
1283
+ "input",
1284
+ {
1285
+ type: param.type === "number" ? "number" : "text",
1286
+ value: localParameterValues[param.id] ?? param.value,
1287
+ onChange: (e) => handleParameterInput(
1288
+ param.id,
1289
+ param.type === "number" ? +e.target.value : e.target.value
1290
+ ),
1291
+ onBlur: () => handleParameterCommit(param.id, localParameterValues[param.id]),
1292
+ disabled: param.readOnly,
1293
+ min: param.min,
1294
+ max: param.max,
1295
+ step: param.step,
1296
+ style: {
1297
+ width: "100%",
1298
+ height: touchSize,
1299
+ backgroundColor: "#ffffff",
1300
+ color: textColor,
1301
+ border: `1px solid ${borderColor}`,
1302
+ borderRadius: "6px",
1303
+ padding: "0 12px",
1304
+ fontSize: "14px",
1305
+ fontFamily: "Arial, sans-serif",
1306
+ fontWeight: 600
1307
+ }
1308
+ }
1309
+ )
1310
+ ]
1311
+ },
1312
+ param.id
1313
+ ))
1314
+ ] }),
1315
+ binding.display?.showStartStop !== false && (binding.capabilities?.canStart || binding.capabilities?.canStop) && /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "8px", marginBottom: "16px" }, children: [
1316
+ binding.capabilities?.canStart && /* @__PURE__ */ jsx8(
1317
+ "button",
1318
+ {
1319
+ onClick: onStart,
1320
+ disabled: binding.state.isRunning,
1321
+ style: {
1322
+ flex: 1,
1323
+ height: touchSize,
1324
+ backgroundColor: binding.state.isRunning ? "#a3a3a3" : "#10b981",
1325
+ color: "#ffffff",
1326
+ border: "none",
1327
+ borderRadius: "6px",
1328
+ fontSize: "12px",
1329
+ fontWeight: 700,
1330
+ fontFamily: "Arial, sans-serif",
1331
+ letterSpacing: "0.5px",
1332
+ cursor: binding.state.isRunning ? "not-allowed" : "pointer",
1333
+ opacity: binding.state.isRunning ? 0.5 : 1
1334
+ },
1335
+ children: "START"
1336
+ }
1337
+ ),
1338
+ binding.capabilities?.canStop && /* @__PURE__ */ jsx8(
1339
+ "button",
1340
+ {
1341
+ onClick: onStop,
1342
+ disabled: !binding.state.isRunning,
1343
+ style: {
1344
+ flex: 1,
1345
+ height: touchSize,
1346
+ backgroundColor: !binding.state.isRunning ? "#a3a3a3" : "#ef4444",
1347
+ color: "#ffffff",
1348
+ border: "none",
1349
+ borderRadius: "6px",
1350
+ fontSize: "12px",
1351
+ fontWeight: 700,
1352
+ fontFamily: "Arial, sans-serif",
1353
+ letterSpacing: "0.5px",
1354
+ cursor: !binding.state.isRunning ? "not-allowed" : "pointer",
1355
+ opacity: !binding.state.isRunning ? 0.5 : 1
1356
+ },
1357
+ children: "STOP"
1358
+ }
1359
+ )
1360
+ ] }),
1361
+ binding.capabilities?.customActions && binding.capabilities.customActions.length > 0 && /* @__PURE__ */ jsx8("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: binding.capabilities.customActions.map((action) => /* @__PURE__ */ jsxs7(
1362
+ "button",
1363
+ {
1364
+ onClick: () => onCustomAction?.(action.id),
1365
+ style: {
1366
+ flex: 1,
1367
+ minWidth: "120px",
1368
+ height: touchSize,
1369
+ backgroundColor: surfaceColor,
1370
+ color: textColor,
1371
+ border: `1px solid ${borderColor}`,
1372
+ borderRadius: "6px",
1373
+ fontSize: "11px",
1374
+ fontWeight: 600,
1375
+ fontFamily: "monospace",
1376
+ letterSpacing: "0.5px",
1377
+ textTransform: "uppercase",
1378
+ cursor: "pointer",
1379
+ transition: "all 0.15s"
1380
+ },
1381
+ children: [
1382
+ action.icon && /* @__PURE__ */ jsx8("span", { style: { marginRight: "6px" }, children: action.icon }),
1383
+ action.label
1384
+ ]
1385
+ },
1386
+ action.id
1387
+ )) }),
1388
+ binding.state.lastCommandResult && /* @__PURE__ */ jsx8(
1389
+ "div",
1390
+ {
1391
+ style: {
1392
+ marginTop: "16px",
1393
+ padding: "10px 12px",
1394
+ backgroundColor: surfaceColor,
1395
+ border: `1px solid ${binding.state.lastCommandResult.success ? "#52525b" : borderColor}`,
1396
+ borderRadius: "4px",
1397
+ fontSize: "11px",
1398
+ fontFamily: "monospace",
1399
+ color: binding.state.lastCommandResult.success ? textColor : mutedTextColor
1400
+ },
1401
+ children: binding.state.lastCommandResult.message
1402
+ }
1403
+ ),
1404
+ /* @__PURE__ */ jsx8("style", { children: `
1405
+ @keyframes slideUp {
1406
+ from { transform: translateY(100%); }
1407
+ to { transform: translateY(0); }
1408
+ }
1409
+ @keyframes slideIn {
1410
+ from { transform: translateX(100%); }
1411
+ to { transform: translateX(0); }
1412
+ }
1413
+ ` })
1414
+ ] }) });
1415
+ }
1416
+
710
1417
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
711
- import React5 from "react";
1418
+ import React6 from "react";
712
1419
  import { Tab } from "@mui/material";
713
1420
 
714
1421
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.styles.tsx
715
1422
  import { Box, IconButton, Tabs } from "@mui/material";
716
1423
  import styled from "@emotion/styled";
717
- import { jsx as jsx7 } from "react/jsx-runtime";
1424
+ import { jsx as jsx9 } from "react/jsx-runtime";
718
1425
  var FullscreenContainer = styled(Box)(
719
1426
  ({ leftCollapsed, rightCollapsed }) => {
720
1427
  const getGridTemplateAreas = () => {
@@ -839,7 +1546,7 @@ var DisplayModeToggle = styled(IconButton)(({ mode }) => ({
839
1546
  transform: "translateX(-50%) scale(1.05)"
840
1547
  }
841
1548
  }));
842
- var ChevronLeft = () => /* @__PURE__ */ jsx7(
1549
+ var ChevronLeft = () => /* @__PURE__ */ jsx9(
843
1550
  "div",
844
1551
  {
845
1552
  style: {
@@ -851,7 +1558,7 @@ var ChevronLeft = () => /* @__PURE__ */ jsx7(
851
1558
  }
852
1559
  }
853
1560
  );
854
- var ChevronRight = () => /* @__PURE__ */ jsx7(
1561
+ var ChevronRight = () => /* @__PURE__ */ jsx9(
855
1562
  "div",
856
1563
  {
857
1564
  style: {
@@ -962,10 +1669,10 @@ var ScrollableSVGWrapper = styled("div")({
962
1669
  });
963
1670
 
964
1671
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.overlays.tsx
965
- import { useEffect as useEffect2, useState as useState5 } from "react";
1672
+ import { useEffect as useEffect3, useState as useState6 } from "react";
966
1673
  import { Box as Box2 } from "@mui/material";
967
1674
  import styled2 from "@emotion/styled";
968
- import { jsx as jsx8 } from "react/jsx-runtime";
1675
+ import { jsx as jsx10 } from "react/jsx-runtime";
969
1676
  var baseOverlayStyles = {
970
1677
  tank: {
971
1678
  background: "linear-gradient(135deg, rgba(69, 90, 120, 0.95) 0%, rgba(52, 73, 94, 0.95) 100%)",
@@ -1027,8 +1734,8 @@ var SVGLockedOverlay = ({
1027
1734
  onClick,
1028
1735
  containerSelector = ".tff-svg-container"
1029
1736
  }) => {
1030
- const [svgOffset, setSvgOffset] = useState5({ x: 0, y: 0 });
1031
- useEffect2(() => {
1737
+ const [svgOffset, setSvgOffset] = useState6({ x: 0, y: 0 });
1738
+ useEffect3(() => {
1032
1739
  const updateSvgOffset = () => {
1033
1740
  const containerElement = document.querySelector(containerSelector);
1034
1741
  const svgElement = containerElement?.querySelector("svg");
@@ -1048,7 +1755,7 @@ var SVGLockedOverlay = ({
1048
1755
  const pixelX = svgX + svgOffset.x;
1049
1756
  const pixelY = svgY + svgOffset.y;
1050
1757
  const appliedStyle = theme ? { ...overlayStyles[theme], ...style } : style;
1051
- return /* @__PURE__ */ jsx8(
1758
+ return /* @__PURE__ */ jsx10(
1052
1759
  Box2,
1053
1760
  {
1054
1761
  className: `svg-locked-overlay ${className}`.trim(),
@@ -1112,7 +1819,7 @@ var CardUnit = styled2("div")({
1112
1819
  });
1113
1820
 
1114
1821
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.visuals.tsx
1115
- import { Fragment, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1822
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1116
1823
  var TrendLine = ({
1117
1824
  values,
1118
1825
  width = 60,
@@ -1122,7 +1829,7 @@ var TrendLine = ({
1122
1829
  backgroundColor = "rgba(255, 255, 255, 0.1)"
1123
1830
  }) => {
1124
1831
  if (!values || values.length < 2) {
1125
- return /* @__PURE__ */ jsx9(
1832
+ return /* @__PURE__ */ jsx11(
1126
1833
  "div",
1127
1834
  {
1128
1835
  style: {
@@ -1134,7 +1841,7 @@ var TrendLine = ({
1134
1841
  alignItems: "center",
1135
1842
  justifyContent: "center"
1136
1843
  },
1137
- children: /* @__PURE__ */ jsx9("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" })
1844
+ children: /* @__PURE__ */ jsx11("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" })
1138
1845
  }
1139
1846
  );
1140
1847
  }
@@ -1146,7 +1853,7 @@ var TrendLine = ({
1146
1853
  const x = 4 + i / (values.length - 1) * (width - 8);
1147
1854
  return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
1148
1855
  }).join(" ");
1149
- return /* @__PURE__ */ jsx9(
1856
+ return /* @__PURE__ */ jsx11(
1150
1857
  "div",
1151
1858
  {
1152
1859
  style: {
@@ -1157,8 +1864,8 @@ var TrendLine = ({
1157
1864
  position: "relative",
1158
1865
  overflow: "hidden"
1159
1866
  },
1160
- children: /* @__PURE__ */ jsxs6("svg", { width, height, style: { position: "absolute", top: 0, left: 0 }, children: [
1161
- /* @__PURE__ */ jsx9(
1867
+ children: /* @__PURE__ */ jsxs8("svg", { width, height, style: { position: "absolute", top: 0, left: 0 }, children: [
1868
+ /* @__PURE__ */ jsx11(
1162
1869
  "path",
1163
1870
  {
1164
1871
  d: pathData,
@@ -1169,7 +1876,7 @@ var TrendLine = ({
1169
1876
  strokeLinejoin: "round"
1170
1877
  }
1171
1878
  ),
1172
- /* @__PURE__ */ jsx9(
1879
+ /* @__PURE__ */ jsx11(
1173
1880
  "circle",
1174
1881
  {
1175
1882
  cx: 4 + (width - 8),
@@ -1207,7 +1914,7 @@ var CircularGauge = ({
1207
1914
  const borderColor = isHex ? hexToRgba(color, 0.9) : color;
1208
1915
  const backgroundColor = isHex ? hexToRgba(color, 0.15) : color;
1209
1916
  const borderStrokeColor = isHex ? hexToRgba(color, 0.4) : color;
1210
- return /* @__PURE__ */ jsxs6(
1917
+ return /* @__PURE__ */ jsxs8(
1211
1918
  "div",
1212
1919
  {
1213
1920
  style: {
@@ -1216,7 +1923,7 @@ var CircularGauge = ({
1216
1923
  alignItems: "center"
1217
1924
  },
1218
1925
  children: [
1219
- /* @__PURE__ */ jsxs6(
1926
+ /* @__PURE__ */ jsxs8(
1220
1927
  "div",
1221
1928
  {
1222
1929
  style: {
@@ -1228,8 +1935,8 @@ var CircularGauge = ({
1228
1935
  border: `1px solid ${borderStrokeColor}`
1229
1936
  },
1230
1937
  children: [
1231
- /* @__PURE__ */ jsxs6("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
1232
- /* @__PURE__ */ jsx9(
1938
+ /* @__PURE__ */ jsxs8("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
1939
+ /* @__PURE__ */ jsx11(
1233
1940
  "circle",
1234
1941
  {
1235
1942
  cx: size / 2,
@@ -1240,7 +1947,7 @@ var CircularGauge = ({
1240
1947
  strokeWidth: 1
1241
1948
  }
1242
1949
  ),
1243
- /* @__PURE__ */ jsx9(
1950
+ /* @__PURE__ */ jsx11(
1244
1951
  "circle",
1245
1952
  {
1246
1953
  cx: size / 2,
@@ -1251,7 +1958,7 @@ var CircularGauge = ({
1251
1958
  fill: "transparent"
1252
1959
  }
1253
1960
  ),
1254
- /* @__PURE__ */ jsx9(
1961
+ /* @__PURE__ */ jsx11(
1255
1962
  "circle",
1256
1963
  {
1257
1964
  cx: size / 2,
@@ -1267,7 +1974,7 @@ var CircularGauge = ({
1267
1974
  }
1268
1975
  )
1269
1976
  ] }),
1270
- /* @__PURE__ */ jsxs6(
1977
+ /* @__PURE__ */ jsxs8(
1271
1978
  "div",
1272
1979
  {
1273
1980
  style: {
@@ -1283,14 +1990,14 @@ var CircularGauge = ({
1283
1990
  },
1284
1991
  children: [
1285
1992
  value.toFixed(1),
1286
- /* @__PURE__ */ jsx9("div", { style: { fontSize: "8px", opacity: 0.9, color: "white" }, children: unit })
1993
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "8px", opacity: 0.9, color: "white" }, children: unit })
1287
1994
  ]
1288
1995
  }
1289
1996
  )
1290
1997
  ]
1291
1998
  }
1292
1999
  ),
1293
- /* @__PURE__ */ jsx9(
2000
+ /* @__PURE__ */ jsx11(
1294
2001
  "div",
1295
2002
  {
1296
2003
  style: {
@@ -1321,7 +2028,7 @@ var Sparkline = ({ data, width, height, color }) => {
1321
2028
  const y = height - 8 - (value - min) / range * (height - 8);
1322
2029
  return `${x + 4},${y + 4}`;
1323
2030
  }).join(" ");
1324
- return /* @__PURE__ */ jsx9(
2031
+ return /* @__PURE__ */ jsx11(
1325
2032
  "div",
1326
2033
  {
1327
2034
  style: {
@@ -1335,7 +2042,7 @@ var Sparkline = ({ data, width, height, color }) => {
1335
2042
  justifyContent: "center",
1336
2043
  boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
1337
2044
  },
1338
- children: /* @__PURE__ */ jsx9("svg", { width, height, children: /* @__PURE__ */ jsx9("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) })
2045
+ children: /* @__PURE__ */ jsx11("svg", { width, height, children: /* @__PURE__ */ jsx11("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) })
1339
2046
  }
1340
2047
  );
1341
2048
  };
@@ -1349,8 +2056,8 @@ var EquipmentIndicatorWithSparkline = ({
1349
2056
  sparklineOffset = { x: 40, y: 0 }
1350
2057
  }) => {
1351
2058
  const hasSparkline = sparklineData && sparklineData.length > 1 && Math.max(...sparklineData) !== Math.min(...sparklineData);
1352
- return /* @__PURE__ */ jsxs6(Fragment, { children: [
1353
- /* @__PURE__ */ jsx9(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ jsx9(
2059
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
2060
+ /* @__PURE__ */ jsx11(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ jsx11(
1354
2061
  "div",
1355
2062
  {
1356
2063
  style: {
@@ -1371,7 +2078,7 @@ var EquipmentIndicatorWithSparkline = ({
1371
2078
  children: label
1372
2079
  }
1373
2080
  ) }),
1374
- hasSparkline && /* @__PURE__ */ jsx9(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ jsx9(Sparkline, { data: sparklineData, width: 60, height: 20, color }) })
2081
+ hasSparkline && /* @__PURE__ */ jsx11(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ jsx11(Sparkline, { data: sparklineData, width: 60, height: 20, color }) })
1375
2082
  ] });
1376
2083
  };
1377
2084
  var EquipmentIndicator = ({
@@ -1384,7 +2091,7 @@ var EquipmentIndicator = ({
1384
2091
  className
1385
2092
  }) => {
1386
2093
  const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
1387
- return /* @__PURE__ */ jsx9(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ jsx9(
2094
+ return /* @__PURE__ */ jsx11(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ jsx11(
1388
2095
  "div",
1389
2096
  {
1390
2097
  style: {
@@ -1408,7 +2115,7 @@ var EquipmentIndicator = ({
1408
2115
  };
1409
2116
 
1410
2117
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
1411
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2118
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1412
2119
  function FullscreenWorkspace({
1413
2120
  svgContent,
1414
2121
  overlays,
@@ -1436,10 +2143,10 @@ function FullscreenWorkspace({
1436
2143
  className = "",
1437
2144
  ...rest
1438
2145
  }) {
1439
- const [leftCollapsedState, setLeftCollapsedState] = React5.useState(defaultLeftCollapsed);
1440
- const [rightCollapsedState, setRightCollapsedState] = React5.useState(defaultRightCollapsed);
1441
- const [internalDisplayMode, setInternalDisplayMode] = React5.useState(displayMode ?? defaultDisplayMode);
1442
- const [internalTab, setInternalTab] = React5.useState(
2146
+ const [leftCollapsedState, setLeftCollapsedState] = React6.useState(defaultLeftCollapsed);
2147
+ const [rightCollapsedState, setRightCollapsedState] = React6.useState(defaultRightCollapsed);
2148
+ const [internalDisplayMode, setInternalDisplayMode] = React6.useState(displayMode ?? defaultDisplayMode);
2149
+ const [internalTab, setInternalTab] = React6.useState(
1443
2150
  tabs && tabs.length > 0 ? tabs[0].value : ""
1444
2151
  );
1445
2152
  const leftCollapsed = leftCollapsedProp ?? leftCollapsedState;
@@ -1447,10 +2154,10 @@ function FullscreenWorkspace({
1447
2154
  const isDisplayModeControlled = displayMode !== void 0;
1448
2155
  const currentDisplayMode = isDisplayModeControlled ? displayMode : internalDisplayMode;
1449
2156
  const isTabControlled = activeTab !== void 0 && activeTab !== null;
1450
- const resolvedTabs = React5.useMemo(() => tabs ?? [], [tabs]);
2157
+ const resolvedTabs = React6.useMemo(() => tabs ?? [], [tabs]);
1451
2158
  const defaultTabValue = resolvedTabs.length > 0 ? resolvedTabs[0].value : "";
1452
2159
  const currentTab = isTabControlled ? activeTab : internalTab || defaultTabValue;
1453
- React5.useEffect(() => {
2160
+ React6.useEffect(() => {
1454
2161
  if (resolvedTabs.length > 0) {
1455
2162
  const values = resolvedTabs.map((tab) => tab.value);
1456
2163
  if (!values.includes(currentTab)) {
@@ -1459,7 +2166,7 @@ function FullscreenWorkspace({
1459
2166
  }
1460
2167
  }
1461
2168
  }, [resolvedTabs, activeTab, currentTab, defaultTabValue]);
1462
- React5.useEffect(() => {
2169
+ React6.useEffect(() => {
1463
2170
  if (displayMode !== void 0) {
1464
2171
  setInternalDisplayMode(displayMode);
1465
2172
  }
@@ -1494,7 +2201,7 @@ function FullscreenWorkspace({
1494
2201
  const currentTabContent = resolvedTabs.find((tab) => tab.value === currentTab)?.content ?? null;
1495
2202
  const showLeftToggle = Boolean(leftPanel);
1496
2203
  const showRightToggle = Boolean(rightPanel);
1497
- return /* @__PURE__ */ jsxs7(
2204
+ return /* @__PURE__ */ jsxs9(
1498
2205
  FullscreenContainer,
1499
2206
  {
1500
2207
  leftCollapsed,
@@ -1502,25 +2209,25 @@ function FullscreenWorkspace({
1502
2209
  className: `tff-svg-container ${className}`.trim(),
1503
2210
  ...rest,
1504
2211
  children: [
1505
- showLeftToggle && /* @__PURE__ */ jsx10(
2212
+ showLeftToggle && /* @__PURE__ */ jsx12(
1506
2213
  LeftToggleButton,
1507
2214
  {
1508
2215
  collapsed: leftCollapsed,
1509
2216
  onClick: handleLeftToggle,
1510
2217
  "aria-label": leftCollapsed ? "Expand left panel" : "Collapse left panel",
1511
- children: leftCollapsed ? /* @__PURE__ */ jsx10(ChevronRight, {}) : /* @__PURE__ */ jsx10(ChevronLeft, {})
2218
+ children: leftCollapsed ? /* @__PURE__ */ jsx12(ChevronRight, {}) : /* @__PURE__ */ jsx12(ChevronLeft, {})
1512
2219
  }
1513
2220
  ),
1514
- showRightToggle && /* @__PURE__ */ jsx10(
2221
+ showRightToggle && /* @__PURE__ */ jsx12(
1515
2222
  RightToggleButton,
1516
2223
  {
1517
2224
  collapsed: rightCollapsed,
1518
2225
  onClick: handleRightToggle,
1519
2226
  "aria-label": rightCollapsed ? "Expand right panel" : "Collapse right panel",
1520
- children: rightCollapsed ? /* @__PURE__ */ jsx10(ChevronLeft, {}) : /* @__PURE__ */ jsx10(ChevronRight, {})
2227
+ children: rightCollapsed ? /* @__PURE__ */ jsx12(ChevronLeft, {}) : /* @__PURE__ */ jsx12(ChevronRight, {})
1521
2228
  }
1522
2229
  ),
1523
- showDisplayModeToggle && /* @__PURE__ */ jsx10(
2230
+ showDisplayModeToggle && /* @__PURE__ */ jsx12(
1524
2231
  DisplayModeToggle,
1525
2232
  {
1526
2233
  mode: currentDisplayMode,
@@ -1529,9 +2236,9 @@ function FullscreenWorkspace({
1529
2236
  children: currentDisplayMode === "dashboard" ? "Dashboard Mode" : "Standard Mode"
1530
2237
  }
1531
2238
  ),
1532
- /* @__PURE__ */ jsxs7(SVGArea, { children: [
1533
- showZoomControls && /* @__PURE__ */ jsxs7(ZoomControls, { children: [
1534
- /* @__PURE__ */ jsx10(
2239
+ /* @__PURE__ */ jsxs9(SVGArea, { children: [
2240
+ showZoomControls && /* @__PURE__ */ jsxs9(ZoomControls, { children: [
2241
+ /* @__PURE__ */ jsx12(
1535
2242
  ZoomButton,
1536
2243
  {
1537
2244
  onClick: onZoomIn,
@@ -1541,7 +2248,7 @@ function FullscreenWorkspace({
1541
2248
  children: "+"
1542
2249
  }
1543
2250
  ),
1544
- /* @__PURE__ */ jsx10(
2251
+ /* @__PURE__ */ jsx12(
1545
2252
  ZoomButton,
1546
2253
  {
1547
2254
  onClick: onZoomOut,
@@ -1551,7 +2258,7 @@ function FullscreenWorkspace({
1551
2258
  children: "-"
1552
2259
  }
1553
2260
  ),
1554
- onResetZoom && /* @__PURE__ */ jsx10(
2261
+ onResetZoom && /* @__PURE__ */ jsx12(
1555
2262
  ZoomButton,
1556
2263
  {
1557
2264
  onClick: onResetZoom,
@@ -1562,33 +2269,33 @@ function FullscreenWorkspace({
1562
2269
  }
1563
2270
  )
1564
2271
  ] }),
1565
- /* @__PURE__ */ jsx10(ScrollableSVGWrapper, { className: "svg-scroll-wrapper", children: /* @__PURE__ */ jsxs7(FixedSVGContainer, { className: "svg-container", children: [
2272
+ /* @__PURE__ */ jsx12(ScrollableSVGWrapper, { className: "svg-scroll-wrapper", children: /* @__PURE__ */ jsxs9(FixedSVGContainer, { className: "svg-container", children: [
1566
2273
  svgContent,
1567
2274
  overlays
1568
2275
  ] }) })
1569
2276
  ] }),
1570
- /* @__PURE__ */ jsx10(LeftPanel, { collapsed: leftCollapsed, children: resolvedTabs.length > 0 ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1571
- /* @__PURE__ */ jsx10(
2277
+ /* @__PURE__ */ jsx12(LeftPanel, { collapsed: leftCollapsed, children: resolvedTabs.length > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
2278
+ /* @__PURE__ */ jsx12(
1572
2279
  PanelTabs,
1573
2280
  {
1574
2281
  value: currentTab,
1575
2282
  onChange: handleTabChangeInternal,
1576
2283
  "aria-label": "Left panel tabs",
1577
- children: resolvedTabs.map((tab) => /* @__PURE__ */ jsx10(Tab, { label: tab.label, value: tab.value }, tab.value))
2284
+ children: resolvedTabs.map((tab) => /* @__PURE__ */ jsx12(Tab, { label: tab.label, value: tab.value }, tab.value))
1578
2285
  }
1579
2286
  ),
1580
- /* @__PURE__ */ jsx10(PanelContent, { children: currentTabContent })
2287
+ /* @__PURE__ */ jsx12(PanelContent, { children: currentTabContent })
1581
2288
  ] }) : leftPanel }),
1582
- /* @__PURE__ */ jsx10(RightPanel, { collapsed: rightCollapsed, children: rightPanel }),
1583
- /* @__PURE__ */ jsx10(FooterPanel, { children: footer })
2289
+ /* @__PURE__ */ jsx12(RightPanel, { collapsed: rightCollapsed, children: rightPanel }),
2290
+ /* @__PURE__ */ jsx12(FooterPanel, { children: footer })
1584
2291
  ]
1585
2292
  }
1586
2293
  );
1587
2294
  }
1588
2295
 
1589
2296
  // src/theme/ThemeContext.tsx
1590
- import { createContext, useContext, useState as useState6, useEffect as useEffect3 } from "react";
1591
- import { jsx as jsx11 } from "react/jsx-runtime";
2297
+ import { createContext, useContext, useState as useState7, useEffect as useEffect4 } from "react";
2298
+ import { jsx as jsx13 } from "react/jsx-runtime";
1592
2299
  var ThemeContext = createContext(void 0);
1593
2300
  var useTheme = () => {
1594
2301
  const context = useContext(ThemeContext);
@@ -1598,8 +2305,8 @@ var useTheme = () => {
1598
2305
  return context;
1599
2306
  };
1600
2307
  var ThemeProvider = ({ children }) => {
1601
- const [theme, setThemeState] = useState6("modern");
1602
- useEffect3(() => {
2308
+ const [theme, setThemeState] = useState7("modern");
2309
+ useEffect4(() => {
1603
2310
  document.body.className = "";
1604
2311
  document.body.classList.add(`theme-${theme}`);
1605
2312
  document.documentElement.className = "";
@@ -1611,11 +2318,11 @@ var ThemeProvider = ({ children }) => {
1611
2318
  const setTheme = (newTheme) => {
1612
2319
  setThemeState(newTheme);
1613
2320
  };
1614
- return /* @__PURE__ */ jsx11(ThemeContext.Provider, { value: { theme, toggleTheme, setTheme }, children });
2321
+ return /* @__PURE__ */ jsx13(ThemeContext.Provider, { value: { theme, toggleTheme, setTheme }, children });
1615
2322
  };
1616
2323
 
1617
2324
  // src/theme/ThemeToggle.tsx
1618
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2325
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1619
2326
  var ThemeToggle = ({
1620
2327
  size = "medium",
1621
2328
  position = "top-right",
@@ -1686,15 +2393,15 @@ var ThemeToggle = ({
1686
2393
  transition: "all 0.2s ease",
1687
2394
  boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)"
1688
2395
  };
1689
- return /* @__PURE__ */ jsxs8("div", { style: currentStyle, onClick: toggleTheme, className, ...props, children: [
1690
- /* @__PURE__ */ jsx12("span", { children: theme === "modern" ? "\u{1F3A8} Modern" : "\u{1F3ED} HMI" }),
1691
- /* @__PURE__ */ jsx12("div", { style: toggleStyle, children: /* @__PURE__ */ jsx12("div", { style: thumbStyle }) }),
1692
- /* @__PURE__ */ jsx12("span", { style: { fontSize: "0.75rem", opacity: 0.8 }, children: theme === "modern" ? "Industrial Design" : "High Performance" })
2396
+ return /* @__PURE__ */ jsxs10("div", { style: currentStyle, onClick: toggleTheme, className, ...props, children: [
2397
+ /* @__PURE__ */ jsx14("span", { children: theme === "modern" ? "\u{1F3A8} Modern" : "\u{1F3ED} HMI" }),
2398
+ /* @__PURE__ */ jsx14("div", { style: toggleStyle, children: /* @__PURE__ */ jsx14("div", { style: thumbStyle }) }),
2399
+ /* @__PURE__ */ jsx14("span", { style: { fontSize: "0.75rem", opacity: 0.8 }, children: theme === "modern" ? "Industrial Design" : "High Performance" })
1693
2400
  ] });
1694
2401
  };
1695
2402
 
1696
2403
  // src/components/PIDCanvas/PIDCanvas.tsx
1697
- import { useCallback as useCallback7, useState as useState12, useEffect as useEffect10, useRef as useRef5 } from "react";
2404
+ import { useCallback as useCallback7, useState as useState13, useEffect as useEffect11, useRef as useRef5 } from "react";
1698
2405
 
1699
2406
  // src/diagram/symbols/mockSymbolLibrary.ts
1700
2407
  var mockSymbolLibrary = {
@@ -1702,13 +2409,13 @@ var mockSymbolLibrary = {
1702
2409
  id: "valve-gate",
1703
2410
  name: "Gate Valve (Powered)",
1704
2411
  category: "valve",
1705
- viewBox: { x: 0, y: 0, width: 52, height: 52 },
2412
+ viewBox: { x: 0, y: 0, width: 26, height: 26 },
1706
2413
  ports: [
1707
- { id: "inlet", x: 0, y: 36, type: "inlet", direction: "in" },
1708
- { id: "outlet", x: 52, y: 36, type: "outlet", direction: "out" }
2414
+ { id: "inlet", x: 0, y: 18, type: "inlet", direction: "in" },
2415
+ { id: "outlet", x: 26, y: 18, type: "outlet", direction: "out" }
1709
2416
  ],
1710
2417
  svgContent: `
1711
- <g>
2418
+ <g transform="scale(0.5)">
1712
2419
  <!-- Actuator box -->
1713
2420
  <rect x="16.74" y="0" width="18.02" height="18.02" fill="var(--symbol-fill, #edeeef)" stroke="var(--symbol-stroke, #666666)" pointer-events="all"/>
1714
2421
  <!-- Actuator stem -->
@@ -1726,13 +2433,13 @@ var mockSymbolLibrary = {
1726
2433
  id: "pump-positive-displacement",
1727
2434
  name: "Positive Displacement Pump",
1728
2435
  category: "pump",
1729
- viewBox: { x: 0, y: 0, width: 56.25, height: 56.25 },
2436
+ viewBox: { x: 0, y: 0, width: 28.125, height: 28.125 },
1730
2437
  ports: [
1731
- { id: "inlet", x: 0, y: 28.125, type: "inlet", direction: "in" },
1732
- { id: "outlet", x: 56.25, y: 28.125, type: "outlet", direction: "out" }
2438
+ { id: "inlet", x: 0, y: 14.0625, type: "inlet", direction: "in" },
2439
+ { id: "outlet", x: 28.125, y: 14.0625, type: "outlet", direction: "out" }
1733
2440
  ],
1734
2441
  svgContent: `
1735
- <g transform="scale(0.5625)">
2442
+ <g transform="scale(0.28125)">
1736
2443
  <!-- Outer circle -->
1737
2444
  <ellipse
1738
2445
  cx="50"
@@ -1774,13 +2481,13 @@ var mockSymbolLibrary = {
1774
2481
  id: "vannefold-bottom",
1775
2482
  name: "Vannefold Block (Bottom)",
1776
2483
  category: "valve",
1777
- viewBox: { x: -0.5, y: -0.5, width: 88, height: 67.785156 },
2484
+ viewBox: { x: -0.25, y: -0.25, width: 44, height: 33.892578 },
1778
2485
  ports: [
1779
- { id: "bottom-inlet", x: 0, y: 25, type: "control", direction: "in" },
1780
- { id: "top-outlet", x: 48, y: 65, type: "control", direction: "out" }
2486
+ { id: "bottom-inlet", x: 0, y: 12.5, type: "control", direction: "in" },
2487
+ { id: "top-outlet", x: 24, y: 32.5, type: "control", direction: "out" }
1781
2488
  ],
1782
2489
  svgContent: `
1783
- <g>
2490
+ <g transform="scale(0.5)">
1784
2491
  <!-- Bottom inlet pipe -->
1785
2492
  <path
1786
2493
  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"
@@ -1838,13 +2545,13 @@ var mockSymbolLibrary = {
1838
2545
  id: "vannefold-middle",
1839
2546
  name: "Vannefold Block (Middle)",
1840
2547
  category: "valve",
1841
- viewBox: { x: -0.5, y: -0.5, width: 95, height: 53.097656 },
2548
+ viewBox: { x: -0.25, y: -0.25, width: 47.5, height: 26.548828 },
1842
2549
  ports: [
1843
- { id: "left-inlet", x: 0, y: 27.3125, type: "control", direction: "in" },
1844
- { id: "right-outlet", x: 95, y: 27.3125, type: "control", direction: "out" }
2550
+ { id: "left-inlet", x: 0, y: 13.65625, type: "control", direction: "in" },
2551
+ { id: "right-outlet", x: 47.5, y: 13.65625, type: "control", direction: "out" }
1845
2552
  ],
1846
2553
  svgContent: `
1847
- <g>
2554
+ <g transform="scale(0.5)">
1848
2555
  <!-- Left inlet pipe -->
1849
2556
  <path
1850
2557
  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"
@@ -1893,13 +2600,13 @@ var mockSymbolLibrary = {
1893
2600
  id: "vannefold-top",
1894
2601
  name: "Vannefold Block (Top)",
1895
2602
  category: "valve",
1896
- viewBox: { x: -0.5, y: -0.5, width: 95, height: 60.785156 },
2603
+ viewBox: { x: -0.25, y: -0.25, width: 47.5, height: 30.392578 },
1897
2604
  ports: [
1898
- { id: "bottom-inlet", x: 0, y: 35, type: "control", direction: "in" },
1899
- { id: "top-outlet", x: 48, y: 0, type: "control", direction: "out" }
2605
+ { id: "bottom-inlet", x: 0, y: 17.5, type: "control", direction: "in" },
2606
+ { id: "top-outlet", x: 24, y: 0, type: "control", direction: "out" }
1900
2607
  ],
1901
2608
  svgContent: `
1902
- <g>
2609
+ <g transform="scale(0.5)">
1903
2610
  <!-- Bottom inlet pipe -->
1904
2611
  <path
1905
2612
  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"
@@ -1949,13 +2656,13 @@ var mockSymbolLibrary = {
1949
2656
  id: "vannefold-top-top-inlet",
1950
2657
  name: "Vannefold Block (Top, Top Inlet)",
1951
2658
  category: "valve",
1952
- viewBox: { x: 0, y: 0, width: 88, height: 67.576004 },
2659
+ viewBox: { x: 0, y: 0, width: 44, height: 33.788002 },
1953
2660
  ports: [
1954
- { id: "top-inlet", x: 47, y: 0, type: "control", direction: "in" },
1955
- { id: "bottom-outlet", x: 0, y: 42, type: "control", direction: "out" }
2661
+ { id: "top-inlet", x: 23.5, y: 0, type: "control", direction: "in" },
2662
+ { id: "bottom-outlet", x: 0, y: 21, type: "control", direction: "out" }
1956
2663
  ],
1957
2664
  svgContent: `
1958
- <g>
2665
+ <g transform="scale(0.5)">
1959
2666
  <!-- Top inlet pipe flange -->
1960
2667
  <path
1961
2668
  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"
@@ -2013,13 +2720,13 @@ var mockSymbolLibrary = {
2013
2720
  id: "vannefold-left",
2014
2721
  name: "Vannefold Block (Left)",
2015
2722
  category: "valve",
2016
- viewBox: { x: 0, y: 0, width: 67.785156, height: 88.5 },
2723
+ viewBox: { x: 0, y: 0, width: 33.892578, height: 44.25 },
2017
2724
  ports: [
2018
- { id: "left-inlet", x: 0, y: 41.5, type: "control", direction: "in" },
2019
- { id: "right-outlet", x: 42, y: 87, type: "control", direction: "out" }
2725
+ { id: "left-inlet", x: 0, y: 20.75, type: "control", direction: "in" },
2726
+ { id: "right-outlet", x: 21, y: 43.5, type: "control", direction: "out" }
2020
2727
  ],
2021
2728
  svgContent: `
2022
- <g>
2729
+ <g transform="scale(0.5)">
2023
2730
  <!-- Left inlet pipe flange -->
2024
2731
  <path
2025
2732
  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"
@@ -2076,13 +2783,13 @@ var mockSymbolLibrary = {
2076
2783
  id: "vannefold-right",
2077
2784
  name: "Vannefold Block (Right)",
2078
2785
  category: "valve",
2079
- viewBox: { x: 0, y: 0, width: 67.785156, height: 88.5 },
2786
+ viewBox: { x: 0, y: 0, width: 33.892578, height: 44.25 },
2080
2787
  ports: [
2081
- { id: "right-inlet", x: 67.785156, y: 41.5, type: "control", direction: "in" },
2082
- { id: "left-outlet", x: 24.785156, y: 87.5, type: "control", direction: "out" }
2788
+ { id: "right-inlet", x: 33.892578, y: 20.75, type: "control", direction: "in" },
2789
+ { id: "left-outlet", x: 12.392578, y: 43.75, type: "control", direction: "out" }
2083
2790
  ],
2084
2791
  svgContent: `
2085
- <g>
2792
+ <g transform="scale(0.5)">
2086
2793
  <!-- Right inlet pipe flange -->
2087
2794
  <path
2088
2795
  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"
@@ -2139,16 +2846,16 @@ var mockSymbolLibrary = {
2139
2846
  id: "pixer",
2140
2847
  name: "Pixer",
2141
2848
  category: "pump",
2142
- viewBox: { x: 0, y: 0, width: 50.625, height: 46.6875 },
2849
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 23.34375 },
2143
2850
  ports: [
2144
- { id: "right-outlet", x: 50.625, y: 30.9375, type: "control", direction: "out" },
2145
- { id: "left-outlet", x: 0, y: 30.9375, type: "control", direction: "out" },
2146
- { id: "left-inlet", x: 50.625, y: 19.6875, type: "control", direction: "in" },
2147
- { id: "right-inlet", x: 0, y: 19.6875, type: "control", direction: "in" },
2148
- { id: "top-inlet", x: 25.3125, y: 0, type: "control", direction: "in" }
2851
+ { id: "right-outlet", x: 25.3125, y: 15.46875, type: "control", direction: "out" },
2852
+ { id: "left-outlet", x: 0, y: 15.46875, type: "control", direction: "out" },
2853
+ { id: "left-inlet", x: 25.3125, y: 9.84375, type: "control", direction: "in" },
2854
+ { id: "right-inlet", x: 0, y: 9.84375, type: "control", direction: "in" },
2855
+ { id: "top-inlet", x: 12.65625, y: 0, type: "control", direction: "in" }
2149
2856
  ],
2150
2857
  svgContent: `
2151
- <g transform="scale(0.196875) translate(-195, -186)">
2858
+ <g transform="scale(0.0984375) translate(-195, -186)">
2152
2859
  <!-- Top mounting flange -->
2153
2860
  <rect
2154
2861
  width="141.313"
@@ -2413,16 +3120,16 @@ var mockSymbolLibrary = {
2413
3120
  id: "pixer-barb",
2414
3121
  name: "Pixer (Hose Barb)",
2415
3122
  category: "pump",
2416
- viewBox: { x: 0, y: 0, width: 50.625, height: 46.6875 },
3123
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 23.34375 },
2417
3124
  ports: [
2418
- { id: "right-outlet", x: 50.625, y: 30.9375, type: "control", direction: "out" },
2419
- { id: "left-outlet", x: 0, y: 30.9375, type: "control", direction: "out" },
2420
- { id: "left-inlet", x: 50.625, y: 19.6875, type: "control", direction: "in" },
2421
- { id: "right-inlet", x: 0, y: 19.6875, type: "control", direction: "in" },
2422
- { id: "top-inlet", x: 25.3125, y: 0, type: "control", direction: "in" }
3125
+ { id: "right-outlet", x: 25.3125, y: 15.46875, type: "control", direction: "out" },
3126
+ { id: "left-outlet", x: 0, y: 15.46875, type: "control", direction: "out" },
3127
+ { id: "left-inlet", x: 25.3125, y: 9.84375, type: "control", direction: "in" },
3128
+ { id: "right-inlet", x: 0, y: 9.84375, type: "control", direction: "in" },
3129
+ { id: "top-inlet", x: 12.65625, y: 0, type: "control", direction: "in" }
2423
3130
  ],
2424
3131
  svgContent: `
2425
- <g transform="scale(0.196875) translate(-195, -186)">
3132
+ <g transform="scale(0.0984375) translate(-195, -186)">
2426
3133
  <!-- Hose barb ridges at top -->
2427
3134
  <rect
2428
3135
  width="20"
@@ -2704,15 +3411,15 @@ var mockSymbolLibrary = {
2704
3411
  id: "vessel-balloon",
2705
3412
  name: "Vessel",
2706
3413
  category: "vessel",
2707
- viewBox: { x: 0, y: 0, width: 50.625, height: 91 },
3414
+ viewBox: { x: 0, y: 0, width: 25.3125, height: 45.5 },
2708
3415
  ports: [
2709
- { id: "top", type: "inlet", x: 25.3125, y: 0 },
2710
- { id: "left", type: "inlet", x: 5, y: 39 },
2711
- { id: "right", type: "outlet", x: 45.625, y: 39 },
2712
- { id: "bottom", type: "outlet", x: 25.3125, y: 91 }
3416
+ { id: "top", type: "inlet", x: 12.65625, y: 0 },
3417
+ { id: "left", type: "inlet", x: 2.5, y: 19.5 },
3418
+ { id: "right", type: "outlet", x: 22.8125, y: 19.5 },
3419
+ { id: "bottom", type: "outlet", x: 12.65625, y: 45.5 }
2713
3420
  ],
2714
3421
  svgContent: `
2715
- <g transform="scale(0.196875) translate(-195, -100)">
3422
+ <g transform="scale(0.0984375) translate(-195, -100)">
2716
3423
  <!-- Top opening nozzle -->
2717
3424
  <rect
2718
3425
  width="65"
@@ -2777,19 +3484,53 @@ var mockSymbolLibrary = {
2777
3484
  tags: ["vessel", "storage", "tank", "balloon"]
2778
3485
  }
2779
3486
  },
3487
+ "hollow-fiber": {
3488
+ id: "hollow-fiber",
3489
+ name: "Hollow Fiber",
3490
+ category: "other",
3491
+ viewBox: { x: 0, y: 0, width: 30, height: 109 },
3492
+ ports: [
3493
+ { id: "top-left", type: "outlet", x: 10, y: 2.75 },
3494
+ { id: "top-right", type: "outlet", x: 24.5, y: 22.25 },
3495
+ { id: "bottom-left", type: "inlet", x: 10, y: 106.25 },
3496
+ { id: "bottom-right", type: "inlet", x: 24.5, y: 87.25 }
3497
+ ],
3498
+ svgContent: `
3499
+ <g transform="scale(0.5)">
3500
+ <!-- Top left flange -->
3501
+ <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)" />
3502
+
3503
+ <!-- Top right flange -->
3504
+ <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)" />
3505
+
3506
+ <!-- Bottom left flange -->
3507
+ <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)" />
3508
+
3509
+ <!-- Bottom right flange -->
3510
+ <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)" />
3511
+
3512
+ <!-- Main vessel body -->
3513
+ <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)" />
3514
+ </g>
3515
+ `,
3516
+ metadata: {
3517
+ description: "Hollow fiber filter module with multiple inlet/outlet connections",
3518
+ tags: ["filter", "hollow-fiber", "filtration", "separation", "membrane"]
3519
+ }
3520
+ },
2780
3521
  "psr-f": {
2781
3522
  id: "psr-f",
2782
3523
  name: "PSR F",
2783
3524
  category: "instrument",
2784
- viewBox: { x: 0, y: 0, width: 57, height: 57 },
3525
+ viewBox: { x: 0, y: 0, width: 28.5, height: 28.5 },
2785
3526
  ports: [
2786
- { id: "top", type: "signal", x: 28.5, y: 14.25 },
2787
- { id: "right", type: "signal", x: 42.75, y: 28.5 },
2788
- { id: "bottom", type: "signal", x: 28.5, y: 42.75 },
2789
- { id: "left", type: "signal", x: 14.25, y: 28.5 }
3527
+ { id: "top", type: "signal", x: 14.25, y: 7.125 },
3528
+ { id: "right", type: "signal", x: 21.375, y: 14.25 },
3529
+ { id: "bottom", type: "signal", x: 14.25, y: 21.375 },
3530
+ { id: "left", type: "signal", x: 7.125, y: 14.25 }
2790
3531
  ],
2791
3532
  svgContent: `
2792
- <g transform="scale(0.5) translate(28.5, 28.5)">
3533
+ <g transform="scale(0.25) translate(28.5, 28.5)">
2793
3534
  <!-- Circle background -->
2794
3535
  <ellipse
2795
3536
  cx="28.5"
@@ -2823,15 +3564,15 @@ var mockSymbolLibrary = {
2823
3564
  id: "psr-p",
2824
3565
  name: "PSR P",
2825
3566
  category: "instrument",
2826
- viewBox: { x: 0, y: 0, width: 57, height: 57 },
3567
+ viewBox: { x: 0, y: 0, width: 28.5, height: 28.5 },
2827
3568
  ports: [
2828
- { id: "top", type: "signal", x: 28.5, y: 14.25 },
2829
- { id: "right", type: "signal", x: 42.75, y: 28.5 },
2830
- { id: "bottom", type: "signal", x: 28.5, y: 42.75 },
2831
- { id: "left", type: "signal", x: 14.25, y: 28.5 }
3569
+ { id: "top", type: "signal", x: 14.25, y: 7.125 },
3570
+ { id: "right", type: "signal", x: 21.375, y: 14.25 },
3571
+ { id: "bottom", type: "signal", x: 14.25, y: 21.375 },
3572
+ { id: "left", type: "signal", x: 7.125, y: 14.25 }
2832
3573
  ],
2833
3574
  svgContent: `
2834
- <g transform="scale(0.5) translate(28.5, 28.5)">
3575
+ <g transform="scale(0.25) translate(28.5, 28.5)">
2835
3576
  <!-- Circle background -->
2836
3577
  <ellipse
2837
3578
  cx="28.5"
@@ -2862,15 +3603,26 @@ var mockSymbolLibrary = {
2862
3603
  }
2863
3604
  }
2864
3605
  };
3606
+ var customSymbols = {};
3607
+ function registerSymbol(symbol) {
3608
+ customSymbols[symbol.id] = symbol;
3609
+ }
3610
+ function registerSymbols(symbols) {
3611
+ if (Array.isArray(symbols)) {
3612
+ symbols.forEach((symbol) => registerSymbol(symbol));
3613
+ } else {
3614
+ Object.values(symbols).forEach((symbol) => registerSymbol(symbol));
3615
+ }
3616
+ }
2865
3617
  function getSymbolDefinition(symbolId) {
2866
- return mockSymbolLibrary[symbolId];
3618
+ return customSymbols[symbolId] ?? mockSymbolLibrary[symbolId];
2867
3619
  }
2868
3620
  function getAvailableSymbols() {
2869
- return Object.keys(mockSymbolLibrary);
3621
+ return [...Object.keys(customSymbols), ...Object.keys(mockSymbolLibrary)];
2870
3622
  }
2871
3623
 
2872
3624
  // src/components/PIDCanvas/NodeRenderer.tsx
2873
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
3625
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
2874
3626
  function NodeRenderer({
2875
3627
  nodes: nodes2,
2876
3628
  selectedNodeIds,
@@ -2881,13 +3633,13 @@ function NodeRenderer({
2881
3633
  dragOffset = { x: 0, y: 0 },
2882
3634
  enableDrag = false
2883
3635
  }) {
2884
- return /* @__PURE__ */ jsx13("g", { id: "layer-nodes", children: nodes2.filter((node) => node.visible !== false).map((node) => {
3636
+ return /* @__PURE__ */ jsx15("g", { id: "layer-nodes", children: nodes2.filter((node) => node.visible !== false).map((node) => {
2885
3637
  const symbol = getSymbolDefinition(node.symbolId);
2886
3638
  const isSelected = selectedNodeIds?.has(node.id) ?? false;
2887
3639
  const isDragging = draggingNodeId === node.id;
2888
3640
  if (!symbol) {
2889
- return /* @__PURE__ */ jsxs9("g", { children: [
2890
- /* @__PURE__ */ jsx13(
3641
+ return /* @__PURE__ */ jsxs11("g", { children: [
3642
+ /* @__PURE__ */ jsx15(
2891
3643
  "circle",
2892
3644
  {
2893
3645
  cx: node.transform.x,
@@ -2899,7 +3651,7 @@ function NodeRenderer({
2899
3651
  strokeWidth: "2"
2900
3652
  }
2901
3653
  ),
2902
- /* @__PURE__ */ jsx13(
3654
+ /* @__PURE__ */ jsx15(
2903
3655
  "text",
2904
3656
  {
2905
3657
  x: node.transform.x,
@@ -2956,7 +3708,7 @@ function NodeRenderer({
2956
3708
  };
2957
3709
  const fillColor = node.customColors?.fill ?? getStatusColor(node.status);
2958
3710
  const strokeColor = node.customColors?.stroke ?? "rgb(0, 0, 0)";
2959
- return /* @__PURE__ */ jsxs9(
3711
+ return /* @__PURE__ */ jsxs11(
2960
3712
  "g",
2961
3713
  {
2962
3714
  "data-node-id": node.id,
@@ -2989,7 +3741,7 @@ function NodeRenderer({
2989
3741
  }
2990
3742
  },
2991
3743
  children: [
2992
- isSelected && /* @__PURE__ */ jsx13(
3744
+ isSelected && /* @__PURE__ */ jsx15(
2993
3745
  "rect",
2994
3746
  {
2995
3747
  x: "-5",
@@ -3004,7 +3756,7 @@ function NodeRenderer({
3004
3756
  pointerEvents: "none"
3005
3757
  }
3006
3758
  ),
3007
- /* @__PURE__ */ jsx13(
3759
+ /* @__PURE__ */ jsx15(
3008
3760
  "g",
3009
3761
  {
3010
3762
  dangerouslySetInnerHTML: { __html: symbol.svgContent },
@@ -3012,7 +3764,7 @@ function NodeRenderer({
3012
3764
  style: { pointerEvents: "auto" }
3013
3765
  }
3014
3766
  ),
3015
- /* @__PURE__ */ jsx13(
3767
+ /* @__PURE__ */ jsx15(
3016
3768
  "text",
3017
3769
  {
3018
3770
  x: symbol.viewBox.width / 2 + (node.labelOffset?.x ?? 0),
@@ -3033,7 +3785,7 @@ function NodeRenderer({
3033
3785
  }
3034
3786
 
3035
3787
  // src/components/PIDCanvas/PipeRenderer.tsx
3036
- import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
3788
+ import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3037
3789
  function PipeRenderer({
3038
3790
  pipes,
3039
3791
  selectedPipeIds,
@@ -3048,7 +3800,7 @@ function PipeRenderer({
3048
3800
  waypointDragOffset,
3049
3801
  onPipeSegmentClick
3050
3802
  }) {
3051
- return /* @__PURE__ */ jsx14("g", { id: "layer-pipes", children: pipes.filter((pipe) => pipe.visible !== false).map((pipe) => {
3803
+ return /* @__PURE__ */ jsx16("g", { id: "layer-pipes", children: pipes.filter((pipe) => pipe.visible !== false).map((pipe) => {
3052
3804
  const isSelected = selectedPipeIds?.has(pipe.id) ?? false;
3053
3805
  const isEditing = enableWaypointEditing && editingPipeId === pipe.id;
3054
3806
  const showEditingControls = enableWaypointEditing && isSelected;
@@ -3067,7 +3819,7 @@ function PipeRenderer({
3067
3819
  const strokeWidth = isSelected ? (pipeStyle.strokeWidth || 3) + 2 : pipeStyle.strokeWidth || 3;
3068
3820
  const strokeDasharray = pipeStyle.strokeDasharray;
3069
3821
  const opacity = pipeStyle.opacity !== void 0 ? pipeStyle.opacity : 1;
3070
- return /* @__PURE__ */ jsxs10(
3822
+ return /* @__PURE__ */ jsxs12(
3071
3823
  "g",
3072
3824
  {
3073
3825
  "data-pipe-id": pipe.id,
@@ -3080,8 +3832,8 @@ function PipeRenderer({
3080
3832
  cursor: onPipeClick ? "pointer" : "default"
3081
3833
  },
3082
3834
  children: [
3083
- !enableWaypointEditing && /* @__PURE__ */ jsxs10(Fragment3, { children: [
3084
- onPipeClick && /* @__PURE__ */ jsx14(
3835
+ !enableWaypointEditing && /* @__PURE__ */ jsxs12(Fragment5, { children: [
3836
+ onPipeClick && /* @__PURE__ */ jsx16(
3085
3837
  "polyline",
3086
3838
  {
3087
3839
  points: pointsString,
@@ -3093,7 +3845,7 @@ function PipeRenderer({
3093
3845
  pointerEvents: "stroke"
3094
3846
  }
3095
3847
  ),
3096
- /* @__PURE__ */ jsx14(
3848
+ /* @__PURE__ */ jsx16(
3097
3849
  "polyline",
3098
3850
  {
3099
3851
  points: pointsString,
@@ -3117,7 +3869,7 @@ function PipeRenderer({
3117
3869
  const waypointSize = isSelectedWaypoint ? 8 : isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
3118
3870
  const waypointOpacity = isSelectedWaypoint ? 1 : isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
3119
3871
  const waypointColor = isSelectedWaypoint ? "#3b82f6" : isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
3120
- return /* @__PURE__ */ jsx14(
3872
+ return /* @__PURE__ */ jsx16(
3121
3873
  "circle",
3122
3874
  {
3123
3875
  cx: point.x,
@@ -3154,12 +3906,12 @@ function PipeRenderer({
3154
3906
  `${pipe.id}-point-${idx}`
3155
3907
  );
3156
3908
  }),
3157
- showEditingControls && onPipeSegmentClick && displayPoints.length >= 2 && /* @__PURE__ */ jsx14(Fragment3, { children: displayPoints.slice(0, -1).map((point, idx) => {
3909
+ showEditingControls && onPipeSegmentClick && displayPoints.length >= 2 && /* @__PURE__ */ jsx16(Fragment5, { children: displayPoints.slice(0, -1).map((point, idx) => {
3158
3910
  const nextPoint = displayPoints[idx + 1];
3159
3911
  const midX = (point.x + nextPoint.x) / 2;
3160
3912
  const midY = (point.y + nextPoint.y) / 2;
3161
- return /* @__PURE__ */ jsxs10("g", { children: [
3162
- /* @__PURE__ */ jsx14(
3913
+ return /* @__PURE__ */ jsxs12("g", { children: [
3914
+ /* @__PURE__ */ jsx16(
3163
3915
  "circle",
3164
3916
  {
3165
3917
  cx: midX,
@@ -3175,7 +3927,7 @@ function PipeRenderer({
3175
3927
  }
3176
3928
  }
3177
3929
  ),
3178
- /* @__PURE__ */ jsx14(
3930
+ /* @__PURE__ */ jsx16(
3179
3931
  "circle",
3180
3932
  {
3181
3933
  cx: midX,
@@ -3191,7 +3943,7 @@ function PipeRenderer({
3191
3943
  )
3192
3944
  ] }, `${pipe.id}-segment-${idx}`);
3193
3945
  }) }),
3194
- pipe.showLabel !== false && pipe.label && pipe.routePoints.length >= 2 && /* @__PURE__ */ jsx14(
3946
+ pipe.showLabel !== false && pipe.label && pipe.routePoints.length >= 2 && /* @__PURE__ */ jsx16(
3195
3947
  "text",
3196
3948
  {
3197
3949
  x: pipe.routePoints[Math.floor(pipe.routePoints.length / 2)].x + (pipe.labelOffset?.x ?? 0),
@@ -3213,7 +3965,7 @@ function PipeRenderer({
3213
3965
  }
3214
3966
 
3215
3967
  // src/components/PIDCanvas/DataOverlay.tsx
3216
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
3968
+ import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3217
3969
  function DataOverlay({
3218
3970
  nodeId,
3219
3971
  x,
@@ -3233,7 +3985,7 @@ function DataOverlay({
3233
3985
  const offsetX = display?.offsetX ?? 0;
3234
3986
  const offsetY = display?.offsetY ?? 0;
3235
3987
  if (!showValue) {
3236
- return /* @__PURE__ */ jsx15(Fragment4, {});
3988
+ return /* @__PURE__ */ jsx17(Fragment6, {});
3237
3989
  }
3238
3990
  const formatValue = (val) => {
3239
3991
  if (typeof val === "number") {
@@ -3251,9 +4003,14 @@ function DataOverlay({
3251
4003
  fault: { bg: "#dc2626", text: "#ffffff", border: "#b91c1c" },
3252
4004
  off: { bg: "#6b7280", text: "#ffffff", border: "#4b5563" }
3253
4005
  };
3254
- const customColor = telemetry.statusColors?.[value.status];
3255
- const defaultColorSet = defaultStatusColors[value.status] || defaultStatusColors.normal;
3256
- const statusColors = customColor ? { bg: customColor, text: "#ffffff", border: customColor } : defaultColorSet;
4006
+ const getStatusColors = () => {
4007
+ const customColor = telemetry.statusColors?.[value.status];
4008
+ if (customColor) {
4009
+ return { bg: customColor, text: "#ffffff", border: customColor };
4010
+ }
4011
+ return defaultStatusColors[value.status] || defaultStatusColors.normal;
4012
+ };
4013
+ const statusColors = getStatusColors();
3257
4014
  const colors = backgroundColor && backgroundColor !== "status" ? { bg: backgroundColor, text: textColor || "#ffffff", border: backgroundColor } : statusColors;
3258
4015
  const finalTextColor = textColor || colors.text;
3259
4016
  const formattedValue = formatValue(value.value);
@@ -3280,8 +4037,8 @@ function DataOverlay({
3280
4037
  const overlayWidth = 80 * scale;
3281
4038
  const displayX = x + offsetX;
3282
4039
  const displayY = y + offsetY;
3283
- return /* @__PURE__ */ jsxs11("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
3284
- /* @__PURE__ */ jsx15(
4040
+ return /* @__PURE__ */ jsxs13("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
4041
+ /* @__PURE__ */ jsx17(
3285
4042
  "rect",
3286
4043
  {
3287
4044
  x: displayX - overlayWidth / 2,
@@ -3296,7 +4053,7 @@ function DataOverlay({
3296
4053
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
3297
4054
  }
3298
4055
  ),
3299
- label && /* @__PURE__ */ jsx15(
4056
+ label && /* @__PURE__ */ jsx17(
3300
4057
  "text",
3301
4058
  {
3302
4059
  x: displayX,
@@ -3309,7 +4066,7 @@ function DataOverlay({
3309
4066
  children: label
3310
4067
  }
3311
4068
  ),
3312
- /* @__PURE__ */ jsx15(
4069
+ /* @__PURE__ */ jsx17(
3313
4070
  "text",
3314
4071
  {
3315
4072
  x: displayX,
@@ -3322,7 +4079,7 @@ function DataOverlay({
3322
4079
  children: displayText
3323
4080
  }
3324
4081
  ),
3325
- showStatus && value.status !== "normal" && /* @__PURE__ */ jsx15(
4082
+ showStatus && value.status !== "normal" && /* @__PURE__ */ jsx17(
3326
4083
  "circle",
3327
4084
  {
3328
4085
  cx: displayX + 35 * scale,
@@ -3330,7 +4087,7 @@ function DataOverlay({
3330
4087
  r: 3 * scale,
3331
4088
  fill: finalTextColor,
3332
4089
  opacity: "0.9",
3333
- children: /* @__PURE__ */ jsx15(
4090
+ children: /* @__PURE__ */ jsx17(
3334
4091
  "animate",
3335
4092
  {
3336
4093
  attributeName: "opacity",
@@ -3341,7 +4098,7 @@ function DataOverlay({
3341
4098
  )
3342
4099
  }
3343
4100
  ),
3344
- value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ jsxs11(
4101
+ value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ jsxs13(
3345
4102
  "text",
3346
4103
  {
3347
4104
  x: displayX,
@@ -3358,7 +4115,7 @@ function DataOverlay({
3358
4115
  ]
3359
4116
  }
3360
4117
  ),
3361
- hasSparkline && /* @__PURE__ */ jsx15("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ jsx15(
4118
+ hasSparkline && /* @__PURE__ */ jsx17("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ jsx17(
3362
4119
  "path",
3363
4120
  {
3364
4121
  d: sparklinePath,
@@ -3374,8 +4131,8 @@ function DataOverlay({
3374
4131
  }
3375
4132
 
3376
4133
  // src/components/PIDCanvas/PositionPanel.tsx
3377
- import { useState as useState7, useEffect as useEffect4 } from "react";
3378
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4134
+ import { useState as useState8, useEffect as useEffect5 } from "react";
4135
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
3379
4136
  function PositionPanel({
3380
4137
  selectedNode,
3381
4138
  selectedWaypoint,
@@ -3383,9 +4140,9 @@ function PositionPanel({
3383
4140
  onWaypointPositionChange,
3384
4141
  position = "top-right"
3385
4142
  }) {
3386
- const [x, setX] = useState7("");
3387
- const [y, setY] = useState7("");
3388
- useEffect4(() => {
4143
+ const [x, setX] = useState8("");
4144
+ const [y, setY] = useState8("");
4145
+ useEffect5(() => {
3389
4146
  if (selectedNode) {
3390
4147
  setX(selectedNode.transform.x.toFixed(2));
3391
4148
  setY(selectedNode.transform.y.toFixed(2));
@@ -3444,7 +4201,7 @@ function PositionPanel({
3444
4201
  "bottom-left": { bottom: 10, left: 10 },
3445
4202
  "bottom-right": { bottom: 10, right: 10 }
3446
4203
  };
3447
- return /* @__PURE__ */ jsxs12(
4204
+ return /* @__PURE__ */ jsxs14(
3448
4205
  "div",
3449
4206
  {
3450
4207
  style: {
@@ -3461,11 +4218,11 @@ function PositionPanel({
3461
4218
  fontSize: "13px"
3462
4219
  },
3463
4220
  children: [
3464
- /* @__PURE__ */ jsx16("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
3465
- /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
3466
- /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3467
- /* @__PURE__ */ jsx16("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
3468
- /* @__PURE__ */ jsx16(
4221
+ /* @__PURE__ */ jsx18("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
4222
+ /* @__PURE__ */ jsxs14("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
4223
+ /* @__PURE__ */ jsxs14("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
4224
+ /* @__PURE__ */ jsx18("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
4225
+ /* @__PURE__ */ jsx18(
3469
4226
  "input",
3470
4227
  {
3471
4228
  type: "number",
@@ -3483,9 +4240,9 @@ function PositionPanel({
3483
4240
  }
3484
4241
  )
3485
4242
  ] }),
3486
- /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3487
- /* @__PURE__ */ jsx16("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
3488
- /* @__PURE__ */ jsx16(
4243
+ /* @__PURE__ */ jsxs14("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
4244
+ /* @__PURE__ */ jsx18("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
4245
+ /* @__PURE__ */ jsx18(
3489
4246
  "input",
3490
4247
  {
3491
4248
  type: "number",
@@ -3504,14 +4261,14 @@ function PositionPanel({
3504
4261
  )
3505
4262
  ] })
3506
4263
  ] }),
3507
- /* @__PURE__ */ jsx16("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
4264
+ /* @__PURE__ */ jsx18("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
3508
4265
  ]
3509
4266
  }
3510
4267
  );
3511
4268
  }
3512
4269
 
3513
4270
  // src/diagram/hooks/useViewBox.ts
3514
- import { useState as useState8, useCallback as useCallback3, useRef as useRef2, useEffect as useEffect5 } from "react";
4271
+ import { useState as useState9, useCallback as useCallback3, useRef as useRef2, useEffect as useEffect6 } from "react";
3515
4272
  function useViewBox(options) {
3516
4273
  const {
3517
4274
  initialViewBox,
@@ -3522,7 +4279,7 @@ function useViewBox(options) {
3522
4279
  zoomSensitivity = 1e-3,
3523
4280
  allowLeftClickPan = true
3524
4281
  } = options;
3525
- const [viewBox, setViewBox] = useState8(initialViewBox);
4282
+ const [viewBox, setViewBox] = useState9(initialViewBox);
3526
4283
  const svgRef = useRef2(null);
3527
4284
  const isPanningRef = useRef2(false);
3528
4285
  const lastMousePosRef = useRef2({ x: 0, y: 0 });
@@ -3593,7 +4350,7 @@ function useViewBox(options) {
3593
4350
  height: newHeight
3594
4351
  });
3595
4352
  }, [initialViewBox]);
3596
- useEffect5(() => {
4353
+ useEffect6(() => {
3597
4354
  if (!enableZoom || !svgRef.current) return;
3598
4355
  const svg = svgRef.current;
3599
4356
  const handleWheel = (e) => {
@@ -3626,7 +4383,7 @@ function useViewBox(options) {
3626
4383
  svg.addEventListener("wheel", handleWheel, { passive: false });
3627
4384
  return () => svg.removeEventListener("wheel", handleWheel);
3628
4385
  }, [enableZoom, zoomSensitivity, minZoom, initialViewBox]);
3629
- useEffect5(() => {
4386
+ useEffect6(() => {
3630
4387
  if (!enablePan || !svgRef.current) return;
3631
4388
  const svg = svgRef.current;
3632
4389
  const handleMouseDown = (e) => {
@@ -3702,7 +4459,7 @@ function useViewBox(options) {
3702
4459
  window.removeEventListener("keyup", handleKeyUp);
3703
4460
  };
3704
4461
  }, [enablePan, allowLeftClickPan]);
3705
- useEffect5(() => {
4462
+ useEffect6(() => {
3706
4463
  if (!svgRef.current) return;
3707
4464
  if (!enablePan && !enableZoom) return;
3708
4465
  const svg = svgRef.current;
@@ -3832,14 +4589,14 @@ function useViewBox(options) {
3832
4589
  }
3833
4590
 
3834
4591
  // src/diagram/hooks/useSelection.ts
3835
- import { useState as useState9, useCallback as useCallback4 } from "react";
4592
+ import { useState as useState10, useCallback as useCallback4 } from "react";
3836
4593
  function useSelection(options = {}) {
3837
4594
  const {
3838
4595
  multiSelect = true,
3839
4596
  initialSelection = { selectedNodeIds: /* @__PURE__ */ new Set(), selectedPipeIds: /* @__PURE__ */ new Set() },
3840
4597
  onSelectionChange
3841
4598
  } = options;
3842
- const [selection, setSelection] = useState9(initialSelection);
4599
+ const [selection, setSelection] = useState10(initialSelection);
3843
4600
  const notifyChange = useCallback4(
3844
4601
  (newSelection) => {
3845
4602
  if (onSelectionChange) {
@@ -3937,14 +4694,14 @@ function useSelection(options = {}) {
3937
4694
  }
3938
4695
 
3939
4696
  // src/diagram/hooks/useTelemetry.ts
3940
- import { useState as useState10, useCallback as useCallback5, useEffect as useEffect6, useRef as useRef3 } from "react";
4697
+ import { useState as useState11, useCallback as useCallback5, useEffect as useEffect7, useRef as useRef3 } from "react";
3941
4698
  function useTelemetry(options = {}) {
3942
4699
  const {
3943
4700
  initialBindings = [],
3944
4701
  onTelemetryChange,
3945
4702
  refreshInterval = 0
3946
4703
  } = options;
3947
- const [telemetry, setTelemetry] = useState10(() => {
4704
+ const [telemetry, setTelemetry] = useState11(() => {
3948
4705
  const map = /* @__PURE__ */ new Map();
3949
4706
  initialBindings.forEach((binding) => {
3950
4707
  map.set(binding.nodeId, binding);
@@ -4065,7 +4822,7 @@ function useTelemetry(options = {}) {
4065
4822
  },
4066
4823
  [notifyChange]
4067
4824
  );
4068
- useEffect6(() => {
4825
+ useEffect7(() => {
4069
4826
  if (refreshInterval <= 0) return;
4070
4827
  const intervalId = setInterval(() => {
4071
4828
  notifyChange(telemetryRef.current);
@@ -4083,10 +4840,10 @@ function useTelemetry(options = {}) {
4083
4840
  }
4084
4841
 
4085
4842
  // src/diagram/hooks/useTelemetryStatus.ts
4086
- import { useEffect as useEffect7 } from "react";
4843
+ import { useEffect as useEffect8 } from "react";
4087
4844
  function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
4088
4845
  const { enabled = true, mapStatus } = options;
4089
- useEffect7(() => {
4846
+ useEffect8(() => {
4090
4847
  if (!enabled) return;
4091
4848
  const defaultMapStatus = (telemetryStatus) => {
4092
4849
  switch (telemetryStatus) {
@@ -4125,7 +4882,7 @@ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
4125
4882
  }
4126
4883
 
4127
4884
  // src/diagram/hooks/useDrag.ts
4128
- import { useState as useState11, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect8 } from "react";
4885
+ import { useState as useState12, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect9 } from "react";
4129
4886
  function useDrag(options = {}) {
4130
4887
  const {
4131
4888
  onDragStart,
@@ -4135,13 +4892,13 @@ function useDrag(options = {}) {
4135
4892
  dragThreshold = 3,
4136
4893
  screenToWorld = (x, y) => ({ x, y })
4137
4894
  } = options;
4138
- const [dragState, setDragState] = useState11({
4895
+ const [dragState, setDragState] = useState12({
4139
4896
  isDragging: false,
4140
4897
  startPosition: null,
4141
4898
  offset: { x: 0, y: 0 },
4142
4899
  draggedId: null
4143
4900
  });
4144
- const [isListening, setIsListening] = useState11(false);
4901
+ const [isListening, setIsListening] = useState12(false);
4145
4902
  const dragStartRef = useRef4(null);
4146
4903
  const hasMovedRef = useRef4(false);
4147
4904
  const applySnap = useCallback6(
@@ -4310,7 +5067,7 @@ function useDrag(options = {}) {
4310
5067
  },
4311
5068
  [screenToWorld, applySnap, onDragEnd]
4312
5069
  );
4313
- useEffect8(() => {
5070
+ useEffect9(() => {
4314
5071
  if (!isListening) return;
4315
5072
  document.addEventListener("mousemove", handleMouseMove);
4316
5073
  document.addEventListener("mouseup", handleMouseUp);
@@ -4379,7 +5136,7 @@ function useDrag(options = {}) {
4379
5136
  }
4380
5137
 
4381
5138
  // src/diagram/hooks/useKeyboardShortcuts.ts
4382
- import { useEffect as useEffect9 } from "react";
5139
+ import { useEffect as useEffect10 } from "react";
4383
5140
  function useKeyboardShortcuts(options) {
4384
5141
  const {
4385
5142
  onDelete,
@@ -4390,7 +5147,7 @@ function useKeyboardShortcuts(options) {
4390
5147
  onSelectAll,
4391
5148
  enabled = true
4392
5149
  } = options;
4393
- useEffect9(() => {
5150
+ useEffect10(() => {
4394
5151
  if (!enabled) return;
4395
5152
  const handleKeyDown = (event) => {
4396
5153
  const target = event.target;
@@ -4499,7 +5256,7 @@ function nodeHasPort(node, portId) {
4499
5256
  }
4500
5257
 
4501
5258
  // src/components/PIDCanvas/PIDCanvas.tsx
4502
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
5259
+ import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
4503
5260
  function PIDCanvas({
4504
5261
  diagram,
4505
5262
  className = "",
@@ -4526,21 +5283,21 @@ function PIDCanvas({
4526
5283
  telemetry,
4527
5284
  ...props
4528
5285
  }) {
4529
- const [localDiagram, setLocalDiagram] = useState12(diagram);
4530
- const [dragOverPosition, setDragOverPosition] = useState12(null);
4531
- const [isConnecting, setIsConnecting] = useState12(false);
4532
- const [connectionSource, setConnectionSource] = useState12(null);
4533
- const [connectionSourcePort, setConnectionSourcePort] = useState12(null);
4534
- const [connectionCursor, setConnectionCursor] = useState12(null);
4535
- const [hoveredNode, setHoveredNode] = useState12(null);
4536
- const [hoveredPort, setHoveredPort] = useState12(null);
4537
- const [selectedWaypoint, setSelectedWaypoint] = useState12(null);
5286
+ const [localDiagram, setLocalDiagram] = useState13(diagram);
5287
+ const [dragOverPosition, setDragOverPosition] = useState13(null);
5288
+ const [isConnecting, setIsConnecting] = useState13(false);
5289
+ const [connectionSource, setConnectionSource] = useState13(null);
5290
+ const [connectionSourcePort, setConnectionSourcePort] = useState13(null);
5291
+ const [connectionCursor, setConnectionCursor] = useState13(null);
5292
+ const [hoveredNode, setHoveredNode] = useState13(null);
5293
+ const [hoveredPort, setHoveredPort] = useState13(null);
5294
+ const [selectedWaypoint, setSelectedWaypoint] = useState13(null);
4538
5295
  const historyStack = useRef5([]);
4539
5296
  const redoStack = useRef5([]);
4540
5297
  const isUndoRedoAction = useRef5(false);
4541
5298
  const isInternalChange = useRef5(false);
4542
5299
  const lastInternalDiagram = useRef5(null);
4543
- useEffect10(() => {
5300
+ useEffect11(() => {
4544
5301
  const isExternal = !isInternalChange.current && diagram !== lastInternalDiagram.current;
4545
5302
  if (isExternal) {
4546
5303
  setLocalDiagram(diagram);
@@ -4585,7 +5342,7 @@ function PIDCanvas({
4585
5342
  });
4586
5343
  return { minX, minY, maxX, maxY };
4587
5344
  }, [localDiagram.nodes]);
4588
- useEffect10(() => {
5345
+ useEffect11(() => {
4589
5346
  if (onMounted) {
4590
5347
  const controls = {
4591
5348
  fitToContent: (padding = 50) => {
@@ -4664,7 +5421,7 @@ function PIDCanvas({
4664
5421
  isUndoRedoAction.current = false;
4665
5422
  }, 0);
4666
5423
  }, [localDiagram, onDiagramChange]);
4667
- useEffect10(() => {
5424
+ useEffect11(() => {
4668
5425
  const handleKeyDown = (e) => {
4669
5426
  const isCtrlOrCmd = e.ctrlKey || e.metaKey;
4670
5427
  if (isCtrlOrCmd && e.key === "z" && !e.shiftKey) {
@@ -4681,7 +5438,7 @@ function PIDCanvas({
4681
5438
  window.addEventListener("keydown", handleKeyDown);
4682
5439
  return () => window.removeEventListener("keydown", handleKeyDown);
4683
5440
  }, [undo, redo]);
4684
- useEffect10(() => {
5441
+ useEffect11(() => {
4685
5442
  if (!features.dragNodes) return;
4686
5443
  const handleKeyDown = (e) => {
4687
5444
  if (selection.selectedNodeIds.size === 0 && !selectedWaypoint) return;
@@ -5091,7 +5848,7 @@ function PIDCanvas({
5091
5848
  const viewBox = controlledViewBox;
5092
5849
  const displayDiagram = localDiagram;
5093
5850
  const selectedNode = selectionEnabled && selection.selectedNodeIds.size === 1 ? localDiagram.nodes.find((n) => selection.selectedNodeIds.has(n.id)) || null : null;
5094
- return /* @__PURE__ */ jsxs13(
5851
+ return /* @__PURE__ */ jsxs15(
5095
5852
  "div",
5096
5853
  {
5097
5854
  ref: containerRef,
@@ -5106,7 +5863,7 @@ function PIDCanvas({
5106
5863
  },
5107
5864
  ...props,
5108
5865
  children: [
5109
- /* @__PURE__ */ jsxs13(
5866
+ /* @__PURE__ */ jsxs15(
5110
5867
  "svg",
5111
5868
  {
5112
5869
  ref: svgRef,
@@ -5125,15 +5882,15 @@ function PIDCanvas({
5125
5882
  onDrop: handleDrop,
5126
5883
  onDragLeave: handleDragLeave,
5127
5884
  children: [
5128
- features.showGrid !== false ? /* @__PURE__ */ jsxs13(Fragment5, { children: [
5129
- /* @__PURE__ */ jsxs13("defs", { children: [
5130
- /* @__PURE__ */ jsx17("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx17("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
5131
- /* @__PURE__ */ jsxs13("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
5132
- /* @__PURE__ */ jsx17("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
5133
- /* @__PURE__ */ jsx17("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
5885
+ features.showGrid !== false ? /* @__PURE__ */ jsxs15(Fragment7, { children: [
5886
+ /* @__PURE__ */ jsxs15("defs", { children: [
5887
+ /* @__PURE__ */ jsx19("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx19("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
5888
+ /* @__PURE__ */ jsxs15("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
5889
+ /* @__PURE__ */ jsx19("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
5890
+ /* @__PURE__ */ jsx19("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
5134
5891
  ] })
5135
5892
  ] }),
5136
- /* @__PURE__ */ jsx17(
5893
+ /* @__PURE__ */ jsx19(
5137
5894
  "rect",
5138
5895
  {
5139
5896
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
@@ -5143,7 +5900,7 @@ function PIDCanvas({
5143
5900
  fill: "url(#grid-major)"
5144
5901
  }
5145
5902
  )
5146
- ] }) : /* @__PURE__ */ jsx17(
5903
+ ] }) : /* @__PURE__ */ jsx19(
5147
5904
  "rect",
5148
5905
  {
5149
5906
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
@@ -5153,7 +5910,7 @@ function PIDCanvas({
5153
5910
  fill: features.backgroundColor ?? "#ffffff"
5154
5911
  }
5155
5912
  ),
5156
- /* @__PURE__ */ jsx17(
5913
+ /* @__PURE__ */ jsx19(
5157
5914
  PipeRenderer,
5158
5915
  {
5159
5916
  pipes: displayDiagram.pipes,
@@ -5167,12 +5924,12 @@ function PIDCanvas({
5167
5924
  onPipeSegmentClick: void 0
5168
5925
  }
5169
5926
  ),
5170
- /* @__PURE__ */ jsx17(
5927
+ /* @__PURE__ */ jsx19(
5171
5928
  NodeRenderer,
5172
5929
  {
5173
5930
  nodes: displayDiagram.nodes,
5174
5931
  selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
5175
- onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
5932
+ onNodeClick: onNodeClick ? handleNodeClick : selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
5176
5933
  onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
5177
5934
  enableDrag: dragEnabled && !isConnecting,
5178
5935
  onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
@@ -5180,7 +5937,7 @@ function PIDCanvas({
5180
5937
  dragOffset: dragState.offset
5181
5938
  }
5182
5939
  ),
5183
- (features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx17(
5940
+ (features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx19(
5184
5941
  PipeRenderer,
5185
5942
  {
5186
5943
  pipes: displayDiagram.pipes,
@@ -5197,14 +5954,14 @@ function PIDCanvas({
5197
5954
  onPipeSegmentClick: handlePipeSegmentClick
5198
5955
  }
5199
5956
  ),
5200
- telemetryEnabled && telemetry && /* @__PURE__ */ jsx17("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
5957
+ telemetryEnabled && telemetry && /* @__PURE__ */ jsx19("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
5201
5958
  const binding = telemetry.get(node.id);
5202
5959
  if (!binding) return null;
5203
5960
  const symbol = getSymbolDefinition(node.symbolId);
5204
5961
  if (!symbol) return null;
5205
5962
  const overlayX = node.transform.x + symbol.viewBox.width / 2;
5206
5963
  const overlayY = node.transform.y + symbol.viewBox.height + 50;
5207
- return /* @__PURE__ */ jsx17(
5964
+ return /* @__PURE__ */ jsx19(
5208
5965
  DataOverlay,
5209
5966
  {
5210
5967
  nodeId: node.id,
@@ -5216,7 +5973,7 @@ function PIDCanvas({
5216
5973
  `telemetry-${node.id}`
5217
5974
  );
5218
5975
  }) }),
5219
- dragOverPosition && /* @__PURE__ */ jsx17(
5976
+ dragOverPosition && /* @__PURE__ */ jsx19(
5220
5977
  "circle",
5221
5978
  {
5222
5979
  cx: dragOverPosition.x,
@@ -5229,7 +5986,7 @@ function PIDCanvas({
5229
5986
  pointerEvents: "none"
5230
5987
  }
5231
5988
  ),
5232
- isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ jsx17("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
5989
+ isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ jsx19("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
5233
5990
  const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
5234
5991
  if (!sourceNode) return null;
5235
5992
  const symbol = getSymbolDefinition(sourceNode.symbolId);
@@ -5245,8 +6002,8 @@ function PIDCanvas({
5245
6002
  }
5246
6003
  }
5247
6004
  }
5248
- return /* @__PURE__ */ jsxs13(Fragment5, { children: [
5249
- /* @__PURE__ */ jsx17(
6005
+ return /* @__PURE__ */ jsxs15(Fragment7, { children: [
6006
+ /* @__PURE__ */ jsx19(
5250
6007
  "circle",
5251
6008
  {
5252
6009
  cx: lineStartX,
@@ -5258,7 +6015,7 @@ function PIDCanvas({
5258
6015
  opacity: 0.6
5259
6016
  }
5260
6017
  ),
5261
- /* @__PURE__ */ jsx17(
6018
+ /* @__PURE__ */ jsx19(
5262
6019
  "line",
5263
6020
  {
5264
6021
  x1: lineStartX,
@@ -5271,7 +6028,7 @@ function PIDCanvas({
5271
6028
  opacity: 0.8
5272
6029
  }
5273
6030
  ),
5274
- /* @__PURE__ */ jsx17(
6031
+ /* @__PURE__ */ jsx19(
5275
6032
  "circle",
5276
6033
  {
5277
6034
  cx: connectionCursor.x,
@@ -5283,7 +6040,7 @@ function PIDCanvas({
5283
6040
  )
5284
6041
  ] });
5285
6042
  })() }),
5286
- features.connectionMode && /* @__PURE__ */ jsx17("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
6043
+ features.connectionMode && /* @__PURE__ */ jsx19("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
5287
6044
  const symbol = getSymbolDefinition(node.symbolId);
5288
6045
  if (!symbol?.ports) return null;
5289
6046
  return symbol.ports.map((port) => {
@@ -5291,8 +6048,8 @@ function PIDCanvas({
5291
6048
  if (!portPos) return null;
5292
6049
  const isHovered = hoveredPort === port.id && hoveredNode === node.id;
5293
6050
  const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
5294
- return /* @__PURE__ */ jsxs13("g", { children: [
5295
- /* @__PURE__ */ jsx17(
6051
+ return /* @__PURE__ */ jsxs15("g", { children: [
6052
+ /* @__PURE__ */ jsx19(
5296
6053
  "circle",
5297
6054
  {
5298
6055
  cx: portPos.x,
@@ -5325,7 +6082,7 @@ function PIDCanvas({
5325
6082
  }
5326
6083
  }
5327
6084
  ),
5328
- /* @__PURE__ */ jsx17(
6085
+ /* @__PURE__ */ jsx19(
5329
6086
  "circle",
5330
6087
  {
5331
6088
  cx: portPos.x,
@@ -5344,7 +6101,7 @@ function PIDCanvas({
5344
6101
  ]
5345
6102
  }
5346
6103
  ),
5347
- features.dragNodes && /* @__PURE__ */ jsx17(
6104
+ features.dragNodes && /* @__PURE__ */ jsx19(
5348
6105
  PositionPanel,
5349
6106
  {
5350
6107
  selectedNode,
@@ -5364,16 +6121,16 @@ function PIDCanvas({
5364
6121
  }
5365
6122
 
5366
6123
  // src/components/SymbolLibrary/SymbolLibrary.tsx
5367
- import { useState as useState13 } from "react";
5368
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
6124
+ import { useState as useState14 } from "react";
6125
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
5369
6126
  function SymbolLibrary({
5370
6127
  className = "",
5371
6128
  onSymbolDragStart,
5372
6129
  showCategories = true,
5373
6130
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
5374
6131
  }) {
5375
- const [selectedCategory, setSelectedCategory] = useState13("all");
5376
- const [searchQuery, setSearchQuery] = useState13("");
6132
+ const [selectedCategory, setSelectedCategory] = useState14("all");
6133
+ const [searchQuery, setSearchQuery] = useState14("");
5377
6134
  const symbolIds = getAvailableSymbols();
5378
6135
  const allSymbols = symbolIds.map((id) => getSymbolDefinition(id)).filter((symbol) => symbol !== void 0);
5379
6136
  const categoryMap = {
@@ -5394,9 +6151,9 @@ function SymbolLibrary({
5394
6151
  event.dataTransfer.effectAllowed = "copy";
5395
6152
  onSymbolDragStart?.(symbol.id, event);
5396
6153
  };
5397
- return /* @__PURE__ */ jsxs14("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
5398
- /* @__PURE__ */ jsx18("div", { style: styles.header, children: /* @__PURE__ */ jsx18("h3", { style: styles.title, children: "Symbol Library" }) }),
5399
- /* @__PURE__ */ jsx18("div", { style: styles.searchContainer, children: /* @__PURE__ */ jsx18(
6154
+ return /* @__PURE__ */ jsxs16("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
6155
+ /* @__PURE__ */ jsx20("div", { style: styles.header, children: /* @__PURE__ */ jsx20("h3", { style: styles.title, children: "Symbol Library" }) }),
6156
+ /* @__PURE__ */ jsx20("div", { style: styles.searchContainer, children: /* @__PURE__ */ jsx20(
5400
6157
  "input",
5401
6158
  {
5402
6159
  type: "text",
@@ -5406,8 +6163,8 @@ function SymbolLibrary({
5406
6163
  style: styles.searchInput
5407
6164
  }
5408
6165
  ) }),
5409
- showCategories && /* @__PURE__ */ jsxs14("div", { style: styles.categories, children: [
5410
- /* @__PURE__ */ jsx18(
6166
+ showCategories && /* @__PURE__ */ jsxs16("div", { style: styles.categories, children: [
6167
+ /* @__PURE__ */ jsx20(
5411
6168
  "button",
5412
6169
  {
5413
6170
  onClick: () => setSelectedCategory("all"),
@@ -5418,7 +6175,7 @@ function SymbolLibrary({
5418
6175
  children: "All"
5419
6176
  }
5420
6177
  ),
5421
- categories.map((category) => /* @__PURE__ */ jsx18(
6178
+ categories.map((category) => /* @__PURE__ */ jsx20(
5422
6179
  "button",
5423
6180
  {
5424
6181
  onClick: () => setSelectedCategory(category),
@@ -5431,7 +6188,7 @@ function SymbolLibrary({
5431
6188
  category
5432
6189
  ))
5433
6190
  ] }),
5434
- /* @__PURE__ */ jsx18("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ jsx18("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ jsxs14(
6191
+ /* @__PURE__ */ jsx20("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ jsx20("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ jsxs16(
5435
6192
  "div",
5436
6193
  {
5437
6194
  draggable: true,
@@ -5439,15 +6196,15 @@ function SymbolLibrary({
5439
6196
  style: styles.symbolCard,
5440
6197
  title: symbol.metadata?.description || symbol.name,
5441
6198
  children: [
5442
- /* @__PURE__ */ jsx18("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx18(
6199
+ /* @__PURE__ */ jsx20("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx20(
5443
6200
  "svg",
5444
6201
  {
5445
6202
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
5446
6203
  style: styles.symbolSvg,
5447
- children: /* @__PURE__ */ jsx18("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
6204
+ children: /* @__PURE__ */ jsx20("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
5448
6205
  }
5449
6206
  ) }),
5450
- /* @__PURE__ */ jsx18("div", { style: styles.symbolName, children: symbol.name })
6207
+ /* @__PURE__ */ jsx20("div", { style: styles.symbolName, children: symbol.name })
5451
6208
  ]
5452
6209
  },
5453
6210
  symbol.id
@@ -5556,7 +6313,7 @@ var styles = {
5556
6313
  };
5557
6314
 
5558
6315
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
5559
- import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
6316
+ import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
5560
6317
  function NodeConfigPanel({
5561
6318
  node,
5562
6319
  binding,
@@ -5575,15 +6332,15 @@ function NodeConfigPanel({
5575
6332
  padding: "20px",
5576
6333
  ...style
5577
6334
  };
5578
- return /* @__PURE__ */ jsxs15("div", { className, style: defaultStyle, children: [
5579
- /* @__PURE__ */ jsxs15("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
6335
+ return /* @__PURE__ */ jsxs17("div", { className, style: defaultStyle, children: [
6336
+ /* @__PURE__ */ jsxs17("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
5580
6337
  "Configure: ",
5581
6338
  node.id
5582
6339
  ] }),
5583
- /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
5584
- /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
5585
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
5586
- /* @__PURE__ */ jsx19(
6340
+ /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "20px" }, children: [
6341
+ /* @__PURE__ */ jsx21("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
6342
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
6343
+ /* @__PURE__ */ jsx21(
5587
6344
  "input",
5588
6345
  {
5589
6346
  type: "text",
@@ -5599,10 +6356,10 @@ function NodeConfigPanel({
5599
6356
  }
5600
6357
  }
5601
6358
  ),
5602
- /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5603
- /* @__PURE__ */ jsxs15("div", { children: [
5604
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
5605
- /* @__PURE__ */ jsx19(
6359
+ /* @__PURE__ */ jsxs17("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
6360
+ /* @__PURE__ */ jsxs17("div", { children: [
6361
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
6362
+ /* @__PURE__ */ jsx21(
5606
6363
  "input",
5607
6364
  {
5608
6365
  type: "number",
@@ -5623,9 +6380,9 @@ function NodeConfigPanel({
5623
6380
  }
5624
6381
  )
5625
6382
  ] }),
5626
- /* @__PURE__ */ jsxs15("div", { children: [
5627
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
5628
- /* @__PURE__ */ jsx19(
6383
+ /* @__PURE__ */ jsxs17("div", { children: [
6384
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
6385
+ /* @__PURE__ */ jsx21(
5629
6386
  "input",
5630
6387
  {
5631
6388
  type: "number",
@@ -5647,9 +6404,9 @@ function NodeConfigPanel({
5647
6404
  )
5648
6405
  ] })
5649
6406
  ] }),
5650
- /* @__PURE__ */ jsxs15("div", { children: [
5651
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
5652
- /* @__PURE__ */ jsx19(
6407
+ /* @__PURE__ */ jsxs17("div", { children: [
6408
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
6409
+ /* @__PURE__ */ jsx21(
5653
6410
  "input",
5654
6411
  {
5655
6412
  type: "number",
@@ -5669,9 +6426,9 @@ function NodeConfigPanel({
5669
6426
  }
5670
6427
  )
5671
6428
  ] }),
5672
- /* @__PURE__ */ jsxs15("div", { children: [
5673
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
5674
- /* @__PURE__ */ jsx19(
6429
+ /* @__PURE__ */ jsxs17("div", { children: [
6430
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
6431
+ /* @__PURE__ */ jsx21(
5675
6432
  "input",
5676
6433
  {
5677
6434
  type: "number",
@@ -5696,9 +6453,9 @@ function NodeConfigPanel({
5696
6453
  )
5697
6454
  ] })
5698
6455
  ] }),
5699
- /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
5700
- /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
5701
- !binding ? /* @__PURE__ */ jsx19(
6456
+ /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "20px" }, children: [
6457
+ /* @__PURE__ */ jsx21("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
6458
+ !binding ? /* @__PURE__ */ jsx21(
5702
6459
  "button",
5703
6460
  {
5704
6461
  onClick: () => {
@@ -5741,9 +6498,9 @@ function NodeConfigPanel({
5741
6498
  },
5742
6499
  children: "+ Add Telemetry"
5743
6500
  }
5744
- ) : /* @__PURE__ */ jsxs15(Fragment6, { children: [
5745
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
5746
- /* @__PURE__ */ jsx19(
6501
+ ) : /* @__PURE__ */ jsxs17(Fragment8, { children: [
6502
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
6503
+ /* @__PURE__ */ jsx21(
5747
6504
  "input",
5748
6505
  {
5749
6506
  type: "number",
@@ -5765,8 +6522,8 @@ function NodeConfigPanel({
5765
6522
  }
5766
6523
  }
5767
6524
  ),
5768
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
5769
- /* @__PURE__ */ jsx19(
6525
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
6526
+ /* @__PURE__ */ jsx21(
5770
6527
  "input",
5771
6528
  {
5772
6529
  type: "text",
@@ -5788,8 +6545,8 @@ function NodeConfigPanel({
5788
6545
  }
5789
6546
  }
5790
6547
  ),
5791
- /* @__PURE__ */ jsxs15("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
5792
- /* @__PURE__ */ jsx19(
6548
+ /* @__PURE__ */ jsxs17("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
6549
+ /* @__PURE__ */ jsx21(
5793
6550
  "input",
5794
6551
  {
5795
6552
  type: "checkbox",
@@ -5811,8 +6568,8 @@ function NodeConfigPanel({
5811
6568
  ),
5812
6569
  "Show Sparkline"
5813
6570
  ] }),
5814
- /* @__PURE__ */ jsxs15("div", { style: { marginTop: "12px" }, children: [
5815
- /* @__PURE__ */ jsx19(
6571
+ /* @__PURE__ */ jsxs17("div", { style: { marginTop: "12px" }, children: [
6572
+ /* @__PURE__ */ jsx21(
5816
6573
  "label",
5817
6574
  {
5818
6575
  style: {
@@ -5824,10 +6581,10 @@ function NodeConfigPanel({
5824
6581
  children: "Telemetry Position Offset:"
5825
6582
  }
5826
6583
  ),
5827
- /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5828
- /* @__PURE__ */ jsxs15("div", { children: [
5829
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
5830
- /* @__PURE__ */ jsx19(
6584
+ /* @__PURE__ */ jsxs17("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
6585
+ /* @__PURE__ */ jsxs17("div", { children: [
6586
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
6587
+ /* @__PURE__ */ jsx21(
5831
6588
  "input",
5832
6589
  {
5833
6590
  type: "number",
@@ -5854,9 +6611,9 @@ function NodeConfigPanel({
5854
6611
  }
5855
6612
  )
5856
6613
  ] }),
5857
- /* @__PURE__ */ jsxs15("div", { children: [
5858
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
5859
- /* @__PURE__ */ jsx19(
6614
+ /* @__PURE__ */ jsxs17("div", { children: [
6615
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
6616
+ /* @__PURE__ */ jsx21(
5860
6617
  "input",
5861
6618
  {
5862
6619
  type: "number",
@@ -5884,8 +6641,8 @@ function NodeConfigPanel({
5884
6641
  )
5885
6642
  ] })
5886
6643
  ] }),
5887
- /* @__PURE__ */ jsxs15("div", { style: { marginTop: "16px" }, children: [
5888
- /* @__PURE__ */ jsx19(
6644
+ /* @__PURE__ */ jsxs17("div", { style: { marginTop: "16px" }, children: [
6645
+ /* @__PURE__ */ jsx21(
5889
6646
  "label",
5890
6647
  {
5891
6648
  style: {
@@ -5897,10 +6654,10 @@ function NodeConfigPanel({
5897
6654
  children: "Display Colors:"
5898
6655
  }
5899
6656
  ),
5900
- /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
5901
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
5902
- /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
5903
- /* @__PURE__ */ jsx19(
6657
+ /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "8px" }, children: [
6658
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
6659
+ /* @__PURE__ */ jsxs17("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6660
+ /* @__PURE__ */ jsx21(
5904
6661
  "input",
5905
6662
  {
5906
6663
  type: "color",
@@ -5926,7 +6683,7 @@ function NodeConfigPanel({
5926
6683
  }
5927
6684
  }
5928
6685
  ),
5929
- /* @__PURE__ */ jsx19(
6686
+ /* @__PURE__ */ jsx21(
5930
6687
  "input",
5931
6688
  {
5932
6689
  type: "text",
@@ -5956,10 +6713,10 @@ function NodeConfigPanel({
5956
6713
  )
5957
6714
  ] })
5958
6715
  ] }),
5959
- binding.display?.showSparkline && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
5960
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
5961
- /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
5962
- /* @__PURE__ */ jsx19(
6716
+ binding.display?.showSparkline && /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "8px" }, children: [
6717
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
6718
+ /* @__PURE__ */ jsxs17("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6719
+ /* @__PURE__ */ jsx21(
5963
6720
  "input",
5964
6721
  {
5965
6722
  type: "color",
@@ -5985,7 +6742,7 @@ function NodeConfigPanel({
5985
6742
  }
5986
6743
  }
5987
6744
  ),
5988
- /* @__PURE__ */ jsx19(
6745
+ /* @__PURE__ */ jsx21(
5989
6746
  "input",
5990
6747
  {
5991
6748
  type: "text",
@@ -6015,9 +6772,9 @@ function NodeConfigPanel({
6015
6772
  )
6016
6773
  ] })
6017
6774
  ] }),
6018
- /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6019
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
6020
- /* @__PURE__ */ jsx19(
6775
+ /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "8px" }, children: [
6776
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
6777
+ /* @__PURE__ */ jsx21(
6021
6778
  "input",
6022
6779
  {
6023
6780
  type: "text",
@@ -6045,20 +6802,20 @@ function NodeConfigPanel({
6045
6802
  }
6046
6803
  }
6047
6804
  ),
6048
- /* @__PURE__ */ jsx19("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
6805
+ /* @__PURE__ */ jsx21("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
6049
6806
  ] })
6050
6807
  ] }),
6051
6808
  " "
6052
6809
  ] })
6053
6810
  ] })
6054
6811
  ] }),
6055
- binding && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
6056
- /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
6812
+ binding && /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "20px" }, children: [
6813
+ /* @__PURE__ */ jsx21("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
6057
6814
  ["highHigh", "high", "low", "lowLow"].map((key) => {
6058
6815
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
6059
- return /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6060
- /* @__PURE__ */ jsx19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
6061
- /* @__PURE__ */ jsx19(
6816
+ return /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "8px" }, children: [
6817
+ /* @__PURE__ */ jsx21("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
6818
+ /* @__PURE__ */ jsx21(
6062
6819
  "input",
6063
6820
  {
6064
6821
  type: "number",
@@ -6082,8 +6839,8 @@ function NodeConfigPanel({
6082
6839
  ] }, key);
6083
6840
  })
6084
6841
  ] }),
6085
- binding && /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "20px" }, children: [
6086
- /* @__PURE__ */ jsx19("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
6842
+ binding && /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "20px" }, children: [
6843
+ /* @__PURE__ */ jsx21("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
6087
6844
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
6088
6845
  const defaultColors = {
6089
6846
  normal: "#10b981",
@@ -6093,13 +6850,13 @@ function NodeConfigPanel({
6093
6850
  off: "#6b7280"
6094
6851
  };
6095
6852
  const label = status === "normal" ? "Normal" : status === "warning" ? "Warning" : status === "alarm" ? "Alarm" : status === "fault" ? "Fault" : "Off";
6096
- return /* @__PURE__ */ jsxs15("div", { style: { marginBottom: "8px" }, children: [
6097
- /* @__PURE__ */ jsxs15("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
6853
+ return /* @__PURE__ */ jsxs17("div", { style: { marginBottom: "8px" }, children: [
6854
+ /* @__PURE__ */ jsxs17("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
6098
6855
  label,
6099
6856
  ":"
6100
6857
  ] }),
6101
- /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6102
- /* @__PURE__ */ jsx19(
6858
+ /* @__PURE__ */ jsxs17("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6859
+ /* @__PURE__ */ jsx21(
6103
6860
  "input",
6104
6861
  {
6105
6862
  type: "color",
@@ -6120,7 +6877,7 @@ function NodeConfigPanel({
6120
6877
  }
6121
6878
  }
6122
6879
  ),
6123
- /* @__PURE__ */ jsx19(
6880
+ /* @__PURE__ */ jsx21(
6124
6881
  "input",
6125
6882
  {
6126
6883
  type: "text",
@@ -6147,7 +6904,7 @@ function NodeConfigPanel({
6147
6904
  ] }, status);
6148
6905
  })
6149
6906
  ] }),
6150
- /* @__PURE__ */ jsx19("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ jsx19(
6907
+ /* @__PURE__ */ jsx21("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ jsx21(
6151
6908
  "button",
6152
6909
  {
6153
6910
  onClick: onRemove,
@@ -6386,8 +7143,280 @@ var DEFAULT_THRESHOLDS = {
6386
7143
  lowLow: 15
6387
7144
  };
6388
7145
 
7146
+ // src/diagram/schema/DeviceControlBinding.ts
7147
+ function createControlBinding(nodeId, deviceConfig, actions) {
7148
+ const parametersByMode = {};
7149
+ deviceConfig.parameters.forEach((param) => {
7150
+ const modes = param.modes || deviceConfig.modes.map((m) => m.value);
7151
+ modes.forEach((mode) => {
7152
+ if (!parametersByMode[mode]) {
7153
+ parametersByMode[mode] = [];
7154
+ }
7155
+ parametersByMode[mode].push({
7156
+ id: param.id,
7157
+ label: param.label,
7158
+ unit: param.unit,
7159
+ min: param.min,
7160
+ max: param.max,
7161
+ value: param.value,
7162
+ type: typeof param.value === "number" ? "number" : "string"
7163
+ });
7164
+ });
7165
+ });
7166
+ return {
7167
+ nodeId,
7168
+ tag: deviceConfig.tag,
7169
+ deviceName: deviceConfig.name,
7170
+ deviceType: deviceConfig.type,
7171
+ modes: deviceConfig.modes,
7172
+ state: {
7173
+ currentMode: deviceConfig.currentMode,
7174
+ isRunning: deviceConfig.isRunning,
7175
+ status: deviceConfig.status
7176
+ },
7177
+ parameters: parametersByMode,
7178
+ capabilities: deviceConfig.capabilities,
7179
+ actions
7180
+ };
7181
+ }
7182
+
7183
+ // src/hooks/useDeviceControls.ts
7184
+ import { useState as useState15, useCallback as useCallback8 } from "react";
7185
+ function useDeviceControls(initialBindings) {
7186
+ const [controls, setControls] = useState15(() => {
7187
+ const map = /* @__PURE__ */ new Map();
7188
+ if (initialBindings) {
7189
+ initialBindings.forEach((binding) => {
7190
+ map.set(binding.nodeId, binding);
7191
+ });
7192
+ }
7193
+ return map;
7194
+ });
7195
+ const updateControl = useCallback8((nodeId, updates) => {
7196
+ setControls((prev) => {
7197
+ const newMap = new Map(prev);
7198
+ const existing = newMap.get(nodeId);
7199
+ if (existing) {
7200
+ newMap.set(nodeId, { ...existing, ...updates });
7201
+ } else {
7202
+ if ("tag" in updates && "modes" in updates && "state" in updates && "parameters" in updates && "actions" in updates) {
7203
+ newMap.set(nodeId, { nodeId, ...updates });
7204
+ }
7205
+ }
7206
+ return newMap;
7207
+ });
7208
+ }, []);
7209
+ const updateControlBatch = useCallback8((updates) => {
7210
+ setControls((prev) => {
7211
+ const newMap = new Map(prev);
7212
+ Object.entries(updates).forEach(([nodeId, update]) => {
7213
+ const existing = newMap.get(nodeId);
7214
+ if (existing) {
7215
+ newMap.set(nodeId, { ...existing, ...update });
7216
+ }
7217
+ });
7218
+ return newMap;
7219
+ });
7220
+ }, []);
7221
+ const updateDeviceState = useCallback8(
7222
+ (nodeId, stateUpdates) => {
7223
+ setControls((prev) => {
7224
+ const newMap = new Map(prev);
7225
+ const existing = newMap.get(nodeId);
7226
+ if (existing) {
7227
+ newMap.set(nodeId, {
7228
+ ...existing,
7229
+ state: { ...existing.state, ...stateUpdates }
7230
+ });
7231
+ }
7232
+ return newMap;
7233
+ });
7234
+ },
7235
+ []
7236
+ );
7237
+ const updateParameter = useCallback8(
7238
+ (nodeId, parameterId, value) => {
7239
+ setControls((prev) => {
7240
+ const newMap = new Map(prev);
7241
+ const existing = newMap.get(nodeId);
7242
+ if (existing) {
7243
+ const updatedParameters = { ...existing.parameters };
7244
+ Object.keys(updatedParameters).forEach((mode) => {
7245
+ const paramIndex = updatedParameters[mode].findIndex((p) => p.id === parameterId);
7246
+ if (paramIndex !== -1) {
7247
+ updatedParameters[mode] = [...updatedParameters[mode]];
7248
+ updatedParameters[mode][paramIndex] = {
7249
+ ...updatedParameters[mode][paramIndex],
7250
+ value
7251
+ };
7252
+ }
7253
+ });
7254
+ newMap.set(nodeId, {
7255
+ ...existing,
7256
+ parameters: updatedParameters
7257
+ });
7258
+ }
7259
+ return newMap;
7260
+ });
7261
+ },
7262
+ []
7263
+ );
7264
+ const setControlBinding = useCallback8((binding) => {
7265
+ setControls((prev) => {
7266
+ const newMap = new Map(prev);
7267
+ newMap.set(binding.nodeId, binding);
7268
+ return newMap;
7269
+ });
7270
+ }, []);
7271
+ const removeControlBinding = useCallback8((nodeId) => {
7272
+ setControls((prev) => {
7273
+ const newMap = new Map(prev);
7274
+ newMap.delete(nodeId);
7275
+ return newMap;
7276
+ });
7277
+ }, []);
7278
+ const getControlBinding = useCallback8(
7279
+ (nodeId) => {
7280
+ return controls.get(nodeId);
7281
+ },
7282
+ [controls]
7283
+ );
7284
+ const sendModeChange = useCallback8(
7285
+ async (nodeId, mode) => {
7286
+ const binding = controls.get(nodeId);
7287
+ if (!binding) return;
7288
+ updateDeviceState(nodeId, { currentMode: mode });
7289
+ try {
7290
+ await binding.actions.onModeChange?.(mode);
7291
+ updateDeviceState(nodeId, {
7292
+ lastCommandTime: Date.now(),
7293
+ lastCommandResult: { success: true, message: `Mode changed to ${mode}` }
7294
+ });
7295
+ } catch (error) {
7296
+ updateDeviceState(nodeId, {
7297
+ currentMode: binding.state.currentMode,
7298
+ lastCommandResult: {
7299
+ success: false,
7300
+ message: error instanceof Error ? error.message : "Mode change failed"
7301
+ }
7302
+ });
7303
+ }
7304
+ },
7305
+ [controls, updateDeviceState]
7306
+ );
7307
+ const sendParameterChange = useCallback8(
7308
+ async (nodeId, parameterId, value) => {
7309
+ const binding = controls.get(nodeId);
7310
+ if (!binding) return;
7311
+ updateParameter(nodeId, parameterId, value);
7312
+ try {
7313
+ await binding.actions.onParameterChange?.(parameterId, value);
7314
+ updateDeviceState(nodeId, {
7315
+ lastCommandTime: Date.now(),
7316
+ lastCommandResult: { success: true, message: `Parameter ${parameterId} updated` }
7317
+ });
7318
+ } catch (error) {
7319
+ updateDeviceState(nodeId, {
7320
+ lastCommandResult: {
7321
+ success: false,
7322
+ message: error instanceof Error ? error.message : "Parameter change failed"
7323
+ }
7324
+ });
7325
+ }
7326
+ },
7327
+ [controls, updateParameter, updateDeviceState]
7328
+ );
7329
+ const sendStartCommand = useCallback8(
7330
+ async (nodeId) => {
7331
+ const binding = controls.get(nodeId);
7332
+ if (!binding) return;
7333
+ updateDeviceState(nodeId, { isRunning: true, status: "starting" });
7334
+ try {
7335
+ await binding.actions.onStart?.();
7336
+ updateDeviceState(nodeId, {
7337
+ status: "normal",
7338
+ lastCommandTime: Date.now(),
7339
+ lastCommandResult: { success: true, message: "Device started" }
7340
+ });
7341
+ } catch (error) {
7342
+ updateDeviceState(nodeId, {
7343
+ isRunning: false,
7344
+ status: "fault",
7345
+ lastCommandResult: {
7346
+ success: false,
7347
+ message: error instanceof Error ? error.message : "Start failed"
7348
+ }
7349
+ });
7350
+ }
7351
+ },
7352
+ [controls, updateDeviceState]
7353
+ );
7354
+ const sendStopCommand = useCallback8(
7355
+ async (nodeId) => {
7356
+ const binding = controls.get(nodeId);
7357
+ if (!binding) return;
7358
+ updateDeviceState(nodeId, { isRunning: false, status: "stopping" });
7359
+ try {
7360
+ await binding.actions.onStop?.();
7361
+ updateDeviceState(nodeId, {
7362
+ status: "off",
7363
+ lastCommandTime: Date.now(),
7364
+ lastCommandResult: { success: true, message: "Device stopped" }
7365
+ });
7366
+ } catch (error) {
7367
+ updateDeviceState(nodeId, {
7368
+ isRunning: true,
7369
+ status: "fault",
7370
+ lastCommandResult: {
7371
+ success: false,
7372
+ message: error instanceof Error ? error.message : "Stop failed"
7373
+ }
7374
+ });
7375
+ }
7376
+ },
7377
+ [controls, updateDeviceState]
7378
+ );
7379
+ const sendCustomAction = useCallback8(
7380
+ async (nodeId, actionId) => {
7381
+ const binding = controls.get(nodeId);
7382
+ if (!binding) return;
7383
+ try {
7384
+ await binding.actions.onCustomAction?.(actionId);
7385
+ updateDeviceState(nodeId, {
7386
+ lastCommandTime: Date.now(),
7387
+ lastCommandResult: { success: true, message: `Action ${actionId} executed` }
7388
+ });
7389
+ } catch (error) {
7390
+ updateDeviceState(nodeId, {
7391
+ lastCommandResult: {
7392
+ success: false,
7393
+ message: error instanceof Error ? error.message : "Action failed"
7394
+ }
7395
+ });
7396
+ }
7397
+ },
7398
+ [controls, updateDeviceState]
7399
+ );
7400
+ return {
7401
+ controls,
7402
+ updateControl,
7403
+ updateControlBatch,
7404
+ updateDeviceState,
7405
+ updateParameter,
7406
+ setControlBinding,
7407
+ removeControlBinding,
7408
+ getControlBinding,
7409
+ // Command helpers
7410
+ sendModeChange,
7411
+ sendParameterChange,
7412
+ sendStartCommand,
7413
+ sendStopCommand,
7414
+ sendCustomAction
7415
+ };
7416
+ }
7417
+
6389
7418
  // src/diagram/hooks/useSimulation.ts
6390
- import { useEffect as useEffect11 } from "react";
7419
+ import { useEffect as useEffect12 } from "react";
6391
7420
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
6392
7421
  const {
6393
7422
  interval = 2e3,
@@ -6398,7 +7427,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
6398
7427
  historyLength = 10,
6399
7428
  generateValue
6400
7429
  } = options;
6401
- useEffect11(() => {
7430
+ useEffect12(() => {
6402
7431
  if (!enabled) return;
6403
7432
  const intervalId = setInterval(() => {
6404
7433
  const updates = [];
@@ -6455,6 +7484,7 @@ export {
6455
7484
  ControlPanel,
6456
7485
  DEFAULT_THRESHOLDS,
6457
7486
  DashboardCard,
7487
+ DeviceControlPanel,
6458
7488
  DisplayModeToggle,
6459
7489
  EquipmentIndicator,
6460
7490
  EquipmentIndicatorWithSparkline,
@@ -6466,6 +7496,7 @@ export {
6466
7496
  LeftToggleButton,
6467
7497
  LegacyValueEntry,
6468
7498
  NodeConfigPanel,
7499
+ NodeControlsPanel,
6469
7500
  PIDCanvas,
6470
7501
  PanelContent,
6471
7502
  PanelTabs,
@@ -6485,6 +7516,7 @@ export {
6485
7516
  ZoomButton,
6486
7517
  ZoomControls,
6487
7518
  calculateThresholdStatus,
7519
+ createControlBinding,
6488
7520
  exampleDiagram,
6489
7521
  exportDiagram,
6490
7522
  generateRoutePoints,
@@ -6495,6 +7527,9 @@ export {
6495
7527
  mockSymbolLibrary,
6496
7528
  nodeHasPort,
6497
7529
  overlayStyles,
7530
+ registerSymbol,
7531
+ registerSymbols,
7532
+ useDeviceControls,
6498
7533
  useDrag,
6499
7534
  useKeyboardShortcuts,
6500
7535
  useSelection,