@procaaso/alphinity-ui-components 1.0.10 → 1.0.12

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
@@ -906,13 +906,18 @@ function DeviceControlPanel({
906
906
  fontScale = 1,
907
907
  maxHeight,
908
908
  maxWidth,
909
+ toastDuration = 3e3,
909
910
  onModeChange,
910
911
  onParameterChange,
911
912
  onStart,
912
913
  onStop,
913
- onCustomAction
914
+ onCustomAction,
915
+ onOpenConfig,
916
+ showConfigButton = true,
917
+ modeSelectionStyle = "buttons"
914
918
  }) {
915
919
  const [localParameterValues, setLocalParameterValues] = useState5({});
920
+ const [showToast, setShowToast] = useState5(true);
916
921
  useEffect2(() => {
917
922
  if (binding) {
918
923
  const currentParams2 = binding.parameters[binding.state.currentMode] || [];
@@ -923,6 +928,15 @@ function DeviceControlPanel({
923
928
  setLocalParameterValues(values);
924
929
  }
925
930
  }, [binding, binding?.state.currentMode]);
931
+ useEffect2(() => {
932
+ if (binding?.state.lastCommandResult) {
933
+ setShowToast(true);
934
+ const timer = setTimeout(() => {
935
+ setShowToast(false);
936
+ }, toastDuration);
937
+ return () => clearTimeout(timer);
938
+ }
939
+ }, [binding?.state.lastCommandResult, toastDuration]);
926
940
  if (!binding) return null;
927
941
  const currentMode = binding.state.currentMode;
928
942
  const currentParams = binding.parameters[currentMode] || [];
@@ -979,14 +993,13 @@ function DeviceControlPanel({
979
993
  position: "fixed",
980
994
  ...position === "bottom" ? {
981
995
  bottom: 0,
982
- ...align === "left" ? { left: 0, right: "auto" } : align === "right" ? { right: 0, left: "auto" } : { left: 0, right: 0, marginLeft: "auto", marginRight: "auto" },
983
- animation: "slideUp 0.3s ease-out",
984
- maxWidth
996
+ ...align === "left" ? { left: 0, right: "auto", width: maxWidth } : align === "right" ? { right: 0, left: "auto", width: maxWidth } : { left: 0, right: 0, marginLeft: "auto", marginRight: "auto", maxWidth },
997
+ animation: "slideUp 0.3s ease-out"
985
998
  } : {
986
999
  right: 0,
987
1000
  top: 0,
988
1001
  bottom: 0,
989
- width: "400px",
1002
+ width: maxWidth || "400px",
990
1003
  animation: "slideIn 0.3s ease-out"
991
1004
  },
992
1005
  backgroundColor,
@@ -1001,442 +1014,534 @@ function DeviceControlPanel({
1001
1014
  ...binding.display?.style
1002
1015
  };
1003
1016
  const titleText = binding.display?.title || binding.deviceName || binding.tag || binding.nodeId;
1004
- return /* @__PURE__ */ jsx8(Fragment2, { children: /* @__PURE__ */ jsxs7("div", { style: panelStyle, className: binding.display?.className, children: [
1005
- /* @__PURE__ */ jsxs7(
1006
- "div",
1007
- {
1008
- style: {
1009
- display: "flex",
1010
- justifyContent: "space-between",
1011
- alignItems: "center",
1012
- marginBottom: "16px",
1013
- paddingBottom: "12px",
1014
- borderBottom: `2px solid ${borderColor}`
1015
- },
1016
- children: [
1017
- /* @__PURE__ */ jsxs7("div", { children: [
1017
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
1018
+ /* @__PURE__ */ jsxs7("div", { style: panelStyle, className: binding.display?.className, children: [
1019
+ /* @__PURE__ */ jsxs7(
1020
+ "div",
1021
+ {
1022
+ style: {
1023
+ display: "flex",
1024
+ justifyContent: "space-between",
1025
+ alignItems: "center",
1026
+ marginBottom: "16px",
1027
+ paddingBottom: "12px",
1028
+ borderBottom: `2px solid ${borderColor}`
1029
+ },
1030
+ children: [
1031
+ /* @__PURE__ */ jsxs7("div", { children: [
1032
+ /* @__PURE__ */ jsx8(
1033
+ "h3",
1034
+ {
1035
+ style: {
1036
+ margin: 0,
1037
+ fontSize: `${fontSize.title}px`,
1038
+ fontWeight: 600,
1039
+ fontFamily: "Arial, sans-serif",
1040
+ letterSpacing: "0.5px",
1041
+ textTransform: "uppercase",
1042
+ color: textColor
1043
+ },
1044
+ children: titleText
1045
+ }
1046
+ ),
1047
+ binding.deviceType && /* @__PURE__ */ jsx8(
1048
+ "div",
1049
+ {
1050
+ style: {
1051
+ fontSize: "11px",
1052
+ color: mutedTextColor,
1053
+ marginTop: "4px",
1054
+ fontFamily: "Arial, sans-serif"
1055
+ },
1056
+ children: binding.deviceType
1057
+ }
1058
+ )
1059
+ ] }),
1060
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "8px" }, children: [
1061
+ showConfigButton && onOpenConfig && /* @__PURE__ */ jsx8(
1062
+ "button",
1063
+ {
1064
+ onClick: onOpenConfig,
1065
+ title: "Open Configuration",
1066
+ style: {
1067
+ width: touchSize * 0.7,
1068
+ height: touchSize * 0.7,
1069
+ backgroundColor: surfaceColor,
1070
+ color: textColor,
1071
+ border: `2px solid ${borderColor}`,
1072
+ borderRadius: "2px",
1073
+ fontSize: "16px",
1074
+ cursor: "pointer",
1075
+ fontWeight: 600,
1076
+ fontFamily: "Arial, sans-serif",
1077
+ padding: "0"
1078
+ },
1079
+ children: "\u2699"
1080
+ }
1081
+ ),
1082
+ /* @__PURE__ */ jsx8(
1083
+ "button",
1084
+ {
1085
+ onClick: onClose,
1086
+ style: {
1087
+ width: touchSize * 0.7,
1088
+ height: touchSize * 0.7,
1089
+ backgroundColor: surfaceColor,
1090
+ color: textColor,
1091
+ border: `2px solid ${borderColor}`,
1092
+ borderRadius: "2px",
1093
+ fontSize: "18px",
1094
+ cursor: "pointer",
1095
+ fontWeight: "bold",
1096
+ fontFamily: "Arial, sans-serif"
1097
+ },
1098
+ children: "\xD7"
1099
+ }
1100
+ )
1101
+ ] })
1102
+ ]
1103
+ }
1104
+ ),
1105
+ binding.state.status && /* @__PURE__ */ jsxs7(
1106
+ "div",
1107
+ {
1108
+ style: {
1109
+ display: "flex",
1110
+ alignItems: "center",
1111
+ gap: "12px",
1112
+ padding: spacing.itemPadding,
1113
+ backgroundColor: surfaceColor,
1114
+ border: `1px solid ${borderColor}`,
1115
+ borderRadius: "6px",
1116
+ marginBottom: spacing.marginBottom
1117
+ },
1118
+ children: [
1018
1119
  /* @__PURE__ */ jsx8(
1019
- "h3",
1020
- {
1021
- style: {
1022
- margin: 0,
1023
- fontSize: `${fontSize.title}px`,
1024
- fontWeight: 600,
1025
- fontFamily: "Arial, sans-serif",
1026
- letterSpacing: "0.5px",
1027
- textTransform: "uppercase",
1028
- color: textColor
1029
- },
1030
- children: titleText
1031
- }
1032
- ),
1033
- binding.deviceType && /* @__PURE__ */ jsx8(
1034
1120
  "div",
1035
1121
  {
1036
1122
  style: {
1037
- fontSize: "11px",
1038
- color: mutedTextColor,
1039
- marginTop: "4px",
1040
- fontFamily: "Arial, sans-serif"
1041
- },
1042
- children: binding.deviceType
1043
- }
1044
- )
1045
- ] }),
1046
- /* @__PURE__ */ jsx8(
1047
- "button",
1048
- {
1049
- onClick: onClose,
1050
- style: {
1051
- width: touchSize * 0.7,
1052
- height: touchSize * 0.7,
1053
- backgroundColor: surfaceColor,
1054
- color: textColor,
1055
- border: `2px solid ${borderColor}`,
1056
- borderRadius: "2px",
1057
- fontSize: "18px",
1058
- cursor: "pointer",
1059
- fontWeight: "bold",
1060
- fontFamily: "Arial, sans-serif"
1061
- },
1062
- children: "\xD7"
1063
- }
1064
- )
1065
- ]
1066
- }
1067
- ),
1068
- binding.state.status && /* @__PURE__ */ jsxs7(
1069
- "div",
1070
- {
1071
- style: {
1072
- display: "flex",
1073
- alignItems: "center",
1074
- gap: "12px",
1075
- padding: spacing.itemPadding,
1076
- backgroundColor: surfaceColor,
1077
- border: `1px solid ${borderColor}`,
1078
- borderRadius: "6px",
1079
- marginBottom: spacing.marginBottom
1080
- },
1081
- children: [
1082
- /* @__PURE__ */ jsx8(
1083
- "div",
1084
- {
1085
- style: {
1086
- width: "10px",
1087
- height: "10px",
1088
- borderRadius: "50%",
1089
- backgroundColor: statusColor,
1090
- border: "1px solid #333"
1123
+ width: "10px",
1124
+ height: "10px",
1125
+ borderRadius: "50%",
1126
+ backgroundColor: statusColor,
1127
+ border: "1px solid #333"
1128
+ }
1091
1129
  }
1092
- }
1093
- ),
1094
- /* @__PURE__ */ jsxs7("div", { style: { flex: 1 }, children: [
1095
- /* @__PURE__ */ jsx8(
1130
+ ),
1131
+ /* @__PURE__ */ jsxs7("div", { style: { flex: 1 }, children: [
1132
+ /* @__PURE__ */ jsx8(
1133
+ "div",
1134
+ {
1135
+ style: {
1136
+ fontWeight: 600,
1137
+ textTransform: "uppercase",
1138
+ fontSize: `${fontSize.label}px`,
1139
+ fontFamily: "Arial, sans-serif",
1140
+ letterSpacing: "0.5px",
1141
+ color: textColor
1142
+ },
1143
+ children: binding.state.status
1144
+ }
1145
+ ),
1146
+ binding.state.statusMessage && /* @__PURE__ */ jsx8(
1147
+ "div",
1148
+ {
1149
+ style: {
1150
+ fontSize: "10px",
1151
+ color: mutedTextColor,
1152
+ marginTop: "2px",
1153
+ fontFamily: "Arial, sans-serif"
1154
+ },
1155
+ children: binding.state.statusMessage
1156
+ }
1157
+ )
1158
+ ] }),
1159
+ binding.state.isRunning !== void 0 && /* @__PURE__ */ jsx8(
1096
1160
  "div",
1097
1161
  {
1098
1162
  style: {
1163
+ padding: "4px 10px",
1164
+ backgroundColor: binding.state.isRunning ? selectedColor : surfaceColor,
1165
+ border: `1px solid ${borderColor}`,
1166
+ borderRadius: "2px",
1167
+ fontSize: "10px",
1099
1168
  fontWeight: 600,
1100
- textTransform: "uppercase",
1101
- fontSize: `${fontSize.label}px`,
1102
1169
  fontFamily: "Arial, sans-serif",
1103
1170
  letterSpacing: "0.5px",
1104
1171
  color: textColor
1105
1172
  },
1106
- children: binding.state.status
1107
- }
1108
- ),
1109
- binding.state.statusMessage && /* @__PURE__ */ jsx8(
1110
- "div",
1111
- {
1112
- style: {
1113
- fontSize: "10px",
1114
- color: mutedTextColor,
1115
- marginTop: "2px",
1116
- fontFamily: "Arial, sans-serif"
1117
- },
1118
- children: binding.state.statusMessage
1173
+ children: binding.state.isRunning ? "RUNNING" : "STOPPED"
1119
1174
  }
1120
1175
  )
1121
- ] }),
1122
- binding.state.isRunning !== void 0 && /* @__PURE__ */ jsx8(
1123
- "div",
1124
- {
1125
- style: {
1126
- padding: "4px 10px",
1127
- backgroundColor: binding.state.isRunning ? selectedColor : surfaceColor,
1128
- border: `1px solid ${borderColor}`,
1129
- borderRadius: "2px",
1130
- fontSize: "10px",
1131
- fontWeight: 600,
1132
- fontFamily: "Arial, sans-serif",
1133
- letterSpacing: "0.5px",
1134
- color: textColor
1135
- },
1136
- children: binding.state.isRunning ? "RUNNING" : "STOPPED"
1137
- }
1138
- )
1139
- ]
1140
- }
1141
- ),
1142
- binding.display?.showModeSelector !== false && binding.modes.length > 0 && /* @__PURE__ */ jsxs7("div", { style: { marginBottom: "16px" }, children: [
1143
- /* @__PURE__ */ jsx8(
1144
- "label",
1145
- {
1146
- style: {
1147
- display: "block",
1148
- marginBottom: "8px",
1149
- fontSize: "11px",
1150
- fontWeight: 600,
1151
- textTransform: "uppercase",
1152
- letterSpacing: "0.5px",
1153
- fontFamily: "Arial, sans-serif",
1154
- color: mutedTextColor
1155
- },
1156
- children: "Mode"
1176
+ ]
1157
1177
  }
1158
1178
  ),
1159
- /* @__PURE__ */ jsx8("div", { style: { display: "flex", gap: spacing.gap, flexWrap: "wrap" }, children: binding.modes.map((mode) => {
1160
- const isSelected = mode.value === currentMode;
1161
- const isEnabled = mode.enabled !== false;
1162
- return /* @__PURE__ */ jsx8(
1163
- "button",
1179
+ binding.display?.showModeSelector !== false && binding.modes.length > 0 && /* @__PURE__ */ jsxs7("div", { style: { marginBottom: "16px" }, children: [
1180
+ /* @__PURE__ */ jsx8(
1181
+ "label",
1164
1182
  {
1165
- onClick: () => isEnabled && handleModeChange(mode.value),
1166
- disabled: !isEnabled,
1167
1183
  style: {
1168
- flex: 1,
1169
- minWidth: "80px",
1184
+ display: "block",
1185
+ marginBottom: "8px",
1186
+ fontSize: "11px",
1187
+ fontWeight: 600,
1188
+ textTransform: "uppercase",
1189
+ letterSpacing: "0.5px",
1190
+ fontFamily: "Arial, sans-serif",
1191
+ color: mutedTextColor
1192
+ },
1193
+ children: "Mode"
1194
+ }
1195
+ ),
1196
+ modeSelectionStyle === "dropdown" ? /* @__PURE__ */ jsx8(
1197
+ "select",
1198
+ {
1199
+ value: currentMode,
1200
+ onChange: (e) => handleModeChange(e.target.value),
1201
+ style: {
1202
+ width: "100%",
1170
1203
  height: touchSize,
1171
- backgroundColor: isSelected ? selectedColor : surfaceColor,
1172
- color: isSelected ? "#000000" : textColor,
1173
- border: `1px solid ${isSelected ? "#b4d0fe" : borderColor}`,
1204
+ backgroundColor: surfaceColor,
1205
+ color: textColor,
1206
+ border: `1px solid ${borderColor}`,
1174
1207
  borderRadius: "6px",
1175
1208
  fontSize: `${fontSize.label}px`,
1176
1209
  fontWeight: 600,
1177
1210
  fontFamily: "Arial, sans-serif",
1178
1211
  letterSpacing: "0.5px",
1179
1212
  textTransform: "uppercase",
1180
- cursor: isEnabled ? "pointer" : "not-allowed",
1181
- opacity: isEnabled ? 1 : 0.4
1213
+ paddingLeft: "12px",
1214
+ paddingRight: "12px",
1215
+ cursor: "pointer"
1216
+ },
1217
+ children: binding.modes.map((mode) => /* @__PURE__ */ jsx8("option", { value: mode.value, disabled: mode.enabled === false, children: mode.label }, mode.value))
1218
+ }
1219
+ ) : /* @__PURE__ */ jsx8(
1220
+ "div",
1221
+ {
1222
+ style: {
1223
+ display: "flex",
1224
+ gap: spacing.gap,
1225
+ overflowX: "auto",
1226
+ overflowY: "hidden",
1227
+ scrollbarWidth: "thin",
1228
+ scrollbarColor: `${borderColor} transparent`,
1229
+ paddingBottom: "4px",
1230
+ marginBottom: "-4px"
1231
+ },
1232
+ children: binding.modes.map((mode) => {
1233
+ const isSelected = mode.value === currentMode;
1234
+ const isEnabled = mode.enabled !== false;
1235
+ return /* @__PURE__ */ jsx8(
1236
+ "button",
1237
+ {
1238
+ onClick: () => isEnabled && handleModeChange(mode.value),
1239
+ disabled: !isEnabled,
1240
+ style: {
1241
+ flex: "0 0 auto",
1242
+ minWidth: "90px",
1243
+ height: touchSize,
1244
+ backgroundColor: isSelected ? selectedColor : surfaceColor,
1245
+ color: isSelected ? "#000000" : textColor,
1246
+ border: `1px solid ${isSelected ? "#b4d0fe" : borderColor}`,
1247
+ borderRadius: "6px",
1248
+ fontSize: `${fontSize.label}px`,
1249
+ fontWeight: 600,
1250
+ fontFamily: "Arial, sans-serif",
1251
+ letterSpacing: "0.5px",
1252
+ textTransform: "uppercase",
1253
+ cursor: isEnabled ? "pointer" : "not-allowed",
1254
+ opacity: isEnabled ? 1 : 0.4,
1255
+ whiteSpace: "nowrap"
1256
+ },
1257
+ children: mode.label
1258
+ },
1259
+ mode.value
1260
+ );
1261
+ })
1262
+ }
1263
+ )
1264
+ ] }),
1265
+ currentParams.length > 0 && /* @__PURE__ */ jsxs7("div", { style: { marginBottom: spacing.marginBottom }, children: [
1266
+ /* @__PURE__ */ jsx8(
1267
+ "label",
1268
+ {
1269
+ style: {
1270
+ display: "block",
1271
+ marginBottom: spacing.gap,
1272
+ fontSize: `${fontSize.label}px`,
1273
+ fontWeight: 600,
1274
+ textTransform: "uppercase",
1275
+ letterSpacing: "0.5px",
1276
+ fontFamily: "Arial, sans-serif",
1277
+ color: mutedTextColor
1278
+ },
1279
+ children: "Parameters"
1280
+ }
1281
+ ),
1282
+ currentParams.map((param) => /* @__PURE__ */ jsxs7(
1283
+ "div",
1284
+ {
1285
+ style: {
1286
+ marginBottom: spacing.gap,
1287
+ padding: spacing.itemPadding,
1288
+ backgroundColor: surfaceColor,
1289
+ border: `1px solid ${borderColor}`,
1290
+ borderRadius: "6px"
1182
1291
  },
1183
- children: mode.label
1292
+ children: [
1293
+ /* @__PURE__ */ jsxs7(
1294
+ "div",
1295
+ {
1296
+ style: {
1297
+ display: "flex",
1298
+ justifyContent: "space-between",
1299
+ marginBottom: spacing.gap
1300
+ },
1301
+ children: [
1302
+ /* @__PURE__ */ jsx8(
1303
+ "span",
1304
+ {
1305
+ style: {
1306
+ fontSize: `${fontSize.label}px`,
1307
+ fontWeight: 600,
1308
+ textTransform: "uppercase",
1309
+ letterSpacing: "0.5px",
1310
+ fontFamily: "Arial, sans-serif",
1311
+ color: mutedTextColor
1312
+ },
1313
+ children: param.label
1314
+ }
1315
+ ),
1316
+ /* @__PURE__ */ jsxs7(
1317
+ "span",
1318
+ {
1319
+ style: {
1320
+ fontSize: `${fontSize.value}px`,
1321
+ fontWeight: 700,
1322
+ fontFamily: "Arial, sans-serif",
1323
+ color: textColor
1324
+ },
1325
+ children: [
1326
+ localParameterValues[param.id] ?? param.value,
1327
+ " ",
1328
+ param.unit && /* @__PURE__ */ jsx8("span", { style: { color: mutedTextColor, fontSize: "10px", fontWeight: 600 }, children: param.unit })
1329
+ ]
1330
+ }
1331
+ )
1332
+ ]
1333
+ }
1334
+ ),
1335
+ param.type === "boolean" ? /* @__PURE__ */ jsx8(
1336
+ "button",
1337
+ {
1338
+ onClick: () => {
1339
+ const newValue = !localParameterValues[param.id];
1340
+ handleParameterInput(param.id, newValue);
1341
+ handleParameterCommit(param.id, newValue);
1342
+ },
1343
+ disabled: param.readOnly,
1344
+ style: {
1345
+ width: "100%",
1346
+ height: touchSize,
1347
+ backgroundColor: localParameterValues[param.id] ? "#3f3f46" : surfaceColor,
1348
+ color: localParameterValues[param.id] ? textColor : mutedTextColor,
1349
+ border: `1px solid ${localParameterValues[param.id] ? "#52525b" : borderColor}`,
1350
+ borderRadius: "4px",
1351
+ fontSize: "12px",
1352
+ fontWeight: 600,
1353
+ fontFamily: "monospace",
1354
+ letterSpacing: "0.5px",
1355
+ cursor: param.readOnly ? "not-allowed" : "pointer",
1356
+ transition: "all 0.15s"
1357
+ },
1358
+ children: localParameterValues[param.id] ? "ON" : "OFF"
1359
+ }
1360
+ ) : param.type === "select" ? /* @__PURE__ */ jsx8(
1361
+ "select",
1362
+ {
1363
+ value: localParameterValues[param.id] || param.value,
1364
+ onChange: (e) => {
1365
+ handleParameterInput(param.id, e.target.value);
1366
+ handleParameterCommit(param.id, e.target.value);
1367
+ },
1368
+ disabled: param.readOnly,
1369
+ style: {
1370
+ width: "100%",
1371
+ height: touchSize,
1372
+ backgroundColor: surfaceColor,
1373
+ color: textColor,
1374
+ border: `1px solid ${borderColor}`,
1375
+ borderRadius: "4px",
1376
+ padding: "0 12px",
1377
+ fontSize: "12px",
1378
+ fontFamily: "monospace",
1379
+ cursor: "pointer"
1380
+ },
1381
+ children: param.options?.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
1382
+ }
1383
+ ) : /* @__PURE__ */ jsx8(
1384
+ "input",
1385
+ {
1386
+ type: param.type === "number" ? "number" : "text",
1387
+ value: localParameterValues[param.id] ?? param.value,
1388
+ onChange: (e) => handleParameterInput(
1389
+ param.id,
1390
+ param.type === "number" ? +e.target.value : e.target.value
1391
+ ),
1392
+ onBlur: () => handleParameterCommit(param.id, localParameterValues[param.id]),
1393
+ disabled: param.readOnly,
1394
+ min: param.min,
1395
+ max: param.max,
1396
+ step: param.step,
1397
+ style: {
1398
+ width: "100%",
1399
+ height: touchSize,
1400
+ backgroundColor: "#ffffff",
1401
+ color: textColor,
1402
+ border: `1px solid ${borderColor}`,
1403
+ borderRadius: "6px",
1404
+ padding: "0 12px",
1405
+ fontSize: `${fontSize.input}px`,
1406
+ fontFamily: "Arial, sans-serif",
1407
+ fontWeight: 600
1408
+ }
1409
+ }
1410
+ )
1411
+ ]
1184
1412
  },
1185
- mode.value
1186
- );
1187
- }) })
1188
- ] }),
1189
- currentParams.length > 0 && /* @__PURE__ */ jsxs7("div", { style: { marginBottom: spacing.marginBottom }, children: [
1190
- /* @__PURE__ */ jsx8(
1191
- "label",
1413
+ param.id
1414
+ ))
1415
+ ] }),
1416
+ binding.display?.showStartStop !== false && (activeCapabilities?.canStart || activeCapabilities?.canStop) && /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom }, children: [
1417
+ activeCapabilities?.canStart && /* @__PURE__ */ jsx8(
1418
+ "button",
1419
+ {
1420
+ onClick: onStart,
1421
+ disabled: binding.state.isRunning,
1422
+ style: {
1423
+ flex: 1,
1424
+ height: touchSize,
1425
+ backgroundColor: binding.state.isRunning ? "#a3a3a3" : "#10b981",
1426
+ color: "#ffffff",
1427
+ border: "none",
1428
+ borderRadius: "6px",
1429
+ fontSize: "12px",
1430
+ fontWeight: 700,
1431
+ fontFamily: "Arial, sans-serif",
1432
+ letterSpacing: "0.5px",
1433
+ cursor: binding.state.isRunning ? "not-allowed" : "pointer",
1434
+ opacity: binding.state.isRunning ? 0.5 : 1
1435
+ },
1436
+ children: "START"
1437
+ }
1438
+ ),
1439
+ activeCapabilities?.canStop && /* @__PURE__ */ jsx8(
1440
+ "button",
1441
+ {
1442
+ onClick: onStop,
1443
+ disabled: !binding.state.isRunning,
1444
+ style: {
1445
+ flex: 1,
1446
+ height: touchSize,
1447
+ backgroundColor: !binding.state.isRunning ? "#a3a3a3" : "#ef4444",
1448
+ color: "#ffffff",
1449
+ border: "none",
1450
+ borderRadius: "6px",
1451
+ fontSize: "12px",
1452
+ fontWeight: 700,
1453
+ fontFamily: "Arial, sans-serif",
1454
+ letterSpacing: "0.5px",
1455
+ cursor: !binding.state.isRunning ? "not-allowed" : "pointer",
1456
+ opacity: !binding.state.isRunning ? 0.5 : 1
1457
+ },
1458
+ children: "STOP"
1459
+ }
1460
+ )
1461
+ ] }),
1462
+ activeCapabilities?.customActions && activeCapabilities.customActions.length > 0 && /* @__PURE__ */ jsx8("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: activeCapabilities.customActions.map((action) => /* @__PURE__ */ jsxs7(
1463
+ "button",
1192
1464
  {
1465
+ onClick: () => onCustomAction?.(action.id),
1193
1466
  style: {
1194
- display: "block",
1195
- marginBottom: spacing.gap,
1196
- fontSize: `${fontSize.label}px`,
1467
+ flex: 1,
1468
+ minWidth: "120px",
1469
+ height: touchSize,
1470
+ backgroundColor: surfaceColor,
1471
+ color: textColor,
1472
+ border: `1px solid ${borderColor}`,
1473
+ borderRadius: "6px",
1474
+ fontSize: "11px",
1197
1475
  fontWeight: 600,
1198
- textTransform: "uppercase",
1476
+ fontFamily: "monospace",
1199
1477
  letterSpacing: "0.5px",
1200
- fontFamily: "Arial, sans-serif",
1201
- color: mutedTextColor
1202
- },
1203
- children: "Parameters"
1204
- }
1205
- ),
1206
- currentParams.map((param) => /* @__PURE__ */ jsxs7(
1207
- "div",
1208
- {
1209
- style: {
1210
- marginBottom: spacing.gap,
1211
- padding: spacing.itemPadding,
1212
- backgroundColor: surfaceColor,
1213
- border: `1px solid ${borderColor}`,
1214
- borderRadius: "6px"
1478
+ textTransform: "uppercase",
1479
+ cursor: "pointer",
1480
+ transition: "all 0.15s"
1215
1481
  },
1216
1482
  children: [
1217
- /* @__PURE__ */ jsxs7(
1218
- "div",
1219
- {
1220
- style: {
1221
- display: "flex",
1222
- justifyContent: "space-between",
1223
- marginBottom: spacing.gap
1224
- },
1225
- children: [
1226
- /* @__PURE__ */ jsx8(
1227
- "span",
1228
- {
1229
- style: {
1230
- fontSize: `${fontSize.label}px`,
1231
- fontWeight: 600,
1232
- textTransform: "uppercase",
1233
- letterSpacing: "0.5px",
1234
- fontFamily: "Arial, sans-serif",
1235
- color: mutedTextColor
1236
- },
1237
- children: param.label
1238
- }
1239
- ),
1240
- /* @__PURE__ */ jsxs7(
1241
- "span",
1242
- {
1243
- style: {
1244
- fontSize: `${fontSize.value}px`,
1245
- fontWeight: 700,
1246
- fontFamily: "Arial, sans-serif",
1247
- color: textColor
1248
- },
1249
- children: [
1250
- localParameterValues[param.id] ?? param.value,
1251
- " ",
1252
- param.unit && /* @__PURE__ */ jsx8("span", { style: { color: mutedTextColor, fontSize: "10px", fontWeight: 600 }, children: param.unit })
1253
- ]
1254
- }
1255
- )
1256
- ]
1257
- }
1258
- ),
1259
- param.type === "boolean" ? /* @__PURE__ */ jsx8(
1260
- "button",
1261
- {
1262
- onClick: () => {
1263
- const newValue = !localParameterValues[param.id];
1264
- handleParameterInput(param.id, newValue);
1265
- handleParameterCommit(param.id, newValue);
1266
- },
1267
- disabled: param.readOnly,
1268
- style: {
1269
- width: "100%",
1270
- height: touchSize,
1271
- backgroundColor: localParameterValues[param.id] ? "#3f3f46" : surfaceColor,
1272
- color: localParameterValues[param.id] ? textColor : mutedTextColor,
1273
- border: `1px solid ${localParameterValues[param.id] ? "#52525b" : borderColor}`,
1274
- borderRadius: "4px",
1275
- fontSize: "12px",
1276
- fontWeight: 600,
1277
- fontFamily: "monospace",
1278
- letterSpacing: "0.5px",
1279
- cursor: param.readOnly ? "not-allowed" : "pointer",
1280
- transition: "all 0.15s"
1281
- },
1282
- children: localParameterValues[param.id] ? "ON" : "OFF"
1283
- }
1284
- ) : param.type === "select" ? /* @__PURE__ */ jsx8(
1285
- "select",
1286
- {
1287
- value: localParameterValues[param.id] || param.value,
1288
- onChange: (e) => {
1289
- handleParameterInput(param.id, e.target.value);
1290
- handleParameterCommit(param.id, e.target.value);
1291
- },
1292
- disabled: param.readOnly,
1293
- style: {
1294
- width: "100%",
1295
- height: touchSize,
1296
- backgroundColor: surfaceColor,
1297
- color: textColor,
1298
- border: `1px solid ${borderColor}`,
1299
- borderRadius: "4px",
1300
- padding: "0 12px",
1301
- fontSize: "12px",
1302
- fontFamily: "monospace",
1303
- cursor: "pointer"
1304
- },
1305
- children: param.options?.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
1306
- }
1307
- ) : /* @__PURE__ */ jsx8(
1308
- "input",
1309
- {
1310
- type: param.type === "number" ? "number" : "text",
1311
- value: localParameterValues[param.id] ?? param.value,
1312
- onChange: (e) => handleParameterInput(
1313
- param.id,
1314
- param.type === "number" ? +e.target.value : e.target.value
1315
- ),
1316
- onBlur: () => handleParameterCommit(param.id, localParameterValues[param.id]),
1317
- disabled: param.readOnly,
1318
- min: param.min,
1319
- max: param.max,
1320
- step: param.step,
1321
- style: {
1322
- width: "100%",
1323
- height: touchSize,
1324
- backgroundColor: "#ffffff",
1325
- color: textColor,
1326
- border: `1px solid ${borderColor}`,
1327
- borderRadius: "6px",
1328
- padding: "0 12px",
1329
- fontSize: `${fontSize.input}px`,
1330
- fontFamily: "Arial, sans-serif",
1331
- fontWeight: 600
1332
- }
1333
- }
1334
- )
1483
+ action.icon && /* @__PURE__ */ jsx8("span", { style: { marginRight: "6px" }, children: action.icon }),
1484
+ action.label
1335
1485
  ]
1336
1486
  },
1337
- param.id
1338
- ))
1339
- ] }),
1340
- binding.display?.showStartStop !== false && (activeCapabilities?.canStart || activeCapabilities?.canStop) && /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom }, children: [
1341
- activeCapabilities?.canStart && /* @__PURE__ */ jsx8(
1342
- "button",
1343
- {
1344
- onClick: onStart,
1345
- disabled: binding.state.isRunning,
1346
- style: {
1347
- flex: 1,
1348
- height: touchSize,
1349
- backgroundColor: binding.state.isRunning ? "#a3a3a3" : "#10b981",
1350
- color: "#ffffff",
1351
- border: "none",
1352
- borderRadius: "6px",
1353
- fontSize: "12px",
1354
- fontWeight: 700,
1355
- fontFamily: "Arial, sans-serif",
1356
- letterSpacing: "0.5px",
1357
- cursor: binding.state.isRunning ? "not-allowed" : "pointer",
1358
- opacity: binding.state.isRunning ? 0.5 : 1
1359
- },
1360
- children: "START"
1361
- }
1362
- ),
1363
- activeCapabilities?.canStop && /* @__PURE__ */ jsx8(
1364
- "button",
1365
- {
1366
- onClick: onStop,
1367
- disabled: !binding.state.isRunning,
1368
- style: {
1369
- flex: 1,
1370
- height: touchSize,
1371
- backgroundColor: !binding.state.isRunning ? "#a3a3a3" : "#ef4444",
1372
- color: "#ffffff",
1373
- border: "none",
1374
- borderRadius: "6px",
1375
- fontSize: "12px",
1376
- fontWeight: 700,
1377
- fontFamily: "Arial, sans-serif",
1378
- letterSpacing: "0.5px",
1379
- cursor: !binding.state.isRunning ? "not-allowed" : "pointer",
1380
- opacity: !binding.state.isRunning ? 0.5 : 1
1381
- },
1382
- children: "STOP"
1383
- }
1384
- )
1487
+ action.id
1488
+ )) })
1385
1489
  ] }),
1386
- activeCapabilities?.customActions && activeCapabilities.customActions.length > 0 && /* @__PURE__ */ jsx8("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: activeCapabilities.customActions.map((action) => /* @__PURE__ */ jsxs7(
1387
- "button",
1388
- {
1389
- onClick: () => onCustomAction?.(action.id),
1390
- style: {
1391
- flex: 1,
1392
- minWidth: "120px",
1393
- height: touchSize,
1394
- backgroundColor: surfaceColor,
1395
- color: textColor,
1396
- border: `1px solid ${borderColor}`,
1397
- borderRadius: "6px",
1398
- fontSize: "11px",
1399
- fontWeight: 600,
1400
- fontFamily: "monospace",
1401
- letterSpacing: "0.5px",
1402
- textTransform: "uppercase",
1403
- cursor: "pointer",
1404
- transition: "all 0.15s"
1405
- },
1406
- children: [
1407
- action.icon && /* @__PURE__ */ jsx8("span", { style: { marginRight: "6px" }, children: action.icon }),
1408
- action.label
1409
- ]
1410
- },
1411
- action.id
1412
- )) }),
1413
- binding.state.lastCommandResult && /* @__PURE__ */ jsx8(
1490
+ binding.state.lastCommandResult && showToast && /* @__PURE__ */ jsx8(
1414
1491
  "div",
1415
1492
  {
1416
1493
  style: {
1417
- marginTop: "16px",
1418
- padding: "10px 12px",
1419
- backgroundColor: surfaceColor,
1420
- border: `1px solid ${binding.state.lastCommandResult.success ? "#52525b" : borderColor}`,
1421
- borderRadius: "4px",
1422
- fontSize: "11px",
1423
- fontFamily: "monospace",
1424
- color: binding.state.lastCommandResult.success ? textColor : mutedTextColor
1494
+ position: "fixed",
1495
+ bottom: "20px",
1496
+ ...position === "bottom" && align === "right" ? { left: "20px", right: "auto" } : { right: "20px", left: "auto" },
1497
+ // Default to right
1498
+ maxWidth: "400px",
1499
+ padding: "12px 16px",
1500
+ backgroundColor: binding.state.lastCommandResult.success ? "#10b981" : "#ef4444",
1501
+ color: "#ffffff",
1502
+ border: "none",
1503
+ borderRadius: "8px",
1504
+ fontSize: `${fontSize.label}px`,
1505
+ fontFamily: "Arial, sans-serif",
1506
+ fontWeight: 600,
1507
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
1508
+ zIndex: 1e4,
1509
+ animation: position === "bottom" && align === "right" ? "slideInToastLeft 0.3s ease-out" : "slideInToast 0.3s ease-out"
1425
1510
  },
1426
1511
  children: binding.state.lastCommandResult.message
1427
1512
  }
1428
1513
  ),
1429
1514
  /* @__PURE__ */ jsx8("style", { children: `
1430
- @keyframes slideUp {
1431
- from { transform: translateY(100%); }
1432
- to { transform: translateY(0); }
1515
+ @keyframes slideInToast {
1516
+ from {
1517
+ opacity: 0;
1518
+ transform: translateX(20px);
1519
+ }
1520
+ to {
1521
+ opacity: 1;
1522
+ transform: translateX(0);
1523
+ }
1524
+ }
1525
+ @keyframes slideInToastLeft {
1526
+ from {
1527
+ opacity: 0;
1528
+ transform: translateX(-20px);
1433
1529
  }
1434
- @keyframes slideIn {
1435
- from { transform: translateX(100%); }
1436
- to { transform: translateX(0); }
1530
+ to {
1531
+ opacity: 1;
1532
+ transform: translateX(0);
1437
1533
  }
1438
- ` })
1439
- ] }) });
1534
+ }
1535
+ @keyframes slideUp {
1536
+ from { transform: translateY(100%); }
1537
+ to { transform: translateY(0); }
1538
+ }
1539
+ @keyframes slideIn {
1540
+ from { transform: translateX(100%); }
1541
+ to { transform: translateX(0); }
1542
+ }
1543
+ ` })
1544
+ ] });
1440
1545
  }
1441
1546
 
1442
1547
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
@@ -7440,19 +7545,949 @@ function useDeviceControls(initialBindings) {
7440
7545
  };
7441
7546
  }
7442
7547
 
7443
- // src/diagram/hooks/useSimulation.ts
7444
- import { useEffect as useEffect12 } from "react";
7445
- function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7446
- const {
7447
- interval = 2e3,
7448
- enabled = true,
7449
- maxChange = 10,
7450
- minValue = 0,
7451
- maxValue = 100,
7452
- historyLength = 10,
7453
- generateValue
7454
- } = options;
7455
- useEffect12(() => {
7548
+ // src/diagram/schema/DeviceConfigBinding.ts
7549
+ function createConfigBinding(nodeId, tag, parameters, actions, options) {
7550
+ return {
7551
+ nodeId,
7552
+ tag,
7553
+ deviceName: options?.deviceName,
7554
+ deviceType: options?.deviceType,
7555
+ sections: options?.sections,
7556
+ parameters,
7557
+ state: {
7558
+ isDirty: false,
7559
+ isSaving: false,
7560
+ errors: {}
7561
+ },
7562
+ actions,
7563
+ display: options?.display,
7564
+ theme: options?.theme,
7565
+ metadata: options?.metadata
7566
+ };
7567
+ }
7568
+
7569
+ // src/hooks/useDeviceConfigs.ts
7570
+ import { useState as useState16, useCallback as useCallback9 } from "react";
7571
+ function useDeviceConfigs(initialConfigs) {
7572
+ const [configs, setConfigs] = useState16(() => {
7573
+ const map = /* @__PURE__ */ new Map();
7574
+ if (initialConfigs) {
7575
+ initialConfigs.forEach((config) => {
7576
+ map.set(config.nodeId, config);
7577
+ });
7578
+ }
7579
+ return map;
7580
+ });
7581
+ const getConfig = useCallback9(
7582
+ (nodeId) => {
7583
+ return configs.get(nodeId);
7584
+ },
7585
+ [configs]
7586
+ );
7587
+ const updateConfig = useCallback9((nodeId, updates) => {
7588
+ setConfigs((prev) => {
7589
+ const newMap = new Map(prev);
7590
+ const existing = newMap.get(nodeId);
7591
+ if (existing) {
7592
+ newMap.set(nodeId, { ...existing, ...updates });
7593
+ } else {
7594
+ if ("tag" in updates && "parameters" in updates && "state" in updates && "actions" in updates) {
7595
+ newMap.set(nodeId, { nodeId, ...updates });
7596
+ }
7597
+ }
7598
+ return newMap;
7599
+ });
7600
+ }, []);
7601
+ const updateConfigState = useCallback9((nodeId, stateUpdates) => {
7602
+ setConfigs((prev) => {
7603
+ const newMap = new Map(prev);
7604
+ const existing = newMap.get(nodeId);
7605
+ if (existing) {
7606
+ newMap.set(nodeId, {
7607
+ ...existing,
7608
+ state: { ...existing.state, ...stateUpdates }
7609
+ });
7610
+ }
7611
+ return newMap;
7612
+ });
7613
+ }, []);
7614
+ const updateConfigBatch = useCallback9((updates) => {
7615
+ setConfigs((prev) => {
7616
+ const newMap = new Map(prev);
7617
+ Object.entries(updates).forEach(([nodeId, update]) => {
7618
+ const existing = newMap.get(nodeId);
7619
+ if (existing) {
7620
+ newMap.set(nodeId, { ...existing, ...update });
7621
+ }
7622
+ });
7623
+ return newMap;
7624
+ });
7625
+ }, []);
7626
+ const updateParameterValue = useCallback9((nodeId, parameterId, value) => {
7627
+ setConfigs((prev) => {
7628
+ const newMap = new Map(prev);
7629
+ const existing = newMap.get(nodeId);
7630
+ if (existing) {
7631
+ const updatedParameters = existing.parameters.map(
7632
+ (param) => param.id === parameterId ? { ...param, value } : param
7633
+ );
7634
+ newMap.set(nodeId, {
7635
+ ...existing,
7636
+ parameters: updatedParameters,
7637
+ state: { ...existing.state, isDirty: true }
7638
+ });
7639
+ }
7640
+ return newMap;
7641
+ });
7642
+ }, []);
7643
+ const applyConfig = useCallback9(
7644
+ async (nodeId) => {
7645
+ const config = configs.get(nodeId);
7646
+ if (!config || !config.actions.onApply) {
7647
+ return { success: false, message: "No apply handler defined" };
7648
+ }
7649
+ updateConfigState(nodeId, { isSaving: true });
7650
+ try {
7651
+ const values = config.parameters.reduce(
7652
+ (acc, param) => {
7653
+ acc[param.id] = param.value;
7654
+ return acc;
7655
+ },
7656
+ {}
7657
+ );
7658
+ const result = await config.actions.onApply(values);
7659
+ updateConfigState(nodeId, {
7660
+ isSaving: false,
7661
+ lastSaveResult: result,
7662
+ isDirty: result.success ? false : config.state.isDirty
7663
+ });
7664
+ return result;
7665
+ } catch (error) {
7666
+ const message = error instanceof Error ? error.message : "Apply failed";
7667
+ updateConfigState(nodeId, {
7668
+ isSaving: false,
7669
+ lastSaveResult: { success: false, message }
7670
+ });
7671
+ return { success: false, message };
7672
+ }
7673
+ },
7674
+ [configs, updateConfigState]
7675
+ );
7676
+ const saveConfig = useCallback9(
7677
+ async (nodeId) => {
7678
+ const config = configs.get(nodeId);
7679
+ if (!config || !config.actions.onSave) {
7680
+ return { success: false, message: "No save handler defined" };
7681
+ }
7682
+ updateConfigState(nodeId, { isSaving: true });
7683
+ try {
7684
+ const values = config.parameters.reduce(
7685
+ (acc, param) => {
7686
+ acc[param.id] = param.value;
7687
+ return acc;
7688
+ },
7689
+ {}
7690
+ );
7691
+ const result = await config.actions.onSave(values);
7692
+ updateConfigState(nodeId, {
7693
+ isSaving: false,
7694
+ lastSaved: result.success ? Date.now() : config.state.lastSaved,
7695
+ lastSaveResult: result,
7696
+ isDirty: result.success ? false : config.state.isDirty
7697
+ });
7698
+ return result;
7699
+ } catch (error) {
7700
+ const message = error instanceof Error ? error.message : "Save failed";
7701
+ updateConfigState(nodeId, {
7702
+ isSaving: false,
7703
+ lastSaveResult: { success: false, message }
7704
+ });
7705
+ return { success: false, message };
7706
+ }
7707
+ },
7708
+ [configs, updateConfigState]
7709
+ );
7710
+ const resetConfig = useCallback9(
7711
+ async (nodeId) => {
7712
+ const config = configs.get(nodeId);
7713
+ if (!config) {
7714
+ return { success: false, message: "Configuration not found" };
7715
+ }
7716
+ try {
7717
+ const resetParameters = config.parameters.map((param) => ({
7718
+ ...param,
7719
+ value: param.defaultValue !== void 0 ? param.defaultValue : param.value
7720
+ }));
7721
+ updateConfig(nodeId, {
7722
+ parameters: resetParameters,
7723
+ state: { ...config.state, isDirty: true }
7724
+ });
7725
+ if (config.actions.onReset) {
7726
+ const result = await config.actions.onReset();
7727
+ updateConfigState(nodeId, {
7728
+ lastSaveResult: result
7729
+ });
7730
+ return result;
7731
+ }
7732
+ return { success: true, message: "Reset to defaults" };
7733
+ } catch (error) {
7734
+ const message = error instanceof Error ? error.message : "Reset failed";
7735
+ return { success: false, message };
7736
+ }
7737
+ },
7738
+ [configs, updateConfig, updateConfigState]
7739
+ );
7740
+ const cancelConfig = useCallback9(
7741
+ (nodeId) => {
7742
+ const config = configs.get(nodeId);
7743
+ if (config?.actions.onCancel) {
7744
+ config.actions.onCancel();
7745
+ }
7746
+ },
7747
+ [configs]
7748
+ );
7749
+ const validateConfig = useCallback9(
7750
+ (nodeId) => {
7751
+ const config = configs.get(nodeId);
7752
+ if (!config) {
7753
+ return { valid: false, errors: { _general: "Configuration not found" } };
7754
+ }
7755
+ const errors = {};
7756
+ const values = config.parameters.reduce(
7757
+ (acc, param) => {
7758
+ acc[param.id] = param.value;
7759
+ return acc;
7760
+ },
7761
+ {}
7762
+ );
7763
+ config.parameters.forEach((param) => {
7764
+ if (param.required && (param.value === void 0 || param.value === null || param.value === "")) {
7765
+ errors[param.id] = `${param.label} is required`;
7766
+ return;
7767
+ }
7768
+ if (param.type === "number" && typeof param.value === "number") {
7769
+ if (param.min !== void 0 && param.value < param.min) {
7770
+ errors[param.id] = `${param.label} must be at least ${param.min}`;
7771
+ }
7772
+ if (param.max !== void 0 && param.value > param.max) {
7773
+ errors[param.id] = `${param.label} must be at most ${param.max}`;
7774
+ }
7775
+ }
7776
+ if (param.validate) {
7777
+ const result = param.validate(param.value);
7778
+ if (result !== true) {
7779
+ errors[param.id] = typeof result === "string" ? result : `${param.label} is invalid`;
7780
+ }
7781
+ }
7782
+ if (param.visibleWhen && !param.visibleWhen(values)) {
7783
+ delete errors[param.id];
7784
+ }
7785
+ });
7786
+ updateConfigState(nodeId, { errors });
7787
+ return { valid: Object.keys(errors).length === 0, errors };
7788
+ },
7789
+ [configs, updateConfigState]
7790
+ );
7791
+ const setConfig = useCallback9((config) => {
7792
+ setConfigs((prev) => {
7793
+ const newMap = new Map(prev);
7794
+ newMap.set(config.nodeId, config);
7795
+ return newMap;
7796
+ });
7797
+ }, []);
7798
+ const removeConfig = useCallback9((nodeId) => {
7799
+ setConfigs((prev) => {
7800
+ const newMap = new Map(prev);
7801
+ newMap.delete(nodeId);
7802
+ return newMap;
7803
+ });
7804
+ }, []);
7805
+ const clearConfigs = useCallback9(() => {
7806
+ setConfigs(/* @__PURE__ */ new Map());
7807
+ }, []);
7808
+ return {
7809
+ configs,
7810
+ getConfig,
7811
+ setConfig,
7812
+ removeConfig,
7813
+ clearConfigs,
7814
+ updateConfig,
7815
+ updateConfigState,
7816
+ updateConfigBatch,
7817
+ updateParameterValue,
7818
+ applyConfig,
7819
+ saveConfig,
7820
+ resetConfig,
7821
+ cancelConfig,
7822
+ validateConfig
7823
+ };
7824
+ }
7825
+
7826
+ // src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
7827
+ import { useState as useState17, useEffect as useEffect12, useMemo, useRef as useRef6 } from "react";
7828
+ import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
7829
+ function DeviceConfigPanel({
7830
+ binding,
7831
+ onClose,
7832
+ position = "bottom",
7833
+ align = "center",
7834
+ touchOptimized = true,
7835
+ compact = false,
7836
+ fontScale = 1,
7837
+ maxHeight,
7838
+ maxWidth,
7839
+ toastDuration = 3e3,
7840
+ onOpenControls,
7841
+ showControlsButton = true
7842
+ }) {
7843
+ const [localValues, setLocalValues] = useState17(() => {
7844
+ if (binding) {
7845
+ const values = {};
7846
+ binding.parameters.forEach((param) => {
7847
+ values[param.id] = param.value;
7848
+ });
7849
+ return values;
7850
+ }
7851
+ return {};
7852
+ });
7853
+ const [collapsedSections, setCollapsedSections] = useState17(() => {
7854
+ const collapsed = /* @__PURE__ */ new Set();
7855
+ if (binding?.sections) {
7856
+ binding.sections.forEach((section) => {
7857
+ if (section.collapsible && section.defaultCollapsed) {
7858
+ collapsed.add(section.id);
7859
+ }
7860
+ });
7861
+ }
7862
+ return collapsed;
7863
+ });
7864
+ const [showToast, setShowToast] = useState17(true);
7865
+ const [bindingId, setBindingId] = useState17(binding?.nodeId || null);
7866
+ const lastResultTimestampRef = useRef6(0);
7867
+ if (binding && binding.nodeId !== bindingId) {
7868
+ const values = {};
7869
+ binding.parameters.forEach((param) => {
7870
+ values[param.id] = param.value;
7871
+ });
7872
+ setLocalValues(values);
7873
+ const collapsed = /* @__PURE__ */ new Set();
7874
+ binding.sections?.forEach((section) => {
7875
+ if (section.collapsible && section.defaultCollapsed) {
7876
+ collapsed.add(section.id);
7877
+ }
7878
+ });
7879
+ setCollapsedSections(collapsed);
7880
+ setBindingId(binding.nodeId);
7881
+ }
7882
+ useEffect12(() => {
7883
+ const currentTimestamp = binding?.state.lastSaved || 0;
7884
+ if (binding?.state.lastSaveResult && currentTimestamp !== lastResultTimestampRef.current) {
7885
+ lastResultTimestampRef.current = currentTimestamp;
7886
+ const showTimer = setTimeout(() => {
7887
+ setShowToast(true);
7888
+ const hideTimer = setTimeout(() => {
7889
+ setShowToast(false);
7890
+ }, toastDuration);
7891
+ return () => clearTimeout(hideTimer);
7892
+ }, 0);
7893
+ return () => clearTimeout(showTimer);
7894
+ }
7895
+ }, [binding?.state.lastSaveResult, binding?.state.lastSaved, toastDuration]);
7896
+ const parametersBySection = useMemo(() => {
7897
+ if (!binding) return { _unsectioned: [] };
7898
+ const groups = { _unsectioned: [] };
7899
+ binding.parameters.forEach((param) => {
7900
+ const sectionId = param.section || "_unsectioned";
7901
+ if (!groups[sectionId]) {
7902
+ groups[sectionId] = [];
7903
+ }
7904
+ groups[sectionId].push(param);
7905
+ });
7906
+ Object.keys(groups).forEach((sectionId) => {
7907
+ groups[sectionId].sort((a, b) => (a.order || 0) - (b.order || 0));
7908
+ });
7909
+ return groups;
7910
+ }, [binding]);
7911
+ const sortedSections = useMemo(() => {
7912
+ if (!binding?.sections) return [];
7913
+ return [...binding.sections].sort((a, b) => (a.order || 0) - (b.order || 0));
7914
+ }, [binding]);
7915
+ if (!binding) return null;
7916
+ const touchSize = touchOptimized ? 56 : 40;
7917
+ const spacing = {
7918
+ padding: compact ? "12px" : "20px",
7919
+ gap: compact ? "6px" : "8px",
7920
+ marginBottom: compact ? "10px" : "16px",
7921
+ itemPadding: compact ? "8px 10px" : "10px 12px"
7922
+ };
7923
+ const fontSize = {
7924
+ title: Math.round(14 * fontScale),
7925
+ label: Math.round(11 * fontScale),
7926
+ value: Math.round(13 * fontScale),
7927
+ input: Math.round(13 * fontScale),
7928
+ button: Math.round(12 * fontScale),
7929
+ section: Math.round(12 * fontScale),
7930
+ description: Math.round(10 * fontScale)
7931
+ };
7932
+ const backgroundColor = binding.theme?.backgroundColor || "#edeeef";
7933
+ const surfaceColor = binding.theme?.surfaceColor || "#f5f6f7";
7934
+ const borderColor = binding.theme?.borderColor || "#d2d2d2";
7935
+ const textColor = binding.theme?.textColor || "#000000";
7936
+ const mutedTextColor = binding.theme?.mutedTextColor || "#666666";
7937
+ const accentColor = binding.theme?.accentColor || "#b4d0fe";
7938
+ const errorColor = binding.theme?.errorColor || "#ef4444";
7939
+ const successColor = binding.theme?.successColor || "#10b981";
7940
+ const handleValueChange = (parameterId, value) => {
7941
+ setLocalValues((prev) => ({ ...prev, [parameterId]: value }));
7942
+ };
7943
+ const handleApply = async () => {
7944
+ if (binding.actions.onApply) {
7945
+ await binding.actions.onApply(localValues);
7946
+ }
7947
+ };
7948
+ const handleSave = async () => {
7949
+ if (binding.actions.onSave) {
7950
+ await binding.actions.onSave(localValues);
7951
+ }
7952
+ };
7953
+ const handleReset = async () => {
7954
+ if (binding.display?.resetConfirmMessage) {
7955
+ if (!confirm(binding.display.resetConfirmMessage)) {
7956
+ return;
7957
+ }
7958
+ }
7959
+ if (binding.actions.onReset) {
7960
+ await binding.actions.onReset();
7961
+ }
7962
+ };
7963
+ const handleCancel = () => {
7964
+ if (binding.actions.onCancel) {
7965
+ binding.actions.onCancel();
7966
+ }
7967
+ onClose();
7968
+ };
7969
+ const toggleSection = (sectionId) => {
7970
+ setCollapsedSections((prev) => {
7971
+ const next = new Set(prev);
7972
+ if (next.has(sectionId)) {
7973
+ next.delete(sectionId);
7974
+ } else {
7975
+ next.add(sectionId);
7976
+ }
7977
+ return next;
7978
+ });
7979
+ };
7980
+ const renderParameter = (param) => {
7981
+ if (param.visibleWhen && !param.visibleWhen(localValues)) {
7982
+ return null;
7983
+ }
7984
+ const value = localValues[param.id];
7985
+ const error = binding.state.errors?.[param.id];
7986
+ const isDisabled = param.readOnly || param.enabled === false;
7987
+ return /* @__PURE__ */ jsxs18(
7988
+ "div",
7989
+ {
7990
+ style: {
7991
+ marginBottom: spacing.marginBottom
7992
+ },
7993
+ children: [
7994
+ /* @__PURE__ */ jsxs18(
7995
+ "label",
7996
+ {
7997
+ style: {
7998
+ display: "block",
7999
+ fontSize: `${fontSize.label}px`,
8000
+ fontWeight: 600,
8001
+ color: textColor,
8002
+ marginBottom: "4px",
8003
+ fontFamily: "Arial, sans-serif",
8004
+ textTransform: "uppercase",
8005
+ letterSpacing: "0.5px"
8006
+ },
8007
+ children: [
8008
+ param.label,
8009
+ param.required && /* @__PURE__ */ jsx22("span", { style: { color: errorColor }, children: " *" }),
8010
+ param.unit && /* @__PURE__ */ jsxs18("span", { style: { color: mutedTextColor, fontWeight: "normal" }, children: [
8011
+ " (",
8012
+ param.unit,
8013
+ ")"
8014
+ ] })
8015
+ ]
8016
+ }
8017
+ ),
8018
+ param.description && /* @__PURE__ */ jsx22(
8019
+ "div",
8020
+ {
8021
+ style: {
8022
+ fontSize: `${fontSize.description}px`,
8023
+ color: mutedTextColor,
8024
+ marginBottom: "6px",
8025
+ fontFamily: "Arial, sans-serif"
8026
+ },
8027
+ children: param.description
8028
+ }
8029
+ ),
8030
+ param.type === "number" && /* @__PURE__ */ jsx22(
8031
+ "input",
8032
+ {
8033
+ type: "number",
8034
+ value: value ?? "",
8035
+ onChange: (e) => handleValueChange(param.id, parseFloat(e.target.value)),
8036
+ min: param.min,
8037
+ max: param.max,
8038
+ step: param.step,
8039
+ disabled: isDisabled,
8040
+ style: {
8041
+ width: "100%",
8042
+ height: `${touchSize * 0.8}px`,
8043
+ padding: spacing.itemPadding,
8044
+ fontSize: `${fontSize.input}px`,
8045
+ fontFamily: "Arial, sans-serif",
8046
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8047
+ color: textColor,
8048
+ border: `1px solid ${error ? errorColor : borderColor}`,
8049
+ borderRadius: "2px",
8050
+ outline: "none"
8051
+ }
8052
+ }
8053
+ ),
8054
+ param.type === "string" && /* @__PURE__ */ jsx22(
8055
+ "input",
8056
+ {
8057
+ type: "text",
8058
+ value: value ?? "",
8059
+ onChange: (e) => handleValueChange(param.id, e.target.value),
8060
+ disabled: isDisabled,
8061
+ style: {
8062
+ width: "100%",
8063
+ height: `${touchSize * 0.8}px`,
8064
+ padding: spacing.itemPadding,
8065
+ fontSize: `${fontSize.input}px`,
8066
+ fontFamily: "Arial, sans-serif",
8067
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8068
+ color: textColor,
8069
+ border: `1px solid ${error ? errorColor : borderColor}`,
8070
+ borderRadius: "2px",
8071
+ outline: "none"
8072
+ }
8073
+ }
8074
+ ),
8075
+ param.type === "boolean" && /* @__PURE__ */ jsxs18(
8076
+ "label",
8077
+ {
8078
+ style: {
8079
+ display: "flex",
8080
+ alignItems: "center",
8081
+ gap: "8px",
8082
+ cursor: isDisabled ? "not-allowed" : "pointer"
8083
+ },
8084
+ children: [
8085
+ /* @__PURE__ */ jsx22(
8086
+ "input",
8087
+ {
8088
+ type: "checkbox",
8089
+ checked: value ?? false,
8090
+ onChange: (e) => handleValueChange(param.id, e.target.checked),
8091
+ disabled: isDisabled,
8092
+ style: {
8093
+ width: "20px",
8094
+ height: "20px",
8095
+ cursor: isDisabled ? "not-allowed" : "pointer"
8096
+ }
8097
+ }
8098
+ ),
8099
+ /* @__PURE__ */ jsx22("span", { style: { fontSize: `${fontSize.value}px`, color: textColor }, children: value ? "Enabled" : "Disabled" })
8100
+ ]
8101
+ }
8102
+ ),
8103
+ param.type === "select" && /* @__PURE__ */ jsx22(
8104
+ "select",
8105
+ {
8106
+ value: value ?? "",
8107
+ onChange: (e) => handleValueChange(param.id, e.target.value),
8108
+ disabled: isDisabled,
8109
+ style: {
8110
+ width: "100%",
8111
+ height: `${touchSize * 0.8}px`,
8112
+ padding: spacing.itemPadding,
8113
+ fontSize: `${fontSize.input}px`,
8114
+ fontFamily: "Arial, sans-serif",
8115
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8116
+ color: textColor,
8117
+ border: `1px solid ${error ? errorColor : borderColor}`,
8118
+ borderRadius: "2px",
8119
+ outline: "none"
8120
+ },
8121
+ children: param.options?.map((option) => /* @__PURE__ */ jsx22("option", { value: option.value, children: option.label }, option.value))
8122
+ }
8123
+ ),
8124
+ error && /* @__PURE__ */ jsx22(
8125
+ "div",
8126
+ {
8127
+ style: {
8128
+ fontSize: `${fontSize.description}px`,
8129
+ color: errorColor,
8130
+ marginTop: "4px",
8131
+ fontFamily: "Arial, sans-serif"
8132
+ },
8133
+ children: error
8134
+ }
8135
+ )
8136
+ ]
8137
+ },
8138
+ param.id
8139
+ );
8140
+ };
8141
+ const renderSection = (section) => {
8142
+ const params = parametersBySection[section.id] || [];
8143
+ const isCollapsed = collapsedSections.has(section.id);
8144
+ return /* @__PURE__ */ jsxs18(
8145
+ "div",
8146
+ {
8147
+ style: {
8148
+ marginBottom: spacing.marginBottom,
8149
+ border: `1px solid ${borderColor}`,
8150
+ borderRadius: "4px",
8151
+ backgroundColor: surfaceColor
8152
+ },
8153
+ children: [
8154
+ /* @__PURE__ */ jsxs18(
8155
+ "div",
8156
+ {
8157
+ onClick: () => section.collapsible && toggleSection(section.id),
8158
+ style: {
8159
+ padding: spacing.itemPadding,
8160
+ borderBottom: isCollapsed ? "none" : `1px solid ${borderColor}`,
8161
+ cursor: section.collapsible ? "pointer" : "default",
8162
+ display: "flex",
8163
+ justifyContent: "space-between",
8164
+ alignItems: "center"
8165
+ },
8166
+ children: [
8167
+ /* @__PURE__ */ jsxs18("div", { children: [
8168
+ /* @__PURE__ */ jsxs18(
8169
+ "div",
8170
+ {
8171
+ style: {
8172
+ fontSize: `${fontSize.section}px`,
8173
+ fontWeight: 600,
8174
+ color: textColor,
8175
+ fontFamily: "Arial, sans-serif",
8176
+ textTransform: "uppercase",
8177
+ letterSpacing: "0.5px"
8178
+ },
8179
+ children: [
8180
+ section.icon && /* @__PURE__ */ jsx22("span", { style: { marginRight: "8px" }, children: section.icon }),
8181
+ section.title
8182
+ ]
8183
+ }
8184
+ ),
8185
+ section.description && /* @__PURE__ */ jsx22(
8186
+ "div",
8187
+ {
8188
+ style: {
8189
+ fontSize: `${fontSize.description}px`,
8190
+ color: mutedTextColor,
8191
+ marginTop: "4px",
8192
+ fontFamily: "Arial, sans-serif"
8193
+ },
8194
+ children: section.description
8195
+ }
8196
+ )
8197
+ ] }),
8198
+ section.collapsible && /* @__PURE__ */ jsx22("span", { style: { fontSize: "16px", color: mutedTextColor }, children: isCollapsed ? "\u25BC" : "\u25B2" })
8199
+ ]
8200
+ }
8201
+ ),
8202
+ !isCollapsed && /* @__PURE__ */ jsx22("div", { style: { padding: spacing.padding }, children: params.map(renderParameter) })
8203
+ ]
8204
+ },
8205
+ section.id
8206
+ );
8207
+ };
8208
+ const panelStyle = {
8209
+ position: "fixed",
8210
+ ...position === "bottom" ? {
8211
+ bottom: 0,
8212
+ ...align === "left" ? { left: 0, right: "auto", width: maxWidth } : align === "right" ? { right: 0, left: "auto", width: maxWidth } : { left: 0, right: 0, marginLeft: "auto", marginRight: "auto", maxWidth },
8213
+ animation: "slideUp 0.3s ease-out"
8214
+ } : {
8215
+ right: 0,
8216
+ top: 0,
8217
+ bottom: 0,
8218
+ width: maxWidth || "400px",
8219
+ animation: "slideIn 0.3s ease-out"
8220
+ },
8221
+ backgroundColor,
8222
+ color: textColor,
8223
+ borderRadius: position === "bottom" ? "8px 8px 0 0" : "8px 0 0 8px",
8224
+ boxShadow: "none",
8225
+ border: `2px solid ${borderColor}`,
8226
+ padding: spacing.padding,
8227
+ zIndex: 1e4,
8228
+ maxHeight: maxHeight || (position === "bottom" ? "70vh" : "100vh"),
8229
+ overflowY: "auto",
8230
+ display: "flex",
8231
+ flexDirection: "column",
8232
+ ...binding.display?.style
8233
+ };
8234
+ const titleText = binding.display?.title || binding.deviceName || binding.tag || binding.nodeId;
8235
+ const showApply = binding.display?.showApply !== false && binding.actions.onApply;
8236
+ const showSave = binding.display?.showSave !== false && binding.actions.onSave;
8237
+ const showReset = binding.display?.showReset !== false && binding.actions.onReset;
8238
+ const showCancel = binding.display?.showCancel !== false;
8239
+ return /* @__PURE__ */ jsxs18(Fragment9, { children: [
8240
+ /* @__PURE__ */ jsxs18("div", { style: panelStyle, className: binding.display?.className, children: [
8241
+ /* @__PURE__ */ jsxs18(
8242
+ "div",
8243
+ {
8244
+ style: {
8245
+ display: "flex",
8246
+ justifyContent: "space-between",
8247
+ alignItems: "center",
8248
+ marginBottom: "16px",
8249
+ paddingBottom: "12px",
8250
+ borderBottom: `2px solid ${borderColor}`
8251
+ },
8252
+ children: [
8253
+ /* @__PURE__ */ jsxs18("div", { children: [
8254
+ /* @__PURE__ */ jsx22(
8255
+ "h3",
8256
+ {
8257
+ style: {
8258
+ margin: 0,
8259
+ fontSize: `${fontSize.title}px`,
8260
+ fontWeight: 600,
8261
+ fontFamily: "Arial, sans-serif",
8262
+ letterSpacing: "0.5px",
8263
+ textTransform: "uppercase",
8264
+ color: textColor
8265
+ },
8266
+ children: titleText
8267
+ }
8268
+ ),
8269
+ binding.display?.subtitle && /* @__PURE__ */ jsx22(
8270
+ "div",
8271
+ {
8272
+ style: {
8273
+ fontSize: `${fontSize.description}px`,
8274
+ color: mutedTextColor,
8275
+ marginTop: "4px",
8276
+ fontFamily: "Arial, sans-serif"
8277
+ },
8278
+ children: binding.display.subtitle
8279
+ }
8280
+ )
8281
+ ] }),
8282
+ /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: "8px" }, children: [
8283
+ showControlsButton && onOpenControls && /* @__PURE__ */ jsx22(
8284
+ "button",
8285
+ {
8286
+ onClick: onOpenControls,
8287
+ title: "Open Controls",
8288
+ style: {
8289
+ minWidth: `${touchSize * 1.2}px`,
8290
+ height: touchSize * 0.7,
8291
+ backgroundColor: surfaceColor,
8292
+ color: textColor,
8293
+ border: `2px solid ${borderColor}`,
8294
+ borderRadius: "2px",
8295
+ fontSize: `${fontSize.button}px`,
8296
+ cursor: "pointer",
8297
+ fontWeight: 600,
8298
+ fontFamily: "Arial, sans-serif",
8299
+ padding: "0 8px",
8300
+ textTransform: "uppercase",
8301
+ letterSpacing: "0.5px"
8302
+ },
8303
+ children: "CTRL"
8304
+ }
8305
+ ),
8306
+ /* @__PURE__ */ jsx22(
8307
+ "button",
8308
+ {
8309
+ onClick: onClose,
8310
+ style: {
8311
+ width: touchSize * 0.7,
8312
+ height: touchSize * 0.7,
8313
+ backgroundColor: surfaceColor,
8314
+ color: textColor,
8315
+ border: `2px solid ${borderColor}`,
8316
+ borderRadius: "2px",
8317
+ fontSize: "18px",
8318
+ cursor: "pointer",
8319
+ fontWeight: "bold",
8320
+ fontFamily: "Arial, sans-serif"
8321
+ },
8322
+ children: "\xD7"
8323
+ }
8324
+ )
8325
+ ] })
8326
+ ]
8327
+ }
8328
+ ),
8329
+ binding.state.isDirty && /* @__PURE__ */ jsx22(
8330
+ "div",
8331
+ {
8332
+ style: {
8333
+ padding: spacing.itemPadding,
8334
+ backgroundColor: "#fff3cd",
8335
+ border: "1px solid #ffc107",
8336
+ borderRadius: "4px",
8337
+ marginBottom: spacing.marginBottom,
8338
+ fontSize: `${fontSize.description}px`,
8339
+ fontFamily: "Arial, sans-serif"
8340
+ },
8341
+ children: "Unsaved changes"
8342
+ }
8343
+ ),
8344
+ /* @__PURE__ */ jsx22("div", { style: { flex: 1, overflowY: "auto", marginBottom: spacing.marginBottom }, children: sortedSections.length > 0 ? sortedSections.map(renderSection) : (
8345
+ /* Render unsectioned parameters */
8346
+ /* @__PURE__ */ jsx22("div", { children: parametersBySection._unsectioned?.map(renderParameter) })
8347
+ ) }),
8348
+ /* @__PURE__ */ jsxs18(
8349
+ "div",
8350
+ {
8351
+ style: {
8352
+ display: "flex",
8353
+ gap: spacing.gap,
8354
+ paddingTop: "12px",
8355
+ borderTop: `2px solid ${borderColor}`,
8356
+ flexWrap: "wrap"
8357
+ },
8358
+ children: [
8359
+ showApply && /* @__PURE__ */ jsx22(
8360
+ "button",
8361
+ {
8362
+ onClick: handleApply,
8363
+ disabled: binding.state.isSaving,
8364
+ style: {
8365
+ flex: 1,
8366
+ minWidth: "80px",
8367
+ height: `${touchSize}px`,
8368
+ backgroundColor: accentColor,
8369
+ color: "#000000",
8370
+ border: `2px solid ${borderColor}`,
8371
+ borderRadius: "2px",
8372
+ fontSize: `${fontSize.button}px`,
8373
+ fontWeight: 600,
8374
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8375
+ fontFamily: "Arial, sans-serif",
8376
+ textTransform: "uppercase",
8377
+ letterSpacing: "0.5px"
8378
+ },
8379
+ children: binding.state.isSaving ? "Applying..." : "Apply"
8380
+ }
8381
+ ),
8382
+ showSave && /* @__PURE__ */ jsx22(
8383
+ "button",
8384
+ {
8385
+ onClick: handleSave,
8386
+ disabled: binding.state.isSaving,
8387
+ style: {
8388
+ flex: 1,
8389
+ minWidth: "80px",
8390
+ height: `${touchSize}px`,
8391
+ backgroundColor: successColor,
8392
+ color: "#ffffff",
8393
+ border: `2px solid ${borderColor}`,
8394
+ borderRadius: "2px",
8395
+ fontSize: `${fontSize.button}px`,
8396
+ fontWeight: 600,
8397
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8398
+ fontFamily: "Arial, sans-serif",
8399
+ textTransform: "uppercase",
8400
+ letterSpacing: "0.5px"
8401
+ },
8402
+ children: binding.state.isSaving ? "Saving..." : "Save"
8403
+ }
8404
+ ),
8405
+ showReset && /* @__PURE__ */ jsx22(
8406
+ "button",
8407
+ {
8408
+ onClick: handleReset,
8409
+ disabled: binding.state.isSaving,
8410
+ style: {
8411
+ flex: 1,
8412
+ minWidth: "80px",
8413
+ height: `${touchSize}px`,
8414
+ backgroundColor: surfaceColor,
8415
+ color: textColor,
8416
+ border: `2px solid ${borderColor}`,
8417
+ borderRadius: "2px",
8418
+ fontSize: `${fontSize.button}px`,
8419
+ fontWeight: 600,
8420
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8421
+ fontFamily: "Arial, sans-serif",
8422
+ textTransform: "uppercase",
8423
+ letterSpacing: "0.5px"
8424
+ },
8425
+ children: "Reset"
8426
+ }
8427
+ ),
8428
+ showCancel && /* @__PURE__ */ jsx22(
8429
+ "button",
8430
+ {
8431
+ onClick: handleCancel,
8432
+ disabled: binding.state.isSaving,
8433
+ style: {
8434
+ flex: 1,
8435
+ minWidth: "80px",
8436
+ height: `${touchSize}px`,
8437
+ backgroundColor: surfaceColor,
8438
+ color: textColor,
8439
+ border: `2px solid ${borderColor}`,
8440
+ borderRadius: "2px",
8441
+ fontSize: `${fontSize.button}px`,
8442
+ fontWeight: 600,
8443
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8444
+ fontFamily: "Arial, sans-serif",
8445
+ textTransform: "uppercase",
8446
+ letterSpacing: "0.5px"
8447
+ },
8448
+ children: "Cancel"
8449
+ }
8450
+ )
8451
+ ]
8452
+ }
8453
+ )
8454
+ ] }),
8455
+ binding.state.lastSaveResult && showToast && /* @__PURE__ */ jsx22(
8456
+ "div",
8457
+ {
8458
+ style: {
8459
+ position: "fixed",
8460
+ bottom: "20px",
8461
+ ...align === "right" ? { left: "20px" } : { right: "20px" },
8462
+ backgroundColor: binding.state.lastSaveResult.success ? successColor : errorColor,
8463
+ color: "#ffffff",
8464
+ padding: "12px 20px",
8465
+ borderRadius: "4px",
8466
+ boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
8467
+ zIndex: 1e4,
8468
+ fontSize: `${fontSize.value}px`,
8469
+ fontFamily: "Arial, sans-serif",
8470
+ fontWeight: 600
8471
+ },
8472
+ children: binding.state.lastSaveResult.message || (binding.state.lastSaveResult.success ? "Success" : "Error")
8473
+ }
8474
+ )
8475
+ ] });
8476
+ }
8477
+
8478
+ // src/diagram/hooks/useSimulation.ts
8479
+ import { useEffect as useEffect13 } from "react";
8480
+ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
8481
+ const {
8482
+ interval = 2e3,
8483
+ enabled = true,
8484
+ maxChange = 10,
8485
+ minValue = 0,
8486
+ maxValue = 100,
8487
+ historyLength = 10,
8488
+ generateValue
8489
+ } = options;
8490
+ useEffect13(() => {
7456
8491
  if (!enabled) return;
7457
8492
  const intervalId = setInterval(() => {
7458
8493
  const updates = [];
@@ -7509,6 +8544,7 @@ export {
7509
8544
  ControlPanel,
7510
8545
  DEFAULT_THRESHOLDS,
7511
8546
  DashboardCard,
8547
+ DeviceConfigPanel,
7512
8548
  DeviceControlPanel,
7513
8549
  DisplayModeToggle,
7514
8550
  EquipmentIndicator,
@@ -7541,6 +8577,7 @@ export {
7541
8577
  ZoomButton,
7542
8578
  ZoomControls,
7543
8579
  calculateThresholdStatus,
8580
+ createConfigBinding,
7544
8581
  createControlBinding,
7545
8582
  exampleDiagram,
7546
8583
  exportDiagram,
@@ -7554,6 +8591,7 @@ export {
7554
8591
  overlayStyles,
7555
8592
  registerSymbol,
7556
8593
  registerSymbols,
8594
+ useDeviceConfigs,
7557
8595
  useDeviceControls,
7558
8596
  useDrag,
7559
8597
  useKeyboardShortcuts,