@procaaso/alphinity-ui-components 1.0.11 → 1.1.0

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
@@ -911,9 +911,17 @@ function DeviceControlPanel({
911
911
  onParameterChange,
912
912
  onStart,
913
913
  onStop,
914
- onCustomAction
914
+ onCustomAction,
915
+ onOpenConfig,
916
+ showConfigButton = true,
917
+ onUndock,
918
+ undockIcon = "\u21F1",
919
+ modeSelectionStyle = "buttons",
920
+ embedded = false
915
921
  }) {
916
922
  const [localParameterValues, setLocalParameterValues] = useState5({});
923
+ const [showModeModal, setShowModeModal] = useState5(false);
924
+ const [pendingMode, setPendingMode] = useState5(null);
917
925
  const [showToast, setShowToast] = useState5(true);
918
926
  useEffect2(() => {
919
927
  if (binding) {
@@ -950,7 +958,8 @@ function DeviceControlPanel({
950
958
  value: Math.round(13 * fontScale),
951
959
  input: Math.round(14 * fontScale),
952
960
  button: Math.round(12 * fontScale),
953
- status: Math.round(10 * fontScale)
961
+ status: Math.round(10 * fontScale),
962
+ description: Math.round(13 * fontScale)
954
963
  };
955
964
  const currentModeObj = binding.modes.find((m) => m.value === currentMode);
956
965
  const activeCapabilities = currentModeObj?.capabilities ?? binding.capabilities ?? {};
@@ -986,18 +995,37 @@ function DeviceControlPanel({
986
995
  const handleParameterInput = (parameterId, value) => {
987
996
  setLocalParameterValues((prev) => ({ ...prev, [parameterId]: value }));
988
997
  };
989
- const panelStyle = {
998
+ const panelStyle = embedded ? {
999
+ // Embedded mode: static positioning for container layouts
1000
+ position: "static",
1001
+ width: "100%",
1002
+ backgroundColor,
1003
+ color: textColor,
1004
+ borderRadius: "8px 8px 0 0",
1005
+ boxShadow: "none",
1006
+ border: `2px solid ${borderColor}`,
1007
+ padding: spacing.padding,
1008
+ maxHeight: maxHeight || "70vh",
1009
+ overflowY: "auto",
1010
+ ...binding.display?.style
1011
+ } : {
1012
+ // Fixed mode: traditional overlay positioning
990
1013
  position: "fixed",
991
1014
  ...position === "bottom" ? {
992
1015
  bottom: 0,
993
- ...align === "left" ? { left: 0, right: "auto" } : align === "right" ? { right: 0, left: "auto" } : { left: 0, right: 0, marginLeft: "auto", marginRight: "auto" },
994
- animation: "slideUp 0.3s ease-out",
995
- maxWidth
1016
+ ...align === "left" ? { left: 0, right: "auto", width: maxWidth } : align === "right" ? { right: 0, left: "auto", width: maxWidth } : {
1017
+ left: 0,
1018
+ right: 0,
1019
+ marginLeft: "auto",
1020
+ marginRight: "auto",
1021
+ maxWidth
1022
+ },
1023
+ animation: "slideUp 0.3s ease-out"
996
1024
  } : {
997
1025
  right: 0,
998
1026
  top: 0,
999
1027
  bottom: 0,
1000
- width: "400px",
1028
+ width: maxWidth || "400px",
1001
1029
  animation: "slideIn 0.3s ease-out"
1002
1030
  },
1003
1031
  backgroundColor,
@@ -1037,9 +1065,14 @@ function DeviceControlPanel({
1037
1065
  fontFamily: "Arial, sans-serif",
1038
1066
  letterSpacing: "0.5px",
1039
1067
  textTransform: "uppercase",
1040
- color: textColor
1068
+ color: textColor,
1069
+ overflow: "hidden",
1070
+ textOverflow: "ellipsis",
1071
+ whiteSpace: "nowrap",
1072
+ maxWidth: "200px"
1041
1073
  },
1042
- children: titleText
1074
+ title: titleText.length > 25 ? titleText : void 0,
1075
+ children: titleText.length > 25 ? titleText.slice(0, 25) + "..." : titleText
1043
1076
  }
1044
1077
  ),
1045
1078
  binding.deviceType && /* @__PURE__ */ jsx8(
@@ -1055,25 +1088,69 @@ function DeviceControlPanel({
1055
1088
  }
1056
1089
  )
1057
1090
  ] }),
1058
- /* @__PURE__ */ jsx8(
1059
- "button",
1060
- {
1061
- onClick: onClose,
1062
- style: {
1063
- width: touchSize * 0.7,
1064
- height: touchSize * 0.7,
1065
- backgroundColor: surfaceColor,
1066
- color: textColor,
1067
- border: `2px solid ${borderColor}`,
1068
- borderRadius: "2px",
1069
- fontSize: "18px",
1070
- cursor: "pointer",
1071
- fontWeight: "bold",
1072
- fontFamily: "Arial, sans-serif"
1073
- },
1074
- children: "\xD7"
1075
- }
1076
- )
1091
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "8px" }, children: [
1092
+ showConfigButton && onOpenConfig && /* @__PURE__ */ jsx8(
1093
+ "button",
1094
+ {
1095
+ onClick: onOpenConfig,
1096
+ title: "Open Configuration",
1097
+ style: {
1098
+ width: touchSize * 0.7,
1099
+ height: touchSize * 0.7,
1100
+ backgroundColor: surfaceColor,
1101
+ color: textColor,
1102
+ border: `2px solid ${borderColor}`,
1103
+ borderRadius: "2px",
1104
+ fontSize: "16px",
1105
+ cursor: "pointer",
1106
+ fontWeight: 600,
1107
+ fontFamily: "Arial, sans-serif",
1108
+ padding: "0"
1109
+ },
1110
+ children: "\u2699"
1111
+ }
1112
+ ),
1113
+ onUndock && /* @__PURE__ */ jsx8(
1114
+ "button",
1115
+ {
1116
+ onClick: onUndock,
1117
+ title: "Undock panel",
1118
+ style: {
1119
+ width: touchSize * 0.7,
1120
+ height: touchSize * 0.7,
1121
+ backgroundColor: surfaceColor,
1122
+ color: textColor,
1123
+ border: `2px solid ${borderColor}`,
1124
+ borderRadius: "2px",
1125
+ fontSize: "16px",
1126
+ cursor: "pointer",
1127
+ fontWeight: 600,
1128
+ fontFamily: "Arial, sans-serif",
1129
+ padding: "0"
1130
+ },
1131
+ children: undockIcon
1132
+ }
1133
+ ),
1134
+ /* @__PURE__ */ jsx8(
1135
+ "button",
1136
+ {
1137
+ onClick: onClose,
1138
+ style: {
1139
+ width: touchSize * 0.7,
1140
+ height: touchSize * 0.7,
1141
+ backgroundColor: surfaceColor,
1142
+ color: textColor,
1143
+ border: `2px solid ${borderColor}`,
1144
+ borderRadius: "2px",
1145
+ fontSize: "18px",
1146
+ cursor: "pointer",
1147
+ fontWeight: "bold",
1148
+ fontFamily: "Arial, sans-serif"
1149
+ },
1150
+ children: "\xD7"
1151
+ }
1152
+ )
1153
+ ] })
1077
1154
  ]
1078
1155
  }
1079
1156
  ),
