@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.cjs CHANGED
@@ -38,6 +38,7 @@ __export(index_exports, {
38
38
  ControlPanel: () => ControlPanel,
39
39
  DEFAULT_THRESHOLDS: () => DEFAULT_THRESHOLDS,
40
40
  DashboardCard: () => DashboardCard,
41
+ DeviceConfigPanel: () => DeviceConfigPanel,
41
42
  DeviceControlPanel: () => DeviceControlPanel,
42
43
  DisplayModeToggle: () => DisplayModeToggle,
43
44
  EquipmentIndicator: () => EquipmentIndicator,
@@ -70,6 +71,7 @@ __export(index_exports, {
70
71
  ZoomButton: () => ZoomButton,
71
72
  ZoomControls: () => ZoomControls,
72
73
  calculateThresholdStatus: () => calculateThresholdStatus,
74
+ createConfigBinding: () => createConfigBinding,
73
75
  createControlBinding: () => createControlBinding,
74
76
  exampleDiagram: () => exampleDiagram,
75
77
  exportDiagram: () => exportDiagram,
@@ -83,6 +85,7 @@ __export(index_exports, {
83
85
  overlayStyles: () => overlayStyles,
84
86
  registerSymbol: () => registerSymbol,
85
87
  registerSymbols: () => registerSymbols,
88
+ useDeviceConfigs: () => useDeviceConfigs,
86
89
  useDeviceControls: () => useDeviceControls,
87
90
  useDrag: () => useDrag,
88
91
  useKeyboardShortcuts: () => useKeyboardShortcuts,
@@ -1009,9 +1012,17 @@ function DeviceControlPanel({
1009
1012
  onParameterChange,
1010
1013
  onStart,
1011
1014
  onStop,
1012
- onCustomAction
1015
+ onCustomAction,
1016
+ onOpenConfig,
1017
+ showConfigButton = true,
1018
+ onUndock,
1019
+ undockIcon = "\u21F1",
1020
+ modeSelectionStyle = "buttons",
1021
+ embedded = false
1013
1022
  }) {
1014
1023
  const [localParameterValues, setLocalParameterValues] = (0, import_react5.useState)({});
1024
+ const [showModeModal, setShowModeModal] = (0, import_react5.useState)(false);
1025
+ const [pendingMode, setPendingMode] = (0, import_react5.useState)(null);
1015
1026
  const [showToast, setShowToast] = (0, import_react5.useState)(true);
1016
1027
  (0, import_react5.useEffect)(() => {
1017
1028
  if (binding) {
@@ -1048,7 +1059,8 @@ function DeviceControlPanel({
1048
1059
  value: Math.round(13 * fontScale),
1049
1060
  input: Math.round(14 * fontScale),
1050
1061
  button: Math.round(12 * fontScale),
1051
- status: Math.round(10 * fontScale)
1062
+ status: Math.round(10 * fontScale),
1063
+ description: Math.round(13 * fontScale)
1052
1064
  };
1053
1065
  const currentModeObj = binding.modes.find((m) => m.value === currentMode);
1054
1066
  const activeCapabilities = currentModeObj?.capabilities ?? binding.capabilities ?? {};
@@ -1084,18 +1096,37 @@ function DeviceControlPanel({
1084
1096
  const handleParameterInput = (parameterId, value) => {
1085
1097
  setLocalParameterValues((prev) => ({ ...prev, [parameterId]: value }));
1086
1098
  };
1087
- const panelStyle = {
1099
+ const panelStyle = embedded ? {
1100
+ // Embedded mode: static positioning for container layouts
1101
+ position: "static",
1102
+ width: "100%",
1103
+ backgroundColor,
1104
+ color: textColor,
1105
+ borderRadius: "8px 8px 0 0",
1106
+ boxShadow: "none",
1107
+ border: `2px solid ${borderColor}`,
1108
+ padding: spacing.padding,
1109
+ maxHeight: maxHeight || "70vh",
1110
+ overflowY: "auto",
1111
+ ...binding.display?.style
1112
+ } : {
1113
+ // Fixed mode: traditional overlay positioning
1088
1114
  position: "fixed",
1089
1115
  ...position === "bottom" ? {
1090
1116
  bottom: 0,
1091
- ...align === "left" ? { left: 0, right: "auto" } : align === "right" ? { right: 0, left: "auto" } : { left: 0, right: 0, marginLeft: "auto", marginRight: "auto" },
1092
- animation: "slideUp 0.3s ease-out",
1093
- maxWidth
1117
+ ...align === "left" ? { left: 0, right: "auto", width: maxWidth } : align === "right" ? { right: 0, left: "auto", width: maxWidth } : {
1118
+ left: 0,
1119
+ right: 0,
1120
+ marginLeft: "auto",
1121
+ marginRight: "auto",
1122
+ maxWidth
1123
+ },
1124
+ animation: "slideUp 0.3s ease-out"
1094
1125
  } : {
1095
1126
  right: 0,
1096
1127
  top: 0,
1097
1128
  bottom: 0,
1098
- width: "400px",
1129
+ width: maxWidth || "400px",
1099
1130
  animation: "slideIn 0.3s ease-out"
1100
1131
  },
1101
1132
  backgroundColor,
@@ -1135,9 +1166,14 @@ function DeviceControlPanel({
1135
1166
  fontFamily: "Arial, sans-serif",
1136
1167
  letterSpacing: "0.5px",
1137
1168
  textTransform: "uppercase",
1138
- color: textColor
1169
+ color: textColor,
1170
+ overflow: "hidden",
1171
+ textOverflow: "ellipsis",
1172
+ whiteSpace: "nowrap",
1173
+ maxWidth: "200px"
1139
1174
  },
1140
- children: titleText
1175
+ title: titleText.length > 25 ? titleText : void 0,
1176
+ children: titleText.length > 25 ? titleText.slice(0, 25) + "..." : titleText
1141
1177
  }
1142
1178
  ),
1143
1179
  binding.deviceType && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
@@ -1153,25 +1189,69 @@ function DeviceControlPanel({
1153
1189
  }
1154
1190
  )
1155
1191
  ] }),
1156
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1157
- "button",
1158
- {
1159
- onClick: onClose,
1160
- style: {
1161
- width: touchSize * 0.7,
1162
- height: touchSize * 0.7,
1163
- backgroundColor: surfaceColor,
1164
- color: textColor,
1165
- border: `2px solid ${borderColor}`,
1166
- borderRadius: "2px",
1167
- fontSize: "18px",
1168
- cursor: "pointer",
1169
- fontWeight: "bold",
1170
- fontFamily: "Arial, sans-serif"
1171
- },
1172
- children: "\xD7"
1173
- }
1174
- )
1192
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1193
+ showConfigButton && onOpenConfig && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1194
+ "button",
1195
+ {
1196
+ onClick: onOpenConfig,
1197
+ title: "Open Configuration",
1198
+ style: {
1199
+ width: touchSize * 0.7,
1200
+ height: touchSize * 0.7,
1201
+ backgroundColor: surfaceColor,
1202
+ color: textColor,
1203
+ border: `2px solid ${borderColor}`,
1204
+ borderRadius: "2px",
1205
+ fontSize: "16px",
1206
+ cursor: "pointer",
1207
+ fontWeight: 600,
1208
+ fontFamily: "Arial, sans-serif",
1209
+ padding: "0"
1210
+ },
1211
+ children: "\u2699"
1212
+ }
1213
+ ),
1214
+ onUndock && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1215
+ "button",
1216
+ {
1217
+ onClick: onUndock,
1218
+ title: "Undock panel",
1219
+ style: {
1220
+ width: touchSize * 0.7,
1221
+ height: touchSize * 0.7,
1222
+ backgroundColor: surfaceColor,
1223
+ color: textColor,
1224
+ border: `2px solid ${borderColor}`,
1225
+ borderRadius: "2px",
1226
+ fontSize: "16px",
1227
+ cursor: "pointer",
1228
+ fontWeight: 600,
1229
+ fontFamily: "Arial, sans-serif",
1230
+ padding: "0"
1231
+ },
1232
+ children: undockIcon
1233
+ }
1234
+ ),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1236
+ "button",
1237
+ {
1238
+ onClick: onClose,
1239
+ style: {
1240
+ width: touchSize * 0.7,
1241
+ height: touchSize * 0.7,
1242
+ backgroundColor: surfaceColor,
1243
+ color: textColor,
1244
+ border: `2px solid ${borderColor}`,
1245
+ borderRadius: "2px",
1246
+ fontSize: "18px",
1247
+ cursor: "pointer",
1248
+ fontWeight: "bold",
1249
+ fontFamily: "Arial, sans-serif"
1250
+ },
1251
+ children: "\xD7"
1252
+ }
1253
+ )
1254
+ ] })
1175
1255
  ]
1176
1256
  }
1177
1257
  ),
@@ -1266,7 +1346,228 @@ function DeviceControlPanel({
1266
1346
  children: "Mode"
1267
1347
  }
1268
1348
  ),
1269
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1349
+ modeSelectionStyle === "dropdown" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1350
+ "select",
1351
+ {
1352
+ value: currentMode,
1353
+ onChange: (e) => handleModeChange(e.target.value),
1354
+ style: {
1355
+ width: "100%",
1356
+ height: touchSize,
1357
+ backgroundColor: surfaceColor,
1358
+ color: textColor,
1359
+ border: `1px solid ${borderColor}`,
1360
+ borderRadius: "6px",
1361
+ fontSize: `${fontSize.label}px`,
1362
+ fontWeight: 600,
1363
+ fontFamily: "Arial, sans-serif",
1364
+ letterSpacing: "0.5px",
1365
+ textTransform: "uppercase",
1366
+ paddingLeft: "12px",
1367
+ paddingRight: "12px",
1368
+ cursor: "pointer"
1369
+ },
1370
+ children: binding.modes.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: mode.value, disabled: mode.enabled === false, children: mode.label }, mode.value))
1371
+ }
1372
+ ) : modeSelectionStyle === "modal" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1373
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1374
+ "div",
1375
+ {
1376
+ onClick: () => setShowModeModal(true),
1377
+ style: {
1378
+ padding: "12px",
1379
+ backgroundColor: surfaceColor,
1380
+ border: `2px solid ${borderColor}`,
1381
+ borderRadius: "6px",
1382
+ fontSize: `${fontSize.label}px`,
1383
+ fontWeight: 600,
1384
+ fontFamily: "Arial, sans-serif",
1385
+ letterSpacing: "0.5px",
1386
+ textTransform: "uppercase",
1387
+ cursor: "pointer",
1388
+ display: "flex",
1389
+ justifyContent: "space-between",
1390
+ alignItems: "center"
1391
+ },
1392
+ children: [
1393
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: binding.modes.find((m) => m.value === currentMode)?.label || currentMode }),
1394
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "20px", marginLeft: "8px" }, children: "\u25BC" })
1395
+ ]
1396
+ }
1397
+ ),
1398
+ showModeModal && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1399
+ "div",
1400
+ {
1401
+ style: {
1402
+ position: "fixed",
1403
+ top: 0,
1404
+ left: 0,
1405
+ right: 0,
1406
+ bottom: 0,
1407
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
1408
+ display: "flex",
1409
+ alignItems: "center",
1410
+ justifyContent: "center",
1411
+ zIndex: 2e4
1412
+ },
1413
+ onClick: () => {
1414
+ setShowModeModal(false);
1415
+ setPendingMode(null);
1416
+ },
1417
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1418
+ "div",
1419
+ {
1420
+ onClick: (e) => e.stopPropagation(),
1421
+ style: {
1422
+ backgroundColor,
1423
+ borderRadius: "8px",
1424
+ padding: "24px",
1425
+ minWidth: "300px",
1426
+ maxWidth: "400px",
1427
+ border: `2px solid ${borderColor}`
1428
+ },
1429
+ children: [
1430
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1431
+ "h3",
1432
+ {
1433
+ style: {
1434
+ margin: "0 0 4px 0",
1435
+ fontSize: `${fontSize.title}px`,
1436
+ fontWeight: 600,
1437
+ fontFamily: "Arial, sans-serif",
1438
+ color: textColor
1439
+ },
1440
+ children: "Select Mode"
1441
+ }
1442
+ ),
1443
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1444
+ "div",
1445
+ {
1446
+ style: {
1447
+ marginBottom: "16px",
1448
+ fontSize: `${fontSize.label}px`,
1449
+ fontWeight: 600,
1450
+ fontFamily: "Arial, sans-serif",
1451
+ color: mutedTextColor,
1452
+ textTransform: "uppercase"
1453
+ },
1454
+ children: binding.display?.title || binding.deviceName || binding.tag || binding.nodeId
1455
+ }
1456
+ ),
1457
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1458
+ "select",
1459
+ {
1460
+ value: pendingMode || currentMode,
1461
+ onChange: (e) => setPendingMode(e.target.value),
1462
+ style: {
1463
+ width: "100%",
1464
+ height: `${touchSize}px`,
1465
+ marginBottom: "8px",
1466
+ backgroundColor: surfaceColor,
1467
+ color: textColor,
1468
+ border: `2px solid ${borderColor}`,
1469
+ borderRadius: "6px",
1470
+ fontSize: `${fontSize.label}px`,
1471
+ fontWeight: 600,
1472
+ fontFamily: "Arial, sans-serif",
1473
+ letterSpacing: "0.5px",
1474
+ textTransform: "uppercase",
1475
+ paddingLeft: "12px",
1476
+ paddingRight: "12px",
1477
+ cursor: "pointer"
1478
+ },
1479
+ children: binding.modes.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1480
+ "option",
1481
+ {
1482
+ value: mode.value,
1483
+ disabled: mode.enabled === false,
1484
+ children: mode.label
1485
+ },
1486
+ mode.value
1487
+ ))
1488
+ }
1489
+ ),
1490
+ (() => {
1491
+ const selectedMode = binding.modes.find(
1492
+ (m) => m.value === (pendingMode || currentMode)
1493
+ );
1494
+ return selectedMode?.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1495
+ "div",
1496
+ {
1497
+ style: {
1498
+ marginBottom: "16px",
1499
+ padding: "12px",
1500
+ backgroundColor: surfaceColor,
1501
+ border: `1px solid ${borderColor}`,
1502
+ borderRadius: "6px",
1503
+ fontSize: `${fontSize.description}px`,
1504
+ color: mutedTextColor,
1505
+ fontFamily: "Arial, sans-serif",
1506
+ lineHeight: "1.4"
1507
+ },
1508
+ children: selectedMode.description
1509
+ }
1510
+ ) : null;
1511
+ })(),
1512
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "12px" }, children: [
1513
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1514
+ "button",
1515
+ {
1516
+ onClick: () => {
1517
+ if (pendingMode && pendingMode !== currentMode) {
1518
+ handleModeChange(pendingMode);
1519
+ }
1520
+ setShowModeModal(false);
1521
+ setPendingMode(null);
1522
+ },
1523
+ style: {
1524
+ flex: 1,
1525
+ height: `${touchSize}px`,
1526
+ backgroundColor: "#4ade80",
1527
+ color: "#000000",
1528
+ border: "2px solid #22c55e",
1529
+ borderRadius: "6px",
1530
+ fontSize: `${fontSize.button}px`,
1531
+ fontWeight: 600,
1532
+ fontFamily: "Arial, sans-serif",
1533
+ letterSpacing: "0.5px",
1534
+ textTransform: "uppercase",
1535
+ cursor: "pointer"
1536
+ },
1537
+ children: "Confirm"
1538
+ }
1539
+ ),
1540
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1541
+ "button",
1542
+ {
1543
+ onClick: () => {
1544
+ setShowModeModal(false);
1545
+ setPendingMode(null);
1546
+ },
1547
+ style: {
1548
+ flex: 1,
1549
+ height: `${touchSize}px`,
1550
+ backgroundColor: surfaceColor,
1551
+ color: textColor,
1552
+ border: `2px solid ${borderColor}`,
1553
+ borderRadius: "6px",
1554
+ fontSize: `${fontSize.button}px`,
1555
+ fontWeight: 600,
1556
+ fontFamily: "Arial, sans-serif",
1557
+ letterSpacing: "0.5px",
1558
+ textTransform: "uppercase",
1559
+ cursor: "pointer"
1560
+ },
1561
+ children: "Cancel"
1562
+ }
1563
+ )
1564
+ ] })
1565
+ ]
1566
+ }
1567
+ )
1568
+ }
1569
+ )
1570
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1270
1571
  "div",