@@ -1168,7 +1245,228 @@ function DeviceControlPanel({
1168
1245
  children: "Mode"
1169
1246
  }
1170
1247
  ),
1171
- /* @__PURE__ */ jsx8(
1248
+ modeSelectionStyle === "dropdown" ? /* @__PURE__ */ jsx8(
1249
+ "select",
1250
+ {
1251
+ value: currentMode,
1252
+ onChange: (e) => handleModeChange(e.target.value),
1253
+ style: {
1254
+ width: "100%",
1255
+ height: touchSize,
1256
+ backgroundColor: surfaceColor,
1257
+ color: textColor,
1258
+ border: `1px solid ${borderColor}`,
1259
+ borderRadius: "6px",
1260
+ fontSize: `${fontSize.label}px`,
1261
+ fontWeight: 600,
1262
+ fontFamily: "Arial, sans-serif",
1263
+ letterSpacing: "0.5px",
1264
+ textTransform: "uppercase",
1265
+ paddingLeft: "12px",
1266
+ paddingRight: "12px",
1267
+ cursor: "pointer"
1268
+ },
1269
+ children: binding.modes.map((mode) => /* @__PURE__ */ jsx8("option", { value: mode.value, disabled: mode.enabled === false, children: mode.label }, mode.value))
1270
+ }
1271
+ ) : modeSelectionStyle === "modal" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1272
+ /* @__PURE__ */ jsxs7(
1273
+ "div",
1274
+ {
1275
+ onClick: () => setShowModeModal(true),
1276
+ style: {
1277
+ padding: "12px",
1278
+ backgroundColor: surfaceColor,
1279
+ border: `2px solid ${borderColor}`,
1280
+ borderRadius: "6px",
1281
+ fontSize: `${fontSize.label}px`,
1282
+ fontWeight: 600,
1283
+ fontFamily: "Arial, sans-serif",
1284
+ letterSpacing: "0.5px",
1285
+ textTransform: "uppercase",
1286
+ cursor: "pointer",
1287
+ display: "flex",
1288
+ justifyContent: "space-between",
1289
+ alignItems: "center"
1290
+ },
1291
+ children: [
1292
+ /* @__PURE__ */ jsx8("span", { children: binding.modes.find((m) => m.value === currentMode)?.label || currentMode }),
1293
+ /* @__PURE__ */ jsx8("span", { style: { fontSize: "20px", marginLeft: "8px" }, children: "\u25BC" })
1294
+ ]
1295
+ }
1296
+ ),
1297
+ showModeModal && /* @__PURE__ */ jsx8(
1298
+ "div",
1299
+ {
1300
+ style: {
1301
+ position: "fixed",
1302
+ top: 0,
1303
+ left: 0,
1304
+ right: 0,
1305
+ bottom: 0,
1306
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
1307
+ display: "flex",
1308
+ alignItems: "center",
1309
+ justifyContent: "center",
1310
+ zIndex: 2e4
1311
+ },
1312
+ onClick: () => {
1313
+ setShowModeModal(false);
1314
+ setPendingMode(null);
1315
+ },
1316
+ children: /* @__PURE__ */ jsxs7(
1317
+ "div",
1318
+ {
1319
+ onClick: (e) => e.stopPropagation(),
1320
+ style: {
1321
+ backgroundColor,
1322
+ borderRadius: "8px",
1323
+ padding: "24px",
1324
+ minWidth: "300px",
1325
+ maxWidth: "400px",
1326
+ border: `2px solid ${borderColor}`
1327
+ },
1328
+ children: [
1329
+ /* @__PURE__ */ jsx8(
1330
+ "h3",
1331
+ {
1332
+ style: {
1333
+ margin: "0 0 4px 0",
1334
+ fontSize: `${fontSize.title}px`,
1335
+ fontWeight: 600,
1336
+ fontFamily: "Arial, sans-serif",
1337
+ color: textColor
1338
+ },
1339
+ children: "Select Mode"
1340
+ }
1341
+ ),
1342
+ /* @__PURE__ */ jsx8(
1343
+ "div",
1344
+ {
1345
+ style: {
1346
+ marginBottom: "16px",
1347
+ fontSize: `${fontSize.label}px`,
1348
+ fontWeight: 600,
1349
+ fontFamily: "Arial, sans-serif",
1350
+ color: mutedTextColor,
1351
+ textTransform: "uppercase"
1352
+ },
1353
+ children: binding.display?.title || binding.deviceName || binding.tag || binding.nodeId
1354
+ }
1355
+ ),
1356
+ /* @__PURE__ */ jsx8(
1357
+ "select",
1358
+ {
1359
+ value: pendingMode || currentMode,
1360
+ onChange: (e) => setPendingMode(e.target.value),
1361
+ style: {
1362
+ width: "100%",
1363
+ height: `${touchSize}px`,
1364
+ marginBottom: "8px",
1365
+ backgroundColor: surfaceColor,
1366
+ color: textColor,
1367
+ border: `2px solid ${borderColor}`,
1368
+ borderRadius: "6px",
1369
+ fontSize: `${fontSize.label}px`,
1370
+ fontWeight: 600,
1371
+ fontFamily: "Arial, sans-serif",
1372
+ letterSpacing: "0.5px",
1373
+ textTransform: "uppercase",
1374
+ paddingLeft: "12px",
1375
+ paddingRight: "12px",
1376
+ cursor: "pointer"
1377
+ },
1378
+ children: binding.modes.map((mode) => /* @__PURE__ */ jsx8(
1379
+ "option",
1380
+ {
1381
+ value: mode.value,
1382
+ disabled: mode.enabled === false,
1383
+ children: mode.label
1384
+ },
1385
+ mode.value
1386
+ ))
1387
+ }
1388
+ ),
1389
+ (() => {
1390
+ const selectedMode = binding.modes.find(
1391
+ (m) => m.value === (pendingMode || currentMode)
1392
+ );
1393
+ return selectedMode?.description ? /* @__PURE__ */ jsx8(
1394
+ "div",
1395
+ {
1396
+ style: {
1397
+ marginBottom: "16px",
1398
+ padding: "12px",
1399
+ backgroundColor: surfaceColor,
1400
+ border: `1px solid ${borderColor}`,
1401
+ borderRadius: "6px",
1402
+ fontSize: `${fontSize.description}px`,
1403
+ color: mutedTextColor,
1404
+ fontFamily: "Arial, sans-serif",
1405
+ lineHeight: "1.4"
1406
+ },
1407
+ children: selectedMode.description
1408
+ }
1409
+ ) : null;
1410
+ })(),
1411
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "12px" }, children: [
1412
+ /* @__PURE__ */ jsx8(
1413
+ "button",
1414
+ {
1415
+ onClick: () => {
1416
+ if (pendingMode && pendingMode !== currentMode) {
1417
+ handleModeChange(pendingMode);
1418
+ }
1419
+ setShowModeModal(false);
1420
+ setPendingMode(null);
1421
+ },
1422
+ style: {
1423
+ flex: 1,
1424
+ height: `${touchSize}px`,
1425
+ backgroundColor: "#4ade80",
1426
+ color: "#000000",
1427
+ border: "2px solid #22c55e",
1428
+ borderRadius: "6px",
1429
+ fontSize: `${fontSize.button}px`,
1430
+ fontWeight: 600,
1431
+ fontFamily: "Arial, sans-serif",
1432
+ letterSpacing: "0.5px",
1433
+ textTransform: "uppercase",
1434
+ cursor: "pointer"
1435
+ },
1436
+ children: "Confirm"
1437
+ }
1438
+ ),
1439
+ /* @__PURE__ */ jsx8(
1440
+ "button",
1441
+ {
1442
+ onClick: () => {
1443
+ setShowModeModal(false);
1444
+ setPendingMode(null);
1445
+ },
1446
+ style: {
1447
+ flex: 1,
1448
+ height: `${touchSize}px`,
1449
+ backgroundColor: surfaceColor,
1450
+ color: textColor,
1451
+ border: `2px solid ${borderColor}`,
1452
+ borderRadius: "6px",
1453
+ fontSize: `${fontSize.button}px`,
1454
+ fontWeight: 600,
1455
+ fontFamily: "Arial, sans-serif",
1456
+ letterSpacing: "0.5px",
1457
+ textTransform: "uppercase",
1458
+ cursor: "pointer"
1459
+ },
1460
+ children: "Cancel"
1461
+ }
1462
+ )
1463
+ ] })
1464
+ ]
1465
+ }
1466
+ )
1467
+ }
1468
+ )
1469
+ ] }) : /* @__PURE__ */ jsx8(
1172
1470
  "div",
1173
1471
  {
1174
1472
  style: {
@@ -7497,19 +7795,1000 @@ function useDeviceControls(initialBindings) {
7497
7795
  };
7498
7796
  }
7499
7797
 
7500
- // src/diagram/hooks/useSimulation.ts
7501
- import { useEffect as useEffect12 } from "react";
7502
- function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7503
- const {
7504
- interval = 2e3,
7505
- enabled = true,
7506
- maxChange = 10,
7507
- minValue = 0,
7508
- maxValue = 100,
7509
- historyLength = 10,
7510
- generateValue
7511
- } = options;
7512
- useEffect12(() => {
7798
+ // src/diagram/schema/DeviceConfigBinding.ts
7799
+ function createConfigBinding(nodeId, tag, parameters, actions, options) {
7800
+ return {
7801
+ nodeId,
7802
+ tag,
7803
+ deviceName: options?.deviceName,
7804
+ deviceType: options?.deviceType,
7805
+ sections: options?.sections,
7806
+ parameters,
7807
+ state: {
7808
+ isDirty: false,
7809
+ isSaving: false,
7810
+ errors: {}
7811
+ },
7812
+ actions,
7813
+ display: options?.display,
7814
+ theme: options?.theme,
7815
+ metadata: options?.metadata
7816
+ };
7817
+ }
7818
+
7819
+ // src/hooks/useDeviceConfigs.ts
7820
+ import { useState as useState16, useCallback as useCallback9 } from "react";
7821
+ function useDeviceConfigs(initialConfigs) {
7822
+ const [configs, setConfigs] = useState16(() => {
7823
+ const map = /* @__PURE__ */ new Map();
7824
+ if (initialConfigs) {
7825
+ initialConfigs.forEach((config) => {
7826
+ map.set(config.nodeId, config);
7827
+ });
7828
+ }
7829
+ return map;
7830
+ });
7831
+ const getConfig = useCallback9(
7832
+ (nodeId) => {
7833
+ return configs.get(nodeId);
7834
+ },
7835
+ [configs]
7836
+ );
7837
+ const updateConfig = useCallback9((nodeId, updates) => {
7838
+ setConfigs((prev) => {
7839
+ const newMap = new Map(prev);
7840
+ const existing = newMap.get(nodeId);
7841
+ if (existing) {
7842
+ newMap.set(nodeId, { ...existing, ...updates });
7843
+ } else {
7844
+ if ("tag" in updates && "parameters" in updates && "state" in updates && "actions" in updates) {
7845
+ newMap.set(nodeId, { nodeId, ...updates });
7846
+ }
7847
+ }
7848
+ return newMap;
7849
+ });
7850
+ }, []);
7851
+ const updateConfigState = useCallback9((nodeId, stateUpdates) => {
7852
+ setConfigs((prev) => {
7853
+ const newMap = new Map(prev);
7854
+ const existing = newMap.get(nodeId);
7855
+ if (existing) {
7856
+ newMap.set(nodeId, {
7857
+ ...existing,
7858
+ state: { ...existing.state, ...stateUpdates }
7859
+ });
7860
+ }
7861
+ return newMap;
7862
+ });
7863
+ }, []);
7864
+ const updateConfigBatch = useCallback9((updates) => {
7865
+ setConfigs((prev) => {
7866
+ const newMap = new Map(prev);
7867
+ Object.entries(updates).forEach(([nodeId, update]) => {
7868
+ const existing = newMap.get(nodeId);
7869
+ if (existing) {
7870
+ newMap.set(nodeId, { ...existing, ...update });
7871
+ }
7872
+ });
7873
+ return newMap;
7874
+ });
7875
+ }, []);
7876
+ const updateParameterValue = useCallback9((nodeId, parameterId, value) => {
7877
+ setConfigs((prev) => {
7878
+ const newMap = new Map(prev);
7879
+ const existing = newMap.get(nodeId);
7880
+ if (existing) {
7881
+ const updatedParameters = existing.parameters.map(
7882
+ (param) => param.id === parameterId ? { ...param, value } : param
7883
+ );
7884
+ newMap.set(nodeId, {
7885
+ ...existing,
7886
+ parameters: updatedParameters,
7887
+ state: { ...existing.state, isDirty: true }
7888
+ });
7889
+ }
7890
+ return newMap;
7891
+ });
7892
+ }, []);
7893
+ const applyConfig = useCallback9(
7894
+ async (nodeId) => {
7895
+ const config = configs.get(nodeId);
7896
+ if (!config || !config.actions.onApply) {
7897
+ return { success: false, message: "No apply handler defined" };
7898
+ }
7899
+ updateConfigState(nodeId, { isSaving: true });
7900
+ try {
7901
+ const values = config.parameters.reduce(
7902
+ (acc, param) => {
7903
+ acc[param.id] = param.value;
7904
+ return acc;
7905
+ },
7906
+ {}
7907
+ );
7908
+ const result = await config.actions.onApply(values);
7909
+ updateConfigState(nodeId, {
7910
+ isSaving: false,
7911
+ lastSaveResult: result,
7912
+ isDirty: result.success ? false : config.state.isDirty
7913
+ });
7914
+ return result;
7915
+ } catch (error) {
7916
+ const message = error instanceof Error ? error.message : "Apply failed";
7917
+ updateConfigState(nodeId, {
7918
+ isSaving: false,
7919
+ lastSaveResult: { success: false, message }
7920
+ });
7921
+ return { success: false, message };
7922
+ }
7923
+ },
7924
+ [configs, updateConfigState]
7925
+ );
7926
+ const saveConfig = useCallback9(
7927
+ async (nodeId) => {
7928
+ const config = configs.get(nodeId);
7929
+ if (!config || !config.actions.onSave) {
7930
+ return { success: false, message: "No save handler defined" };
7931
+ }
7932
+ updateConfigState(nodeId, { isSaving: true });
7933
+ try {
7934
+ const values = config.parameters.reduce(
7935
+ (acc, param) => {
7936
+ acc[param.id] = param.value;
7937
+ return acc;
7938
+ },
7939
+ {}
7940
+ );
7941
+ const result = await config.actions.onSave(values);
7942
+ updateConfigState(nodeId, {
7943
+ isSaving: false,
7944
+ lastSaved: result.success ? Date.now() : config.state.lastSaved,
7945
+ lastSaveResult: result,
7946
+ isDirty: result.success ? false : config.state.isDirty
7947
+ });
7948
+ return result;
7949
+ } catch (error) {
7950
+ const message = error instanceof Error ? error.message : "Save failed";
7951
+ updateConfigState(nodeId, {
7952
+ isSaving: false,
7953
+ lastSaveResult: { success: false, message }
7954
+ });
7955
+ return { success: false, message };
7956
+ }
7957
+ },
7958
+ [configs, updateConfigState]
7959
+ );
7960
+ const resetConfig = useCallback9(
7961
+ async (nodeId) => {
7962
+ const config = configs.get(nodeId);
7963
+ if (!config) {
7964
+ return { success: false, message: "Configuration not found" };
7965
+ }
7966
+ try {
7967
+ const resetParameters = config.parameters.map((param) => ({
7968
+ ...param,
7969
+ value: param.defaultValue !== void 0 ? param.defaultValue : param.value
7970
+ }));
7971
+ updateConfig(nodeId, {
7972
+ parameters: resetParameters,
7973
+ state: { ...config.state, isDirty: true }
7974
+ });
7975
+ if (config.actions.onReset) {
7976
+ const result = await config.actions.onReset();
7977
+ updateConfigState(nodeId, {
7978
+ lastSaveResult: result
7979
+ });
7980
+ return result;
7981
+ }
7982
+ return { success: true, message: "Reset to defaults" };
7983
+ } catch (error) {
7984
+ const message = error instanceof Error ? error.message : "Reset failed";
7985
+ return { success: false, message };
7986
+ }
7987
+ },
7988
+ [configs, updateConfig, updateConfigState]
7989
+ );
7990
+ const cancelConfig = useCallback9(
7991
+ (nodeId) => {
7992
+ const config = configs.get(nodeId);
7993
+ if (config?.actions.onCancel) {
7994
+ config.actions.onCancel();
7995
+ }
7996
+ },
7997
+ [configs]
7998
+ );
7999
+ const validateConfig = useCallback9(
8000
+ (nodeId) => {
8001
+ const config = configs.get(nodeId);
8002
+ if (!config) {
8003
+ return { valid: false, errors: { _general: "Configuration not found" } };
8004
+ }
8005
+ const errors = {};
8006
+ const values = config.parameters.reduce(
8007
+ (acc, param) => {
8008
+ acc[param.id] = param.value;
8009
+ return acc;
8010
+ },
8011
+ {}
8012
+ );
8013
+ config.parameters.forEach((param) => {
8014
+ if (param.required && (param.value === void 0 || param.value === null || param.value === "")) {
8015
+ errors[param.id] = `${param.label} is required`;
8016
+ return;
8017
+ }
8018
+ if (param.type === "number" && typeof param.value === "number") {
8019
+ if (param.min !== void 0 && param.value < param.min) {
8020
+ errors[param.id] = `${param.label} must be at least ${param.min}`;
8021
+ }
8022
+ if (param.max !== void 0 && param.value > param.max) {
8023
+ errors[param.id] = `${param.label} must be at most ${param.max}`;
8024
+ }
8025
+ }
8026
+ if (param.validate) {
8027
+ const result = param.validate(param.value);
8028
+ if (result !== true) {
8029
+ errors[param.id] = typeof result === "string" ? result : `${param.label} is invalid`;
8030
+ }
8031
+ }
8032
+ if (param.visibleWhen && !param.visibleWhen(values)) {
8033
+ delete errors[param.id];
8034
+ }
8035
+ });
8036
+ updateConfigState(nodeId, { errors });
8037
+ return { valid: Object.keys(errors).length === 0, errors };
8038
+ },
8039
+ [configs, updateConfigState]
8040
+ );
8041
+ const setConfig = useCallback9((config) => {
8042
+ setConfigs((prev) => {
8043
+ const newMap = new Map(prev);
8044
+ newMap.set(config.nodeId, config);
8045
+ return newMap;
8046
+ });
8047
+ }, []);
8048
+ const removeConfig = useCallback9((nodeId) => {
8049
+ setConfigs((prev) => {
8050
+ const newMap = new Map(prev);
8051
+ newMap.delete(nodeId);
8052
+ return newMap;
8053
+ });
8054
+ }, []);
8055
+ const clearConfigs = useCallback9(() => {
8056
+ setConfigs(/* @__PURE__ */ new Map());
8057
+ }, []);
8058
+ return {
8059
+ configs,
8060
+ getConfig,
8061
+ setConfig,
8062
+ removeConfig,
8063
+ clearConfigs,
8064
+ updateConfig,
8065
+ updateConfigState,
8066
+ updateConfigBatch,
8067
+ updateParameterValue,
8068
+ applyConfig,
8069
+ saveConfig,
8070
+ resetConfig,
8071
+ cancelConfig,
8072
+ validateConfig
8073
+ };
8074
+ }
8075
+
8076
+ // src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
8077
+ import { useState as useState17, useEffect as useEffect12, useMemo, useRef as useRef6 } from "react";
8078
+ import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
8079
+ function DeviceConfigPanel({
8080
+ binding,
8081
+ onClose,
8082
+ onUndock,
8083
+ undockIcon = "\u21F1",
8084
+ position = "bottom",
8085
+ align = "center",
8086
+ touchOptimized = true,
8087
+ compact = false,
8088
+ fontScale = 1,
8089
+ maxHeight,
8090
+ maxWidth,
8091
+ toastDuration = 3e3,
8092
+ onOpenControls,
8093
+ showControlsButton = true,
8094
+ embedded = false
8095
+ }) {
8096
+ const [localValues, setLocalValues] = useState17(() => {
8097
+ if (binding) {
8098
+ const values = {};
8099
+ binding.parameters.forEach((param) => {
8100
+ values[param.id] = param.value;
8101
+ });
8102
+ return values;
8103
+ }
8104
+ return {};
8105
+ });
8106
+ const [collapsedSections, setCollapsedSections] = useState17(() => {
8107
+ const collapsed = /* @__PURE__ */ new Set();
8108
+ if (binding?.sections) {
8109
+ binding.sections.forEach((section) => {
8110
+ if (section.collapsible && section.defaultCollapsed) {
8111
+ collapsed.add(section.id);
8112
+ }
8113
+ });
8114
+ }
8115
+ return collapsed;
8116
+ });
8117
+ const [showToast, setShowToast] = useState17(true);
8118
+ const [bindingId, setBindingId] = useState17(binding?.nodeId || null);
8119
+ const lastResultTimestampRef = useRef6(0);
8120
+ if (binding && binding.nodeId !== bindingId) {
8121
+ const values = {};
8122
+ binding.parameters.forEach((param) => {
8123
+ values[param.id] = param.value;
8124
+ });
8125
+ setLocalValues(values);
8126
+ const collapsed = /* @__PURE__ */ new Set();
8127
+ binding.sections?.forEach((section) => {
8128
+ if (section.collapsible && section.defaultCollapsed) {
8129
+ collapsed.add(section.id);
8130
+ }
8131
+ });
8132
+ setCollapsedSections(collapsed);
8133
+ setBindingId(binding.nodeId);
8134
+ }
8135
+ useEffect12(() => {
8136
+ const currentTimestamp = binding?.state.lastSaved || 0;
8137
+ if (binding?.state.lastSaveResult && currentTimestamp !== lastResultTimestampRef.current) {
8138
+ lastResultTimestampRef.current = currentTimestamp;
8139
+ const showTimer = setTimeout(() => {
8140
+ setShowToast(true);
8141
+ const hideTimer = setTimeout(() => {
8142
+ setShowToast(false);
8143
+ }, toastDuration);
8144
+ return () => clearTimeout(hideTimer);
8145
+ }, 0);
8146
+ return () => clearTimeout(showTimer);
8147
+ }
8148
+ }, [binding?.state.lastSaveResult, binding?.state.lastSaved, toastDuration]);
8149
+ const parametersBySection = useMemo(() => {
8150
+ if (!binding) return { _unsectioned: [] };
8151
+ const groups = { _unsectioned: [] };
8152
+ binding.parameters.forEach((param) => {
8153
+ const sectionId = param.section || "_unsectioned";
8154
+ if (!groups[sectionId]) {
8155
+ groups[sectionId] = [];
8156
+ }
8157
+ groups[sectionId].push(param);
8158
+ });
8159
+ Object.keys(groups).forEach((sectionId) => {
8160
+ groups[sectionId].sort((a, b) => (a.order || 0) - (b.order || 0));
8161
+ });
8162
+ return groups;
8163
+ }, [binding]);
8164
+ const sortedSections = useMemo(() => {
8165
+ if (!binding?.sections) return [];
8166
+ return [...binding.sections].sort((a, b) => (a.order || 0) - (b.order || 0));
8167
+ }, [binding]);
8168
+ if (!binding) return null;
8169
+ const touchSize = touchOptimized ? 56 : 40;
8170
+ const spacing = {
8171
+ padding: compact ? "12px" : "20px",
8172
+ gap: compact ? "6px" : "8px",
8173
+ marginBottom: compact ? "10px" : "16px",
8174
+ itemPadding: compact ? "8px 10px" : "10px 12px"
8175
+ };
8176
+ const fontSize = {
8177
+ title: Math.round(14 * fontScale),
8178
+ label: Math.round(11 * fontScale),
8179
+ value: Math.round(13 * fontScale),
8180
+ input: Math.round(13 * fontScale),
8181
+ button: Math.round(12 * fontScale),
8182
+ section: Math.round(12 * fontScale),
8183
+ description: Math.round(10 * fontScale)
8184
+ };
8185
+ const backgroundColor = binding.theme?.backgroundColor || "#edeeef";
8186
+ const surfaceColor = binding.theme?.surfaceColor || "#f5f6f7";
8187
+ const borderColor = binding.theme?.borderColor || "#d2d2d2";
8188
+ const textColor = binding.theme?.textColor || "#000000";
8189
+ const mutedTextColor = binding.theme?.mutedTextColor || "#666666";
8190
+ const accentColor = binding.theme?.accentColor || "#b4d0fe";
8191
+ const errorColor = binding.theme?.errorColor || "#ef4444";
8192
+ const successColor = binding.theme?.successColor || "#10b981";
8193
+ const handleValueChange = (parameterId, value) => {
8194
+ setLocalValues((prev) => ({ ...prev, [parameterId]: value }));
8195
+ };
8196
+ const handleApply = async () => {
8197
+ if (binding.actions.onApply) {
8198
+ await binding.actions.onApply(localValues);
8199
+ }
8200
+ };
8201
+ const handleSave = async () => {
8202
+ if (binding.actions.onSave) {
8203
+ await binding.actions.onSave(localValues);
8204
+ }
8205
+ };
8206
+ const handleReset = async () => {
8207
+ if (binding.display?.resetConfirmMessage) {
8208
+ if (!confirm(binding.display.resetConfirmMessage)) {
8209
+ return;
8210
+ }
8211
+ }
8212
+ if (binding.actions.onReset) {
8213
+ await binding.actions.onReset();
8214
+ }
8215
+ };
8216
+ const handleCancel = () => {
8217
+ if (binding.actions.onCancel) {
8218
+ binding.actions.onCancel();
8219
+ }
8220
+ onClose();
8221
+ };
8222
+ const toggleSection = (sectionId) => {
8223
+ setCollapsedSections((prev) => {
8224
+ const next = new Set(prev);
8225
+ if (next.has(sectionId)) {
8226
+ next.delete(sectionId);
8227
+ } else {
8228
+ next.add(sectionId);
8229
+ }
8230
+ return next;
8231
+ });
8232
+ };
8233
+ const renderParameter = (param) => {
8234
+ if (param.visibleWhen && !param.visibleWhen(localValues)) {
8235
+ return null;
8236
+ }
8237
+ const value = localValues[param.id];
8238
+ const error = binding.state.errors?.[param.id];
8239
+ const isDisabled = param.readOnly || param.enabled === false;
8240
+ return /* @__PURE__ */ jsxs18(
8241
+ "div",
8242
+ {
8243
+ style: {
8244
+ marginBottom: spacing.marginBottom
8245
+ },
8246
+ children: [
8247
+ /* @__PURE__ */ jsxs18(
8248
+ "label",
8249
+ {
8250
+ style: {
8251
+ display: "block",
8252
+ fontSize: `${fontSize.label}px`,
8253
+ fontWeight: 600,
8254
+ color: textColor,
8255
+ marginBottom: "4px",
8256
+ fontFamily: "Arial, sans-serif",
8257
+ textTransform: "uppercase",
8258
+ letterSpacing: "0.5px"
8259
+ },
8260
+ children: [
8261
+ param.label,
8262
+ param.required && /* @__PURE__ */ jsx22("span", { style: { color: errorColor }, children: " *" }),
8263
+ param.unit && /* @__PURE__ */ jsxs18("span", { style: { color: mutedTextColor, fontWeight: "normal" }, children: [
8264
+ " (",
8265
+ param.unit,
8266
+ ")"
8267
+ ] })
8268
+ ]
8269
+ }
8270
+ ),
8271
+ param.description && /* @__PURE__ */ jsx22(
8272
+ "div",
8273
+ {
8274
+ style: {
8275
+ fontSize: `${fontSize.description}px`,
8276
+ color: mutedTextColor,
8277
+ marginBottom: "6px",
8278
+ fontFamily: "Arial, sans-serif"
8279
+ },
8280
+ children: param.description
8281
+ }
8282
+ ),
8283
+ param.type === "number" && /* @__PURE__ */ jsx22(
8284
+ "input",
8285
+ {
8286
+ type: "number",
8287
+ value: value ?? "",
8288
+ onChange: (e) => handleValueChange(param.id, parseFloat(e.target.value)),
8289
+ min: param.min,
8290
+ max: param.max,
8291
+ step: param.step,
8292
+ disabled: isDisabled,
8293
+ style: {
8294
+ width: "100%",
8295
+ height: `${touchSize * 0.8}px`,
8296
+ padding: spacing.itemPadding,
8297
+ fontSize: `${fontSize.input}px`,
8298
+ fontFamily: "Arial, sans-serif",
8299
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8300
+ color: textColor,
8301
+ border: `1px solid ${error ? errorColor : borderColor}`,
8302
+ borderRadius: "2px",
8303
+ outline: "none"
8304
+ }
8305
+ }
8306
+ ),
8307
+ param.type === "string" && /* @__PURE__ */ jsx22(
8308
+ "input",
8309
+ {
8310
+ type: "text",
8311
+ value: value ?? "",
8312
+ onChange: (e) => handleValueChange(param.id, e.target.value),
8313
+ disabled: isDisabled,
8314
+ style: {
8315
+ width: "100%",
8316
+ height: `${touchSize * 0.8}px`,
8317
+ padding: spacing.itemPadding,
8318
+ fontSize: `${fontSize.input}px`,
8319
+ fontFamily: "Arial, sans-serif",
8320
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8321
+ color: textColor,
8322
+ border: `1px solid ${error ? errorColor : borderColor}`,
8323
+ borderRadius: "2px",
8324
+ outline: "none"
8325
+ }
8326
+ }
8327
+ ),
8328
+ param.type === "boolean" && /* @__PURE__ */ jsxs18(
8329
+ "label",
8330
+ {
8331
+ style: {
8332
+ display: "flex",
8333
+ alignItems: "center",
8334
+ gap: "8px",
8335
+ cursor: isDisabled ? "not-allowed" : "pointer"
8336
+ },
8337
+ children: [
8338
+ /* @__PURE__ */ jsx22(
8339
+ "input",
8340
+ {
8341
+ type: "checkbox",
8342
+ checked: value ?? false,
8343
+ onChange: (e) => handleValueChange(param.id, e.target.checked),
8344
+ disabled: isDisabled,
8345
+ style: {
8346
+ width: "20px",
8347
+ height: "20px",
8348
+ cursor: isDisabled ? "not-allowed" : "pointer"
8349
+ }
8350
+ }
8351
+ ),
8352
+ /* @__PURE__ */ jsx22("span", { style: { fontSize: `${fontSize.value}px`, color: textColor }, children: value ? "Enabled" : "Disabled" })
8353
+ ]
8354
+ }
8355
+ ),
8356
+ param.type === "select" && /* @__PURE__ */ jsx22(
8357
+ "select",
8358
+ {
8359
+ value: value ?? "",
8360
+ onChange: (e) => handleValueChange(param.id, e.target.value),
8361
+ disabled: isDisabled,
8362
+ style: {
8363
+ width: "100%",
8364
+ height: `${touchSize * 0.8}px`,
8365
+ padding: spacing.itemPadding,
8366
+ fontSize: `${fontSize.input}px`,
8367
+ fontFamily: "Arial, sans-serif",
8368
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8369
+ color: textColor,
8370
+ border: `1px solid ${error ? errorColor : borderColor}`,
8371
+ borderRadius: "2px",
8372
+ outline: "none"
8373
+ },
8374
+ children: param.options?.map((option) => /* @__PURE__ */ jsx22("option", { value: option.value, children: option.label }, option.value))
8375
+ }
8376
+ ),
8377
+ error && /* @__PURE__ */ jsx22(
8378
+ "div",
8379
+ {
8380
+ style: {
8381
+ fontSize: `${fontSize.description}px`,
8382
+ color: errorColor,
8383
+ marginTop: "4px",
8384
+ fontFamily: "Arial, sans-serif"
8385
+ },
8386
+ children: error
8387
+ }
8388
+ )
8389
+ ]
8390
+ },
8391
+ param.id
8392
+ );
8393
+ };
8394
+ const renderSection = (section) => {
8395
+ const params = parametersBySection[section.id] || [];
8396
+ const isCollapsed = collapsedSections.has(section.id);
8397
+ return /* @__PURE__ */ jsxs18(
8398
+ "div",
8399
+ {
8400
+ style: {
8401
+ marginBottom: spacing.marginBottom,
8402
+ border: `1px solid ${borderColor}`,
8403
+ borderRadius: "4px",
8404
+ backgroundColor: surfaceColor
8405
+ },
8406
+ children: [
8407
+ /* @__PURE__ */ jsxs18(
8408
+ "div",
8409
+ {
8410
+ onClick: () => section.collapsible && toggleSection(section.id),
8411
+ style: {
8412
+ padding: spacing.itemPadding,
8413
+ borderBottom: isCollapsed ? "none" : `1px solid ${borderColor}`,
8414
+ cursor: section.collapsible ? "pointer" : "default",
8415
+ display: "flex",
8416
+ justifyContent: "space-between",
8417
+ alignItems: "center"
8418
+ },
8419
+ children: [
8420
+ /* @__PURE__ */ jsxs18("div", { children: [
8421
+ /* @__PURE__ */ jsxs18(
8422
+ "div",
8423
+ {
8424
+ style: {
8425
+ fontSize: `${fontSize.section}px`,
8426
+ fontWeight: 600,
8427
+ color: textColor,
8428
+ fontFamily: "Arial, sans-serif",
8429
+ textTransform: "uppercase",
8430
+ letterSpacing: "0.5px"
8431
+ },
8432
+ children: [
8433
+ section.icon && /* @__PURE__ */ jsx22("span", { style: { marginRight: "8px" }, children: section.icon }),
8434
+ section.title
8435
+ ]
8436
+ }
8437
+ ),
8438
+ section.description && /* @__PURE__ */ jsx22(
8439
+ "div",
8440
+ {
8441
+ style: {
8442
+ fontSize: `${fontSize.description}px`,
8443
+ color: mutedTextColor,
8444
+ marginTop: "4px",
8445
+ fontFamily: "Arial, sans-serif"
8446
+ },
8447
+ children: section.description
8448
+ }
8449
+ )
8450
+ ] }),
8451
+ section.collapsible && /* @__PURE__ */ jsx22("span", { style: { fontSize: "16px", color: mutedTextColor }, children: isCollapsed ? "\u25BC" : "\u25B2" })
8452
+ ]
8453
+ }
8454
+ ),
8455
+ !isCollapsed && /* @__PURE__ */ jsx22("div", { style: { padding: spacing.padding }, children: params.map(renderParameter) })
8456
+ ]
8457
+ },
8458
+ section.id
8459
+ );
8460
+ };
8461
+ const panelStyle = embedded ? {
8462
+ // Embedded mode: static positioning for container layouts
8463
+ position: "static",
8464
+ width: "100%",
8465
+ backgroundColor,
8466
+ color: textColor,
8467
+ borderRadius: "8px 8px 0 0",
8468
+ boxShadow: "none",
8469
+ border: `2px solid ${borderColor}`,
8470
+ padding: spacing.padding,
8471
+ maxHeight: maxHeight || "70vh",
8472
+ overflowY: "auto",
8473
+ display: "flex",
8474
+ flexDirection: "column",
8475
+ ...binding.display?.style
8476
+ } : {
8477
+ // Fixed mode: traditional overlay positioning
8478
+ position: "fixed",
8479
+ ...position === "bottom" ? {
8480
+ bottom: 0,
8481
+ ...align === "left" ? { left: 0, right: "auto", width: maxWidth } : align === "right" ? { right: 0, left: "auto", width: maxWidth } : {
8482
+ left: 0,
8483
+ right: 0,
8484
+ marginLeft: "auto",
8485
+ marginRight: "auto",
8486
+ maxWidth
8487
+ },
8488
+ animation: "slideUp 0.3s ease-out"
8489
+ } : {
8490
+ right: 0,
8491
+ top: 0,
8492
+ bottom: 0,
8493
+ width: maxWidth || "400px",
8494
+ animation: "slideIn 0.3s ease-out"
8495
+ },
8496
+ backgroundColor,
8497
+ color: textColor,
8498
+ borderRadius: position === "bottom" ? "8px 8px 0 0" : "8px 0 0 8px",
8499
+ boxShadow: "none",
8500
+ border: `2px solid ${borderColor}`,
8501
+ padding: spacing.padding,
8502
+ zIndex: 1e4,
8503
+ maxHeight: maxHeight || (position === "bottom" ? "70vh" : "100vh"),
8504
+ overflowY: "auto",
8505
+ display: "flex",
8506
+ flexDirection: "column",
8507
+ ...binding.display?.style
8508
+ };
8509
+ const titleText = binding.display?.title || binding.deviceName || binding.tag || binding.nodeId;
8510
+ const showApply = binding.display?.showApply !== false && binding.actions.onApply;
8511
+ const showSave = binding.display?.showSave !== false && binding.actions.onSave;
8512
+ const showReset = binding.display?.showReset !== false && binding.actions.onReset;
8513
+ const showCancel = binding.display?.showCancel !== false;
8514
+ return /* @__PURE__ */ jsxs18(Fragment9, { children: [
8515
+ /* @__PURE__ */ jsxs18("div", { style: panelStyle, className: binding.display?.className, children: [
8516
+ /* @__PURE__ */ jsxs18(
8517
+ "div",
8518
+ {
8519
+ style: {
8520
+ display: "flex",
8521
+ justifyContent: "space-between",
8522
+ alignItems: "center",
8523
+ marginBottom: "16px",
8524
+ paddingBottom: "12px",
8525
+ borderBottom: `2px solid ${borderColor}`
8526
+ },
8527
+ children: [
8528
+ /* @__PURE__ */ jsxs18("div", { children: [
8529
+ /* @__PURE__ */ jsx22(
8530
+ "h3",
8531
+ {
8532
+ style: {
8533
+ margin: 0,
8534
+ fontSize: `${fontSize.title}px`,
8535
+ fontWeight: 600,
8536
+ fontFamily: "Arial, sans-serif",
8537
+ letterSpacing: "0.5px",
8538
+ textTransform: "uppercase",
8539
+ color: textColor,
8540
+ overflow: "hidden",
8541
+ textOverflow: "ellipsis",
8542
+ whiteSpace: "nowrap",
8543
+ maxWidth: "200px"
8544
+ },
8545
+ title: titleText.length > 25 ? titleText : void 0,
8546
+ children: titleText.length > 25 ? titleText.slice(0, 25) + "..." : titleText
8547
+ }
8548
+ ),
8549
+ binding.display?.subtitle && /* @__PURE__ */ jsx22(
8550
+ "div",
8551
+ {
8552
+ style: {
8553
+ fontSize: `${fontSize.description}px`,
8554
+ color: mutedTextColor,
8555
+ marginTop: "4px",
8556
+ fontFamily: "Arial, sans-serif"
8557
+ },
8558
+ children: binding.display.subtitle
8559
+ }
8560
+ )
8561
+ ] }),
8562
+ /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: "8px" }, children: [
8563
+ showControlsButton && onOpenControls && /* @__PURE__ */ jsx22(
8564
+ "button",
8565
+ {
8566
+ onClick: onOpenControls,
8567
+ title: "Open Controls",
8568
+ style: {
8569
+ minWidth: `${touchSize * 1.2}px`,
8570
+ height: touchSize * 0.7,
8571
+ backgroundColor: surfaceColor,
8572
+ color: textColor,
8573
+ border: `2px solid ${borderColor}`,
8574
+ borderRadius: "2px",
8575
+ fontSize: `${fontSize.button}px`,
8576
+ cursor: "pointer",
8577
+ fontWeight: 600,
8578
+ fontFamily: "Arial, sans-serif",
8579
+ padding: "0 8px",
8580
+ textTransform: "uppercase",
8581
+ letterSpacing: "0.5px"
8582
+ },
8583
+ children: "CTRL"
8584
+ }
8585
+ ),
8586
+ onUndock && /* @__PURE__ */ jsx22(
8587
+ "button",
8588
+ {
8589
+ onClick: onUndock,
8590
+ title: "Undock panel",
8591
+ style: {
8592
+ width: touchSize * 0.7,
8593
+ height: touchSize * 0.7,
8594
+ backgroundColor: surfaceColor,
8595
+ color: textColor,
8596
+ border: `2px solid ${borderColor}`,
8597
+ borderRadius: "2px",
8598
+ fontSize: "16px",
8599
+ cursor: "pointer",
8600
+ fontWeight: 600,
8601
+ fontFamily: "Arial, sans-serif",
8602
+ padding: "0"
8603
+ },
8604
+ children: undockIcon
8605
+ }
8606
+ ),
8607
+ /* @__PURE__ */ jsx22(
8608
+ "button",
8609
+ {
8610
+ onClick: onClose,
8611
+ style: {
8612
+ width: touchSize * 0.7,
8613
+ height: touchSize * 0.7,
8614
+ backgroundColor: surfaceColor,
8615
+ color: textColor,
8616
+ border: `2px solid ${borderColor}`,
8617
+ borderRadius: "2px",
8618
+ fontSize: "18px",
8619
+ cursor: "pointer",
8620
+ fontWeight: "bold",
8621
+ fontFamily: "Arial, sans-serif"
8622
+ },
8623
+ children: "\xD7"
8624
+ }
8625
+ )
8626
+ ] })
8627
+ ]
8628
+ }
8629
+ ),
8630
+ binding.state.isDirty && /* @__PURE__ */ jsx22(
8631
+ "div",
8632
+ {
8633
+ style: {
8634
+ padding: spacing.itemPadding,
8635
+ backgroundColor: "#fff3cd",
8636
+ border: "1px solid #ffc107",
8637
+ borderRadius: "4px",
8638
+ marginBottom: spacing.marginBottom,
8639
+ fontSize: `${fontSize.description}px`,
8640
+ fontFamily: "Arial, sans-serif"
8641
+ },
8642
+ children: "Unsaved changes"
8643
+ }
8644
+ ),
8645
+ /* @__PURE__ */ jsx22("div", { style: { flex: 1, overflowY: "auto", marginBottom: spacing.marginBottom }, children: sortedSections.length > 0 ? sortedSections.map(renderSection) : (
8646
+ /* Render unsectioned parameters */
8647
+ /* @__PURE__ */ jsx22("div", { children: parametersBySection._unsectioned?.map(renderParameter) })
8648
+ ) }),
8649
+ /* @__PURE__ */ jsxs18(
8650
+ "div",
8651
+ {
8652
+ style: {
8653
+ display: "flex",
8654
+ gap: spacing.gap,
8655
+ paddingTop: "12px",
8656
+ borderTop: `2px solid ${borderColor}`,
8657
+ flexWrap: "wrap"
8658
+ },
8659
+ children: [
8660
+ showApply && /* @__PURE__ */ jsx22(
8661
+ "button",
8662
+ {
8663
+ onClick: handleApply,
8664
+ disabled: binding.state.isSaving,
8665
+ style: {
8666
+ flex: 1,
8667
+ minWidth: "80px",
8668
+ height: `${touchSize}px`,
8669
+ backgroundColor: accentColor,
8670
+ color: "#000000",
8671
+ border: `2px solid ${borderColor}`,
8672
+ borderRadius: "2px",
8673
+ fontSize: `${fontSize.button}px`,
8674
+ fontWeight: 600,
8675
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8676
+ fontFamily: "Arial, sans-serif",
8677
+ textTransform: "uppercase",
8678
+ letterSpacing: "0.5px"
8679
+ },
8680
+ children: binding.state.isSaving ? "Applying..." : "Apply"
8681
+ }
8682
+ ),
8683
+ showSave && /* @__PURE__ */ jsx22(
8684
+ "button",
8685
+ {
8686
+ onClick: handleSave,
8687
+ disabled: binding.state.isSaving,
8688
+ style: {
8689
+ flex: 1,
8690
+ minWidth: "80px",
8691
+ height: `${touchSize}px`,
8692
+ backgroundColor: successColor,
8693
+ color: "#ffffff",
8694
+ border: `2px solid ${borderColor}`,
8695
+ borderRadius: "2px",
8696
+ fontSize: `${fontSize.button}px`,
8697
+ fontWeight: 600,
8698
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8699
+ fontFamily: "Arial, sans-serif",
8700
+ textTransform: "uppercase",
8701
+ letterSpacing: "0.5px"
8702
+ },
8703
+ children: binding.state.isSaving ? "Saving..." : "Save"
8704
+ }
8705
+ ),
8706
+ showReset && /* @__PURE__ */ jsx22(
8707
+ "button",
8708
+ {
8709
+ onClick: handleReset,
8710
+ disabled: binding.state.isSaving,
8711
+ style: {
8712
+ flex: 1,
8713
+ minWidth: "80px",
8714
+ height: `${touchSize}px`,
8715
+ backgroundColor: surfaceColor,
8716
+ color: textColor,
8717
+ border: `2px solid ${borderColor}`,
8718
+ borderRadius: "2px",
8719
+ fontSize: `${fontSize.button}px`,
8720
+ fontWeight: 600,
8721
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8722
+ fontFamily: "Arial, sans-serif",
8723
+ textTransform: "uppercase",
8724
+ letterSpacing: "0.5px"
8725
+ },
8726
+ children: "Reset"
8727
+ }
8728
+ ),
8729
+ showCancel && /* @__PURE__ */ jsx22(
8730
+ "button",
8731
+ {
8732
+ onClick: handleCancel,
8733
+ disabled: binding.state.isSaving,
8734
+ style: {
8735
+ flex: 1,
8736
+ minWidth: "80px",
8737
+ height: `${touchSize}px`,
8738
+ backgroundColor: surfaceColor,
8739
+ color: textColor,
8740
+ border: `2px solid ${borderColor}`,
8741
+ borderRadius: "2px",
8742
+ fontSize: `${fontSize.button}px`,
8743
+ fontWeight: 600,
8744
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8745
+ fontFamily: "Arial, sans-serif",
8746
+ textTransform: "uppercase",
8747
+ letterSpacing: "0.5px"
8748
+ },
8749
+ children: "Cancel"
8750
+ }
8751
+ )
8752
+ ]
8753
+ }
8754
+ )
8755
+ ] }),
8756
+ binding.state.lastSaveResult && showToast && /* @__PURE__ */ jsx22(
8757
+ "div",
8758
+ {
8759
+ style: {
8760
+ position: "fixed",
8761
+ bottom: "20px",
8762
+ ...align === "right" ? { left: "20px" } : { right: "20px" },
8763
+ backgroundColor: binding.state.lastSaveResult.success ? successColor : errorColor,
8764
+ color: "#ffffff",
8765
+ padding: "12px 20px",
8766
+ borderRadius: "4px",
8767
+ boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
8768
+ zIndex: 1e4,
8769
+ fontSize: `${fontSize.value}px`,
8770
+ fontFamily: "Arial, sans-serif",
8771
+ fontWeight: 600
8772
+ },
8773
+ children: binding.state.lastSaveResult.message || (binding.state.lastSaveResult.success ? "Success" : "Error")
8774
+ }
8775
+ )
8776
+ ] });
8777
+ }
8778
+
8779
+ // src/diagram/hooks/useSimulation.ts
8780
+ import { useEffect as useEffect13 } from "react";
8781
+ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
8782
+ const {
8783
+ interval = 2e3,
8784
+ enabled = true,
8785
+ maxChange = 10,
8786
+ minValue = 0,
8787
+ maxValue = 100,
8788
+ historyLength = 10,
8789
+ generateValue
8790
+ } = options;
8791
+ useEffect13(() => {
7513
8792
  if (!enabled) return;
7514
8793
  const intervalId = setInterval(() => {
7515
8794
  const updates = [];
@@ -7566,6 +8845,7 @@ export {
7566
8845
  ControlPanel,
7567
8846
  DEFAULT_THRESHOLDS,
7568
8847
  DashboardCard,
8848
+ DeviceConfigPanel,
7569
8849
  DeviceControlPanel,
7570
8850
  DisplayModeToggle,
7571
8851
  EquipmentIndicator,
@@ -7598,6 +8878,7 @@ export {
7598
8878
  ZoomButton,
7599
8879
  ZoomControls,
7600
8880
  calculateThresholdStatus,
8881
+ createConfigBinding,
7601
8882
  createControlBinding,
7602
8883
  exampleDiagram,
7603
8884
  exportDiagram,
@@ -7611,6 +8892,7 @@ export {
7611
8892
  overlayStyles,
7612
8893
  registerSymbol,
7613
8894
  registerSymbols,
8895
+ useDeviceConfigs,
7614
8896
  useDeviceControls,
7615
8897
  useDrag,
7616
8898
  useKeyboardShortcuts,