1271
1572
  {
1272
1573
  style: {
@@ -7595,8 +7896,989 @@ function useDeviceControls(initialBindings) {
7595
7896
  };
7596
7897
  }
7597
7898
 
7598
- // src/diagram/hooks/useSimulation.ts
7899
+ // src/diagram/schema/DeviceConfigBinding.ts
7900
+ function createConfigBinding(nodeId, tag, parameters, actions, options) {
7901
+ return {
7902
+ nodeId,
7903
+ tag,
7904
+ deviceName: options?.deviceName,
7905
+ deviceType: options?.deviceType,
7906
+ sections: options?.sections,
7907
+ parameters,
7908
+ state: {
7909
+ isDirty: false,
7910
+ isSaving: false,
7911
+ errors: {}
7912
+ },
7913
+ actions,
7914
+ display: options?.display,
7915
+ theme: options?.theme,
7916
+ metadata: options?.metadata
7917
+ };
7918
+ }
7919
+
7920
+ // src/hooks/useDeviceConfigs.ts
7599
7921
  var import_react19 = require("react");
7922
+ function useDeviceConfigs(initialConfigs) {
7923
+ const [configs, setConfigs] = (0, import_react19.useState)(() => {
7924
+ const map = /* @__PURE__ */ new Map();
7925
+ if (initialConfigs) {
7926
+ initialConfigs.forEach((config) => {
7927
+ map.set(config.nodeId, config);
7928
+ });
7929
+ }
7930
+ return map;
7931
+ });
7932
+ const getConfig = (0, import_react19.useCallback)(
7933
+ (nodeId) => {
7934
+ return configs.get(nodeId);
7935
+ },
7936
+ [configs]
7937
+ );
7938
+ const updateConfig = (0, import_react19.useCallback)((nodeId, updates) => {
7939
+ setConfigs((prev) => {
7940
+ const newMap = new Map(prev);
7941
+ const existing = newMap.get(nodeId);
7942
+ if (existing) {
7943
+ newMap.set(nodeId, { ...existing, ...updates });
7944
+ } else {
7945
+ if ("tag" in updates && "parameters" in updates && "state" in updates && "actions" in updates) {
7946
+ newMap.set(nodeId, { nodeId, ...updates });
7947
+ }
7948
+ }
7949
+ return newMap;
7950
+ });
7951
+ }, []);
7952
+ const updateConfigState = (0, import_react19.useCallback)((nodeId, stateUpdates) => {
7953
+ setConfigs((prev) => {
7954
+ const newMap = new Map(prev);
7955
+ const existing = newMap.get(nodeId);
7956
+ if (existing) {
7957
+ newMap.set(nodeId, {
7958
+ ...existing,
7959
+ state: { ...existing.state, ...stateUpdates }
7960
+ });
7961
+ }
7962
+ return newMap;
7963
+ });
7964
+ }, []);
7965
+ const updateConfigBatch = (0, import_react19.useCallback)((updates) => {
7966
+ setConfigs((prev) => {
7967
+ const newMap = new Map(prev);
7968
+ Object.entries(updates).forEach(([nodeId, update]) => {
7969
+ const existing = newMap.get(nodeId);
7970
+ if (existing) {
7971
+ newMap.set(nodeId, { ...existing, ...update });
7972
+ }
7973
+ });
7974
+ return newMap;
7975
+ });
7976
+ }, []);
7977
+ const updateParameterValue = (0, import_react19.useCallback)((nodeId, parameterId, value) => {
7978
+ setConfigs((prev) => {
7979
+ const newMap = new Map(prev);
7980
+ const existing = newMap.get(nodeId);
7981
+ if (existing) {
7982
+ const updatedParameters = existing.parameters.map(
7983
+ (param) => param.id === parameterId ? { ...param, value } : param
7984
+ );
7985
+ newMap.set(nodeId, {
7986
+ ...existing,
7987
+ parameters: updatedParameters,
7988
+ state: { ...existing.state, isDirty: true }
7989
+ });
7990
+ }
7991
+ return newMap;
7992
+ });
7993
+ }, []);
7994
+ const applyConfig = (0, import_react19.useCallback)(
7995
+ async (nodeId) => {
7996
+ const config = configs.get(nodeId);
7997
+ if (!config || !config.actions.onApply) {
7998
+ return { success: false, message: "No apply handler defined" };
7999
+ }
8000
+ updateConfigState(nodeId, { isSaving: true });
8001
+ try {
8002
+ const values = config.parameters.reduce(
8003
+ (acc, param) => {
8004
+ acc[param.id] = param.value;
8005
+ return acc;
8006
+ },
8007
+ {}
8008
+ );
8009
+ const result = await config.actions.onApply(values);
8010
+ updateConfigState(nodeId, {
8011
+ isSaving: false,
8012
+ lastSaveResult: result,
8013
+ isDirty: result.success ? false : config.state.isDirty
8014
+ });
8015
+ return result;
8016
+ } catch (error) {
8017
+ const message = error instanceof Error ? error.message : "Apply failed";
8018
+ updateConfigState(nodeId, {
8019
+ isSaving: false,
8020
+ lastSaveResult: { success: false, message }
8021
+ });
8022
+ return { success: false, message };
8023
+ }
8024
+ },
8025
+ [configs, updateConfigState]
8026
+ );
8027
+ const saveConfig = (0, import_react19.useCallback)(
8028
+ async (nodeId) => {
8029
+ const config = configs.get(nodeId);
8030
+ if (!config || !config.actions.onSave) {
8031
+ return { success: false, message: "No save handler defined" };
8032
+ }
8033
+ updateConfigState(nodeId, { isSaving: true });
8034
+ try {
8035
+ const values = config.parameters.reduce(
8036
+ (acc, param) => {
8037
+ acc[param.id] = param.value;
8038
+ return acc;
8039
+ },
8040
+ {}
8041
+ );
8042
+ const result = await config.actions.onSave(values);
8043
+ updateConfigState(nodeId, {
8044
+ isSaving: false,
8045
+ lastSaved: result.success ? Date.now() : config.state.lastSaved,
8046
+ lastSaveResult: result,
8047
+ isDirty: result.success ? false : config.state.isDirty
8048
+ });
8049
+ return result;
8050
+ } catch (error) {
8051
+ const message = error instanceof Error ? error.message : "Save failed";
8052
+ updateConfigState(nodeId, {
8053
+ isSaving: false,
8054
+ lastSaveResult: { success: false, message }
8055
+ });
8056
+ return { success: false, message };
8057
+ }
8058
+ },
8059
+ [configs, updateConfigState]
8060
+ );
8061
+ const resetConfig = (0, import_react19.useCallback)(
8062
+ async (nodeId) => {
8063
+ const config = configs.get(nodeId);
8064
+ if (!config) {
8065
+ return { success: false, message: "Configuration not found" };
8066
+ }
8067
+ try {
8068
+ const resetParameters = config.parameters.map((param) => ({
8069
+ ...param,
8070
+ value: param.defaultValue !== void 0 ? param.defaultValue : param.value
8071
+ }));
8072
+ updateConfig(nodeId, {
8073
+ parameters: resetParameters,
8074
+ state: { ...config.state, isDirty: true }
8075
+ });
8076
+ if (config.actions.onReset) {
8077
+ const result = await config.actions.onReset();
8078
+ updateConfigState(nodeId, {
8079
+ lastSaveResult: result
8080
+ });
8081
+ return result;
8082
+ }
8083
+ return { success: true, message: "Reset to defaults" };
8084
+ } catch (error) {
8085
+ const message = error instanceof Error ? error.message : "Reset failed";
8086
+ return { success: false, message };
8087
+ }
8088
+ },
8089
+ [configs, updateConfig, updateConfigState]
8090
+ );
8091
+ const cancelConfig = (0, import_react19.useCallback)(
8092
+ (nodeId) => {
8093
+ const config = configs.get(nodeId);
8094
+ if (config?.actions.onCancel) {
8095
+ config.actions.onCancel();
8096
+ }
8097
+ },
8098
+ [configs]
8099
+ );
8100
+ const validateConfig = (0, import_react19.useCallback)(
8101
+ (nodeId) => {
8102
+ const config = configs.get(nodeId);
8103
+ if (!config) {
8104
+ return { valid: false, errors: { _general: "Configuration not found" } };
8105
+ }
8106
+ const errors = {};
8107
+ const values = config.parameters.reduce(
8108
+ (acc, param) => {
8109
+ acc[param.id] = param.value;
8110
+ return acc;
8111
+ },
8112
+ {}
8113
+ );
8114
+ config.parameters.forEach((param) => {
8115
+ if (param.required && (param.value === void 0 || param.value === null || param.value === "")) {
8116
+ errors[param.id] = `${param.label} is required`;
8117
+ return;
8118
+ }
8119
+ if (param.type === "number" && typeof param.value === "number") {
8120
+ if (param.min !== void 0 && param.value < param.min) {
8121
+ errors[param.id] = `${param.label} must be at least ${param.min}`;
8122
+ }
8123
+ if (param.max !== void 0 && param.value > param.max) {
8124
+ errors[param.id] = `${param.label} must be at most ${param.max}`;
8125
+ }
8126
+ }
8127
+ if (param.validate) {
8128
+ const result = param.validate(param.value);
8129
+ if (result !== true) {
8130
+ errors[param.id] = typeof result === "string" ? result : `${param.label} is invalid`;
8131
+ }
8132
+ }
8133
+ if (param.visibleWhen && !param.visibleWhen(values)) {
8134
+ delete errors[param.id];
8135
+ }
8136
+ });
8137
+ updateConfigState(nodeId, { errors });
8138
+ return { valid: Object.keys(errors).length === 0, errors };
8139
+ },
8140
+ [configs, updateConfigState]
8141
+ );
8142
+ const setConfig = (0, import_react19.useCallback)((config) => {
8143
+ setConfigs((prev) => {
8144
+ const newMap = new Map(prev);
8145
+ newMap.set(config.nodeId, config);
8146
+ return newMap;
8147
+ });
8148
+ }, []);
8149
+ const removeConfig = (0, import_react19.useCallback)((nodeId) => {
8150
+ setConfigs((prev) => {
8151
+ const newMap = new Map(prev);
8152
+ newMap.delete(nodeId);
8153
+ return newMap;
8154
+ });
8155
+ }, []);
8156
+ const clearConfigs = (0, import_react19.useCallback)(() => {
8157
+ setConfigs(/* @__PURE__ */ new Map());
8158
+ }, []);
8159
+ return {
8160
+ configs,
8161
+ getConfig,
8162
+ setConfig,
8163
+ removeConfig,
8164
+ clearConfigs,
8165
+ updateConfig,
8166
+ updateConfigState,
8167
+ updateConfigBatch,
8168
+ updateParameterValue,
8169
+ applyConfig,
8170
+ saveConfig,
8171
+ resetConfig,
8172
+ cancelConfig,
8173
+ validateConfig
8174
+ };
8175
+ }
8176
+
8177
+ // src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
8178
+ var import_react20 = require("react");
8179
+ var import_jsx_runtime22 = require("react/jsx-runtime");
8180
+ function DeviceConfigPanel({
8181
+ binding,
8182
+ onClose,
8183
+ onUndock,
8184
+ undockIcon = "\u21F1",
8185
+ position = "bottom",
8186
+ align = "center",
8187
+ touchOptimized = true,
8188
+ compact = false,
8189
+ fontScale = 1,
8190
+ maxHeight,
8191
+ maxWidth,
8192
+ toastDuration = 3e3,
8193
+ onOpenControls,
8194
+ showControlsButton = true,
8195
+ embedded = false
8196
+ }) {
8197
+ const [localValues, setLocalValues] = (0, import_react20.useState)(() => {
8198
+ if (binding) {
8199
+ const values = {};
8200
+ binding.parameters.forEach((param) => {
8201
+ values[param.id] = param.value;
8202
+ });
8203
+ return values;
8204
+ }
8205
+ return {};
8206
+ });
8207
+ const [collapsedSections, setCollapsedSections] = (0, import_react20.useState)(() => {
8208
+ const collapsed = /* @__PURE__ */ new Set();
8209
+ if (binding?.sections) {
8210
+ binding.sections.forEach((section) => {
8211
+ if (section.collapsible && section.defaultCollapsed) {
8212
+ collapsed.add(section.id);
8213
+ }
8214
+ });
8215
+ }
8216
+ return collapsed;
8217
+ });
8218
+ const [showToast, setShowToast] = (0, import_react20.useState)(true);
8219
+ const [bindingId, setBindingId] = (0, import_react20.useState)(binding?.nodeId || null);
8220
+ const lastResultTimestampRef = (0, import_react20.useRef)(0);
8221
+ if (binding && binding.nodeId !== bindingId) {
8222
+ const values = {};
8223
+ binding.parameters.forEach((param) => {
8224
+ values[param.id] = param.value;
8225
+ });
8226
+ setLocalValues(values);
8227
+ const collapsed = /* @__PURE__ */ new Set();
8228
+ binding.sections?.forEach((section) => {
8229
+ if (section.collapsible && section.defaultCollapsed) {
8230
+ collapsed.add(section.id);
8231
+ }
8232
+ });
8233
+ setCollapsedSections(collapsed);
8234
+ setBindingId(binding.nodeId);
8235
+ }
8236
+ (0, import_react20.useEffect)(() => {
8237
+ const currentTimestamp = binding?.state.lastSaved || 0;
8238
+ if (binding?.state.lastSaveResult && currentTimestamp !== lastResultTimestampRef.current) {
8239
+ lastResultTimestampRef.current = currentTimestamp;
8240
+ const showTimer = setTimeout(() => {
8241
+ setShowToast(true);
8242
+ const hideTimer = setTimeout(() => {
8243
+ setShowToast(false);
8244
+ }, toastDuration);
8245
+ return () => clearTimeout(hideTimer);
8246
+ }, 0);
8247
+ return () => clearTimeout(showTimer);
8248
+ }
8249
+ }, [binding?.state.lastSaveResult, binding?.state.lastSaved, toastDuration]);
8250
+ const parametersBySection = (0, import_react20.useMemo)(() => {
8251
+ if (!binding) return { _unsectioned: [] };
8252
+ const groups = { _unsectioned: [] };
8253
+ binding.parameters.forEach((param) => {
8254
+ const sectionId = param.section || "_unsectioned";
8255
+ if (!groups[sectionId]) {
8256
+ groups[sectionId] = [];
8257
+ }
8258
+ groups[sectionId].push(param);
8259
+ });
8260
+ Object.keys(groups).forEach((sectionId) => {
8261
+ groups[sectionId].sort((a, b) => (a.order || 0) - (b.order || 0));
8262
+ });
8263
+ return groups;
8264
+ }, [binding]);
8265
+ const sortedSections = (0, import_react20.useMemo)(() => {
8266
+ if (!binding?.sections) return [];
8267
+ return [...binding.sections].sort((a, b) => (a.order || 0) - (b.order || 0));
8268
+ }, [binding]);
8269
+ if (!binding) return null;
8270
+ const touchSize = touchOptimized ? 56 : 40;
8271
+ const spacing = {
8272
+ padding: compact ? "12px" : "20px",
8273
+ gap: compact ? "6px" : "8px",
8274
+ marginBottom: compact ? "10px" : "16px",
8275
+ itemPadding: compact ? "8px 10px" : "10px 12px"
8276
+ };
8277
+ const fontSize = {
8278
+ title: Math.round(14 * fontScale),
8279
+ label: Math.round(11 * fontScale),
8280
+ value: Math.round(13 * fontScale),
8281
+ input: Math.round(13 * fontScale),
8282
+ button: Math.round(12 * fontScale),
8283
+ section: Math.round(12 * fontScale),
8284
+ description: Math.round(10 * fontScale)
8285
+ };
8286
+ const backgroundColor = binding.theme?.backgroundColor || "#edeeef";
8287
+ const surfaceColor = binding.theme?.surfaceColor || "#f5f6f7";
8288
+ const borderColor = binding.theme?.borderColor || "#d2d2d2";
8289
+ const textColor = binding.theme?.textColor || "#000000";
8290
+ const mutedTextColor = binding.theme?.mutedTextColor || "#666666";
8291
+ const accentColor = binding.theme?.accentColor || "#b4d0fe";
8292
+ const errorColor = binding.theme?.errorColor || "#ef4444";
8293
+ const successColor = binding.theme?.successColor || "#10b981";
8294
+ const handleValueChange = (parameterId, value) => {
8295
+ setLocalValues((prev) => ({ ...prev, [parameterId]: value }));
8296
+ };
8297
+ const handleApply = async () => {
8298
+ if (binding.actions.onApply) {
8299
+ await binding.actions.onApply(localValues);
8300
+ }
8301
+ };
8302
+ const handleSave = async () => {
8303
+ if (binding.actions.onSave) {
8304
+ await binding.actions.onSave(localValues);
8305
+ }
8306
+ };
8307
+ const handleReset = async () => {
8308
+ if (binding.display?.resetConfirmMessage) {
8309
+ if (!confirm(binding.display.resetConfirmMessage)) {
8310
+ return;
8311
+ }
8312
+ }
8313
+ if (binding.actions.onReset) {
8314
+ await binding.actions.onReset();
8315
+ }
8316
+ };
8317
+ const handleCancel = () => {
8318
+ if (binding.actions.onCancel) {
8319
+ binding.actions.onCancel();
8320
+ }
8321
+ onClose();
8322
+ };
8323
+ const toggleSection = (sectionId) => {
8324
+ setCollapsedSections((prev) => {
8325
+ const next = new Set(prev);
8326
+ if (next.has(sectionId)) {
8327
+ next.delete(sectionId);
8328
+ } else {
8329
+ next.add(sectionId);
8330
+ }
8331
+ return next;
8332
+ });
8333
+ };
8334
+ const renderParameter = (param) => {
8335
+ if (param.visibleWhen && !param.visibleWhen(localValues)) {
8336
+ return null;
8337
+ }
8338
+ const value = localValues[param.id];
8339
+ const error = binding.state.errors?.[param.id];
8340
+ const isDisabled = param.readOnly || param.enabled === false;
8341
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8342
+ "div",
8343
+ {
8344
+ style: {
8345
+ marginBottom: spacing.marginBottom
8346
+ },
8347
+ children: [
8348
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8349
+ "label",
8350
+ {
8351
+ style: {
8352
+ display: "block",
8353
+ fontSize: `${fontSize.label}px`,
8354
+ fontWeight: 600,
8355
+ color: textColor,
8356
+ marginBottom: "4px",
8357
+ fontFamily: "Arial, sans-serif",
8358
+ textTransform: "uppercase",
8359
+ letterSpacing: "0.5px"
8360
+ },
8361
+ children: [
8362
+ param.label,
8363
+ param.required && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { style: { color: errorColor }, children: " *" }),
8364
+ param.unit && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { style: { color: mutedTextColor, fontWeight: "normal" }, children: [
8365
+ " (",
8366
+ param.unit,
8367
+ ")"
8368
+ ] })
8369
+ ]
8370
+ }
8371
+ ),
8372
+ param.description && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8373
+ "div",
8374
+ {
8375
+ style: {
8376
+ fontSize: `${fontSize.description}px`,
8377
+ color: mutedTextColor,
8378
+ marginBottom: "6px",
8379
+ fontFamily: "Arial, sans-serif"
8380
+ },
8381
+ children: param.description
8382
+ }
8383
+ ),
8384
+ param.type === "number" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8385
+ "input",
8386
+ {
8387
+ type: "number",
8388
+ value: value ?? "",
8389
+ onChange: (e) => handleValueChange(param.id, parseFloat(e.target.value)),
8390
+ min: param.min,
8391
+ max: param.max,
8392
+ step: param.step,
8393
+ disabled: isDisabled,
8394
+ style: {
8395
+ width: "100%",
8396
+ height: `${touchSize * 0.8}px`,
8397
+ padding: spacing.itemPadding,
8398
+ fontSize: `${fontSize.input}px`,
8399
+ fontFamily: "Arial, sans-serif",
8400
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8401
+ color: textColor,
8402
+ border: `1px solid ${error ? errorColor : borderColor}`,
8403
+ borderRadius: "2px",
8404
+ outline: "none"
8405
+ }
8406
+ }
8407
+ ),
8408
+ param.type === "string" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8409
+ "input",
8410
+ {
8411
+ type: "text",
8412
+ value: value ?? "",
8413
+ onChange: (e) => handleValueChange(param.id, e.target.value),
8414
+ disabled: isDisabled,
8415
+ style: {
8416
+ width: "100%",
8417
+ height: `${touchSize * 0.8}px`,
8418
+ padding: spacing.itemPadding,
8419
+ fontSize: `${fontSize.input}px`,
8420
+ fontFamily: "Arial, sans-serif",
8421
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8422
+ color: textColor,
8423
+ border: `1px solid ${error ? errorColor : borderColor}`,
8424
+ borderRadius: "2px",
8425
+ outline: "none"
8426
+ }
8427
+ }
8428
+ ),
8429
+ param.type === "boolean" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8430
+ "label",
8431
+ {
8432
+ style: {
8433
+ display: "flex",
8434
+ alignItems: "center",
8435
+ gap: "8px",
8436
+ cursor: isDisabled ? "not-allowed" : "pointer"
8437
+ },
8438
+ children: [
8439
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8440
+ "input",
8441
+ {
8442
+ type: "checkbox",
8443
+ checked: value ?? false,
8444
+ onChange: (e) => handleValueChange(param.id, e.target.checked),
8445
+ disabled: isDisabled,
8446
+ style: {
8447
+ width: "20px",
8448
+ height: "20px",
8449
+ cursor: isDisabled ? "not-allowed" : "pointer"
8450
+ }
8451
+ }
8452
+ ),
8453
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { style: { fontSize: `${fontSize.value}px`, color: textColor }, children: value ? "Enabled" : "Disabled" })
8454
+ ]
8455
+ }
8456
+ ),
8457
+ param.type === "select" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8458
+ "select",
8459
+ {
8460
+ value: value ?? "",
8461
+ onChange: (e) => handleValueChange(param.id, e.target.value),
8462
+ disabled: isDisabled,
8463
+ style: {
8464
+ width: "100%",
8465
+ height: `${touchSize * 0.8}px`,
8466
+ padding: spacing.itemPadding,
8467
+ fontSize: `${fontSize.input}px`,
8468
+ fontFamily: "Arial, sans-serif",
8469
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8470
+ color: textColor,
8471
+ border: `1px solid ${error ? errorColor : borderColor}`,
8472
+ borderRadius: "2px",
8473
+ outline: "none"
8474
+ },
8475
+ children: param.options?.map((option) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: option.value, children: option.label }, option.value))
8476
+ }
8477
+ ),
8478
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8479
+ "div",
8480
+ {
8481
+ style: {
8482
+ fontSize: `${fontSize.description}px`,
8483
+ color: errorColor,
8484
+ marginTop: "4px",
8485
+ fontFamily: "Arial, sans-serif"
8486
+ },
8487
+ children: error
8488
+ }
8489
+ )
8490
+ ]
8491
+ },
8492
+ param.id
8493
+ );
8494
+ };
8495
+ const renderSection = (section) => {
8496
+ const params = parametersBySection[section.id] || [];
8497
+ const isCollapsed = collapsedSections.has(section.id);
8498
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8499
+ "div",
8500
+ {
8501
+ style: {
8502
+ marginBottom: spacing.marginBottom,
8503
+ border: `1px solid ${borderColor}`,
8504
+ borderRadius: "4px",
8505
+ backgroundColor: surfaceColor
8506
+ },
8507
+ children: [
8508
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8509
+ "div",
8510
+ {
8511
+ onClick: () => section.collapsible && toggleSection(section.id),
8512
+ style: {
8513
+ padding: spacing.itemPadding,
8514
+ borderBottom: isCollapsed ? "none" : `1px solid ${borderColor}`,
8515
+ cursor: section.collapsible ? "pointer" : "default",
8516
+ display: "flex",
8517
+ justifyContent: "space-between",
8518
+ alignItems: "center"
8519
+ },
8520
+ children: [
8521
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
8522
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8523
+ "div",
8524
+ {
8525
+ style: {
8526
+ fontSize: `${fontSize.section}px`,
8527
+ fontWeight: 600,
8528
+ color: textColor,
8529
+ fontFamily: "Arial, sans-serif",
8530
+ textTransform: "uppercase",
8531
+ letterSpacing: "0.5px"
8532
+ },
8533
+ children: [
8534
+ section.icon && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { style: { marginRight: "8px" }, children: section.icon }),
8535
+ section.title
8536
+ ]
8537
+ }
8538
+ ),
8539
+ section.description && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8540
+ "div",
8541
+ {
8542
+ style: {
8543
+ fontSize: `${fontSize.description}px`,
8544
+ color: mutedTextColor,
8545
+ marginTop: "4px",
8546
+ fontFamily: "Arial, sans-serif"
8547
+ },
8548
+ children: section.description
8549
+ }
8550
+ )
8551
+ ] }),
8552
+ section.collapsible && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { style: { fontSize: "16px", color: mutedTextColor }, children: isCollapsed ? "\u25BC" : "\u25B2" })
8553
+ ]
8554
+ }
8555
+ ),
8556
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { padding: spacing.padding }, children: params.map(renderParameter) })
8557
+ ]
8558
+ },
8559
+ section.id
8560
+ );
8561
+ };
8562
+ const panelStyle = embedded ? {
8563
+ // Embedded mode: static positioning for container layouts
8564
+ position: "static",
8565
+ width: "100%",
8566
+ backgroundColor,
8567
+ color: textColor,
8568
+ borderRadius: "8px 8px 0 0",
8569
+ boxShadow: "none",
8570
+ border: `2px solid ${borderColor}`,
8571
+ padding: spacing.padding,
8572
+ maxHeight: maxHeight || "70vh",
8573
+ overflowY: "auto",
8574
+ display: "flex",
8575
+ flexDirection: "column",
8576
+ ...binding.display?.style
8577
+ } : {
8578
+ // Fixed mode: traditional overlay positioning
8579
+ position: "fixed",
8580
+ ...position === "bottom" ? {
8581
+ bottom: 0,
8582
+ ...align === "left" ? { left: 0, right: "auto", width: maxWidth } : align === "right" ? { right: 0, left: "auto", width: maxWidth } : {
8583
+ left: 0,
8584
+ right: 0,
8585
+ marginLeft: "auto",
8586
+ marginRight: "auto",
8587
+ maxWidth
8588
+ },
8589
+ animation: "slideUp 0.3s ease-out"
8590
+ } : {
8591
+ right: 0,
8592
+ top: 0,
8593
+ bottom: 0,
8594
+ width: maxWidth || "400px",
8595
+ animation: "slideIn 0.3s ease-out"
8596
+ },
8597
+ backgroundColor,
8598
+ color: textColor,
8599
+ borderRadius: position === "bottom" ? "8px 8px 0 0" : "8px 0 0 8px",
8600
+ boxShadow: "none",
8601
+ border: `2px solid ${borderColor}`,
8602
+ padding: spacing.padding,
8603
+ zIndex: 1e4,
8604
+ maxHeight: maxHeight || (position === "bottom" ? "70vh" : "100vh"),
8605
+ overflowY: "auto",
8606
+ display: "flex",
8607
+ flexDirection: "column",
8608
+ ...binding.display?.style
8609
+ };
8610
+ const titleText = binding.display?.title || binding.deviceName || binding.tag || binding.nodeId;
8611
+ const showApply = binding.display?.showApply !== false && binding.actions.onApply;
8612
+ const showSave = binding.display?.showSave !== false && binding.actions.onSave;
8613
+ const showReset = binding.display?.showReset !== false && binding.actions.onReset;
8614
+ const showCancel = binding.display?.showCancel !== false;
8615
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8616
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: panelStyle, className: binding.display?.className, children: [
8617
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8618
+ "div",
8619
+ {
8620
+ style: {
8621
+ display: "flex",
8622
+ justifyContent: "space-between",
8623
+ alignItems: "center",
8624
+ marginBottom: "16px",
8625
+ paddingBottom: "12px",
8626
+ borderBottom: `2px solid ${borderColor}`
8627
+ },
8628
+ children: [
8629
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
8630
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8631
+ "h3",
8632
+ {
8633
+ style: {
8634
+ margin: 0,
8635
+ fontSize: `${fontSize.title}px`,
8636
+ fontWeight: 600,
8637
+ fontFamily: "Arial, sans-serif",
8638
+ letterSpacing: "0.5px",
8639
+ textTransform: "uppercase",
8640
+ color: textColor,
8641
+ overflow: "hidden",
8642
+ textOverflow: "ellipsis",
8643
+ whiteSpace: "nowrap",
8644
+ maxWidth: "200px"
8645
+ },
8646
+ title: titleText.length > 25 ? titleText : void 0,
8647
+ children: titleText.length > 25 ? titleText.slice(0, 25) + "..." : titleText
8648
+ }
8649
+ ),
8650
+ binding.display?.subtitle && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8651
+ "div",
8652
+ {
8653
+ style: {
8654
+ fontSize: `${fontSize.description}px`,
8655
+ color: mutedTextColor,
8656
+ marginTop: "4px",
8657
+ fontFamily: "Arial, sans-serif"
8658
+ },
8659
+ children: binding.display.subtitle
8660
+ }
8661
+ )
8662
+ ] }),
8663
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
8664
+ showControlsButton && onOpenControls && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8665
+ "button",
8666
+ {
8667
+ onClick: onOpenControls,
8668
+ title: "Open Controls",
8669
+ style: {
8670
+ minWidth: `${touchSize * 1.2}px`,
8671
+ height: touchSize * 0.7,
8672
+ backgroundColor: surfaceColor,
8673
+ color: textColor,
8674
+ border: `2px solid ${borderColor}`,
8675
+ borderRadius: "2px",
8676
+ fontSize: `${fontSize.button}px`,
8677
+ cursor: "pointer",
8678
+ fontWeight: 600,
8679
+ fontFamily: "Arial, sans-serif",
8680
+ padding: "0 8px",
8681
+ textTransform: "uppercase",
8682
+ letterSpacing: "0.5px"
8683
+ },
8684
+ children: "CTRL"
8685
+ }
8686
+ ),
8687
+ onUndock && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8688
+ "button",
8689
+ {
8690
+ onClick: onUndock,
8691
+ title: "Undock panel",
8692
+ style: {
8693
+ width: touchSize * 0.7,
8694
+ height: touchSize * 0.7,
8695
+ backgroundColor: surfaceColor,
8696
+ color: textColor,
8697
+ border: `2px solid ${borderColor}`,
8698
+ borderRadius: "2px",
8699
+ fontSize: "16px",
8700
+ cursor: "pointer",
8701
+ fontWeight: 600,
8702
+ fontFamily: "Arial, sans-serif",
8703
+ padding: "0"
8704
+ },
8705
+ children: undockIcon
8706
+ }
8707
+ ),
8708
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8709
+ "button",
8710
+ {
8711
+ onClick: onClose,
8712
+ style: {
8713
+ width: touchSize * 0.7,
8714
+ height: touchSize * 0.7,
8715
+ backgroundColor: surfaceColor,
8716
+ color: textColor,
8717
+ border: `2px solid ${borderColor}`,
8718
+ borderRadius: "2px",
8719
+ fontSize: "18px",
8720
+ cursor: "pointer",
8721
+ fontWeight: "bold",
8722
+ fontFamily: "Arial, sans-serif"
8723
+ },
8724
+ children: "\xD7"
8725
+ }
8726
+ )
8727
+ ] })
8728
+ ]
8729
+ }
8730
+ ),
8731
+ binding.state.isDirty && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8732
+ "div",
8733
+ {
8734
+ style: {
8735
+ padding: spacing.itemPadding,
8736
+ backgroundColor: "#fff3cd",
8737
+ border: "1px solid #ffc107",
8738
+ borderRadius: "4px",
8739
+ marginBottom: spacing.marginBottom,
8740
+ fontSize: `${fontSize.description}px`,
8741
+ fontFamily: "Arial, sans-serif"
8742
+ },
8743
+ children: "Unsaved changes"
8744
+ }
8745
+ ),
8746
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { flex: 1, overflowY: "auto", marginBottom: spacing.marginBottom }, children: sortedSections.length > 0 ? sortedSections.map(renderSection) : (
8747
+ /* Render unsectioned parameters */
8748
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { children: parametersBySection._unsectioned?.map(renderParameter) })
8749
+ ) }),
8750
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8751
+ "div",
8752
+ {
8753
+ style: {
8754
+ display: "flex",
8755
+ gap: spacing.gap,
8756
+ paddingTop: "12px",
8757
+ borderTop: `2px solid ${borderColor}`,
8758
+ flexWrap: "wrap"
8759
+ },
8760
+ children: [
8761
+ showApply && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8762
+ "button",
8763
+ {
8764
+ onClick: handleApply,
8765
+ disabled: binding.state.isSaving,
8766
+ style: {
8767
+ flex: 1,
8768
+ minWidth: "80px",
8769
+ height: `${touchSize}px`,
8770
+ backgroundColor: accentColor,
8771
+ color: "#000000",
8772
+ border: `2px solid ${borderColor}`,
8773
+ borderRadius: "2px",
8774
+ fontSize: `${fontSize.button}px`,
8775
+ fontWeight: 600,
8776
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8777
+ fontFamily: "Arial, sans-serif",
8778
+ textTransform: "uppercase",
8779
+ letterSpacing: "0.5px"
8780
+ },
8781
+ children: binding.state.isSaving ? "Applying..." : "Apply"
8782
+ }
8783
+ ),
8784
+ showSave && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8785
+ "button",
8786
+ {
8787
+ onClick: handleSave,
8788
+ disabled: binding.state.isSaving,
8789
+ style: {
8790
+ flex: 1,
8791
+ minWidth: "80px",
8792
+ height: `${touchSize}px`,
8793
+ backgroundColor: successColor,
8794
+ color: "#ffffff",
8795
+ border: `2px solid ${borderColor}`,
8796
+ borderRadius: "2px",
8797
+ fontSize: `${fontSize.button}px`,
8798
+ fontWeight: 600,
8799
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8800
+ fontFamily: "Arial, sans-serif",
8801
+ textTransform: "uppercase",
8802
+ letterSpacing: "0.5px"
8803
+ },
8804
+ children: binding.state.isSaving ? "Saving..." : "Save"
8805
+ }
8806
+ ),
8807
+ showReset && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8808
+ "button",
8809
+ {
8810
+ onClick: handleReset,
8811
+ disabled: binding.state.isSaving,
8812
+ style: {
8813
+ flex: 1,
8814
+ minWidth: "80px",
8815
+ height: `${touchSize}px`,
8816
+ backgroundColor: surfaceColor,
8817
+ color: textColor,
8818
+ border: `2px solid ${borderColor}`,
8819
+ borderRadius: "2px",
8820
+ fontSize: `${fontSize.button}px`,
8821
+ fontWeight: 600,
8822
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8823
+ fontFamily: "Arial, sans-serif",
8824
+ textTransform: "uppercase",
8825
+ letterSpacing: "0.5px"
8826
+ },
8827
+ children: "Reset"
8828
+ }
8829
+ ),
8830
+ showCancel && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8831
+ "button",
8832
+ {
8833
+ onClick: handleCancel,
8834
+ disabled: binding.state.isSaving,
8835
+ style: {
8836
+ flex: 1,
8837
+ minWidth: "80px",
8838
+ height: `${touchSize}px`,
8839
+ backgroundColor: surfaceColor,
8840
+ color: textColor,
8841
+ border: `2px solid ${borderColor}`,
8842
+ borderRadius: "2px",
8843
+ fontSize: `${fontSize.button}px`,
8844
+ fontWeight: 600,
8845
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8846
+ fontFamily: "Arial, sans-serif",
8847
+ textTransform: "uppercase",
8848
+ letterSpacing: "0.5px"
8849
+ },
8850
+ children: "Cancel"
8851
+ }
8852
+ )
8853
+ ]
8854
+ }
8855
+ )
8856
+ ] }),
8857
+ binding.state.lastSaveResult && showToast && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8858
+ "div",
8859
+ {
8860
+ style: {
8861
+ position: "fixed",
8862
+ bottom: "20px",
8863
+ ...align === "right" ? { left: "20px" } : { right: "20px" },
8864
+ backgroundColor: binding.state.lastSaveResult.success ? successColor : errorColor,
8865
+ color: "#ffffff",
8866
+ padding: "12px 20px",
8867
+ borderRadius: "4px",
8868
+ boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
8869
+ zIndex: 1e4,
8870
+ fontSize: `${fontSize.value}px`,
8871
+ fontFamily: "Arial, sans-serif",
8872
+ fontWeight: 600
8873
+ },
8874
+ children: binding.state.lastSaveResult.message || (binding.state.lastSaveResult.success ? "Success" : "Error")
8875
+ }
8876
+ )
8877
+ ] });
8878
+ }
8879
+
8880
+ // src/diagram/hooks/useSimulation.ts
8881
+ var import_react21 = require("react");
7600
8882
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7601
8883
  const {
7602
8884
  interval = 2e3,
@@ -7607,7 +8889,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7607
8889
  historyLength = 10,
7608
8890
  generateValue
7609
8891
  } = options;
7610
- (0, import_react19.useEffect)(() => {
8892
+ (0, import_react21.useEffect)(() => {
7611
8893
  if (!enabled) return;
7612
8894
  const intervalId = setInterval(() => {
7613
8895
  const updates = [];
@@ -7665,6 +8947,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7665
8947
  ControlPanel,
7666
8948
  DEFAULT_THRESHOLDS,
7667
8949
  DashboardCard,
8950
+ DeviceConfigPanel,
7668
8951
  DeviceControlPanel,
7669
8952
  DisplayModeToggle,
7670
8953
  EquipmentIndicator,
@@ -7697,6 +8980,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7697
8980
  ZoomButton,
7698
8981
  ZoomControls,
7699
8982
  calculateThresholdStatus,
8983
+ createConfigBinding,
7700
8984
  createControlBinding,
7701
8985
  exampleDiagram,
7702
8986
  exportDiagram,
@@ -7710,6 +8994,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7710
8994
  overlayStyles,
7711
8995
  registerSymbol,
7712
8996
  registerSymbols,
8997
+ useDeviceConfigs,
7713
8998
  useDeviceControls,
7714
8999
  useDrag,
7715
9000
  useKeyboardShortcuts,