@procaaso/alphinity-ui-components 1.10.0 → 1.12.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
@@ -267,6 +267,16 @@ var ValueEntry = ({
267
267
  import { useState as useState3, useCallback as useCallback2 } from "react";
268
268
 
269
269
  // src/theme/statusColors.ts
270
+ var ISA_UNKNOWN = {
271
+ fg: "#4f4f8a",
272
+ bg: "#ebebf5",
273
+ border: "#8f8fc2"
274
+ };
275
+ var ISA_UNKNOWN_DARK = {
276
+ fg: "#b9bbdb",
277
+ bg: "#23263a",
278
+ border: "#6f74a3"
279
+ };
270
280
  var DEFAULT_STATUS_COLORS = {
271
281
  normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
272
282
  warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
@@ -274,7 +284,8 @@ var DEFAULT_STATUS_COLORS = {
274
284
  fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
275
285
  off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
276
286
  starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
277
- stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
287
+ stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
288
+ unknown: { fill: ISA_UNKNOWN.bg, stroke: ISA_UNKNOWN.border, bg: ISA_UNKNOWN.bg, text: ISA_UNKNOWN.fg, border: ISA_UNKNOWN.border }
278
289
  };
279
290
  var FORCED_MARK_COLOR = {
280
291
  fill: "#ede9fe",
@@ -287,9 +298,12 @@ var STATUS_ALIASES = {
287
298
  running: "normal",
288
299
  stopped: "off"
289
300
  };
290
- function resolveStatusColor(status, customMap) {
301
+ function normalizeDeviceStatus(status) {
291
302
  const raw = (status || "off").toLowerCase();
292
- const key = STATUS_ALIASES[raw] ?? raw;
303
+ return STATUS_ALIASES[raw] ?? raw;
304
+ }
305
+ function resolveStatusColor(status, customMap) {
306
+ const key = normalizeDeviceStatus(status);
293
307
  const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
294
308
  const overrides = customMap?.[key];
295
309
  if (!overrides) return defaults;
@@ -729,7 +743,79 @@ var ControlPanel = ({
729
743
  };
730
744
 
731
745
  // src/components/StatusIndicator/StatusIndicator.tsx
746
+ import React5 from "react";
747
+
748
+ // src/theme/unknownStatusGlyph.tsx
732
749
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
750
+ var UNKNOWN_STATUS_GLYPH_COLOR_VAR = "--ui-status-indicator-unknown-glyph-color";
751
+ var UNKNOWN_GLYPH_STROKE_WEIGHTS = {
752
+ small: { ring: 4.5, ringDash: "7 6", mark: 5, dot: 2.9 },
753
+ medium: { ring: 4, ringDash: "6.5 5.5", mark: 4.5, dot: 2.7 },
754
+ large: { ring: 3.5, ringDash: "6.5 5.5", mark: 4, dot: 2.5 }
755
+ };
756
+ var UNKNOWN_GLYPH_RING_DASH = UNKNOWN_GLYPH_STROKE_WEIGHTS.medium.ringDash;
757
+ var UNKNOWN_GLYPH_FILL_STYLE = {
758
+ position: "absolute",
759
+ inset: 0,
760
+ width: "100%",
761
+ height: "100%"
762
+ };
763
+ function unknownGlyphHostStyle(isUnknown, style) {
764
+ return isUnknown ? { position: "relative", ...style } : style;
765
+ }
766
+ function UnknownStatusGlyph({
767
+ size = "medium",
768
+ counterRotate = false,
769
+ style,
770
+ ...rest
771
+ }) {
772
+ const weights = UNKNOWN_GLYPH_STROKE_WEIGHTS[size];
773
+ return /* @__PURE__ */ jsxs5(
774
+ "svg",
775
+ {
776
+ viewBox: "0 0 48 48",
777
+ "aria-hidden": "true",
778
+ focusable: "false",
779
+ style: {
780
+ display: "block",
781
+ color: `var(${UNKNOWN_STATUS_GLYPH_COLOR_VAR}, currentColor)`,
782
+ transform: counterRotate ? "rotate(-45deg)" : void 0,
783
+ ...style
784
+ },
785
+ ...rest,
786
+ children: [
787
+ /* @__PURE__ */ jsx6(
788
+ "circle",
789
+ {
790
+ cx: 24,
791
+ cy: 24,
792
+ r: 20,
793
+ fill: "none",
794
+ stroke: "currentColor",
795
+ strokeDasharray: weights.ringDash,
796
+ strokeLinecap: "round",
797
+ strokeWidth: weights.ring
798
+ }
799
+ ),
800
+ /* @__PURE__ */ jsx6(
801
+ "path",
802
+ {
803
+ d: "M18.4 18.6a5.8 5.8 0 1 1 6.4 8.1v3.1",
804
+ fill: "none",
805
+ stroke: "currentColor",
806
+ strokeLinecap: "round",
807
+ strokeLinejoin: "round",
808
+ strokeWidth: weights.mark
809
+ }
810
+ ),
811
+ /* @__PURE__ */ jsx6("circle", { cx: 24.6, cy: 35.4, r: weights.dot, fill: "currentColor" })
812
+ ]
813
+ }
814
+ );
815
+ }
816
+
817
+ // src/components/StatusIndicator/StatusIndicator.tsx
818
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
733
819
  var getStatusDescription = (status) => {
734
820
  switch (status) {
735
821
  case "alarm":
@@ -744,10 +830,23 @@ var getStatusDescription = (status) => {
744
830
  return "Operating normally";
745
831
  case "off":
746
832
  return "Equipment is off";
833
+ case "unknown":
834
+ return "Device state cannot be confirmed. Check the link or the wiring.";
747
835
  default:
748
836
  return "";
749
837
  }
750
838
  };
839
+ var visuallyHiddenStyle = {
840
+ position: "absolute",
841
+ width: "1px",
842
+ height: "1px",
843
+ padding: 0,
844
+ margin: "-1px",
845
+ overflow: "hidden",
846
+ clip: "rect(0, 0, 0, 0)",
847
+ whiteSpace: "nowrap",
848
+ border: 0
849
+ };
751
850
  var StatusIndicator = ({
752
851
  status,
753
852
  size = "medium",
@@ -756,17 +855,29 @@ var StatusIndicator = ({
756
855
  labelPosition = "right",
757
856
  shape = "circle",
758
857
  led = true,
858
+ visible = true,
759
859
  forced = false,
760
860
  realValue,
761
861
  className = "",
862
+ style,
863
+ "aria-describedby": consumerDescribedBy,
864
+ children,
762
865
  ...props
763
866
  }) => {
867
+ const generatedDescriptionId = `${React5.useId()}-status-description`;
868
+ if (!visible) {
869
+ return null;
870
+ }
871
+ const describedBy = consumerDescribedBy ? `${consumerDescribedBy} ${generatedDescriptionId}` : generatedDescriptionId;
872
+ const description = getStatusDescription(status);
764
873
  const baseClass = "ui-status-indicator";
765
874
  const statusClass = status ? `ui-status-indicator--${status}` : "";
766
875
  const sizeClass = size ? `ui-status-indicator--${size}` : "";
767
876
  const shapeClass = shape ? `ui-status-indicator--${shape}` : "";
768
877
  const animationClass = animation ? `ui-status-indicator--${animation}` : "";
769
878
  const ledClass = led ? "ui-status-indicator--led" : "";
879
+ const isUnknown = status === "unknown";
880
+ const indicatorStyle = unknownGlyphHostStyle(isUnknown, style);
770
881
  const getContainerLayout = () => {
771
882
  switch (labelPosition) {
772
883
  case "left":
@@ -783,19 +894,20 @@ var StatusIndicator = ({
783
894
  };
784
895
  const forcedClass = forced ? "ui-status-indicator--forced" : "";
785
896
  const hasRealValue = forced && realValue !== void 0;
786
- return /* @__PURE__ */ jsxs5("span", { className: getContainerLayout(), children: [
787
- label && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-label", children: label }),
788
- hasRealValue && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ jsxs5("span", { className: "ui-status-indicator-real-value", children: [
897
+ return /* @__PURE__ */ jsxs6("span", { className: getContainerLayout(), children: [
898
+ label && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ jsx7("span", { className: "ui-status-indicator-label", children: label }),
899
+ hasRealValue && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ jsxs6("span", { className: "ui-status-indicator-real-value", children: [
789
900
  "REAL ",
790
901
  realValue
791
902
  ] }),
792
- /* @__PURE__ */ jsxs5("span", { style: { position: "relative", display: "inline-flex" }, children: [
793
- /* @__PURE__ */ jsx6(
903
+ /* @__PURE__ */ jsxs6("span", { style: { position: "relative", display: "inline-flex" }, children: [
904
+ /* @__PURE__ */ jsxs6(
794
905
  "span",
795
906
  {
796
907
  role: "status",
797
908
  "aria-label": label || `Status: ${status}`,
798
- "aria-description": getStatusDescription(status),
909
+ "aria-description": description,
910
+ "aria-describedby": describedBy,
799
911
  "data-status": status,
800
912
  "data-forced": forced,
801
913
  className: `
@@ -808,13 +920,26 @@ var StatusIndicator = ({
808
920
  ${forcedClass}
809
921
  ${className}
810
922
  `.replace(/\s+/g, " ").trim(),
811
- ...props
923
+ style: indicatorStyle,
924
+ ...props,
925
+ children: [
926
+ isUnknown && /* @__PURE__ */ jsx7(
927
+ UnknownStatusGlyph,
928
+ {
929
+ size,
930
+ counterRotate: shape === "diamond",
931
+ style: UNKNOWN_GLYPH_FILL_STYLE
932
+ }
933
+ ),
934
+ children,
935
+ /* @__PURE__ */ jsx7("span", { id: generatedDescriptionId, style: visuallyHiddenStyle, children: description })
936
+ ]
812
937
  }
813
938
  ),
814
- forced && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-forced-badge", "aria-label": "Forced", children: "F" })
939
+ forced && /* @__PURE__ */ jsx7("span", { className: "ui-status-indicator-forced-badge", "aria-label": "Forced", children: "F" })
815
940
  ] }),
816
- label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-label", children: label }),
817
- hasRealValue && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ jsxs5("span", { className: "ui-status-indicator-real-value", children: [
941
+ label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ jsx7("span", { className: "ui-status-indicator-label", children: label }),
942
+ hasRealValue && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ jsxs6("span", { className: "ui-status-indicator-real-value", children: [
818
943
  "REAL ",
819
944
  realValue
820
945
  ] })
@@ -822,7 +947,7 @@ var StatusIndicator = ({
822
947
  };
823
948
 
824
949
  // src/components/NodeControlsPanel/NodeControlsPanel.tsx
825
- import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
950
+ import { Fragment, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
826
951
  var NodeControlsPanel = ({
827
952
  config,
828
953
  onClose,
@@ -895,8 +1020,8 @@ var NodeControlsPanel = ({
895
1020
  overflowY: "auto",
896
1021
  flex: 1
897
1022
  };
898
- return /* @__PURE__ */ jsxs6(Fragment, { children: [
899
- /* @__PURE__ */ jsx7(
1023
+ return /* @__PURE__ */ jsxs7(Fragment, { children: [
1024
+ /* @__PURE__ */ jsx8(
900
1025
  "div",
901
1026
  {
902
1027
  style: {
@@ -909,16 +1034,16 @@ var NodeControlsPanel = ({
909
1034
  onClick: onClose
910
1035
  }
911
1036
  ),
912
- /* @__PURE__ */ jsxs6(
1037
+ /* @__PURE__ */ jsxs7(
913
1038
  "div",
914
1039
  {
915
1040
  className: `node-controls-panel ${touchClass} ${className}`.trim(),
916
1041
  style: containerStyle,
917
1042
  ...props,
918
1043
  children: [
919
- /* @__PURE__ */ jsxs6("div", { style: headerStyle, children: [
920
- /* @__PURE__ */ jsx7("h2", { style: titleStyle, children: config.title }),
921
- /* @__PURE__ */ jsx7(
1044
+ /* @__PURE__ */ jsxs7("div", { style: headerStyle, children: [
1045
+ /* @__PURE__ */ jsx8("h2", { style: titleStyle, children: config.title }),
1046
+ /* @__PURE__ */ jsx8(
922
1047
  "button",
923
1048
  {
924
1049
  style: closeButtonStyle,
@@ -936,7 +1061,7 @@ var NodeControlsPanel = ({
936
1061
  }
937
1062
  )
938
1063
  ] }),
939
- /* @__PURE__ */ jsx7("div", { style: contentStyle, children: /* @__PURE__ */ jsx7(
1064
+ /* @__PURE__ */ jsx8("div", { style: contentStyle, children: /* @__PURE__ */ jsx8(
940
1065
  ControlPanel,
941
1066
  {
942
1067
  title: config.nodeId,
@@ -965,7 +1090,7 @@ var NodeControlsPanel = ({
965
1090
  ]
966
1091
  }
967
1092
  ),
968
- /* @__PURE__ */ jsx7("style", { children: `
1093
+ /* @__PURE__ */ jsx8("style", { children: `
969
1094
  @keyframes slideUp {
970
1095
  from {
971
1096
  transform: translateY(100%);
@@ -1011,8 +1136,8 @@ var NodeControlsPanel = ({
1011
1136
  };
1012
1137
 
1013
1138
  // src/components/DeviceControlPanel/DeviceControlPanel.tsx
1014
- import React5, { useState as useState5, useEffect as useEffect2 } from "react";
1015
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1139
+ import React6, { useState as useState5, useEffect as useEffect2 } from "react";
1140
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1016
1141
  function DeviceControlPanelImpl({
1017
1142
  binding,
1018
1143
  onClose,
@@ -1092,6 +1217,7 @@ function DeviceControlPanelImpl({
1092
1217
  const selectedColor = "#b4d0fe";
1093
1218
  const statusEntry = resolveStatusColor(binding.state.status, binding.theme?.statusColors);
1094
1219
  const statusColor = statusEntry.bg;
1220
+ const isUnknownStatus = normalizeDeviceStatus(binding.state.status) === "unknown";
1095
1221
  const handleModeChange = (mode) => {
1096
1222
  onModeChange?.(mode);
1097
1223
  };
@@ -1146,9 +1272,9 @@ function DeviceControlPanelImpl({
1146
1272
  ...binding.display?.style
1147
1273
  };
1148
1274
  const titleText = binding.display?.title || binding.deviceName || binding.tag || binding.nodeId;
1149
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
1150
- /* @__PURE__ */ jsxs7("div", { style: panelStyle, className: binding.display?.className, children: [
1151
- /* @__PURE__ */ jsxs7(
1275
+ return /* @__PURE__ */ jsxs8(Fragment2, { children: [
1276
+ /* @__PURE__ */ jsxs8("div", { style: panelStyle, className: binding.display?.className, children: [
1277
+ /* @__PURE__ */ jsxs8(
1152
1278
  "div",
1153
1279
  {
1154
1280
  style: {
@@ -1160,8 +1286,8 @@ function DeviceControlPanelImpl({
1160
1286
  borderBottom: `2px solid ${borderColor}`
1161
1287
  },
1162
1288
  children: [
1163
- /* @__PURE__ */ jsxs7("div", { children: [
1164
- /* @__PURE__ */ jsx8(
1289
+ /* @__PURE__ */ jsxs8("div", { children: [
1290
+ /* @__PURE__ */ jsx9(
1165
1291
  "h3",
1166
1292
  {
1167
1293
  style: {
@@ -1181,7 +1307,7 @@ function DeviceControlPanelImpl({
1181
1307
  children: titleText.length > 25 ? titleText.slice(0, 25) + "..." : titleText
1182
1308
  }
1183
1309
  ),
1184
- binding.deviceType && /* @__PURE__ */ jsx8(
1310
+ binding.deviceType && /* @__PURE__ */ jsx9(
1185
1311
  "div",
1186
1312
  {
1187
1313
  style: {
@@ -1194,8 +1320,8 @@ function DeviceControlPanelImpl({
1194
1320
  }
1195
1321
  )
1196
1322
  ] }),
1197
- /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "8px" }, children: [
1198
- showConfigButton && onOpenConfig && /* @__PURE__ */ jsx8(
1323
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: "8px" }, children: [
1324
+ showConfigButton && onOpenConfig && /* @__PURE__ */ jsx9(
1199
1325
  "button",
1200
1326
  {
1201
1327
  onClick: onOpenConfig,
@@ -1216,7 +1342,7 @@ function DeviceControlPanelImpl({
1216
1342
  children: "\u2699"
1217
1343
  }
1218
1344
  ),
1219
- onUndock && /* @__PURE__ */ jsx8(
1345
+ onUndock && /* @__PURE__ */ jsx9(
1220
1346
  "button",
1221
1347
  {
1222
1348
  onClick: onUndock,
@@ -1237,7 +1363,7 @@ function DeviceControlPanelImpl({
1237
1363
  children: undockIcon
1238
1364
  }
1239
1365
  ),
1240
- /* @__PURE__ */ jsx8(
1366
+ /* @__PURE__ */ jsx9(
1241
1367
  "button",
1242
1368
  {
1243
1369
  onClick: onClose,
@@ -1260,7 +1386,7 @@ function DeviceControlPanelImpl({
1260
1386
  ]
1261
1387
  }
1262
1388
  ),
1263
- binding.state.status && /* @__PURE__ */ jsxs7(
1389
+ binding.state.status && /* @__PURE__ */ jsxs8(
1264
1390
  "div",
1265
1391
  {
1266
1392
  style: {
@@ -1274,7 +1400,7 @@ function DeviceControlPanelImpl({
1274
1400
  marginBottom: spacing.marginBottom
1275
1401
  },
1276
1402
  children: [
1277
- /* @__PURE__ */ jsx8(
1403
+ /* @__PURE__ */ jsx9(
1278
1404
  "div",
1279
1405
  {
1280
1406
  style: {
@@ -1282,12 +1408,17 @@ function DeviceControlPanelImpl({
1282
1408
  height: "10px",
1283
1409
  borderRadius: "50%",
1284
1410
  backgroundColor: statusColor,
1285
- border: "1px solid #333"
1286
- }
1411
+ border: "1px solid #333",
1412
+ // The shared helper positions this dot only while it shows
1413
+ // the unknown marker. The glyph then fills the dot with an
1414
+ // absolute inset, the same pattern StatusIndicator uses.
1415
+ ...unknownGlyphHostStyle(isUnknownStatus)
1416
+ },
1417
+ children: isUnknownStatus && /* @__PURE__ */ jsx9(UnknownStatusGlyph, { size: "small", style: UNKNOWN_GLYPH_FILL_STYLE })
1287
1418
  }
1288
1419
  ),
1289
- /* @__PURE__ */ jsxs7("div", { style: { flex: 1 }, children: [
1290
- /* @__PURE__ */ jsx8(
1420
+ /* @__PURE__ */ jsxs8("div", { style: { flex: 1 }, children: [
1421
+ /* @__PURE__ */ jsx9(
1291
1422
  "div",
1292
1423
  {
1293
1424
  style: {
@@ -1301,7 +1432,7 @@ function DeviceControlPanelImpl({
1301
1432
  children: binding.state.status
1302
1433
  }
1303
1434
  ),
1304
- binding.state.statusMessage && /* @__PURE__ */ jsx8(
1435
+ binding.state.statusMessage && /* @__PURE__ */ jsx9(
1305
1436
  "div",
1306
1437
  {
1307
1438
  style: {
@@ -1314,28 +1445,36 @@ function DeviceControlPanelImpl({
1314
1445
  }
1315
1446
  )
1316
1447
  ] }),
1317
- binding.state.isRunning !== void 0 && /* @__PURE__ */ jsx8(
1448
+ binding.state.isRunning !== void 0 && /* @__PURE__ */ jsxs8(
1318
1449
  "div",
1319
1450
  {
1320
1451
  style: {
1321
1452
  padding: "4px 10px",
1322
- backgroundColor: binding.state.isRunning ? selectedColor : surfaceColor,
1323
- border: `1px solid ${borderColor}`,
1453
+ // This badge shows the last published isRunning value. It
1454
+ // never re-derives that value here. When status is
1455
+ // unknown, the value may be stale. Muted colors carry that
1456
+ // warning alongside the word, so color is not the only
1457
+ // channel and the wording is not the only channel either.
1458
+ backgroundColor: isUnknownStatus ? statusEntry.bg : binding.state.isRunning ? selectedColor : surfaceColor,
1459
+ border: `1px solid ${isUnknownStatus ? statusEntry.border : borderColor}`,
1324
1460
  borderRadius: "2px",
1325
1461
  fontSize: "10px",
1326
1462
  fontWeight: 600,
1327
1463
  fontFamily: "Arial, sans-serif",
1328
1464
  letterSpacing: "0.5px",
1329
- color: textColor
1465
+ color: isUnknownStatus ? statusEntry.text : textColor
1330
1466
  },
1331
- children: binding.state.isRunning ? "RUNNING" : "STOPPED"
1467
+ children: [
1468
+ binding.state.isRunning ? "RUNNING" : "STOPPED",
1469
+ isUnknownStatus && " (unconfirmed)"
1470
+ ]
1332
1471
  }
1333
1472
  )
1334
1473
  ]
1335
1474
  }
1336
1475
  ),
1337
- binding.display?.showModeSelector !== false && binding.modes.length > 0 && /* @__PURE__ */ jsxs7("div", { style: { marginBottom: "16px" }, children: [
1338
- /* @__PURE__ */ jsx8(
1476
+ binding.display?.showModeSelector !== false && binding.modes.length > 0 && /* @__PURE__ */ jsxs8("div", { style: { marginBottom: "16px" }, children: [
1477
+ /* @__PURE__ */ jsx9(
1339
1478
  "label",
1340
1479
  {
1341
1480
  style: {
@@ -1351,7 +1490,7 @@ function DeviceControlPanelImpl({
1351
1490
  children: "Mode"
1352
1491
  }
1353
1492
  ),
1354
- modeSelectionStyle === "dropdown" ? /* @__PURE__ */ jsx8(
1493
+ modeSelectionStyle === "dropdown" ? /* @__PURE__ */ jsx9(
1355
1494
  "select",
1356
1495
  {
1357
1496
  value: currentMode,
@@ -1372,10 +1511,10 @@ function DeviceControlPanelImpl({
1372
1511
  paddingRight: "12px",
1373
1512
  cursor: "pointer"
1374
1513
  },
1375
- children: binding.modes.map((mode) => /* @__PURE__ */ jsx8("option", { value: mode.value, disabled: mode.enabled === false, children: mode.label }, mode.value))
1514
+ children: binding.modes.map((mode) => /* @__PURE__ */ jsx9("option", { value: mode.value, disabled: mode.enabled === false, children: mode.label }, mode.value))
1376
1515
  }
1377
- ) : modeSelectionStyle === "modal" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1378
- /* @__PURE__ */ jsxs7(
1516
+ ) : modeSelectionStyle === "modal" ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
1517
+ /* @__PURE__ */ jsxs8(
1379
1518
  "div",
1380
1519
  {
1381
1520
  onClick: () => {
@@ -1398,12 +1537,12 @@ function DeviceControlPanelImpl({
1398
1537
  alignItems: "center"
1399
1538
  },
1400
1539
  children: [
1401
- /* @__PURE__ */ jsx8("span", { children: binding.modes.find((m) => m.value === currentMode)?.label || currentMode }),
1402
- /* @__PURE__ */ jsx8("span", { style: { fontSize: "20px", marginLeft: "8px" }, children: "\u25BC" })
1540
+ /* @__PURE__ */ jsx9("span", { children: binding.modes.find((m) => m.value === currentMode)?.label || currentMode }),
1541
+ /* @__PURE__ */ jsx9("span", { style: { fontSize: "20px", marginLeft: "8px" }, children: "\u25BC" })
1403
1542
  ]
1404
1543
  }
1405
1544
  ),
1406
- showModeModal && /* @__PURE__ */ jsx8(
1545
+ showModeModal && /* @__PURE__ */ jsx9(
1407
1546
  "div",
1408
1547
  {
1409
1548
  style: {
@@ -1422,7 +1561,7 @@ function DeviceControlPanelImpl({
1422
1561
  setShowModeModal(false);
1423
1562
  setPendingMode(null);
1424
1563
  },
1425
- children: /* @__PURE__ */ jsxs7(
1564
+ children: /* @__PURE__ */ jsxs8(
1426
1565
  "div",
1427
1566
  {
1428
1567
  onClick: (e) => e.stopPropagation(),
@@ -1435,7 +1574,7 @@ function DeviceControlPanelImpl({
1435
1574
  border: `2px solid ${borderColor}`
1436
1575
  },
1437
1576
  children: [
1438
- /* @__PURE__ */ jsx8(
1577
+ /* @__PURE__ */ jsx9(
1439
1578
  "h3",
1440
1579
  {
1441
1580
  style: {
@@ -1448,7 +1587,7 @@ function DeviceControlPanelImpl({
1448
1587
  children: "Select Mode"
1449
1588
  }
1450
1589
  ),
1451
- /* @__PURE__ */ jsx8(
1590
+ /* @__PURE__ */ jsx9(
1452
1591
  "div",
1453
1592
  {
1454
1593
  style: {
@@ -1462,7 +1601,7 @@ function DeviceControlPanelImpl({
1462
1601
  children: binding.display?.title || binding.deviceName || binding.tag || binding.nodeId
1463
1602
  }
1464
1603
  ),
1465
- /* @__PURE__ */ jsx8(
1604
+ /* @__PURE__ */ jsx9(
1466
1605
  "select",
1467
1606
  {
1468
1607
  value: pendingMode || currentMode,
@@ -1484,7 +1623,7 @@ function DeviceControlPanelImpl({
1484
1623
  paddingRight: "12px",
1485
1624
  cursor: "pointer"
1486
1625
  },
1487
- children: binding.modes.map((mode) => /* @__PURE__ */ jsx8(
1626
+ children: binding.modes.map((mode) => /* @__PURE__ */ jsx9(
1488
1627
  "option",
1489
1628
  {
1490
1629
  value: mode.value,
@@ -1499,7 +1638,7 @@ function DeviceControlPanelImpl({
1499
1638
  const selectedMode = binding.modes.find(
1500
1639
  (m) => m.value === (pendingMode || currentMode)
1501
1640
  );
1502
- return selectedMode?.description ? /* @__PURE__ */ jsx8(
1641
+ return selectedMode?.description ? /* @__PURE__ */ jsx9(
1503
1642
  "div",
1504
1643
  {
1505
1644
  style: {
@@ -1517,8 +1656,8 @@ function DeviceControlPanelImpl({
1517
1656
  }
1518
1657
  ) : null;
1519
1658
  })(),
1520
- /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "12px" }, children: [
1521
- /* @__PURE__ */ jsx8(
1659
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: "12px" }, children: [
1660
+ /* @__PURE__ */ jsx9(
1522
1661
  "button",
1523
1662
  {
1524
1663
  onClick: () => {
@@ -1546,7 +1685,7 @@ function DeviceControlPanelImpl({
1546
1685
  children: "Confirm"
1547
1686
  }
1548
1687
  ),
1549
- /* @__PURE__ */ jsx8(
1688
+ /* @__PURE__ */ jsx9(
1550
1689
  "button",
1551
1690
  {
1552
1691
  onClick: () => {
@@ -1576,7 +1715,7 @@ function DeviceControlPanelImpl({
1576
1715
  )
1577
1716
  }
1578
1717
  )
1579
- ] }) : /* @__PURE__ */ jsx8(
1718
+ ] }) : /* @__PURE__ */ jsx9(
1580
1719
  "div",
1581
1720
  {
1582
1721
  style: {
@@ -1592,7 +1731,7 @@ function DeviceControlPanelImpl({
1592
1731
  children: binding.modes.map((mode) => {
1593
1732
  const isSelected = mode.value === currentMode;
1594
1733
  const isEnabled = mode.enabled !== false;
1595
- return /* @__PURE__ */ jsx8(
1734
+ return /* @__PURE__ */ jsx9(
1596
1735
  "button",
1597
1736
  {
1598
1737
  onClick: () => isEnabled && handleModeChange(mode.value),
@@ -1622,8 +1761,8 @@ function DeviceControlPanelImpl({
1622
1761
  }
1623
1762
  )
1624
1763
  ] }),
1625
- currentParams.length > 0 && /* @__PURE__ */ jsxs7("div", { style: { marginBottom: spacing.marginBottom }, children: [
1626
- /* @__PURE__ */ jsx8(
1764
+ currentParams.length > 0 && /* @__PURE__ */ jsxs8("div", { style: { marginBottom: spacing.marginBottom }, children: [
1765
+ /* @__PURE__ */ jsx9(
1627
1766
  "label",
1628
1767
  {
1629
1768
  style: {
@@ -1639,7 +1778,7 @@ function DeviceControlPanelImpl({
1639
1778
  children: "Parameters"
1640
1779
  }
1641
1780
  ),
1642
- currentParams.map((param) => /* @__PURE__ */ jsxs7(
1781
+ currentParams.map((param) => /* @__PURE__ */ jsxs8(
1643
1782
  "div",
1644
1783
  {
1645
1784
  style: {
@@ -1650,7 +1789,7 @@ function DeviceControlPanelImpl({
1650
1789
  borderRadius: "6px"
1651
1790
  },
1652
1791
  children: [
1653
- /* @__PURE__ */ jsxs7(
1792
+ /* @__PURE__ */ jsxs8(
1654
1793
  "div",
1655
1794
  {
1656
1795
  style: {
@@ -1659,7 +1798,7 @@ function DeviceControlPanelImpl({
1659
1798
  marginBottom: spacing.gap
1660
1799
  },
1661
1800
  children: [
1662
- /* @__PURE__ */ jsxs7(
1801
+ /* @__PURE__ */ jsxs8(
1663
1802
  "span",
1664
1803
  {
1665
1804
  style: {
@@ -1675,7 +1814,7 @@ function DeviceControlPanelImpl({
1675
1814
  },
1676
1815
  children: [
1677
1816
  param.label,
1678
- param.forced?.active && /* @__PURE__ */ jsx8(
1817
+ param.forced?.active && /* @__PURE__ */ jsx9(
1679
1818
  "span",
1680
1819
  {
1681
1820
  "aria-label": "Forced",
@@ -1698,7 +1837,7 @@ function DeviceControlPanelImpl({
1698
1837
  ]
1699
1838
  }
1700
1839
  ),
1701
- /* @__PURE__ */ jsxs7(
1840
+ /* @__PURE__ */ jsxs8(
1702
1841
  "span",
1703
1842
  {
1704
1843
  style: {
@@ -1710,14 +1849,14 @@ function DeviceControlPanelImpl({
1710
1849
  children: [
1711
1850
  localParameterValues[param.id] ?? param.value,
1712
1851
  " ",
1713
- param.unit && /* @__PURE__ */ jsx8("span", { style: { color: mutedTextColor, fontSize: "10px", fontWeight: 600 }, children: param.unit })
1852
+ param.unit && /* @__PURE__ */ jsx9("span", { style: { color: mutedTextColor, fontSize: "10px", fontWeight: 600 }, children: param.unit })
1714
1853
  ]
1715
1854
  }
1716
1855
  )
1717
1856
  ]
1718
1857
  }
1719
1858
  ),
1720
- param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ jsxs7(
1859
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ jsxs8(
1721
1860
  "div",
1722
1861
  {
1723
1862
  style: {
@@ -1738,7 +1877,7 @@ function DeviceControlPanelImpl({
1738
1877
  ]
1739
1878
  }
1740
1879
  ),
1741
- param.type === "boolean" ? /* @__PURE__ */ jsx8(
1880
+ param.type === "boolean" ? /* @__PURE__ */ jsx9(
1742
1881
  "button",
1743
1882
  {
1744
1883
  onClick: () => {
@@ -1763,7 +1902,7 @@ function DeviceControlPanelImpl({
1763
1902
  },
1764
1903
  children: localParameterValues[param.id] ? "ON" : "OFF"
1765
1904
  }
1766
- ) : param.type === "select" ? /* @__PURE__ */ jsx8(
1905
+ ) : param.type === "select" ? /* @__PURE__ */ jsx9(
1767
1906
  "select",
1768
1907
  {
1769
1908
  value: localParameterValues[param.id] || param.value,
@@ -1784,12 +1923,12 @@ function DeviceControlPanelImpl({
1784
1923
  fontFamily: "monospace",
1785
1924
  cursor: "pointer"
1786
1925
  },
1787
- children: param.options?.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
1926
+ children: param.options?.map((opt) => /* @__PURE__ */ jsx9("option", { value: opt.value, children: opt.label }, opt.value))
1788
1927
  }
1789
1928
  ) : onParameterActivate && !param.readOnly ? (
1790
1929
  // Tappable value: defer editing to the consumer's editor (e.g. a
1791
1930
  // numpad dialog). Commit happens via onParameterChange.
1792
- /* @__PURE__ */ jsxs7(
1931
+ /* @__PURE__ */ jsxs8(
1793
1932
  "button",
1794
1933
  {
1795
1934
  type: "button",
@@ -1816,7 +1955,7 @@ function DeviceControlPanelImpl({
1816
1955
  ]
1817
1956
  }
1818
1957
  )
1819
- ) : /* @__PURE__ */ jsx8(
1958
+ ) : /* @__PURE__ */ jsx9(
1820
1959
  "input",
1821
1960
  {
1822
1961
  type: param.type === "number" ? "number" : "text",
@@ -1849,8 +1988,8 @@ function DeviceControlPanelImpl({
1849
1988
  param.id
1850
1989
  ))
1851
1990
  ] }),
1852
- binding.display?.showStartStop !== false && (activeCapabilities?.canStart || activeCapabilities?.canStop) && /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom }, children: [
1853
- activeCapabilities?.canStart && /* @__PURE__ */ jsx8(
1991
+ binding.display?.showStartStop !== false && (activeCapabilities?.canStart || activeCapabilities?.canStop) && /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom }, children: [
1992
+ activeCapabilities?.canStart && /* @__PURE__ */ jsx9(
1854
1993
  "button",
1855
1994
  {
1856
1995
  onClick: onStart,
@@ -1871,7 +2010,7 @@ function DeviceControlPanelImpl({
1871
2010
  children: startPending ? "STARTING\u2026" : "START"
1872
2011
  }
1873
2012
  ),
1874
- activeCapabilities?.canStop && /* @__PURE__ */ jsx8(
2013
+ activeCapabilities?.canStop && /* @__PURE__ */ jsx9(
1875
2014
  "button",
1876
2015
  {
1877
2016
  onClick: onStop,
@@ -1893,9 +2032,9 @@ function DeviceControlPanelImpl({
1893
2032
  }
1894
2033
  )
1895
2034
  ] }),
1896
- activeCapabilities?.customActions && activeCapabilities.customActions.length > 0 && /* @__PURE__ */ jsx8("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: activeCapabilities.customActions.map((action) => {
2035
+ activeCapabilities?.customActions && activeCapabilities.customActions.length > 0 && /* @__PURE__ */ jsx9("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: activeCapabilities.customActions.map((action) => {
1897
2036
  const actionDisabled = action.disabled === true;
1898
- return /* @__PURE__ */ jsxs7(
2037
+ return /* @__PURE__ */ jsxs8(
1899
2038
  "button",
1900
2039
  {
1901
2040
  onClick: () => {
@@ -1921,7 +2060,7 @@ function DeviceControlPanelImpl({
1921
2060
  transition: "all 0.15s"
1922
2061
  },
1923
2062
  children: [
1924
- action.icon && /* @__PURE__ */ jsx8("span", { style: { marginRight: "6px" }, children: action.icon }),
2063
+ action.icon && /* @__PURE__ */ jsx9("span", { style: { marginRight: "6px" }, children: action.icon }),
1925
2064
  action.label
1926
2065
  ]
1927
2066
  },
@@ -1929,7 +2068,7 @@ function DeviceControlPanelImpl({
1929
2068
  );
1930
2069
  }) })
1931
2070
  ] }),
1932
- binding.state.lastCommandResult && showToast && showBuiltInToast && /* @__PURE__ */ jsx8(
2071
+ binding.state.lastCommandResult && showToast && showBuiltInToast && /* @__PURE__ */ jsx9(
1933
2072
  "div",
1934
2073
  {
1935
2074
  style: {
@@ -1953,7 +2092,7 @@ function DeviceControlPanelImpl({
1953
2092
  children: binding.state.lastCommandResult.message
1954
2093
  }
1955
2094
  ),
1956
- /* @__PURE__ */ jsx8("style", { children: `
2095
+ /* @__PURE__ */ jsx9("style", { children: `
1957
2096
  @keyframes slideInToast {
1958
2097
  from {
1959
2098
  opacity: 0;
@@ -1985,11 +2124,11 @@ function DeviceControlPanelImpl({
1985
2124
  ` })
1986
2125
  ] });
1987
2126
  }
1988
- var DeviceControlPanel = React5.memo(DeviceControlPanelImpl);
2127
+ var DeviceControlPanel = React6.memo(DeviceControlPanelImpl);
1989
2128
 
1990
2129
  // src/components/NumpadDialog/NumpadDialog.tsx
1991
2130
  import { useState as useState6, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef2, useId } from "react";
1992
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2131
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1993
2132
  var NumpadDialog = ({
1994
2133
  isOpen,
1995
2134
  onClose,
@@ -2193,7 +2332,7 @@ var NumpadDialog = ({
2193
2332
  };
2194
2333
  const reason = belowMin ? `Below minimum of ${min} ${unit}` : aboveMax ? `Above maximum of ${max} ${unit}` : "Type or tap to change the value";
2195
2334
  const backspaceInGrid = min >= 0;
2196
- const BackspaceButton = ({ style, label }) => /* @__PURE__ */ jsx9(
2335
+ const BackspaceButton = ({ style, label }) => /* @__PURE__ */ jsx10(
2197
2336
  "button",
2198
2337
  {
2199
2338
  type: "button",
@@ -2210,7 +2349,7 @@ var NumpadDialog = ({
2210
2349
  );
2211
2350
  return (
2212
2351
  // Backdrop
2213
- /* @__PURE__ */ jsxs8(
2352
+ /* @__PURE__ */ jsxs9(
2214
2353
  "div",
2215
2354
  {
2216
2355
  style: {
@@ -2227,14 +2366,14 @@ var NumpadDialog = ({
2227
2366
  },
2228
2367
  onClick: onClose,
2229
2368
  children: [
2230
- /* @__PURE__ */ jsx9("style", { children: `
2369
+ /* @__PURE__ */ jsx10("style", { children: `
2231
2370
  .np-key { transition: background .1s, transform .05s; touch-action: manipulation; }
2232
2371
  .np-key:active:not(:disabled) { background:#e2e8f0; transform: translateY(1px); }
2233
2372
  .np-key:focus-visible, .np-action:focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; }
2234
2373
  @keyframes np-blink { 0%,49%{opacity:1} 50%,100%{opacity:0} }
2235
2374
  .np-caret { display:inline-block; width:2px; height:1.1em; background:#2563eb; animation: np-blink 1s step-end infinite; }
2236
2375
  ` }),
2237
- /* @__PURE__ */ jsxs8(
2376
+ /* @__PURE__ */ jsxs9(
2238
2377
  "div",
2239
2378
  {
2240
2379
  ref: dialogRef,
@@ -2255,9 +2394,9 @@ var NumpadDialog = ({
2255
2394
  },
2256
2395
  onClick: (e) => e.stopPropagation(),
2257
2396
  children: [
2258
- /* @__PURE__ */ jsx9("div", { style: { padding: "14px 16px 0 16px" }, children: /* @__PURE__ */ jsx9("h3", { id: titleId, style: { margin: 0, fontSize: 15, fontWeight: 700, color: "#0f172a" }, children: deviceName ? `${deviceName} - ${parameterLabel}` : parameterLabel }) }),
2259
- /* @__PURE__ */ jsxs8("div", { style: { padding: 16, display: "flex", flexDirection: "column", gap: 10 }, children: [
2260
- /* @__PURE__ */ jsxs8(
2397
+ /* @__PURE__ */ jsx10("div", { style: { padding: "14px 16px 0 16px" }, children: /* @__PURE__ */ jsx10("h3", { id: titleId, style: { margin: 0, fontSize: 15, fontWeight: 700, color: "#0f172a" }, children: deviceName ? `${deviceName} - ${parameterLabel}` : parameterLabel }) }),
2398
+ /* @__PURE__ */ jsxs9("div", { style: { padding: 16, display: "flex", flexDirection: "column", gap: 10 }, children: [
2399
+ /* @__PURE__ */ jsxs9(
2261
2400
  "div",
2262
2401
  {
2263
2402
  style: {
@@ -2267,24 +2406,24 @@ var NumpadDialog = ({
2267
2406
  color: "#64748b"
2268
2407
  },
2269
2408
  children: [
2270
- /* @__PURE__ */ jsxs8("span", { children: [
2409
+ /* @__PURE__ */ jsxs9("span", { children: [
2271
2410
  "Current: ",
2272
2411
  currentValue,
2273
2412
  " ",
2274
2413
  unit
2275
2414
  ] }),
2276
- /* @__PURE__ */ jsxs8("span", { children: [
2415
+ /* @__PURE__ */ jsxs9("span", { children: [
2277
2416
  "Range: ",
2278
- /* @__PURE__ */ jsx9("span", { style: limitStyle(belowMin), children: min }),
2417
+ /* @__PURE__ */ jsx10("span", { style: limitStyle(belowMin), children: min }),
2279
2418
  "-",
2280
- /* @__PURE__ */ jsx9("span", { style: limitStyle(aboveMax), children: max }),
2419
+ /* @__PURE__ */ jsx10("span", { style: limitStyle(aboveMax), children: max }),
2281
2420
  " ",
2282
2421
  unit
2283
2422
  ] })
2284
2423
  ]
2285
2424
  }
2286
2425
  ),
2287
- /* @__PURE__ */ jsxs8(
2426
+ /* @__PURE__ */ jsxs9(
2288
2427
  "div",
2289
2428
  {
2290
2429
  "aria-live": "polite",
@@ -2306,13 +2445,13 @@ var NumpadDialog = ({
2306
2445
  border: outOfRange ? "1.5px solid #fca5a5" : "1.5px solid #e2e8f0"
2307
2446
  },
2308
2447
  children: [
2309
- /* @__PURE__ */ jsx9("span", { children: inputValue || "0" }),
2310
- /* @__PURE__ */ jsx9("span", { className: "np-caret", "aria-hidden": "true" }),
2311
- /* @__PURE__ */ jsx9("span", { style: { fontSize: 14, color: "#94a3b8", fontWeight: 500 }, children: unit })
2448
+ /* @__PURE__ */ jsx10("span", { children: inputValue || "0" }),
2449
+ /* @__PURE__ */ jsx10("span", { className: "np-caret", "aria-hidden": "true" }),
2450
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 14, color: "#94a3b8", fontWeight: 500 }, children: unit })
2312
2451
  ]
2313
2452
  }
2314
2453
  ),
2315
- /* @__PURE__ */ jsx9(
2454
+ /* @__PURE__ */ jsx10(
2316
2455
  "div",
2317
2456
  {
2318
2457
  role: "status",
@@ -2327,8 +2466,8 @@ var NumpadDialog = ({
2327
2466
  children: reason
2328
2467
  }
2329
2468
  ),
2330
- /* @__PURE__ */ jsxs8("div", { style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 5 }, children: [
2331
- ["7", "8", "9", "4", "5", "6", "1", "2", "3"].map((digit) => /* @__PURE__ */ jsx9(
2469
+ /* @__PURE__ */ jsxs9("div", { style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 5 }, children: [
2470
+ ["7", "8", "9", "4", "5", "6", "1", "2", "3"].map((digit) => /* @__PURE__ */ jsx10(
2332
2471
  "button",
2333
2472
  {
2334
2473
  type: "button",
@@ -2340,7 +2479,7 @@ var NumpadDialog = ({
2340
2479
  },
2341
2480
  digit
2342
2481
  )),
2343
- min < 0 ? /* @__PURE__ */ jsx9(
2482
+ min < 0 ? /* @__PURE__ */ jsx10(
2344
2483
  "button",
2345
2484
  {
2346
2485
  type: "button",
@@ -2351,9 +2490,9 @@ var NumpadDialog = ({
2351
2490
  onClick: handleToggleSign,
2352
2491
  children: "+/\u2212"
2353
2492
  }
2354
- ) : /* @__PURE__ */ jsx9(BackspaceButton, { style: { ...keyStyle, fontSize: 20, background: "#f8fafc", color: "#64748b" }, label: "\u232B" }),
2355
- /* @__PURE__ */ jsx9("button", { type: "button", className: "np-key", tabIndex: -1, style: keyStyle, onClick: () => appendDigit("0"), children: "0" }),
2356
- /* @__PURE__ */ jsx9(
2493
+ ) : /* @__PURE__ */ jsx10(BackspaceButton, { style: { ...keyStyle, fontSize: 20, background: "#f8fafc", color: "#64748b" }, label: "\u232B" }),
2494
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "np-key", tabIndex: -1, style: keyStyle, onClick: () => appendDigit("0"), children: "0" }),
2495
+ /* @__PURE__ */ jsx10(
2357
2496
  "button",
2358
2497
  {
2359
2498
  type: "button",
@@ -2371,12 +2510,12 @@ var NumpadDialog = ({
2371
2510
  }
2372
2511
  )
2373
2512
  ] }),
2374
- /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: 5 }, children: [
2375
- /* @__PURE__ */ jsx9("button", { type: "button", className: "np-key", tabIndex: -1, style: utilKeyStyle, onClick: handleClear, children: "Clear" }),
2376
- !backspaceInGrid && /* @__PURE__ */ jsx9(BackspaceButton, { style: utilKeyStyle, label: "Backspace" })
2513
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 5 }, children: [
2514
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "np-key", tabIndex: -1, style: utilKeyStyle, onClick: handleClear, children: "Clear" }),
2515
+ !backspaceInGrid && /* @__PURE__ */ jsx10(BackspaceButton, { style: utilKeyStyle, label: "Backspace" })
2377
2516
  ] }),
2378
- /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: 8, marginTop: 2 }, children: [
2379
- /* @__PURE__ */ jsx9(
2517
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 8, marginTop: 2 }, children: [
2518
+ /* @__PURE__ */ jsx10(
2380
2519
  "button",
2381
2520
  {
2382
2521
  type: "button",
@@ -2396,7 +2535,7 @@ var NumpadDialog = ({
2396
2535
  children: "Cancel"
2397
2536
  }
2398
2537
  ),
2399
- /* @__PURE__ */ jsx9(
2538
+ /* @__PURE__ */ jsx10(
2400
2539
  "button",
2401
2540
  {
2402
2541
  type: "button",
@@ -2431,13 +2570,13 @@ var NumpadDialog = ({
2431
2570
  };
2432
2571
 
2433
2572
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
2434
- import React7 from "react";
2573
+ import React8 from "react";
2435
2574
  import { Tab } from "@mui/material";
2436
2575
 
2437
2576
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.styles.tsx
2438
2577
  import { Box, IconButton, Tabs } from "@mui/material";
2439
2578
  import styled from "@emotion/styled";
2440
- import { jsx as jsx10 } from "react/jsx-runtime";
2579
+ import { jsx as jsx11 } from "react/jsx-runtime";
2441
2580
  var FullscreenContainer = styled(Box)(
2442
2581
  ({ leftCollapsed, rightCollapsed }) => {
2443
2582
  const getGridTemplateAreas = () => {
@@ -2562,7 +2701,7 @@ var DisplayModeToggle = styled(IconButton)(({ mode }) => ({
2562
2701
  transform: "translateX(-50%) scale(1.05)"
2563
2702
  }
2564
2703
  }));
2565
- var ChevronLeft = () => /* @__PURE__ */ jsx10(
2704
+ var ChevronLeft = () => /* @__PURE__ */ jsx11(
2566
2705
  "div",
2567
2706
  {
2568
2707
  style: {
@@ -2574,7 +2713,7 @@ var ChevronLeft = () => /* @__PURE__ */ jsx10(
2574
2713
  }
2575
2714
  }
2576
2715
  );
2577
- var ChevronRight = () => /* @__PURE__ */ jsx10(
2716
+ var ChevronRight = () => /* @__PURE__ */ jsx11(
2578
2717
  "div",
2579
2718
  {
2580
2719
  style: {
@@ -2688,7 +2827,7 @@ var ScrollableSVGWrapper = styled("div")({
2688
2827
  import { useEffect as useEffect4, useState as useState7 } from "react";
2689
2828
  import { Box as Box2 } from "@mui/material";
2690
2829
  import styled2 from "@emotion/styled";
2691
- import { jsx as jsx11 } from "react/jsx-runtime";
2830
+ import { jsx as jsx12 } from "react/jsx-runtime";
2692
2831
  var baseOverlayStyles = {
2693
2832
  tank: {
2694
2833
  background: "linear-gradient(135deg, rgba(69, 90, 120, 0.95) 0%, rgba(52, 73, 94, 0.95) 100%)",
@@ -2771,7 +2910,7 @@ var SVGLockedOverlay = ({
2771
2910
  const pixelX = svgX + svgOffset.x;
2772
2911
  const pixelY = svgY + svgOffset.y;
2773
2912
  const appliedStyle = theme ? { ...overlayStyles[theme], ...style } : style;
2774
- return /* @__PURE__ */ jsx11(
2913
+ return /* @__PURE__ */ jsx12(
2775
2914
  Box2,
2776
2915
  {
2777
2916
  className: `svg-locked-overlay ${className}`.trim(),
@@ -2835,7 +2974,7 @@ var CardUnit = styled2("div")({
2835
2974
  });
2836
2975
 
2837
2976
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.visuals.tsx
2838
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
2977
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
2839
2978
  var forcedTagStyle = {
2840
2979
  position: "absolute",
2841
2980
  top: "-6px",
@@ -2863,7 +3002,7 @@ var TrendLine = ({
2863
3002
  forced = false
2864
3003
  }) => {
2865
3004
  if (!values || values.length < 2) {
2866
- return /* @__PURE__ */ jsxs9(
3005
+ return /* @__PURE__ */ jsxs10(
2867
3006
  "div",
2868
3007
  {
2869
3008
  style: {
@@ -2877,8 +3016,8 @@ var TrendLine = ({
2877
3016
  justifyContent: "center"
2878
3017
  },
2879
3018
  children: [
2880
- /* @__PURE__ */ jsx12("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" }),
2881
- forced && /* @__PURE__ */ jsx12("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3019
+ /* @__PURE__ */ jsx13("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" }),
3020
+ forced && /* @__PURE__ */ jsx13("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
2882
3021
  ]
2883
3022
  }
2884
3023
  );
@@ -2891,7 +3030,7 @@ var TrendLine = ({
2891
3030
  const x = 4 + i / (values.length - 1) * (width - 8);
2892
3031
  return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
2893
3032
  }).join(" ");
2894
- return /* @__PURE__ */ jsxs9(
3033
+ return /* @__PURE__ */ jsxs10(
2895
3034
  "div",
2896
3035
  {
2897
3036
  style: {
@@ -2903,14 +3042,14 @@ var TrendLine = ({
2903
3042
  overflow: "visible"
2904
3043
  },
2905
3044
  children: [
2906
- /* @__PURE__ */ jsxs9(
3045
+ /* @__PURE__ */ jsxs10(
2907
3046
  "svg",
2908
3047
  {
2909
3048
  width,
2910
3049
  height,
2911
3050
  style: { position: "absolute", top: 0, left: 0, overflow: "hidden", borderRadius: "2px" },
2912
3051
  children: [
2913
- /* @__PURE__ */ jsx12(
3052
+ /* @__PURE__ */ jsx13(
2914
3053
  "path",
2915
3054
  {
2916
3055
  d: pathData,
@@ -2921,7 +3060,7 @@ var TrendLine = ({
2921
3060
  strokeLinejoin: "round"
2922
3061
  }
2923
3062
  ),
2924
- /* @__PURE__ */ jsx12(
3063
+ /* @__PURE__ */ jsx13(
2925
3064
  "circle",
2926
3065
  {
2927
3066
  cx: 4 + (width - 8),
@@ -2933,7 +3072,7 @@ var TrendLine = ({
2933
3072
  ]
2934
3073
  }
2935
3074
  ),
2936
- forced && /* @__PURE__ */ jsx12("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3075
+ forced && /* @__PURE__ */ jsx13("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
2937
3076
  ]
2938
3077
  }
2939
3078
  );
@@ -2965,7 +3104,7 @@ var CircularGauge = ({
2965
3104
  const borderColor = isHex ? hexToRgba(color, 0.9) : color;
2966
3105
  const backgroundColor = isHex ? hexToRgba(color, 0.15) : color;
2967
3106
  const borderStrokeColor = isHex ? hexToRgba(color, 0.4) : color;
2968
- return /* @__PURE__ */ jsxs9(
3107
+ return /* @__PURE__ */ jsxs10(
2969
3108
  "div",
2970
3109
  {
2971
3110
  style: {
@@ -2974,7 +3113,7 @@ var CircularGauge = ({
2974
3113
  alignItems: "center"
2975
3114
  },
2976
3115
  children: [
2977
- /* @__PURE__ */ jsxs9(
3116
+ /* @__PURE__ */ jsxs10(
2978
3117
  "div",
2979
3118
  {
2980
3119
  style: {
@@ -2986,8 +3125,8 @@ var CircularGauge = ({
2986
3125
  border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : `1px solid ${borderStrokeColor}`
2987
3126
  },
2988
3127
  children: [
2989
- /* @__PURE__ */ jsxs9("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
2990
- /* @__PURE__ */ jsx12(
3128
+ /* @__PURE__ */ jsxs10("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
3129
+ /* @__PURE__ */ jsx13(
2991
3130
  "circle",
2992
3131
  {
2993
3132
  cx: size / 2,
@@ -2998,7 +3137,7 @@ var CircularGauge = ({
2998
3137
  strokeWidth: 1
2999
3138
  }
3000
3139
  ),
3001
- /* @__PURE__ */ jsx12(
3140
+ /* @__PURE__ */ jsx13(
3002
3141
  "circle",
3003
3142
  {
3004
3143
  cx: size / 2,
@@ -3009,7 +3148,7 @@ var CircularGauge = ({
3009
3148
  fill: "transparent"
3010
3149
  }
3011
3150
  ),
3012
- /* @__PURE__ */ jsx12(
3151
+ /* @__PURE__ */ jsx13(
3013
3152
  "circle",
3014
3153
  {
3015
3154
  cx: size / 2,
@@ -3025,7 +3164,7 @@ var CircularGauge = ({
3025
3164
  }
3026
3165
  )
3027
3166
  ] }),
3028
- /* @__PURE__ */ jsxs9(
3167
+ /* @__PURE__ */ jsxs10(
3029
3168
  "div",
3030
3169
  {
3031
3170
  style: {
@@ -3041,14 +3180,14 @@ var CircularGauge = ({
3041
3180
  },
3042
3181
  children: [
3043
3182
  value.toFixed(1),
3044
- /* @__PURE__ */ jsx12("div", { style: { fontSize: "8px", opacity: 0.9, color: "white" }, children: unit })
3183
+ /* @__PURE__ */ jsx13("div", { style: { fontSize: "8px", opacity: 0.9, color: "white" }, children: unit })
3045
3184
  ]
3046
3185
  }
3047
3186
  )
3048
3187
  ]
3049
3188
  }
3050
3189
  ),
3051
- /* @__PURE__ */ jsx12(
3190
+ /* @__PURE__ */ jsx13(
3052
3191
  "div",
3053
3192
  {
3054
3193
  style: {
@@ -3060,7 +3199,7 @@ var CircularGauge = ({
3060
3199
  children: label
3061
3200
  }
3062
3201
  ),
3063
- forced && /* @__PURE__ */ jsx12(
3202
+ forced && /* @__PURE__ */ jsx13(
3064
3203
  "span",
3065
3204
  {
3066
3205
  "aria-label": "Forced",
@@ -3081,7 +3220,7 @@ var CircularGauge = ({
3081
3220
  children: "F"
3082
3221
  }
3083
3222
  ),
3084
- forced && realValue !== void 0 && /* @__PURE__ */ jsxs9("div", { style: { fontSize: "8px", opacity: 0.8, color: "white", marginTop: "1px" }, children: [
3223
+ forced && realValue !== void 0 && /* @__PURE__ */ jsxs10("div", { style: { fontSize: "8px", opacity: 0.8, color: "white", marginTop: "1px" }, children: [
3085
3224
  "REAL ",
3086
3225
  realValue.toFixed(1),
3087
3226
  " ",
@@ -3106,7 +3245,7 @@ var Sparkline = ({ data, width, height, color, forced = false }) => {
3106
3245
  const y = height - 8 - (value - min) / range * (height - 8);
3107
3246
  return `${x + 4},${y + 4}`;
3108
3247
  }).join(" ");
3109
- return /* @__PURE__ */ jsxs9(
3248
+ return /* @__PURE__ */ jsxs10(
3110
3249
  "div",
3111
3250
  {
3112
3251
  style: {
@@ -3122,13 +3261,13 @@ var Sparkline = ({ data, width, height, color, forced = false }) => {
3122
3261
  boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
3123
3262
  },
3124
3263
  children: [
3125
- /* @__PURE__ */ jsx12("svg", { width, height, children: /* @__PURE__ */ jsx12("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) }),
3126
- forced && /* @__PURE__ */ jsx12("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3264
+ /* @__PURE__ */ jsx13("svg", { width, height, children: /* @__PURE__ */ jsx13("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) }),
3265
+ forced && /* @__PURE__ */ jsx13("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3127
3266
  ]
3128
3267
  }
3129
3268
  );
3130
3269
  };
3131
- var EquipmentForcedBadge = () => /* @__PURE__ */ jsx12(
3270
+ var EquipmentForcedBadge = () => /* @__PURE__ */ jsx13(
3132
3271
  "span",
3133
3272
  {
3134
3273
  "aria-label": "Forced",
@@ -3163,9 +3302,9 @@ var EquipmentIndicatorWithSparkline = ({
3163
3302
  forced = false
3164
3303
  }) => {
3165
3304
  const hasSparkline = sparklineData && sparklineData.length > 1 && Math.max(...sparklineData) !== Math.min(...sparklineData);
3166
- return /* @__PURE__ */ jsxs9(Fragment3, { children: [
3167
- /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ jsxs9("div", { style: { position: "relative", width: size, height: size }, children: [
3168
- /* @__PURE__ */ jsx12(
3305
+ return /* @__PURE__ */ jsxs10(Fragment3, { children: [
3306
+ /* @__PURE__ */ jsx13(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ jsxs10("div", { style: { position: "relative", width: size, height: size }, children: [
3307
+ /* @__PURE__ */ jsx13(
3169
3308
  "div",
3170
3309
  {
3171
3310
  style: {
@@ -3186,9 +3325,9 @@ var EquipmentIndicatorWithSparkline = ({
3186
3325
  children: label
3187
3326
  }
3188
3327
  ),
3189
- forced && /* @__PURE__ */ jsx12(EquipmentForcedBadge, {})
3328
+ forced && /* @__PURE__ */ jsx13(EquipmentForcedBadge, {})
3190
3329
  ] }) }),
3191
- hasSparkline && /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ jsx12(Sparkline, { data: sparklineData, width: 60, height: 20, color, forced }) })
3330
+ hasSparkline && /* @__PURE__ */ jsx13(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ jsx13(Sparkline, { data: sparklineData, width: 60, height: 20, color, forced }) })
3192
3331
  ] });
3193
3332
  };
3194
3333
  var EquipmentIndicator = ({
@@ -3202,8 +3341,8 @@ var EquipmentIndicator = ({
3202
3341
  forced = false
3203
3342
  }) => {
3204
3343
  const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
3205
- return /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ jsxs9("div", { style: { position: "relative", width: size, height: size }, children: [
3206
- /* @__PURE__ */ jsx12(
3344
+ return /* @__PURE__ */ jsx13(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ jsxs10("div", { style: { position: "relative", width: size, height: size }, children: [
3345
+ /* @__PURE__ */ jsx13(
3207
3346
  "div",
3208
3347
  {
3209
3348
  style: {
@@ -3224,12 +3363,12 @@ var EquipmentIndicator = ({
3224
3363
  children: label
3225
3364
  }
3226
3365
  ),
3227
- forced && /* @__PURE__ */ jsx12(EquipmentForcedBadge, {})
3366
+ forced && /* @__PURE__ */ jsx13(EquipmentForcedBadge, {})
3228
3367
  ] }) });
3229
3368
  };
3230
3369
 
3231
3370
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
3232
- import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
3371
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
3233
3372
  function FullscreenWorkspace({
3234
3373
  svgContent,
3235
3374
  overlays,
@@ -3257,10 +3396,10 @@ function FullscreenWorkspace({
3257
3396
  className = "",
3258
3397
  ...rest
3259
3398
  }) {
3260
- const [leftCollapsedState, setLeftCollapsedState] = React7.useState(defaultLeftCollapsed);
3261
- const [rightCollapsedState, setRightCollapsedState] = React7.useState(defaultRightCollapsed);
3262
- const [internalDisplayMode, setInternalDisplayMode] = React7.useState(displayMode ?? defaultDisplayMode);
3263
- const [internalTab, setInternalTab] = React7.useState(
3399
+ const [leftCollapsedState, setLeftCollapsedState] = React8.useState(defaultLeftCollapsed);
3400
+ const [rightCollapsedState, setRightCollapsedState] = React8.useState(defaultRightCollapsed);
3401
+ const [internalDisplayMode, setInternalDisplayMode] = React8.useState(displayMode ?? defaultDisplayMode);
3402
+ const [internalTab, setInternalTab] = React8.useState(
3264
3403
  tabs && tabs.length > 0 ? tabs[0].value : ""
3265
3404
  );
3266
3405
  const leftCollapsed = leftCollapsedProp ?? leftCollapsedState;
@@ -3268,10 +3407,10 @@ function FullscreenWorkspace({
3268
3407
  const isDisplayModeControlled = displayMode !== void 0;
3269
3408
  const currentDisplayMode = isDisplayModeControlled ? displayMode : internalDisplayMode;
3270
3409
  const isTabControlled = activeTab !== void 0 && activeTab !== null;
3271
- const resolvedTabs = React7.useMemo(() => tabs ?? [], [tabs]);
3410
+ const resolvedTabs = React8.useMemo(() => tabs ?? [], [tabs]);
3272
3411
  const defaultTabValue = resolvedTabs.length > 0 ? resolvedTabs[0].value : "";
3273
3412
  const currentTab = isTabControlled ? activeTab : internalTab || defaultTabValue;
3274
- React7.useEffect(() => {
3413
+ React8.useEffect(() => {
3275
3414
  if (resolvedTabs.length > 0) {
3276
3415
  const values = resolvedTabs.map((tab) => tab.value);
3277
3416
  if (!values.includes(currentTab)) {
@@ -3280,7 +3419,7 @@ function FullscreenWorkspace({
3280
3419
  }
3281
3420
  }
3282
3421
  }, [resolvedTabs, activeTab, currentTab, defaultTabValue]);
3283
- React7.useEffect(() => {
3422
+ React8.useEffect(() => {
3284
3423
  if (displayMode !== void 0) {
3285
3424
  setInternalDisplayMode(displayMode);
3286
3425
  }
@@ -3315,7 +3454,7 @@ function FullscreenWorkspace({
3315
3454
  const currentTabContent = resolvedTabs.find((tab) => tab.value === currentTab)?.content ?? null;
3316
3455
  const showLeftToggle = Boolean(leftPanel);
3317
3456
  const showRightToggle = Boolean(rightPanel);
3318
- return /* @__PURE__ */ jsxs10(
3457
+ return /* @__PURE__ */ jsxs11(
3319
3458
  FullscreenContainer,
3320
3459
  {
3321
3460
  leftCollapsed,
@@ -3323,25 +3462,25 @@ function FullscreenWorkspace({
3323
3462
  className: `tff-svg-container ${className}`.trim(),
3324
3463
  ...rest,
3325
3464
  children: [
3326
- showLeftToggle && /* @__PURE__ */ jsx13(
3465
+ showLeftToggle && /* @__PURE__ */ jsx14(
3327
3466
  LeftToggleButton,
3328
3467
  {
3329
3468
  collapsed: leftCollapsed,
3330
3469
  onClick: handleLeftToggle,
3331
3470
  "aria-label": leftCollapsed ? "Expand left panel" : "Collapse left panel",
3332
- children: leftCollapsed ? /* @__PURE__ */ jsx13(ChevronRight, {}) : /* @__PURE__ */ jsx13(ChevronLeft, {})
3471
+ children: leftCollapsed ? /* @__PURE__ */ jsx14(ChevronRight, {}) : /* @__PURE__ */ jsx14(ChevronLeft, {})
3333
3472
  }
3334
3473
  ),
3335
- showRightToggle && /* @__PURE__ */ jsx13(
3474
+ showRightToggle && /* @__PURE__ */ jsx14(
3336
3475
  RightToggleButton,
3337
3476
  {
3338
3477
  collapsed: rightCollapsed,
3339
3478
  onClick: handleRightToggle,
3340
3479
  "aria-label": rightCollapsed ? "Expand right panel" : "Collapse right panel",
3341
- children: rightCollapsed ? /* @__PURE__ */ jsx13(ChevronLeft, {}) : /* @__PURE__ */ jsx13(ChevronRight, {})
3480
+ children: rightCollapsed ? /* @__PURE__ */ jsx14(ChevronLeft, {}) : /* @__PURE__ */ jsx14(ChevronRight, {})
3342
3481
  }
3343
3482
  ),
3344
- showDisplayModeToggle && /* @__PURE__ */ jsx13(
3483
+ showDisplayModeToggle && /* @__PURE__ */ jsx14(
3345
3484
  DisplayModeToggle,
3346
3485
  {
3347
3486
  mode: currentDisplayMode,
@@ -3350,9 +3489,9 @@ function FullscreenWorkspace({
3350
3489
  children: currentDisplayMode === "dashboard" ? "Dashboard Mode" : "Standard Mode"
3351
3490
  }
3352
3491
  ),
3353
- /* @__PURE__ */ jsxs10(SVGArea, { children: [
3354
- showZoomControls && /* @__PURE__ */ jsxs10(ZoomControls, { children: [
3355
- /* @__PURE__ */ jsx13(
3492
+ /* @__PURE__ */ jsxs11(SVGArea, { children: [
3493
+ showZoomControls && /* @__PURE__ */ jsxs11(ZoomControls, { children: [
3494
+ /* @__PURE__ */ jsx14(
3356
3495
  ZoomButton,
3357
3496
  {
3358
3497
  onClick: onZoomIn,
@@ -3362,7 +3501,7 @@ function FullscreenWorkspace({
3362
3501
  children: "+"
3363
3502
  }
3364
3503
  ),
3365
- /* @__PURE__ */ jsx13(
3504
+ /* @__PURE__ */ jsx14(
3366
3505
  ZoomButton,
3367
3506
  {
3368
3507
  onClick: onZoomOut,
@@ -3372,7 +3511,7 @@ function FullscreenWorkspace({
3372
3511
  children: "-"
3373
3512
  }
3374
3513
  ),
3375
- onResetZoom && /* @__PURE__ */ jsx13(
3514
+ onResetZoom && /* @__PURE__ */ jsx14(
3376
3515
  ZoomButton,
3377
3516
  {
3378
3517
  onClick: onResetZoom,
@@ -3383,25 +3522,25 @@ function FullscreenWorkspace({
3383
3522
  }
3384
3523
  )
3385
3524
  ] }),
3386
- /* @__PURE__ */ jsx13(ScrollableSVGWrapper, { className: "svg-scroll-wrapper", children: /* @__PURE__ */ jsxs10(FixedSVGContainer, { className: "svg-container", children: [
3525
+ /* @__PURE__ */ jsx14(ScrollableSVGWrapper, { className: "svg-scroll-wrapper", children: /* @__PURE__ */ jsxs11(FixedSVGContainer, { className: "svg-container", children: [
3387
3526
  svgContent,
3388
3527
  overlays
3389
3528
  ] }) })
3390
3529
  ] }),
3391
- /* @__PURE__ */ jsx13(LeftPanel, { collapsed: leftCollapsed, children: resolvedTabs.length > 0 ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
3392
- /* @__PURE__ */ jsx13(
3530
+ /* @__PURE__ */ jsx14(LeftPanel, { collapsed: leftCollapsed, children: resolvedTabs.length > 0 ? /* @__PURE__ */ jsxs11(Fragment4, { children: [
3531
+ /* @__PURE__ */ jsx14(
3393
3532
  PanelTabs,
3394
3533
  {
3395
3534
  value: currentTab,
3396
3535
  onChange: handleTabChangeInternal,
3397
3536
  "aria-label": "Left panel tabs",
3398
- children: resolvedTabs.map((tab) => /* @__PURE__ */ jsx13(Tab, { label: tab.label, value: tab.value }, tab.value))
3537
+ children: resolvedTabs.map((tab) => /* @__PURE__ */ jsx14(Tab, { label: tab.label, value: tab.value }, tab.value))
3399
3538
  }
3400
3539
  ),
3401
- /* @__PURE__ */ jsx13(PanelContent, { children: currentTabContent })
3540
+ /* @__PURE__ */ jsx14(PanelContent, { children: currentTabContent })
3402
3541
  ] }) : leftPanel }),
3403
- /* @__PURE__ */ jsx13(RightPanel, { collapsed: rightCollapsed, children: rightPanel }),
3404
- /* @__PURE__ */ jsx13(FooterPanel, { children: footer })
3542
+ /* @__PURE__ */ jsx14(RightPanel, { collapsed: rightCollapsed, children: rightPanel }),
3543
+ /* @__PURE__ */ jsx14(FooterPanel, { children: footer })
3405
3544
  ]
3406
3545
  }
3407
3546
  );
@@ -3409,7 +3548,7 @@ function FullscreenWorkspace({
3409
3548
 
3410
3549
  // src/theme/ThemeContext.tsx
3411
3550
  import { createContext, useContext, useState as useState8, useEffect as useEffect5 } from "react";
3412
- import { jsx as jsx14 } from "react/jsx-runtime";
3551
+ import { jsx as jsx15 } from "react/jsx-runtime";
3413
3552
  var ThemeContext = createContext(void 0);
3414
3553
  var useTheme = () => {
3415
3554
  const context = useContext(ThemeContext);
@@ -3432,11 +3571,11 @@ var ThemeProvider = ({ children }) => {
3432
3571
  const setTheme = (newTheme) => {
3433
3572
  setThemeState(newTheme);
3434
3573
  };
3435
- return /* @__PURE__ */ jsx14(ThemeContext.Provider, { value: { theme, toggleTheme, setTheme }, children });
3574
+ return /* @__PURE__ */ jsx15(ThemeContext.Provider, { value: { theme, toggleTheme, setTheme }, children });
3436
3575
  };
3437
3576
 
3438
3577
  // src/theme/ThemeToggle.tsx
3439
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
3578
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3440
3579
  var ThemeToggle = ({
3441
3580
  size = "medium",
3442
3581
  position = "top-right",
@@ -3507,18 +3646,18 @@ var ThemeToggle = ({
3507
3646
  transition: "all 0.2s ease",
3508
3647
  boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)"
3509
3648
  };
3510
- return /* @__PURE__ */ jsxs11("div", { style: currentStyle, onClick: toggleTheme, className, ...props, children: [
3511
- /* @__PURE__ */ jsx15("span", { children: theme === "modern" ? "\u{1F3A8} Modern" : "\u{1F3ED} HMI" }),
3512
- /* @__PURE__ */ jsx15("div", { style: toggleStyle, children: /* @__PURE__ */ jsx15("div", { style: thumbStyle }) }),
3513
- /* @__PURE__ */ jsx15("span", { style: { fontSize: "0.75rem", opacity: 0.8 }, children: theme === "modern" ? "Industrial Design" : "High Performance" })
3649
+ return /* @__PURE__ */ jsxs12("div", { style: currentStyle, onClick: toggleTheme, className, ...props, children: [
3650
+ /* @__PURE__ */ jsx16("span", { children: theme === "modern" ? "\u{1F3A8} Modern" : "\u{1F3ED} HMI" }),
3651
+ /* @__PURE__ */ jsx16("div", { style: toggleStyle, children: /* @__PURE__ */ jsx16("div", { style: thumbStyle }) }),
3652
+ /* @__PURE__ */ jsx16("span", { style: { fontSize: "0.75rem", opacity: 0.8 }, children: theme === "modern" ? "Industrial Design" : "High Performance" })
3514
3653
  ] });
3515
3654
  };
3516
3655
 
3517
3656
  // src/components/PIDCanvas/PIDCanvas.tsx
3518
- import React13, { useCallback as useCallback8, useState as useState14, useEffect as useEffect12, useRef as useRef6 } from "react";
3657
+ import React14, { useCallback as useCallback8, useState as useState14, useEffect as useEffect13, useRef as useRef6 } from "react";
3519
3658
 
3520
3659
  // src/components/PIDCanvas/NodeRenderer.tsx
3521
- import React9 from "react";
3660
+ import React10 from "react";
3522
3661
 
3523
3662
  // src/diagram/symbols/mockSymbolLibrary.ts
3524
3663
  var mockSymbolLibrary = {
@@ -3546,6 +3685,25 @@ var mockSymbolLibrary = {
3546
3685
  tags: ["valve", "gate", "powered", "actuator"]
3547
3686
  }
3548
3687
  },
3688
+ "status-indicator": {
3689
+ id: "status-indicator",
3690
+ name: "Status LED",
3691
+ category: "instrument",
3692
+ viewBox: { x: 0, y: 0, width: 24, height: 24 },
3693
+ ports: [],
3694
+ svgContent: `
3695
+ <rect x="2" y="2" width="20" height="20"
3696
+ rx="var(--led-rx, 50%)"
3697
+ fill="var(--symbol-fill, #64748b)"
3698
+ stroke="rgba(255,255,255,0.4)"
3699
+ stroke-width="var(--led-bezel, 0)"
3700
+ style="transform-box: fill-box; transform: rotate(var(--led-rotate, 0deg)); transform-origin: center; pointer-events: all;"/>
3701
+ `,
3702
+ metadata: {
3703
+ description: "Status LED indicator driven by telemetry status color",
3704
+ tags: ["status", "led", "indicator", "instrument"]
3705
+ }
3706
+ },
3549
3707
  "pump-positive-displacement": {
3550
3708
  id: "pump-positive-displacement",
3551
3709
  name: "Positive Displacement Pump",
@@ -4738,8 +4896,150 @@ function getAvailableSymbols() {
4738
4896
  return [...Object.keys(customSymbols), ...Object.keys(mockSymbolLibrary)];
4739
4897
  }
4740
4898
 
4899
+ // src/diagram/symbols/appearanceSchema.ts
4900
+ var COLOR_PRESETS = {
4901
+ green: "#22c55e",
4902
+ red: "#ef4444",
4903
+ amber: "#f59e0b",
4904
+ blue: "#3b82f6",
4905
+ grey: "#64748b"
4906
+ };
4907
+ function resolvePresetColor(name) {
4908
+ return name && COLOR_PRESETS[name] || COLOR_PRESETS.grey;
4909
+ }
4910
+ var COLOR_OPTIONS = Object.keys(COLOR_PRESETS);
4911
+ var STATUS_LED_APPEARANCE = [
4912
+ {
4913
+ key: "shape",
4914
+ kind: "select",
4915
+ label: "Shape",
4916
+ options: ["circle", "square", "diamond"],
4917
+ default: "circle"
4918
+ },
4919
+ {
4920
+ key: "size",
4921
+ kind: "select",
4922
+ label: "Size",
4923
+ target: "scale",
4924
+ options: ["small", "medium", "large"],
4925
+ default: "medium",
4926
+ valueMap: { small: 0.75, medium: 1, large: 1.5 }
4927
+ },
4928
+ { key: "led", kind: "boolean", label: "Bezel ring", default: true },
4929
+ { key: "glow", kind: "boolean", label: "Diffusion (glow)", default: false },
4930
+ {
4931
+ key: "animation",
4932
+ kind: "select",
4933
+ label: "Animation",
4934
+ options: ["none", "pulse", "blink"],
4935
+ default: "none"
4936
+ },
4937
+ {
4938
+ key: "labelPosition",
4939
+ kind: "select",
4940
+ label: "Label position",
4941
+ options: ["bottom", "top", "left", "right"],
4942
+ default: "bottom"
4943
+ },
4944
+ // Color binding: a named signal drives the on/off color.
4945
+ {
4946
+ key: "colorSource",
4947
+ kind: "tag",
4948
+ label: "Color source (tag)",
4949
+ default: "",
4950
+ help: "Signal/tag that drives color. True (non-zero) shows On color; false shows Off color. Blank = unbound (grey)."
4951
+ },
4952
+ {
4953
+ key: "onColor",
4954
+ kind: "select",
4955
+ label: "On color (true)",
4956
+ options: COLOR_OPTIONS,
4957
+ default: "green",
4958
+ help: "Color when the Color source is true."
4959
+ },
4960
+ {
4961
+ key: "offColor",
4962
+ kind: "select",
4963
+ label: "Off color (false)",
4964
+ options: COLOR_OPTIONS,
4965
+ default: "red",
4966
+ help: "Color when the Color source is false."
4967
+ },
4968
+ // Visibility binding: an independent named signal drives show/hide.
4969
+ {
4970
+ key: "visibleSource",
4971
+ kind: "tag",
4972
+ label: "Visibility source (tag)",
4973
+ default: "",
4974
+ help: "Signal/tag that drives visibility. True shows the LED, false hides it. Blank = always visible (use the Visible checkbox)."
4975
+ },
4976
+ {
4977
+ key: "visible",
4978
+ kind: "boolean",
4979
+ label: "Visible",
4980
+ target: "visible",
4981
+ default: true,
4982
+ help: "Static show/hide, used only when no Visibility source is set."
4983
+ }
4984
+ ];
4985
+ var APPEARANCE_REGISTRY = {
4986
+ "status-indicator": STATUS_LED_APPEARANCE
4987
+ };
4988
+ function getAppearanceSchema(symbolId) {
4989
+ return APPEARANCE_REGISTRY[symbolId] ?? [];
4990
+ }
4991
+ function getAppearanceDefaults(symbolId) {
4992
+ const out = {};
4993
+ for (const field of getAppearanceSchema(symbolId)) {
4994
+ if ((field.target ?? "metadata") === "metadata" && field.default !== void 0) {
4995
+ out[field.key] = field.default;
4996
+ }
4997
+ }
4998
+ return out;
4999
+ }
5000
+ function readAppearanceValue(node, field) {
5001
+ if (field.target === "visible") {
5002
+ return node.visible ?? field.default ?? true;
5003
+ }
5004
+ if (field.target === "scale") {
5005
+ const scale = node.transform.scale ?? 1;
5006
+ if (field.valueMap) {
5007
+ let bestOption = field.options?.[0] ?? String(scale);
5008
+ let bestDiff = Infinity;
5009
+ for (const [option, value2] of Object.entries(field.valueMap)) {
5010
+ const diff = Math.abs(value2 - scale);
5011
+ if (diff < bestDiff) {
5012
+ bestDiff = diff;
5013
+ bestOption = option;
5014
+ }
5015
+ }
5016
+ return bestOption;
5017
+ }
5018
+ return scale;
5019
+ }
5020
+ const value = node.metadata?.[field.key];
5021
+ return value ?? field.default;
5022
+ }
5023
+ function writeAppearanceValue(node, field, value) {
5024
+ if (field.target === "visible") {
5025
+ return { visible: Boolean(value) };
5026
+ }
5027
+ if (field.target === "scale") {
5028
+ const scale = field.valueMap ? field.valueMap[String(value)] ?? 1 : Number(value);
5029
+ return { transform: { ...node.transform, scale } };
5030
+ }
5031
+ return { metadata: { ...node.metadata, [field.key]: value } };
5032
+ }
5033
+
4741
5034
  // src/components/PIDCanvas/NodeRenderer.tsx
4742
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
5035
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
5036
+ var HATCH_TILE_SIZE = 7;
5037
+ var HATCH_STRIPE_WIDTH = 2;
5038
+ var HATCH_STRIPE_ANGLE = 135;
5039
+ var HATCH_STRIPE_OPACITY = 85 / 255;
5040
+ function isUnknownNode(node) {
5041
+ return node.positionUnknown === true || normalizeDeviceStatus(node.status) === "unknown";
5042
+ }
4743
5043
  function NodeRendererImpl({
4744
5044
  nodes: nodes2,
4745
5045
  selectedNodeIds,
@@ -4751,146 +5051,214 @@ function NodeRendererImpl({
4751
5051
  enableDrag = false,
4752
5052
  statusColorMap
4753
5053
  }) {
4754
- return /* @__PURE__ */ jsx16("g", { id: "layer-nodes", children: nodes2.filter((node) => node.visible !== false).map((node) => {
4755
- const symbol = getSymbolDefinition(node.symbolId);
4756
- const isSelected = selectedNodeIds?.has(node.id) ?? false;
4757
- const isDragging = draggingNodeId === node.id;
4758
- if (!symbol) {
4759
- return /* @__PURE__ */ jsxs12("g", { children: [
4760
- /* @__PURE__ */ jsx16(
4761
- "circle",
4762
- {
4763
- cx: node.transform.x,
4764
- cy: node.transform.y,
4765
- r: "15",
4766
- fill: "#ff0000",
4767
- opacity: "0.3",
4768
- stroke: "#ff0000",
4769
- strokeWidth: "2"
4770
- }
4771
- ),
4772
- /* @__PURE__ */ jsx16(
4773
- "text",
4774
- {
4775
- x: node.transform.x,
4776
- y: node.transform.y,
4777
- textAnchor: "middle",
4778
- dominantBaseline: "middle",
4779
- fontSize: "10",
4780
- fill: "#ff0000",
4781
- children: "?"
4782
- }
4783
- )
4784
- ] }, node.id);
4785
- }
4786
- const transforms = [];
4787
- const effectiveX = isDragging ? node.transform.x + dragOffset.x : node.transform.x;
4788
- const effectiveY = isDragging ? node.transform.y + dragOffset.y : node.transform.y;
4789
- transforms.push(`translate(${effectiveX}, ${effectiveY})`);
4790
- if (node.transform.rotation) {
4791
- const centerX = symbol.viewBox.width / 2;
4792
- const centerY = symbol.viewBox.height / 2;
4793
- transforms.push(`rotate(${node.transform.rotation}, ${centerX}, ${centerY})`);
4794
- }
4795
- if (node.transform.scale && node.transform.scale !== 1) {
4796
- const centerX = symbol.viewBox.width / 2;
4797
- const centerY = symbol.viewBox.height / 2;
4798
- transforms.push(
4799
- `translate(${centerX}, ${centerY}) scale(${node.transform.scale}) translate(${-centerX}, ${-centerY})`
4800
- );
4801
- }
4802
- if (node.transform.flipX || node.transform.flipY) {
4803
- const scaleX = node.transform.flipX ? -1 : 1;
4804
- const scaleY = node.transform.flipY ? -1 : 1;
4805
- const centerX = symbol.viewBox.width / 2;
4806
- const centerY = symbol.viewBox.height / 2;
4807
- transforms.push(
4808
- `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
4809
- );
4810
- }
4811
- const statusEntry = resolveStatusColor(node.status, statusColorMap);
4812
- const fillColor = node.customColors?.fill ?? statusEntry.fill;
4813
- const strokeColor = node.customColors?.stroke ?? statusEntry.stroke;
4814
- return /* @__PURE__ */ jsxs12(
4815
- "g",
5054
+ const hatchPatternId = `node-unknown-hatch-${React10.useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
5055
+ const visibleNodes = nodes2.filter((node) => node.visible !== false);
5056
+ const hasUnknownNode = visibleNodes.some(isUnknownNode);
5057
+ const unknownEntry = resolveStatusColor("unknown", statusColorMap);
5058
+ return /* @__PURE__ */ jsxs13("g", { id: "layer-nodes", children: [
5059
+ hasUnknownNode && /* @__PURE__ */ jsx17("defs", { children: /* @__PURE__ */ jsxs13(
5060
+ "pattern",
4816
5061
  {
4817
- "data-node-id": node.id,
4818
- "data-symbol-id": node.symbolId,
4819
- "data-selected": isSelected,
4820
- "data-dragging": isDragging,
4821
- transform: transforms.join(" "),
4822
- style: {
4823
- "--symbol-fill": fillColor,
4824
- "--symbol-stroke": strokeColor,
4825
- cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
4826
- },
4827
- onClick: (e) => {
4828
- e.stopPropagation();
4829
- onNodeClick?.(node.id, e);
4830
- },
4831
- onDoubleClick: (e) => {
4832
- e.stopPropagation();
4833
- onNodeDoubleClick?.(node.id, e);
4834
- },
4835
- onMouseDown: (e) => {
4836
- if (enableDrag) {
4837
- onNodeDragStart?.(node.id, e);
4838
- }
4839
- },
4840
- onTouchStart: (e) => {
4841
- if (enableDrag) {
4842
- e.stopPropagation();
4843
- onNodeDragStart?.(node.id, e);
4844
- }
4845
- },
5062
+ id: hatchPatternId,
5063
+ "data-testid": "unknown-hatch-pattern",
5064
+ patternUnits: "userSpaceOnUse",
5065
+ width: HATCH_TILE_SIZE,
5066
+ height: HATCH_TILE_SIZE,
5067
+ patternTransform: `rotate(${HATCH_STRIPE_ANGLE})`,
4846
5068
  children: [
4847
- isSelected && /* @__PURE__ */ jsx16(
5069
+ /* @__PURE__ */ jsx17("rect", { width: HATCH_TILE_SIZE, height: HATCH_TILE_SIZE, fill: unknownEntry.fill }),
5070
+ /* @__PURE__ */ jsx17(
4848
5071
  "rect",
4849
5072
  {
4850
- x: "-5",
4851
- y: "-5",
4852
- width: symbol.viewBox.width + 10,
4853
- height: symbol.viewBox.height + 10,
4854
- fill: "none",
4855
- stroke: isDragging ? "#10b981" : "#3b82f6",
4856
- strokeWidth: isDragging ? "3" : "2",
4857
- strokeDasharray: "4 2",
4858
- rx: "2",
4859
- pointerEvents: "none"
5073
+ x: HATCH_TILE_SIZE - HATCH_STRIPE_WIDTH,
5074
+ width: HATCH_STRIPE_WIDTH,
5075
+ height: HATCH_TILE_SIZE,
5076
+ fill: unknownEntry.stroke,
5077
+ fillOpacity: HATCH_STRIPE_OPACITY
4860
5078
  }
4861
- ),
4862
- /* @__PURE__ */ jsx16(
4863
- "g",
5079
+ )
5080
+ ]
5081
+ }
5082
+ ) }),
5083
+ visibleNodes.map((node) => {
5084
+ const symbol = getSymbolDefinition(node.symbolId);
5085
+ const isSelected = selectedNodeIds?.has(node.id) ?? false;
5086
+ const isDragging = draggingNodeId === node.id;
5087
+ if (!symbol) {
5088
+ return /* @__PURE__ */ jsxs13("g", { children: [
5089
+ /* @__PURE__ */ jsx17(
5090
+ "circle",
4864
5091
  {
4865
- dangerouslySetInnerHTML: { __html: symbol.svgContent },
4866
- "data-symbol-content": symbol.id,
4867
- style: { pointerEvents: "auto" }
5092
+ cx: node.transform.x,
5093
+ cy: node.transform.y,
5094
+ r: "15",
5095
+ fill: "#ff0000",
5096
+ opacity: "0.3",
5097
+ stroke: "#ff0000",
5098
+ strokeWidth: "2"
4868
5099
  }
4869
5100
  ),
4870
- /* @__PURE__ */ jsx16(
5101
+ /* @__PURE__ */ jsx17(
4871
5102
  "text",
4872
5103
  {
4873
- x: symbol.viewBox.width / 2 + (node.labelOffset?.x ?? 0),
4874
- y: symbol.viewBox.height + 12 + (node.labelOffset?.y ?? 0),
5104
+ x: node.transform.x,
5105
+ y: node.transform.y,
4875
5106
  textAnchor: "middle",
4876
- fontSize: node.labelFontSize ?? 10,
4877
- fontWeight: "600",
4878
- fill: isSelected ? "#3b82f6" : "#1f2937",
4879
- transform: node.transform.rotation ? `rotate(${-node.transform.rotation}, ${symbol.viewBox.width / 2 + (node.labelOffset?.x ?? 0)}, ${symbol.viewBox.height + 12 + (node.labelOffset?.y ?? 0)})` : void 0,
4880
- children: node.label || node.id
5107
+ dominantBaseline: "middle",
5108
+ fontSize: "10",
5109
+ fill: "#ff0000",
5110
+ children: "?"
4881
5111
  }
4882
5112
  )
4883
- ]
4884
- },
4885
- node.id
4886
- );
4887
- }) });
5113
+ ] }, node.id);
5114
+ }
5115
+ const transforms = [];
5116
+ const effectiveX = isDragging ? node.transform.x + dragOffset.x : node.transform.x;
5117
+ const effectiveY = isDragging ? node.transform.y + dragOffset.y : node.transform.y;
5118
+ transforms.push(`translate(${effectiveX}, ${effectiveY})`);
5119
+ if (node.transform.rotation) {
5120
+ const centerX = symbol.viewBox.width / 2;
5121
+ const centerY = symbol.viewBox.height / 2;
5122
+ transforms.push(`rotate(${node.transform.rotation}, ${centerX}, ${centerY})`);
5123
+ }
5124
+ if (node.transform.scale && node.transform.scale !== 1) {
5125
+ const centerX = symbol.viewBox.width / 2;
5126
+ const centerY = symbol.viewBox.height / 2;
5127
+ transforms.push(
5128
+ `translate(${centerX}, ${centerY}) scale(${node.transform.scale}) translate(${-centerX}, ${-centerY})`
5129
+ );
5130
+ }
5131
+ if (node.transform.flipX || node.transform.flipY) {
5132
+ const scaleX = node.transform.flipX ? -1 : 1;
5133
+ const scaleY = node.transform.flipY ? -1 : 1;
5134
+ const centerX = symbol.viewBox.width / 2;
5135
+ const centerY = symbol.viewBox.height / 2;
5136
+ transforms.push(
5137
+ `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
5138
+ );
5139
+ }
5140
+ const statusEntry = resolveStatusColor(node.status, statusColorMap);
5141
+ const isUnknown = isUnknownNode(node);
5142
+ const fillColor = isUnknown ? `url(#${hatchPatternId})` : node.customColors?.fill ?? statusEntry.fill;
5143
+ const strokeColor = isUnknown ? unknownEntry.stroke : node.customColors?.stroke ?? statusEntry.stroke;
5144
+ const appearance = { ...getAppearanceDefaults(node.symbolId), ...node.metadata ?? {} };
5145
+ const ledStyle = {};
5146
+ if (appearance.shape !== void 0) {
5147
+ ledStyle["--led-rx"] = appearance.shape === "circle" ? "50%" : "2";
5148
+ ledStyle["--led-rotate"] = appearance.shape === "diamond" ? "45deg" : "0deg";
5149
+ }
5150
+ if (appearance.led !== void 0) {
5151
+ ledStyle["--led-bezel"] = appearance.led ? "2" : "0";
5152
+ }
5153
+ if (appearance.glow) {
5154
+ const glowColor = isUnknown ? unknownEntry.stroke : fillColor;
5155
+ ledStyle.filter = `drop-shadow(0 0 4px ${glowColor})`;
5156
+ }
5157
+ const ledAnimation = typeof appearance.animation === "string" && appearance.animation !== "none" ? appearance.animation : void 0;
5158
+ const vb = symbol.viewBox;
5159
+ const ox = node.labelOffset?.x ?? 0;
5160
+ const oy = node.labelOffset?.y ?? 0;
5161
+ const labelPos = typeof appearance.labelPosition === "string" ? appearance.labelPosition : "bottom";
5162
+ let labelX = vb.width / 2 + ox;
5163
+ let labelY = vb.height + 12 + oy;
5164
+ let labelAnchor = "middle";
5165
+ if (labelPos === "top") {
5166
+ labelX = vb.width / 2 + ox;
5167
+ labelY = -6 + oy;
5168
+ labelAnchor = "middle";
5169
+ } else if (labelPos === "left") {
5170
+ labelX = -6 + ox;
5171
+ labelY = vb.height / 2 + oy;
5172
+ labelAnchor = "end";
5173
+ } else if (labelPos === "right") {
5174
+ labelX = vb.width + 6 + ox;
5175
+ labelY = vb.height / 2 + oy;
5176
+ labelAnchor = "start";
5177
+ }
5178
+ return /* @__PURE__ */ jsxs13(
5179
+ "g",
5180
+ {
5181
+ "data-node-id": node.id,
5182
+ "data-symbol-id": node.symbolId,
5183
+ "data-selected": isSelected,
5184
+ "data-dragging": isDragging,
5185
+ "data-led-animation": ledAnimation,
5186
+ "data-position-unknown": node.positionUnknown === true ? "true" : void 0,
5187
+ transform: transforms.join(" "),
5188
+ style: {
5189
+ "--symbol-fill": fillColor,
5190
+ "--symbol-stroke": strokeColor,
5191
+ cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default",
5192
+ ...ledStyle
5193
+ },
5194
+ onClick: (e) => {
5195
+ e.stopPropagation();
5196
+ onNodeClick?.(node.id, e);
5197
+ },
5198
+ onDoubleClick: (e) => {
5199
+ e.stopPropagation();
5200
+ onNodeDoubleClick?.(node.id, e);
5201
+ },
5202
+ onMouseDown: (e) => {
5203
+ if (enableDrag) {
5204
+ onNodeDragStart?.(node.id, e);
5205
+ }
5206
+ },
5207
+ onTouchStart: (e) => {
5208
+ if (enableDrag) {
5209
+ e.stopPropagation();
5210
+ onNodeDragStart?.(node.id, e);
5211
+ }
5212
+ },
5213
+ children: [
5214
+ isSelected && /* @__PURE__ */ jsx17(
5215
+ "rect",
5216
+ {
5217
+ x: "-5",
5218
+ y: "-5",
5219
+ width: symbol.viewBox.width + 10,
5220
+ height: symbol.viewBox.height + 10,
5221
+ fill: "none",
5222
+ stroke: isDragging ? "#10b981" : "#3b82f6",
5223
+ strokeWidth: isDragging ? "3" : "2",
5224
+ strokeDasharray: "4 2",
5225
+ rx: "2",
5226
+ pointerEvents: "none"
5227
+ }
5228
+ ),
5229
+ /* @__PURE__ */ jsx17(
5230
+ "g",
5231
+ {
5232
+ dangerouslySetInnerHTML: { __html: symbol.svgContent },
5233
+ "data-symbol-content": symbol.id,
5234
+ style: { pointerEvents: "auto" }
5235
+ }
5236
+ ),
5237
+ /* @__PURE__ */ jsx17(
5238
+ "text",
5239
+ {
5240
+ x: labelX,
5241
+ y: labelY,
5242
+ textAnchor: labelAnchor,
5243
+ fontSize: node.labelFontSize ?? 10,
5244
+ fontWeight: "600",
5245
+ fill: isSelected ? "#3b82f6" : "#1f2937",
5246
+ transform: node.transform.rotation ? `rotate(${-node.transform.rotation}, ${labelX}, ${labelY})` : void 0,
5247
+ children: node.label || node.id
5248
+ }
5249
+ )
5250
+ ]
5251
+ },
5252
+ node.id
5253
+ );
5254
+ })
5255
+ ] });
4888
5256
  }
4889
- var NodeRenderer = React9.memo(NodeRendererImpl);
5257
+ var NodeRenderer = React10.memo(NodeRendererImpl);
4890
5258
 
4891
5259
  // src/components/PIDCanvas/PipeRenderer.tsx
4892
- import React10 from "react";
4893
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
5260
+ import React11 from "react";
5261
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
4894
5262
  function PipeRendererImpl({
4895
5263
  pipes,
4896
5264
  selectedPipeIds,
@@ -4905,7 +5273,7 @@ function PipeRendererImpl({
4905
5273
  waypointDragOffset,
4906
5274
  onPipeSegmentClick
4907
5275
  }) {
4908
- return /* @__PURE__ */ jsx17("g", { id: "layer-pipes", children: pipes.filter((pipe) => pipe.visible !== false).map((pipe) => {
5276
+ return /* @__PURE__ */ jsx18("g", { id: "layer-pipes", children: pipes.filter((pipe) => pipe.visible !== false).map((pipe) => {
4909
5277
  const isSelected = selectedPipeIds?.has(pipe.id) ?? false;
4910
5278
  const isEditing = enableWaypointEditing && editingPipeId === pipe.id;
4911
5279
  const showEditingControls = enableWaypointEditing && isSelected;
@@ -4924,7 +5292,7 @@ function PipeRendererImpl({
4924
5292
  const strokeWidth = isSelected ? (pipeStyle.strokeWidth || 3) + 2 : pipeStyle.strokeWidth || 3;
4925
5293
  const strokeDasharray = pipeStyle.strokeDasharray;
4926
5294
  const opacity = pipeStyle.opacity !== void 0 ? pipeStyle.opacity : 1;
4927
- return /* @__PURE__ */ jsxs13(
5295
+ return /* @__PURE__ */ jsxs14(
4928
5296
  "g",
4929
5297
  {
4930
5298
  "data-pipe-id": pipe.id,
@@ -4937,8 +5305,8 @@ function PipeRendererImpl({
4937
5305
  cursor: onPipeClick ? "pointer" : "default"
4938
5306
  },
4939
5307
  children: [
4940
- !enableWaypointEditing && /* @__PURE__ */ jsxs13(Fragment5, { children: [
4941
- onPipeClick && /* @__PURE__ */ jsx17(
5308
+ !enableWaypointEditing && /* @__PURE__ */ jsxs14(Fragment5, { children: [
5309
+ onPipeClick && /* @__PURE__ */ jsx18(
4942
5310
  "polyline",
4943
5311
  {
4944
5312
  points: pointsString,
@@ -4950,7 +5318,7 @@ function PipeRendererImpl({
4950
5318
  pointerEvents: "stroke"
4951
5319
  }
4952
5320
  ),
4953
- /* @__PURE__ */ jsx17(
5321
+ /* @__PURE__ */ jsx18(
4954
5322
  "polyline",
4955
5323
  {
4956
5324
  points: pointsString,
@@ -4974,7 +5342,7 @@ function PipeRendererImpl({
4974
5342
  const waypointSize = isSelectedWaypoint ? 8 : isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
4975
5343
  const waypointOpacity = isSelectedWaypoint ? 1 : isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
4976
5344
  const waypointColor = isSelectedWaypoint ? "#3b82f6" : isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
4977
- return /* @__PURE__ */ jsx17(
5345
+ return /* @__PURE__ */ jsx18(
4978
5346
  "circle",
4979
5347
  {
4980
5348
  cx: point.x,
@@ -5011,12 +5379,12 @@ function PipeRendererImpl({
5011
5379
  `${pipe.id}-point-${idx}`
5012
5380
  );
5013
5381
  }),
5014
- showEditingControls && onPipeSegmentClick && displayPoints.length >= 2 && /* @__PURE__ */ jsx17(Fragment5, { children: displayPoints.slice(0, -1).map((point, idx) => {
5382
+ showEditingControls && onPipeSegmentClick && displayPoints.length >= 2 && /* @__PURE__ */ jsx18(Fragment5, { children: displayPoints.slice(0, -1).map((point, idx) => {
5015
5383
  const nextPoint = displayPoints[idx + 1];
5016
5384
  const midX = (point.x + nextPoint.x) / 2;
5017
5385
  const midY = (point.y + nextPoint.y) / 2;
5018
- return /* @__PURE__ */ jsxs13("g", { children: [
5019
- /* @__PURE__ */ jsx17(
5386
+ return /* @__PURE__ */ jsxs14("g", { children: [
5387
+ /* @__PURE__ */ jsx18(
5020
5388
  "circle",
5021
5389
  {
5022
5390
  cx: midX,
@@ -5032,7 +5400,7 @@ function PipeRendererImpl({
5032
5400
  }
5033
5401
  }
5034
5402
  ),
5035
- /* @__PURE__ */ jsx17(
5403
+ /* @__PURE__ */ jsx18(
5036
5404
  "circle",
5037
5405
  {
5038
5406
  cx: midX,
@@ -5048,7 +5416,7 @@ function PipeRendererImpl({
5048
5416
  )
5049
5417
  ] }, `${pipe.id}-segment-${idx}`);
5050
5418
  }) }),
5051
- pipe.showLabel !== false && pipe.label && pipe.routePoints.length >= 2 && /* @__PURE__ */ jsx17(
5419
+ pipe.showLabel !== false && pipe.label && pipe.routePoints.length >= 2 && /* @__PURE__ */ jsx18(
5052
5420
  "text",
5053
5421
  {
5054
5422
  x: pipe.routePoints[Math.floor(pipe.routePoints.length / 2)].x + (pipe.labelOffset?.x ?? 0),
@@ -5068,11 +5436,11 @@ function PipeRendererImpl({
5068
5436
  );
5069
5437
  }) });
5070
5438
  }
5071
- var PipeRenderer = React10.memo(PipeRendererImpl);
5439
+ var PipeRenderer = React11.memo(PipeRendererImpl);
5072
5440
 
5073
5441
  // src/components/PIDCanvas/DataOverlay.tsx
5074
- import React11 from "react";
5075
- import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
5442
+ import React12 from "react";
5443
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
5076
5444
  var PULSE_KEYFRAME_ID = "pid-data-overlay-pulse-keyframes";
5077
5445
  var PULSE_ANIMATION = "pid-data-overlay-pulse 1.5s ease-in-out infinite";
5078
5446
  function ensurePulseKeyframes() {
@@ -5083,9 +5451,10 @@ function ensurePulseKeyframes() {
5083
5451
  style.textContent = "@keyframes pid-data-overlay-pulse{0%{opacity:.9}50%{opacity:.3}100%{opacity:.9}}";
5084
5452
  document.head.appendChild(style);
5085
5453
  }
5086
- function renderStatusDot(cx, cy, scale, fill) {
5087
- ensurePulseKeyframes();
5088
- return /* @__PURE__ */ jsx18(
5454
+ function renderStatusDot(cx, cy, scale, fill, status) {
5455
+ const isUnknown = normalizeDeviceStatus(status) === "unknown";
5456
+ if (!isUnknown) ensurePulseKeyframes();
5457
+ return /* @__PURE__ */ jsx19(
5089
5458
  "circle",
5090
5459
  {
5091
5460
  className: "pid-data-overlay-status-dot",
@@ -5093,7 +5462,7 @@ function renderStatusDot(cx, cy, scale, fill) {
5093
5462
  cy: cy - 18 * scale,
5094
5463
  r: 3 * scale,
5095
5464
  fill,
5096
- style: { animation: PULSE_ANIMATION }
5465
+ style: isUnknown ? void 0 : { animation: PULSE_ANIMATION }
5097
5466
  }
5098
5467
  );
5099
5468
  }
@@ -5119,7 +5488,7 @@ function DataOverlayImpl({
5119
5488
  const backgroundColor = display?.backgroundColor;
5120
5489
  const offsetX = display?.offsetX ?? 0;
5121
5490
  const offsetY = display?.offsetY ?? 0;
5122
- const sparklinePath = React11.useMemo(() => {
5491
+ const sparklinePath = React12.useMemo(() => {
5123
5492
  if (!showSparkline || !history || history.length < 2) return "";
5124
5493
  const width = 70 * scale;
5125
5494
  const height = 20 * scale;
@@ -5138,7 +5507,7 @@ function DataOverlayImpl({
5138
5507
  return `M ${points.join(" L ")}`;
5139
5508
  }, [showSparkline, history, scaling, scale]);
5140
5509
  if (!showValue) {
5141
- return /* @__PURE__ */ jsx18(Fragment6, {});
5510
+ return /* @__PURE__ */ jsx19(Fragment6, {});
5142
5511
  }
5143
5512
  const formatValue = (val) => {
5144
5513
  if (typeof val === "number") {
@@ -5163,10 +5532,11 @@ function DataOverlayImpl({
5163
5532
  const extraMarkRows = realDisplayText ? 1 : 0;
5164
5533
  const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale + extraMarkRows * 9 * scale;
5165
5534
  const overlayWidth = 80 * scale;
5535
+ const isUnknownValue = normalizeDeviceStatus(value.status) === "unknown";
5166
5536
  const displayX = x + offsetX;
5167
5537
  const displayY = y + offsetY;
5168
- return /* @__PURE__ */ jsxs14("g", { "data-overlay-node": nodeId, "data-status": value.status, "data-forced": isForced, children: [
5169
- /* @__PURE__ */ jsx18(
5538
+ return /* @__PURE__ */ jsxs15("g", { "data-overlay-node": nodeId, "data-status": value.status, "data-forced": isForced, children: [
5539
+ /* @__PURE__ */ jsx19(
5170
5540
  "rect",
5171
5541
  {
5172
5542
  x: displayX - overlayWidth / 2,
@@ -5181,7 +5551,7 @@ function DataOverlayImpl({
5181
5551
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
5182
5552
  }
5183
5553
  ),
5184
- label && /* @__PURE__ */ jsx18(
5554
+ label && /* @__PURE__ */ jsx19(
5185
5555
  "text",
5186
5556
  {
5187
5557
  x: displayX,
@@ -5194,7 +5564,7 @@ function DataOverlayImpl({
5194
5564
  children: label
5195
5565
  }
5196
5566
  ),
5197
- /* @__PURE__ */ jsx18(
5567
+ /* @__PURE__ */ jsx19(
5198
5568
  "text",
5199
5569
  {
5200
5570
  x: displayX,
@@ -5207,8 +5577,17 @@ function DataOverlayImpl({
5207
5577
  children: displayText
5208
5578
  }
5209
5579
  ),
5210
- showStatus && value.status !== "normal" && renderStatusDot(displayX, displayY, scale, finalTextColor),
5211
- value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ jsxs14(
5580
+ showStatus && value.status !== "normal" && renderStatusDot(displayX, displayY, scale, finalTextColor, value.status),
5581
+ showStatus && isUnknownValue && /* @__PURE__ */ jsx19(
5582
+ UnknownStatusGlyph,
5583
+ {
5584
+ x: displayX - overlayWidth / 2 + 2 * scale,
5585
+ y: displayY - 25 * scale - 6 * scale,
5586
+ width: 12 * scale,
5587
+ height: 12 * scale
5588
+ }
5589
+ ),
5590
+ value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ jsxs15(
5212
5591
  "text",
5213
5592
  {
5214
5593
  x: displayX,
@@ -5225,13 +5604,13 @@ function DataOverlayImpl({
5225
5604
  ]
5226
5605
  }
5227
5606
  ),
5228
- isForced && /* @__PURE__ */ jsxs14(
5607
+ isForced && /* @__PURE__ */ jsxs15(
5229
5608
  "g",
5230
5609
  {
5231
5610
  transform: `translate(${displayX + overlayWidth / 2}, ${displayY - 25 * scale})`,
5232
5611
  "aria-label": "Forced",
5233
5612
  children: [
5234
- /* @__PURE__ */ jsx18(
5613
+ /* @__PURE__ */ jsx19(
5235
5614
  "circle",
5236
5615
  {
5237
5616
  r: 7 * scale,
@@ -5240,7 +5619,7 @@ function DataOverlayImpl({
5240
5619
  strokeWidth: 1.5 * scale
5241
5620
  }
5242
5621
  ),
5243
- /* @__PURE__ */ jsx18(
5622
+ /* @__PURE__ */ jsx19(
5244
5623
  "text",
5245
5624
  {
5246
5625
  textAnchor: "middle",
@@ -5254,7 +5633,7 @@ function DataOverlayImpl({
5254
5633
  ]
5255
5634
  }
5256
5635
  ),
5257
- realDisplayText && /* @__PURE__ */ jsx18(
5636
+ realDisplayText && /* @__PURE__ */ jsx19(
5258
5637
  "text",
5259
5638
  {
5260
5639
  x: displayX,
@@ -5267,7 +5646,7 @@ function DataOverlayImpl({
5267
5646
  children: realDisplayText
5268
5647
  }
5269
5648
  ),
5270
- hasSparkline && /* @__PURE__ */ jsx18("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ jsx18(
5649
+ hasSparkline && /* @__PURE__ */ jsx19("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ jsx19(
5271
5650
  "path",
5272
5651
  {
5273
5652
  d: sparklinePath,
@@ -5281,11 +5660,11 @@ function DataOverlayImpl({
5281
5660
  ) })
5282
5661
  ] });
5283
5662
  }
5284
- var DataOverlay = React11.memo(DataOverlayImpl);
5663
+ var DataOverlay = React12.memo(DataOverlayImpl);
5285
5664
 
5286
5665
  // src/components/PIDCanvas/PositionPanel.tsx
5287
5666
  import { useState as useState9, useEffect as useEffect6 } from "react";
5288
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
5667
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
5289
5668
  function PositionPanel({
5290
5669
  selectedNode,
5291
5670
  selectedWaypoint,
@@ -5354,7 +5733,7 @@ function PositionPanel({
5354
5733
  "bottom-left": { bottom: 10, left: 10 },
5355
5734
  "bottom-right": { bottom: 10, right: 10 }
5356
5735
  };
5357
- return /* @__PURE__ */ jsxs15(
5736
+ return /* @__PURE__ */ jsxs16(
5358
5737
  "div",
5359
5738
  {
5360
5739
  style: {
@@ -5371,11 +5750,11 @@ function PositionPanel({
5371
5750
  fontSize: "13px"
5372
5751
  },
5373
5752
  children: [
5374
- /* @__PURE__ */ jsx19("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
5375
- /* @__PURE__ */ jsxs15("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
5376
- /* @__PURE__ */ jsxs15("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
5377
- /* @__PURE__ */ jsx19("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
5378
- /* @__PURE__ */ jsx19(
5753
+ /* @__PURE__ */ jsx20("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
5754
+ /* @__PURE__ */ jsxs16("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
5755
+ /* @__PURE__ */ jsxs16("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
5756
+ /* @__PURE__ */ jsx20("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
5757
+ /* @__PURE__ */ jsx20(
5379
5758
  "input",
5380
5759
  {
5381
5760
  type: "number",
@@ -5393,9 +5772,9 @@ function PositionPanel({
5393
5772
  }
5394
5773
  )
5395
5774
  ] }),
5396
- /* @__PURE__ */ jsxs15("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
5397
- /* @__PURE__ */ jsx19("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
5398
- /* @__PURE__ */ jsx19(
5775
+ /* @__PURE__ */ jsxs16("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
5776
+ /* @__PURE__ */ jsx20("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
5777
+ /* @__PURE__ */ jsx20(
5399
5778
  "input",
5400
5779
  {
5401
5780
  type: "number",
@@ -5414,7 +5793,7 @@ function PositionPanel({
5414
5793
  )
5415
5794
  ] })
5416
5795
  ] }),
5417
- /* @__PURE__ */ jsx19("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
5796
+ /* @__PURE__ */ jsx20("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
5418
5797
  ]
5419
5798
  }
5420
5799
  );
@@ -6009,8 +6388,13 @@ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
6009
6388
  return "alarm";
6010
6389
  case "off":
6011
6390
  return "stopped";
6012
- default:
6391
+ case "unknown":
6392
+ return "unknown";
6393
+ default: {
6394
+ const exhaustive = telemetryStatus;
6395
+ void exhaustive;
6013
6396
  return "stopped";
6397
+ }
6014
6398
  }
6015
6399
  };
6016
6400
  const statusMapper = mapStatus || defaultMapStatus;
@@ -6034,8 +6418,49 @@ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
6034
6418
  }, [telemetry, diagram, setDiagram, enabled, mapStatus]);
6035
6419
  }
6036
6420
 
6421
+ // src/diagram/hooks/useBooleanIndicators.ts
6422
+ import { useEffect as useEffect10 } from "react";
6423
+ function useBooleanIndicators(telemetry, diagram, setDiagram, options = {}) {
6424
+ const { enabled = true, symbolId = "status-indicator" } = options;
6425
+ useEffect10(() => {
6426
+ if (!enabled) return;
6427
+ const tagIndex = /* @__PURE__ */ new Map();
6428
+ telemetry.forEach((binding) => {
6429
+ if (binding.tag) tagIndex.set(binding.tag, binding.value.value);
6430
+ });
6431
+ let needsUpdate = false;
6432
+ const updatedNodes = diagram.nodes.map((node) => {
6433
+ if (node.symbolId !== symbolId) return node;
6434
+ const meta = node.metadata ?? {};
6435
+ const colorSource = typeof meta.colorSource === "string" ? meta.colorSource : "";
6436
+ const visibleSource = typeof meta.visibleSource === "string" ? meta.visibleSource : "";
6437
+ let next = node;
6438
+ if (colorSource) {
6439
+ const isTrue = Boolean(tagIndex.get(colorSource));
6440
+ const fill = resolvePresetColor(
6441
+ String(isTrue ? meta.onColor ?? "green" : meta.offColor ?? "red")
6442
+ );
6443
+ if (node.customColors?.fill !== fill) {
6444
+ next = { ...next, customColors: { ...next.customColors, fill } };
6445
+ }
6446
+ }
6447
+ if (visibleSource) {
6448
+ const vis = Boolean(tagIndex.get(visibleSource));
6449
+ if ((next.visible ?? true) !== vis) {
6450
+ next = { ...next, visible: vis };
6451
+ }
6452
+ }
6453
+ if (next !== node) needsUpdate = true;
6454
+ return next;
6455
+ });
6456
+ if (needsUpdate) {
6457
+ setDiagram({ ...diagram, nodes: updatedNodes });
6458
+ }
6459
+ }, [telemetry, diagram, setDiagram, enabled, symbolId]);
6460
+ }
6461
+
6037
6462
  // src/diagram/hooks/useDrag.ts
6038
- import { useState as useState13, useCallback as useCallback7, useRef as useRef5, useEffect as useEffect10 } from "react";
6463
+ import { useState as useState13, useCallback as useCallback7, useRef as useRef5, useEffect as useEffect11 } from "react";
6039
6464
  function useDrag(options = {}) {
6040
6465
  const {
6041
6466
  onDragStart,
@@ -6220,7 +6645,7 @@ function useDrag(options = {}) {
6220
6645
  },
6221
6646
  [screenToWorld, applySnap, onDragEnd]
6222
6647
  );
6223
- useEffect10(() => {
6648
+ useEffect11(() => {
6224
6649
  if (!isListening) return;
6225
6650
  document.addEventListener("mousemove", handleMouseMove);
6226
6651
  document.addEventListener("mouseup", handleMouseUp);
@@ -6289,7 +6714,7 @@ function useDrag(options = {}) {
6289
6714
  }
6290
6715
 
6291
6716
  // src/diagram/hooks/useKeyboardShortcuts.ts
6292
- import { useEffect as useEffect11 } from "react";
6717
+ import { useEffect as useEffect12 } from "react";
6293
6718
  function useKeyboardShortcuts(options) {
6294
6719
  const {
6295
6720
  onDelete,
@@ -6300,7 +6725,7 @@ function useKeyboardShortcuts(options) {
6300
6725
  onSelectAll,
6301
6726
  enabled = true
6302
6727
  } = options;
6303
- useEffect11(() => {
6728
+ useEffect12(() => {
6304
6729
  if (!enabled) return;
6305
6730
  const handleKeyDown = (event) => {
6306
6731
  const target = event.target;
@@ -6409,7 +6834,7 @@ function nodeHasPort(node, portId) {
6409
6834
  }
6410
6835
 
6411
6836
  // src/components/PIDCanvas/PIDCanvas.tsx
6412
- import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
6837
+ import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
6413
6838
  var DEFAULT_FEATURES = {
6414
6839
  pan: true,
6415
6840
  zoom: true,
@@ -6452,7 +6877,7 @@ function PIDCanvasImpl({
6452
6877
  const isUndoRedoAction = useRef6(false);
6453
6878
  const isInternalChange = useRef6(false);
6454
6879
  const lastInternalDiagram = useRef6(null);
6455
- useEffect12(() => {
6880
+ useEffect13(() => {
6456
6881
  const isExternal = !isInternalChange.current && diagram !== lastInternalDiagram.current;
6457
6882
  if (isExternal) {
6458
6883
  setLocalDiagram(diagram);
@@ -6497,7 +6922,7 @@ function PIDCanvasImpl({
6497
6922
  });
6498
6923
  return { minX, minY, maxX, maxY };
6499
6924
  }, [localDiagram.nodes]);
6500
- useEffect12(() => {
6925
+ useEffect13(() => {
6501
6926
  if (onMounted) {
6502
6927
  const controls = {
6503
6928
  fitToContent: (padding = 50) => {
@@ -6576,7 +7001,7 @@ function PIDCanvasImpl({
6576
7001
  isUndoRedoAction.current = false;
6577
7002
  }, 0);
6578
7003
  }, [localDiagram, onDiagramChange]);
6579
- useEffect12(() => {
7004
+ useEffect13(() => {
6580
7005
  const handleKeyDown = (e) => {
6581
7006
  const isCtrlOrCmd = e.ctrlKey || e.metaKey;
6582
7007
  if (isCtrlOrCmd && e.key === "z" && !e.shiftKey) {
@@ -6593,7 +7018,7 @@ function PIDCanvasImpl({
6593
7018
  window.addEventListener("keydown", handleKeyDown);
6594
7019
  return () => window.removeEventListener("keydown", handleKeyDown);
6595
7020
  }, [undo, redo]);
6596
- useEffect12(() => {
7021
+ useEffect13(() => {
6597
7022
  if (!features.dragNodes) return;
6598
7023
  const handleKeyDown = (e) => {
6599
7024
  if (selection.selectedNodeIds.size === 0 && !selectedWaypoint) return;
@@ -7022,7 +7447,7 @@ function PIDCanvasImpl({
7022
7447
  const viewBox = controlledViewBox;
7023
7448
  const displayDiagram = localDiagram;
7024
7449
  const selectedNode = selectionEnabled && selection.selectedNodeIds.size === 1 ? localDiagram.nodes.find((n) => selection.selectedNodeIds.has(n.id)) || null : null;
7025
- return /* @__PURE__ */ jsxs16(
7450
+ return /* @__PURE__ */ jsxs17(
7026
7451
  "div",
7027
7452
  {
7028
7453
  ref: containerRef,
@@ -7037,7 +7462,7 @@ function PIDCanvasImpl({
7037
7462
  },
7038
7463
  ...props,
7039
7464
  children: [
7040
- /* @__PURE__ */ jsxs16(
7465
+ /* @__PURE__ */ jsxs17(
7041
7466
  "svg",
7042
7467
  {
7043
7468
  ref: svgRef,
@@ -7056,15 +7481,15 @@ function PIDCanvasImpl({
7056
7481
  onDrop: handleDrop,
7057
7482
  onDragLeave: handleDragLeave,
7058
7483
  children: [
7059
- features.showGrid !== false ? /* @__PURE__ */ jsxs16(Fragment7, { children: [
7060
- /* @__PURE__ */ jsxs16("defs", { children: [
7061
- /* @__PURE__ */ jsx20("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx20("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
7062
- /* @__PURE__ */ jsxs16("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
7063
- /* @__PURE__ */ jsx20("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
7064
- /* @__PURE__ */ jsx20("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
7484
+ features.showGrid !== false ? /* @__PURE__ */ jsxs17(Fragment7, { children: [
7485
+ /* @__PURE__ */ jsxs17("defs", { children: [
7486
+ /* @__PURE__ */ jsx21("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx21("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
7487
+ /* @__PURE__ */ jsxs17("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
7488
+ /* @__PURE__ */ jsx21("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
7489
+ /* @__PURE__ */ jsx21("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
7065
7490
  ] })
7066
7491
  ] }),
7067
- /* @__PURE__ */ jsx20(
7492
+ /* @__PURE__ */ jsx21(
7068
7493
  "rect",
7069
7494
  {
7070
7495
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
@@ -7074,7 +7499,7 @@ function PIDCanvasImpl({
7074
7499
  fill: "url(#grid-major)"
7075
7500
  }
7076
7501
  )
7077
- ] }) : /* @__PURE__ */ jsx20(
7502
+ ] }) : /* @__PURE__ */ jsx21(
7078
7503
  "rect",
7079
7504
  {
7080
7505
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
@@ -7084,7 +7509,7 @@ function PIDCanvasImpl({
7084
7509
  fill: features.backgroundColor ?? "#ffffff"
7085
7510
  }
7086
7511
  ),
7087
- /* @__PURE__ */ jsx20(
7512
+ /* @__PURE__ */ jsx21(
7088
7513
  PipeRenderer,
7089
7514
  {
7090
7515
  pipes: displayDiagram.pipes,
@@ -7098,7 +7523,7 @@ function PIDCanvasImpl({
7098
7523
  onPipeSegmentClick: void 0
7099
7524
  }
7100
7525
  ),
7101
- /* @__PURE__ */ jsx20(
7526
+ /* @__PURE__ */ jsx21(
7102
7527
  NodeRenderer,
7103
7528
  {
7104
7529
  nodes: displayDiagram.nodes,
@@ -7112,7 +7537,7 @@ function PIDCanvasImpl({
7112
7537
  statusColorMap
7113
7538
  }
7114
7539
  ),
7115
- (features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx20(
7540
+ (features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx21(
7116
7541
  PipeRenderer,
7117
7542
  {
7118
7543
  pipes: displayDiagram.pipes,
@@ -7129,14 +7554,15 @@ function PIDCanvasImpl({
7129
7554
  onPipeSegmentClick: handlePipeSegmentClick
7130
7555
  }
7131
7556
  ),
7132
- telemetryEnabled && telemetry && /* @__PURE__ */ jsx20("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
7557
+ telemetryEnabled && telemetry && /* @__PURE__ */ jsx21("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
7558
+ if (node.symbolId === "status-indicator") return null;
7133
7559
  const binding = telemetry.get(node.id);
7134
7560
  if (!binding) return null;
7135
7561
  const symbol = getSymbolDefinition(node.symbolId);
7136
7562
  if (!symbol) return null;
7137
7563
  const overlayX = node.transform.x + symbol.viewBox.width / 2;
7138
7564
  const overlayY = node.transform.y + symbol.viewBox.height + 50;
7139
- return /* @__PURE__ */ jsx20(
7565
+ return /* @__PURE__ */ jsx21(
7140
7566
  DataOverlay,
7141
7567
  {
7142
7568
  nodeId: node.id,
@@ -7149,7 +7575,7 @@ function PIDCanvasImpl({
7149
7575
  `telemetry-${node.id}`
7150
7576
  );
7151
7577
  }) }),
7152
- dragOverPosition && /* @__PURE__ */ jsx20(
7578
+ dragOverPosition && /* @__PURE__ */ jsx21(
7153
7579
  "circle",
7154
7580
  {
7155
7581
  cx: dragOverPosition.x,
@@ -7162,7 +7588,7 @@ function PIDCanvasImpl({
7162
7588
  pointerEvents: "none"
7163
7589
  }
7164
7590
  ),
7165
- isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ jsx20("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
7591
+ isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ jsx21("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
7166
7592
  const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
7167
7593
  if (!sourceNode) return null;
7168
7594
  const symbol = getSymbolDefinition(sourceNode.symbolId);
@@ -7178,8 +7604,8 @@ function PIDCanvasImpl({
7178
7604
  }
7179
7605
  }
7180
7606
  }
7181
- return /* @__PURE__ */ jsxs16(Fragment7, { children: [
7182
- /* @__PURE__ */ jsx20(
7607
+ return /* @__PURE__ */ jsxs17(Fragment7, { children: [
7608
+ /* @__PURE__ */ jsx21(
7183
7609
  "circle",
7184
7610
  {
7185
7611
  cx: lineStartX,
@@ -7191,7 +7617,7 @@ function PIDCanvasImpl({
7191
7617
  opacity: 0.6
7192
7618
  }
7193
7619
  ),
7194
- /* @__PURE__ */ jsx20(
7620
+ /* @__PURE__ */ jsx21(
7195
7621
  "line",
7196
7622
  {
7197
7623
  x1: lineStartX,
@@ -7204,7 +7630,7 @@ function PIDCanvasImpl({
7204
7630
  opacity: 0.8
7205
7631
  }
7206
7632
  ),
7207
- /* @__PURE__ */ jsx20(
7633
+ /* @__PURE__ */ jsx21(
7208
7634
  "circle",
7209
7635
  {
7210
7636
  cx: connectionCursor.x,
@@ -7216,7 +7642,7 @@ function PIDCanvasImpl({
7216
7642
  )
7217
7643
  ] });
7218
7644
  })() }),
7219
- features.connectionMode && /* @__PURE__ */ jsx20("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
7645
+ features.connectionMode && /* @__PURE__ */ jsx21("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
7220
7646
  const symbol = getSymbolDefinition(node.symbolId);
7221
7647
  if (!symbol?.ports) return null;
7222
7648
  return symbol.ports.map((port) => {
@@ -7224,8 +7650,8 @@ function PIDCanvasImpl({
7224
7650
  if (!portPos) return null;
7225
7651
  const isHovered = hoveredPort === port.id && hoveredNode === node.id;
7226
7652
  const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
7227
- return /* @__PURE__ */ jsxs16("g", { children: [
7228
- /* @__PURE__ */ jsx20(
7653
+ return /* @__PURE__ */ jsxs17("g", { children: [
7654
+ /* @__PURE__ */ jsx21(
7229
7655
  "circle",
7230
7656
  {
7231
7657
  cx: portPos.x,
@@ -7258,7 +7684,7 @@ function PIDCanvasImpl({
7258
7684
  }
7259
7685
  }
7260
7686
  ),
7261
- /* @__PURE__ */ jsx20(
7687
+ /* @__PURE__ */ jsx21(
7262
7688
  "circle",
7263
7689
  {
7264
7690
  cx: portPos.x,
@@ -7277,7 +7703,7 @@ function PIDCanvasImpl({
7277
7703
  ]
7278
7704
  }
7279
7705
  ),
7280
- features.dragNodes && /* @__PURE__ */ jsx20(
7706
+ features.dragNodes && /* @__PURE__ */ jsx21(
7281
7707
  PositionPanel,
7282
7708
  {
7283
7709
  selectedNode,
@@ -7295,11 +7721,11 @@ function PIDCanvasImpl({
7295
7721
  }
7296
7722
  );
7297
7723
  }
7298
- var PIDCanvas = React13.memo(PIDCanvasImpl);
7724
+ var PIDCanvas = React14.memo(PIDCanvasImpl);
7299
7725
 
7300
7726
  // src/components/SymbolLibrary/SymbolLibrary.tsx
7301
7727
  import { useState as useState15 } from "react";
7302
- import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
7728
+ import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
7303
7729
  function SymbolLibrary({
7304
7730
  className = "",
7305
7731
  onSymbolDragStart,
@@ -7328,9 +7754,9 @@ function SymbolLibrary({
7328
7754
  event.dataTransfer.effectAllowed = "copy";
7329
7755
  onSymbolDragStart?.(symbol.id, event);
7330
7756
  };
7331
- return /* @__PURE__ */ jsxs17("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
7332
- /* @__PURE__ */ jsx21("div", { style: styles.header, children: /* @__PURE__ */ jsx21("h3", { style: styles.title, children: "Symbol Library" }) }),
7333
- /* @__PURE__ */ jsx21("div", { style: styles.searchContainer, children: /* @__PURE__ */ jsx21(
7757
+ return /* @__PURE__ */ jsxs18("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
7758
+ /* @__PURE__ */ jsx22("div", { style: styles.header, children: /* @__PURE__ */ jsx22("h3", { style: styles.title, children: "Symbol Library" }) }),
7759
+ /* @__PURE__ */ jsx22("div", { style: styles.searchContainer, children: /* @__PURE__ */ jsx22(
7334
7760
  "input",
7335
7761
  {
7336
7762
  type: "text",
@@ -7340,8 +7766,8 @@ function SymbolLibrary({
7340
7766
  style: styles.searchInput
7341
7767
  }
7342
7768
  ) }),
7343
- showCategories && /* @__PURE__ */ jsxs17("div", { style: styles.categories, children: [
7344
- /* @__PURE__ */ jsx21(
7769
+ showCategories && /* @__PURE__ */ jsxs18("div", { style: styles.categories, children: [
7770
+ /* @__PURE__ */ jsx22(
7345
7771
  "button",
7346
7772
  {
7347
7773
  onClick: () => setSelectedCategory("all"),
@@ -7352,7 +7778,7 @@ function SymbolLibrary({
7352
7778
  children: "All"
7353
7779
  }
7354
7780
  ),
7355
- categories.map((category) => /* @__PURE__ */ jsx21(
7781
+ categories.map((category) => /* @__PURE__ */ jsx22(
7356
7782
  "button",
7357
7783
  {
7358
7784
  onClick: () => setSelectedCategory(category),
@@ -7365,7 +7791,7 @@ function SymbolLibrary({
7365
7791
  category
7366
7792
  ))
7367
7793
  ] }),
7368
- /* @__PURE__ */ jsx21("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ jsx21("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ jsxs17(
7794
+ /* @__PURE__ */ jsx22("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ jsx22("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ jsxs18(
7369
7795
  "div",
7370
7796
  {
7371
7797
  draggable: true,
@@ -7373,15 +7799,15 @@ function SymbolLibrary({
7373
7799
  style: styles.symbolCard,
7374
7800
  title: symbol.metadata?.description || symbol.name,
7375
7801
  children: [
7376
- /* @__PURE__ */ jsx21("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx21(
7802
+ /* @__PURE__ */ jsx22("div", { style: styles.symbolPreview, children: /* @__PURE__ */ jsx22(
7377
7803
  "svg",
7378
7804
  {
7379
7805
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
7380
7806
  style: styles.symbolSvg,
7381
- children: /* @__PURE__ */ jsx21("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
7807
+ children: /* @__PURE__ */ jsx22("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
7382
7808
  }
7383
7809
  ) }),
7384
- /* @__PURE__ */ jsx21("div", { style: styles.symbolName, children: symbol.name })
7810
+ /* @__PURE__ */ jsx22("div", { style: styles.symbolName, children: symbol.name })
7385
7811
  ]
7386
7812
  },
7387
7813
  symbol.id
@@ -7490,13 +7916,15 @@ var styles = {
7490
7916
  };
7491
7917
 
7492
7918
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
7493
- import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
7919
+ import { Fragment as Fragment8, jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
7494
7920
  function NodeConfigPanel({
7495
7921
  node,
7496
7922
  binding,
7497
7923
  onUpdateNode,
7498
7924
  onUpdateTelemetry,
7499
7925
  onAddTelemetry,
7926
+ onRemoveTelemetry,
7927
+ availableTags = [],
7500
7928
  onRemove,
7501
7929
  className = "",
7502
7930
  style = {}
@@ -7509,15 +7937,17 @@ function NodeConfigPanel({
7509
7937
  padding: "20px",
7510
7938
  ...style
7511
7939
  };
7512
- return /* @__PURE__ */ jsxs18("div", { className, style: defaultStyle, children: [
7513
- /* @__PURE__ */ jsxs18("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
7940
+ const appearanceFields = getAppearanceSchema(node.symbolId);
7941
+ const showGenericTelemetry = appearanceFields.length === 0;
7942
+ return /* @__PURE__ */ jsxs19("div", { className, style: defaultStyle, children: [
7943
+ /* @__PURE__ */ jsxs19("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
7514
7944
  "Configure: ",
7515
7945
  node.id
7516
7946
  ] }),
7517
- /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "20px" }, children: [
7518
- /* @__PURE__ */ jsx22("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
7519
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
7520
- /* @__PURE__ */ jsx22(
7947
+ /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "20px" }, children: [
7948
+ /* @__PURE__ */ jsx23("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
7949
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
7950
+ /* @__PURE__ */ jsx23(
7521
7951
  "input",
7522
7952
  {
7523
7953
  type: "text",
@@ -7533,10 +7963,10 @@ function NodeConfigPanel({
7533
7963
  }
7534
7964
  }
7535
7965
  ),
7536
- /* @__PURE__ */ jsxs18("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
7537
- /* @__PURE__ */ jsxs18("div", { children: [
7538
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
7539
- /* @__PURE__ */ jsx22(
7966
+ /* @__PURE__ */ jsxs19("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
7967
+ /* @__PURE__ */ jsxs19("div", { children: [
7968
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
7969
+ /* @__PURE__ */ jsx23(
7540
7970
  "input",
7541
7971
  {
7542
7972
  type: "number",
@@ -7557,9 +7987,9 @@ function NodeConfigPanel({
7557
7987
  }
7558
7988
  )
7559
7989
  ] }),
7560
- /* @__PURE__ */ jsxs18("div", { children: [
7561
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
7562
- /* @__PURE__ */ jsx22(
7990
+ /* @__PURE__ */ jsxs19("div", { children: [
7991
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
7992
+ /* @__PURE__ */ jsx23(
7563
7993
  "input",
7564
7994
  {
7565
7995
  type: "number",
@@ -7581,9 +8011,9 @@ function NodeConfigPanel({
7581
8011
  )
7582
8012
  ] })
7583
8013
  ] }),
7584
- /* @__PURE__ */ jsxs18("div", { children: [
7585
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
7586
- /* @__PURE__ */ jsx22(
8014
+ /* @__PURE__ */ jsxs19("div", { children: [
8015
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
8016
+ /* @__PURE__ */ jsx23(
7587
8017
  "input",
7588
8018
  {
7589
8019
  type: "number",
@@ -7603,9 +8033,9 @@ function NodeConfigPanel({
7603
8033
  }
7604
8034
  )
7605
8035
  ] }),
7606
- /* @__PURE__ */ jsxs18("div", { children: [
7607
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
7608
- /* @__PURE__ */ jsx22(
8036
+ /* @__PURE__ */ jsxs19("div", { children: [
8037
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
8038
+ /* @__PURE__ */ jsx23(
7609
8039
  "input",
7610
8040
  {
7611
8041
  type: "number",
@@ -7630,9 +8060,138 @@ function NodeConfigPanel({
7630
8060
  )
7631
8061
  ] })
7632
8062
  ] }),
7633
- /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "20px" }, children: [
7634
- /* @__PURE__ */ jsx22("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
7635
- !binding ? /* @__PURE__ */ jsx22(
8063
+ appearanceFields.length > 0 && /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "20px" }, children: [
8064
+ /* @__PURE__ */ jsx23("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Appearance" }),
8065
+ appearanceFields.map((field) => {
8066
+ const value = readAppearanceValue(node, field);
8067
+ const fieldLabel = field.label ?? field.key;
8068
+ const inputStyle = {
8069
+ width: "100%",
8070
+ padding: "6px 8px",
8071
+ borderRadius: "4px",
8072
+ border: "1px solid #cbd5e1",
8073
+ fontSize: "0.875rem"
8074
+ };
8075
+ let control;
8076
+ if (field.kind === "boolean") {
8077
+ control = /* @__PURE__ */ jsxs19("label", { style: { fontSize: "0.75rem", display: "block" }, children: [
8078
+ /* @__PURE__ */ jsx23(
8079
+ "input",
8080
+ {
8081
+ type: "checkbox",
8082
+ checked: Boolean(value),
8083
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.checked)),
8084
+ style: { marginRight: "6px" }
8085
+ }
8086
+ ),
8087
+ fieldLabel
8088
+ ] });
8089
+ } else if (field.kind === "select") {
8090
+ control = /* @__PURE__ */ jsxs19(Fragment8, { children: [
8091
+ /* @__PURE__ */ jsxs19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8092
+ fieldLabel,
8093
+ ":"
8094
+ ] }),
8095
+ /* @__PURE__ */ jsx23(
8096
+ "select",
8097
+ {
8098
+ value: String(value),
8099
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8100
+ style: inputStyle,
8101
+ children: field.options?.map((opt) => /* @__PURE__ */ jsx23("option", { value: opt, children: opt }, opt))
8102
+ }
8103
+ )
8104
+ ] });
8105
+ } else if (field.kind === "number") {
8106
+ control = /* @__PURE__ */ jsxs19(Fragment8, { children: [
8107
+ /* @__PURE__ */ jsxs19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8108
+ fieldLabel,
8109
+ ":"
8110
+ ] }),
8111
+ /* @__PURE__ */ jsx23(
8112
+ "input",
8113
+ {
8114
+ type: "number",
8115
+ value: Number(value),
8116
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, parseFloat(e.target.value) || 0)),
8117
+ style: inputStyle
8118
+ }
8119
+ )
8120
+ ] });
8121
+ } else if (field.kind === "tag") {
8122
+ const tagValue = String(value);
8123
+ const listId = `tags-${node.id}-${field.key}`;
8124
+ const known = tagValue.length > 0 && availableTags.includes(tagValue);
8125
+ const unknown = tagValue.length > 0 && !known;
8126
+ control = /* @__PURE__ */ jsxs19(Fragment8, { children: [
8127
+ /* @__PURE__ */ jsxs19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8128
+ fieldLabel,
8129
+ ":"
8130
+ ] }),
8131
+ /* @__PURE__ */ jsx23(
8132
+ "input",
8133
+ {
8134
+ type: "text",
8135
+ value: tagValue,
8136
+ list: listId,
8137
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8138
+ placeholder: "type or pick a signal\u2026",
8139
+ style: {
8140
+ ...inputStyle,
8141
+ border: `1px solid ${unknown ? "#f59e0b" : known ? "#22c55e" : "#cbd5e1"}`
8142
+ }
8143
+ }
8144
+ ),
8145
+ /* @__PURE__ */ jsx23("datalist", { id: listId, children: availableTags.map((t) => /* @__PURE__ */ jsx23("option", { value: t }, t)) }),
8146
+ tagValue.length > 0 && /* @__PURE__ */ jsx23(
8147
+ "div",
8148
+ {
8149
+ style: {
8150
+ fontSize: "0.7rem",
8151
+ marginTop: "3px",
8152
+ color: known ? "#16a34a" : "#b45309"
8153
+ },
8154
+ children: known ? "\u2713 matched signal" : "\u26A0 no matching signal (reads as false until published)"
8155
+ }
8156
+ )
8157
+ ] });
8158
+ } else {
8159
+ control = /* @__PURE__ */ jsxs19(Fragment8, { children: [
8160
+ /* @__PURE__ */ jsxs19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8161
+ fieldLabel,
8162
+ ":"
8163
+ ] }),
8164
+ /* @__PURE__ */ jsx23(
8165
+ "input",
8166
+ {
8167
+ type: "text",
8168
+ value: String(value),
8169
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8170
+ style: inputStyle
8171
+ }
8172
+ )
8173
+ ] });
8174
+ }
8175
+ return /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "10px" }, children: [
8176
+ control,
8177
+ field.help && /* @__PURE__ */ jsx23(
8178
+ "div",
8179
+ {
8180
+ style: {
8181
+ fontSize: "0.7rem",
8182
+ color: "#64748b",
8183
+ marginTop: "3px",
8184
+ lineHeight: 1.35
8185
+ },
8186
+ children: field.help
8187
+ }
8188
+ )
8189
+ ] }, field.key);
8190
+ })
8191
+ ] }),
8192
+ showGenericTelemetry && /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "20px" }, children: [
8193
+ /* @__PURE__ */ jsx23("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
8194
+ !binding ? /* @__PURE__ */ jsx23(
7636
8195
  "button",
7637
8196
  {
7638
8197
  onClick: () => {
@@ -7675,9 +8234,30 @@ function NodeConfigPanel({
7675
8234
  },
7676
8235
  children: "+ Add Telemetry"
7677
8236
  }
7678
- ) : /* @__PURE__ */ jsxs18(Fragment8, { children: [
7679
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
7680
- /* @__PURE__ */ jsx22(
8237
+ ) : /* @__PURE__ */ jsxs19(Fragment8, { children: [
8238
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Tag (attribute):" }),
8239
+ /* @__PURE__ */ jsx23(
8240
+ "input",
8241
+ {
8242
+ type: "text",
8243
+ value: binding.tag || "",
8244
+ onChange: (e) => onUpdateTelemetry({
8245
+ nodeId: node.id,
8246
+ tag: e.target.value
8247
+ }),
8248
+ placeholder: "e.g. pump.running",
8249
+ style: {
8250
+ width: "100%",
8251
+ padding: "6px 8px",
8252
+ borderRadius: "4px",
8253
+ border: "1px solid #cbd5e1",
8254
+ fontSize: "0.875rem",
8255
+ marginBottom: "8px"
8256
+ }
8257
+ }
8258
+ ),
8259
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
8260
+ /* @__PURE__ */ jsx23(
7681
8261
  "input",
7682
8262
  {
7683
8263
  type: "number",
@@ -7699,8 +8279,8 @@ function NodeConfigPanel({
7699
8279
  }
7700
8280
  }
7701
8281
  ),
7702
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
7703
- /* @__PURE__ */ jsx22(
8282
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
8283
+ /* @__PURE__ */ jsx23(
7704
8284
  "input",
7705
8285
  {
7706
8286
  type: "text",
@@ -7722,8 +8302,8 @@ function NodeConfigPanel({
7722
8302
  }
7723
8303
  }
7724
8304
  ),
7725
- /* @__PURE__ */ jsxs18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
7726
- /* @__PURE__ */ jsx22(
8305
+ /* @__PURE__ */ jsxs19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8306
+ /* @__PURE__ */ jsx23(
7727
8307
  "input",
7728
8308
  {
7729
8309
  type: "checkbox",
@@ -7745,8 +8325,8 @@ function NodeConfigPanel({
7745
8325
  ),
7746
8326
  "Show Sparkline"
7747
8327
  ] }),
7748
- /* @__PURE__ */ jsxs18("div", { style: { marginTop: "12px" }, children: [
7749
- /* @__PURE__ */ jsx22(
8328
+ /* @__PURE__ */ jsxs19("div", { style: { marginTop: "12px" }, children: [
8329
+ /* @__PURE__ */ jsx23(
7750
8330
  "label",
7751
8331
  {
7752
8332
  style: {
@@ -7758,10 +8338,10 @@ function NodeConfigPanel({
7758
8338
  children: "Telemetry Position Offset:"
7759
8339
  }
7760
8340
  ),
7761
- /* @__PURE__ */ jsxs18("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
7762
- /* @__PURE__ */ jsxs18("div", { children: [
7763
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
7764
- /* @__PURE__ */ jsx22(
8341
+ /* @__PURE__ */ jsxs19("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
8342
+ /* @__PURE__ */ jsxs19("div", { children: [
8343
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
8344
+ /* @__PURE__ */ jsx23(
7765
8345
  "input",
7766
8346
  {
7767
8347
  type: "number",
@@ -7788,9 +8368,9 @@ function NodeConfigPanel({
7788
8368
  }
7789
8369
  )
7790
8370
  ] }),
7791
- /* @__PURE__ */ jsxs18("div", { children: [
7792
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
7793
- /* @__PURE__ */ jsx22(
8371
+ /* @__PURE__ */ jsxs19("div", { children: [
8372
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
8373
+ /* @__PURE__ */ jsx23(
7794
8374
  "input",
7795
8375
  {
7796
8376
  type: "number",
@@ -7818,8 +8398,8 @@ function NodeConfigPanel({
7818
8398
  )
7819
8399
  ] })
7820
8400
  ] }),
7821
- /* @__PURE__ */ jsxs18("div", { style: { marginTop: "16px" }, children: [
7822
- /* @__PURE__ */ jsx22(
8401
+ /* @__PURE__ */ jsxs19("div", { style: { marginTop: "16px" }, children: [
8402
+ /* @__PURE__ */ jsx23(
7823
8403
  "label",
7824
8404
  {
7825
8405
  style: {
@@ -7831,10 +8411,10 @@ function NodeConfigPanel({
7831
8411
  children: "Display Colors:"
7832
8412
  }
7833
8413
  ),
7834
- /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "8px" }, children: [
7835
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
7836
- /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
7837
- /* @__PURE__ */ jsx22(
8414
+ /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "8px" }, children: [
8415
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
8416
+ /* @__PURE__ */ jsxs19("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8417
+ /* @__PURE__ */ jsx23(
7838
8418
  "input",
7839
8419
  {
7840
8420
  type: "color",
@@ -7860,7 +8440,7 @@ function NodeConfigPanel({
7860
8440
  }
7861
8441
  }
7862
8442
  ),
7863
- /* @__PURE__ */ jsx22(
8443
+ /* @__PURE__ */ jsx23(
7864
8444
  "input",
7865
8445
  {
7866
8446
  type: "text",
@@ -7890,10 +8470,10 @@ function NodeConfigPanel({
7890
8470
  )
7891
8471
  ] })
7892
8472
  ] }),
7893
- binding.display?.showSparkline && /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "8px" }, children: [
7894
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
7895
- /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
7896
- /* @__PURE__ */ jsx22(
8473
+ binding.display?.showSparkline && /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "8px" }, children: [
8474
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
8475
+ /* @__PURE__ */ jsxs19("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8476
+ /* @__PURE__ */ jsx23(
7897
8477
  "input",
7898
8478
  {
7899
8479
  type: "color",
@@ -7919,7 +8499,7 @@ function NodeConfigPanel({
7919
8499
  }
7920
8500
  }
7921
8501
  ),
7922
- /* @__PURE__ */ jsx22(
8502
+ /* @__PURE__ */ jsx23(
7923
8503
  "input",
7924
8504
  {
7925
8505
  type: "text",
@@ -7949,9 +8529,9 @@ function NodeConfigPanel({
7949
8529
  )
7950
8530
  ] })
7951
8531
  ] }),
7952
- /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "8px" }, children: [
7953
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
7954
- /* @__PURE__ */ jsx22(
8532
+ /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "8px" }, children: [
8533
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
8534
+ /* @__PURE__ */ jsx23(
7955
8535
  "input",
7956
8536
  {
7957
8537
  type: "text",
@@ -7979,20 +8559,38 @@ function NodeConfigPanel({
7979
8559
  }
7980
8560
  }
7981
8561
  ),
7982
- /* @__PURE__ */ jsx22("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
8562
+ /* @__PURE__ */ jsx23("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
7983
8563
  ] })
7984
8564
  ] }),
7985
8565
  " "
7986
- ] })
8566
+ ] }),
8567
+ onRemoveTelemetry && /* @__PURE__ */ jsx23(
8568
+ "button",
8569
+ {
8570
+ onClick: onRemoveTelemetry,
8571
+ style: {
8572
+ width: "100%",
8573
+ padding: "6px 8px",
8574
+ marginTop: "8px",
8575
+ borderRadius: "4px",
8576
+ border: "1px solid #ef4444",
8577
+ background: "transparent",
8578
+ color: "#ef4444",
8579
+ fontSize: "0.8125rem",
8580
+ cursor: "pointer"
8581
+ },
8582
+ children: "Remove Telemetry"
8583
+ }
8584
+ )
7987
8585
  ] })
7988
8586
  ] }),
7989
- binding && /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "20px" }, children: [
7990
- /* @__PURE__ */ jsx22("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
8587
+ showGenericTelemetry && binding && /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "20px" }, children: [
8588
+ /* @__PURE__ */ jsx23("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
7991
8589
  ["highHigh", "high", "low", "lowLow"].map((key) => {
7992
8590
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
7993
- return /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "8px" }, children: [
7994
- /* @__PURE__ */ jsx22("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
7995
- /* @__PURE__ */ jsx22(
8591
+ return /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "8px" }, children: [
8592
+ /* @__PURE__ */ jsx23("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
8593
+ /* @__PURE__ */ jsx23(
7996
8594
  "input",
7997
8595
  {
7998
8596
  type: "number",
@@ -8016,8 +8614,8 @@ function NodeConfigPanel({
8016
8614
  ] }, key);
8017
8615
  })
8018
8616
  ] }),
8019
- binding && /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "20px" }, children: [
8020
- /* @__PURE__ */ jsx22("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
8617
+ showGenericTelemetry && binding && /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "20px" }, children: [
8618
+ /* @__PURE__ */ jsx23("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
8021
8619
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
8022
8620
  const defaultColors = {
8023
8621
  normal: "#10b981",
@@ -8027,13 +8625,13 @@ function NodeConfigPanel({
8027
8625
  off: "#6b7280"
8028
8626
  };
8029
8627
  const label = status === "normal" ? "Normal" : status === "warning" ? "Warning" : status === "alarm" ? "Alarm" : status === "fault" ? "Fault" : "Off";
8030
- return /* @__PURE__ */ jsxs18("div", { style: { marginBottom: "8px" }, children: [
8031
- /* @__PURE__ */ jsxs18("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8628
+ return /* @__PURE__ */ jsxs19("div", { style: { marginBottom: "8px" }, children: [
8629
+ /* @__PURE__ */ jsxs19("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8032
8630
  label,
8033
8631
  ":"
8034
8632
  ] }),
8035
- /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8036
- /* @__PURE__ */ jsx22(
8633
+ /* @__PURE__ */ jsxs19("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8634
+ /* @__PURE__ */ jsx23(
8037
8635
  "input",
8038
8636
  {
8039
8637
  type: "color",
@@ -8054,7 +8652,7 @@ function NodeConfigPanel({
8054
8652
  }
8055
8653
  }
8056
8654
  ),
8057
- /* @__PURE__ */ jsx22(
8655
+ /* @__PURE__ */ jsx23(
8058
8656
  "input",
8059
8657
  {
8060
8658
  type: "text",
@@ -8081,7 +8679,7 @@ function NodeConfigPanel({
8081
8679
  ] }, status);
8082
8680
  })
8083
8681
  ] }),
8084
- /* @__PURE__ */ jsx22("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ jsx22(
8682
+ /* @__PURE__ */ jsx23("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ jsx23(
8085
8683
  "button",
8086
8684
  {
8087
8685
  onClick: onRemove,
@@ -8875,8 +9473,8 @@ function useDeviceConfigs(initialConfigs) {
8875
9473
  }
8876
9474
 
8877
9475
  // src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
8878
- import React15, { useState as useState18, useEffect as useEffect13, useMemo, useRef as useRef8 } from "react";
8879
- import { Fragment as Fragment9, jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
9476
+ import React16, { useState as useState18, useEffect as useEffect14, useMemo, useRef as useRef8 } from "react";
9477
+ import { Fragment as Fragment9, jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
8880
9478
  function DeviceConfigPanelImpl({
8881
9479
  binding,
8882
9480
  onClose,
@@ -8934,7 +9532,7 @@ function DeviceConfigPanelImpl({
8934
9532
  setCollapsedSections(collapsed);
8935
9533
  setBindingId(binding.nodeId);
8936
9534
  }
8937
- useEffect13(() => {
9535
+ useEffect14(() => {
8938
9536
  const currentTimestamp = binding?.state.lastSaved || 0;
8939
9537
  if (binding?.state.lastSaveResult && currentTimestamp !== lastResultTimestampRef.current) {
8940
9538
  lastResultTimestampRef.current = currentTimestamp;
@@ -9042,14 +9640,14 @@ function DeviceConfigPanelImpl({
9042
9640
  const value = localValues[param.id];
9043
9641
  const error = binding.state.errors?.[param.id];
9044
9642
  const isDisabled = param.readOnly || param.enabled === false;
9045
- return /* @__PURE__ */ jsxs19(
9643
+ return /* @__PURE__ */ jsxs20(
9046
9644
  "div",
9047
9645
  {
9048
9646
  style: {
9049
9647
  marginBottom: spacing.marginBottom
9050
9648
  },
9051
9649
  children: [
9052
- /* @__PURE__ */ jsxs19(
9650
+ /* @__PURE__ */ jsxs20(
9053
9651
  "label",
9054
9652
  {
9055
9653
  style: {
@@ -9064,13 +9662,13 @@ function DeviceConfigPanelImpl({
9064
9662
  },
9065
9663
  children: [
9066
9664
  param.label,
9067
- param.required && /* @__PURE__ */ jsx23("span", { style: { color: errorColor }, children: " *" }),
9068
- param.unit && /* @__PURE__ */ jsxs19("span", { style: { color: mutedTextColor, fontWeight: "normal" }, children: [
9665
+ param.required && /* @__PURE__ */ jsx24("span", { style: { color: errorColor }, children: " *" }),
9666
+ param.unit && /* @__PURE__ */ jsxs20("span", { style: { color: mutedTextColor, fontWeight: "normal" }, children: [
9069
9667
  " (",
9070
9668
  param.unit,
9071
9669
  ")"
9072
9670
  ] }),
9073
- param.forced?.active && /* @__PURE__ */ jsx23(
9671
+ param.forced?.active && /* @__PURE__ */ jsx24(
9074
9672
  "span",
9075
9673
  {
9076
9674
  "aria-label": "Forced",
@@ -9097,7 +9695,7 @@ function DeviceConfigPanelImpl({
9097
9695
  ]
9098
9696
  }
9099
9697
  ),
9100
- param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ jsxs19(
9698
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ jsxs20(
9101
9699
  "div",
9102
9700
  {
9103
9701
  style: {
@@ -9115,7 +9713,7 @@ function DeviceConfigPanelImpl({
9115
9713
  ]
9116
9714
  }
9117
9715
  ),
9118
- param.description && /* @__PURE__ */ jsx23(
9716
+ param.description && /* @__PURE__ */ jsx24(
9119
9717
  "div",
9120
9718
  {
9121
9719
  style: {
@@ -9127,7 +9725,7 @@ function DeviceConfigPanelImpl({
9127
9725
  children: param.description
9128
9726
  }
9129
9727
  ),
9130
- param.type === "number" && /* @__PURE__ */ jsx23(
9728
+ param.type === "number" && /* @__PURE__ */ jsx24(
9131
9729
  "input",
9132
9730
  {
9133
9731
  type: "number",
@@ -9151,7 +9749,7 @@ function DeviceConfigPanelImpl({
9151
9749
  }
9152
9750
  }
9153
9751
  ),
9154
- param.type === "string" && /* @__PURE__ */ jsx23(
9752
+ param.type === "string" && /* @__PURE__ */ jsx24(
9155
9753
  "input",
9156
9754
  {
9157
9755
  type: "text",
@@ -9172,7 +9770,7 @@ function DeviceConfigPanelImpl({
9172
9770
  }
9173
9771
  }
9174
9772
  ),
9175
- param.type === "boolean" && /* @__PURE__ */ jsxs19(
9773
+ param.type === "boolean" && /* @__PURE__ */ jsxs20(
9176
9774
  "label",
9177
9775
  {
9178
9776
  style: {
@@ -9182,7 +9780,7 @@ function DeviceConfigPanelImpl({
9182
9780
  cursor: isDisabled ? "not-allowed" : "pointer"
9183
9781
  },
9184
9782
  children: [
9185
- /* @__PURE__ */ jsx23(
9783
+ /* @__PURE__ */ jsx24(
9186
9784
  "input",
9187
9785
  {
9188
9786
  type: "checkbox",
@@ -9196,11 +9794,11 @@ function DeviceConfigPanelImpl({
9196
9794
  }
9197
9795
  }
9198
9796
  ),
9199
- /* @__PURE__ */ jsx23("span", { style: { fontSize: `${fontSize.value}px`, color: textColor }, children: value ? "Enabled" : "Disabled" })
9797
+ /* @__PURE__ */ jsx24("span", { style: { fontSize: `${fontSize.value}px`, color: textColor }, children: value ? "Enabled" : "Disabled" })
9200
9798
  ]
9201
9799
  }
9202
9800
  ),
9203
- param.type === "select" && /* @__PURE__ */ jsx23(
9801
+ param.type === "select" && /* @__PURE__ */ jsx24(
9204
9802
  "select",
9205
9803
  {
9206
9804
  value: value ?? "",
@@ -9218,10 +9816,10 @@ function DeviceConfigPanelImpl({
9218
9816
  borderRadius: "2px",
9219
9817
  outline: "none"
9220
9818
  },
9221
- children: param.options?.map((option) => /* @__PURE__ */ jsx23("option", { value: option.value, children: option.label }, option.value))
9819
+ children: param.options?.map((option) => /* @__PURE__ */ jsx24("option", { value: option.value, children: option.label }, option.value))
9222
9820
  }
9223
9821
  ),
9224
- error && /* @__PURE__ */ jsx23(
9822
+ error && /* @__PURE__ */ jsx24(
9225
9823
  "div",
9226
9824
  {
9227
9825
  style: {
@@ -9241,7 +9839,7 @@ function DeviceConfigPanelImpl({
9241
9839
  const renderSection = (section) => {
9242
9840
  const params = parametersBySection[section.id] || [];
9243
9841
  const isCollapsed = collapsedSections.has(section.id);
9244
- return /* @__PURE__ */ jsxs19(
9842
+ return /* @__PURE__ */ jsxs20(
9245
9843
  "div",
9246
9844
  {
9247
9845
  style: {
@@ -9251,7 +9849,7 @@ function DeviceConfigPanelImpl({
9251
9849
  backgroundColor: surfaceColor
9252
9850
  },
9253
9851
  children: [
9254
- /* @__PURE__ */ jsxs19(
9852
+ /* @__PURE__ */ jsxs20(
9255
9853
  "div",
9256
9854
  {
9257
9855
  onClick: () => section.collapsible && toggleSection(section.id),
@@ -9264,8 +9862,8 @@ function DeviceConfigPanelImpl({
9264
9862
  alignItems: "center"
9265
9863
  },
9266
9864
  children: [
9267
- /* @__PURE__ */ jsxs19("div", { children: [
9268
- /* @__PURE__ */ jsxs19(
9865
+ /* @__PURE__ */ jsxs20("div", { children: [
9866
+ /* @__PURE__ */ jsxs20(
9269
9867
  "div",
9270
9868
  {
9271
9869
  style: {
@@ -9277,12 +9875,12 @@ function DeviceConfigPanelImpl({
9277
9875
  letterSpacing: "0.5px"
9278
9876
  },
9279
9877
  children: [
9280
- section.icon && /* @__PURE__ */ jsx23("span", { style: { marginRight: "8px" }, children: section.icon }),
9878
+ section.icon && /* @__PURE__ */ jsx24("span", { style: { marginRight: "8px" }, children: section.icon }),
9281
9879
  section.title
9282
9880
  ]
9283
9881
  }
9284
9882
  ),
9285
- section.description && /* @__PURE__ */ jsx23(
9883
+ section.description && /* @__PURE__ */ jsx24(
9286
9884
  "div",
9287
9885
  {
9288
9886
  style: {
@@ -9295,11 +9893,11 @@ function DeviceConfigPanelImpl({
9295
9893
  }
9296
9894
  )
9297
9895
  ] }),
9298
- section.collapsible && /* @__PURE__ */ jsx23("span", { style: { fontSize: "16px", color: mutedTextColor }, children: isCollapsed ? "\u25BC" : "\u25B2" })
9896
+ section.collapsible && /* @__PURE__ */ jsx24("span", { style: { fontSize: "16px", color: mutedTextColor }, children: isCollapsed ? "\u25BC" : "\u25B2" })
9299
9897
  ]
9300
9898
  }
9301
9899
  ),
9302
- !isCollapsed && /* @__PURE__ */ jsx23("div", { style: { padding: spacing.padding }, children: params.map(renderParameter) })
9900
+ !isCollapsed && /* @__PURE__ */ jsx24("div", { style: { padding: spacing.padding }, children: params.map(renderParameter) })
9303
9901
  ]
9304
9902
  },
9305
9903
  section.id
@@ -9358,9 +9956,9 @@ function DeviceConfigPanelImpl({
9358
9956
  const showSave = binding.display?.showSave !== false && binding.actions.onSave;
9359
9957
  const showReset = binding.display?.showReset !== false && binding.actions.onReset;
9360
9958
  const showCancel = binding.display?.showCancel !== false;
9361
- return /* @__PURE__ */ jsxs19(Fragment9, { children: [
9362
- /* @__PURE__ */ jsxs19("div", { style: panelStyle, className: binding.display?.className, children: [
9363
- /* @__PURE__ */ jsxs19(
9959
+ return /* @__PURE__ */ jsxs20(Fragment9, { children: [
9960
+ /* @__PURE__ */ jsxs20("div", { style: panelStyle, className: binding.display?.className, children: [
9961
+ /* @__PURE__ */ jsxs20(
9364
9962
  "div",
9365
9963
  {
9366
9964
  style: {
@@ -9372,8 +9970,8 @@ function DeviceConfigPanelImpl({
9372
9970
  borderBottom: `2px solid ${borderColor}`
9373
9971
  },
9374
9972
  children: [
9375
- /* @__PURE__ */ jsxs19("div", { children: [
9376
- /* @__PURE__ */ jsx23(
9973
+ /* @__PURE__ */ jsxs20("div", { children: [
9974
+ /* @__PURE__ */ jsx24(
9377
9975
  "h3",
9378
9976
  {
9379
9977
  style: {
@@ -9393,7 +9991,7 @@ function DeviceConfigPanelImpl({
9393
9991
  children: titleText.length > 25 ? titleText.slice(0, 25) + "..." : titleText
9394
9992
  }
9395
9993
  ),
9396
- binding.display?.subtitle && /* @__PURE__ */ jsx23(
9994
+ binding.display?.subtitle && /* @__PURE__ */ jsx24(
9397
9995
  "div",
9398
9996
  {
9399
9997
  style: {
@@ -9406,8 +10004,8 @@ function DeviceConfigPanelImpl({
9406
10004
  }
9407
10005
  )
9408
10006
  ] }),
9409
- /* @__PURE__ */ jsxs19("div", { style: { display: "flex", gap: "8px" }, children: [
9410
- showControlsButton && onOpenControls && /* @__PURE__ */ jsx23(
10007
+ /* @__PURE__ */ jsxs20("div", { style: { display: "flex", gap: "8px" }, children: [
10008
+ showControlsButton && onOpenControls && /* @__PURE__ */ jsx24(
9411
10009
  "button",
9412
10010
  {
9413
10011
  onClick: onOpenControls,
@@ -9430,7 +10028,7 @@ function DeviceConfigPanelImpl({
9430
10028
  children: "CTRL"
9431
10029
  }
9432
10030
  ),
9433
- onUndock && /* @__PURE__ */ jsx23(
10031
+ onUndock && /* @__PURE__ */ jsx24(
9434
10032
  "button",
9435
10033
  {
9436
10034
  onClick: onUndock,
@@ -9451,7 +10049,7 @@ function DeviceConfigPanelImpl({
9451
10049
  children: undockIcon
9452
10050
  }
9453
10051
  ),
9454
- /* @__PURE__ */ jsx23(
10052
+ /* @__PURE__ */ jsx24(
9455
10053
  "button",
9456
10054
  {
9457
10055
  onClick: onClose,
@@ -9474,7 +10072,7 @@ function DeviceConfigPanelImpl({
9474
10072
  ]
9475
10073
  }
9476
10074
  ),
9477
- binding.state.isDirty && /* @__PURE__ */ jsx23(
10075
+ binding.state.isDirty && /* @__PURE__ */ jsx24(
9478
10076
  "div",
9479
10077
  {
9480
10078
  style: {
@@ -9489,11 +10087,11 @@ function DeviceConfigPanelImpl({
9489
10087
  children: "Unsaved changes"
9490
10088
  }
9491
10089
  ),
9492
- /* @__PURE__ */ jsx23("div", { style: { flex: 1, overflowY: "auto", marginBottom: spacing.marginBottom }, children: sortedSections.length > 0 ? sortedSections.map(renderSection) : (
10090
+ /* @__PURE__ */ jsx24("div", { style: { flex: 1, overflowY: "auto", marginBottom: spacing.marginBottom }, children: sortedSections.length > 0 ? sortedSections.map(renderSection) : (
9493
10091
  /* Render unsectioned parameters */
9494
- /* @__PURE__ */ jsx23("div", { children: parametersBySection._unsectioned?.map(renderParameter) })
10092
+ /* @__PURE__ */ jsx24("div", { children: parametersBySection._unsectioned?.map(renderParameter) })
9495
10093
  ) }),
9496
- /* @__PURE__ */ jsxs19(
10094
+ /* @__PURE__ */ jsxs20(
9497
10095
  "div",
9498
10096
  {
9499
10097
  style: {
@@ -9504,7 +10102,7 @@ function DeviceConfigPanelImpl({
9504
10102
  flexWrap: "wrap"
9505
10103
  },
9506
10104
  children: [
9507
- showApply && /* @__PURE__ */ jsx23(
10105
+ showApply && /* @__PURE__ */ jsx24(
9508
10106
  "button",
9509
10107
  {
9510
10108
  onClick: handleApply,
@@ -9527,7 +10125,7 @@ function DeviceConfigPanelImpl({
9527
10125
  children: binding.state.isSaving ? "Applying..." : "Apply"
9528
10126
  }
9529
10127
  ),
9530
- showSave && /* @__PURE__ */ jsx23(
10128
+ showSave && /* @__PURE__ */ jsx24(
9531
10129
  "button",
9532
10130
  {
9533
10131
  onClick: handleSave,
@@ -9550,7 +10148,7 @@ function DeviceConfigPanelImpl({
9550
10148
  children: binding.state.isSaving ? "Saving..." : "Save"
9551
10149
  }
9552
10150
  ),
9553
- showReset && /* @__PURE__ */ jsx23(
10151
+ showReset && /* @__PURE__ */ jsx24(
9554
10152
  "button",
9555
10153
  {
9556
10154
  onClick: handleReset,
@@ -9573,7 +10171,7 @@ function DeviceConfigPanelImpl({
9573
10171
  children: "Reset"
9574
10172
  }
9575
10173
  ),
9576
- showCancel && /* @__PURE__ */ jsx23(
10174
+ showCancel && /* @__PURE__ */ jsx24(
9577
10175
  "button",
9578
10176
  {
9579
10177
  onClick: handleCancel,
@@ -9600,7 +10198,7 @@ function DeviceConfigPanelImpl({
9600
10198
  }
9601
10199
  )
9602
10200
  ] }),
9603
- binding.state.lastSaveResult && showToast && showBuiltInToast && /* @__PURE__ */ jsx23(
10201
+ binding.state.lastSaveResult && showToast && showBuiltInToast && /* @__PURE__ */ jsx24(
9604
10202
  "div",
9605
10203
  {
9606
10204
  style: {
@@ -9622,10 +10220,10 @@ function DeviceConfigPanelImpl({
9622
10220
  )
9623
10221
  ] });
9624
10222
  }
9625
- var DeviceConfigPanel = React15.memo(DeviceConfigPanelImpl);
10223
+ var DeviceConfigPanel = React16.memo(DeviceConfigPanelImpl);
9626
10224
 
9627
10225
  // src/diagram/hooks/useSimulation.ts
9628
- import { useEffect as useEffect14 } from "react";
10226
+ import { useEffect as useEffect15 } from "react";
9629
10227
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9630
10228
  const {
9631
10229
  interval = 2e3,
@@ -9636,7 +10234,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9636
10234
  historyLength = 10,
9637
10235
  generateValue
9638
10236
  } = options;
9639
- useEffect14(() => {
10237
+ useEffect15(() => {
9640
10238
  if (!enabled) return;
9641
10239
  const intervalId = setInterval(() => {
9642
10240
  const updates = [];
@@ -9686,6 +10284,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9686
10284
  }
9687
10285
  export {
9688
10286
  Button,
10287
+ COLOR_PRESETS,
9689
10288
  CardHeader,
9690
10289
  CardUnit,
9691
10290
  CardValue,
@@ -9704,6 +10303,8 @@ export {
9704
10303
  FooterPanel,
9705
10304
  FullscreenContainer,
9706
10305
  FullscreenWorkspace,
10306
+ ISA_UNKNOWN,
10307
+ ISA_UNKNOWN_DARK,
9707
10308
  LeftPanel,
9708
10309
  LeftToggleButton,
9709
10310
  LegacyValueEntry,
@@ -9715,6 +10316,7 @@ export {
9715
10316
  PanelTabs,
9716
10317
  RightPanel,
9717
10318
  RightToggleButton,
10319
+ STATUS_LED_APPEARANCE,
9718
10320
  SVGArea,
9719
10321
  SVGLockedOverlay,
9720
10322
  ScrollableSVGWrapper,
@@ -9725,6 +10327,9 @@ export {
9725
10327
  ThemeProvider,
9726
10328
  ThemeToggle,
9727
10329
  TrendLine,
10330
+ UNKNOWN_GLYPH_RING_DASH,
10331
+ UNKNOWN_STATUS_GLYPH_COLOR_VAR,
10332
+ UnknownStatusGlyph,
9728
10333
  ValueEntry,
9729
10334
  ZoomButton,
9730
10335
  ZoomControls,
@@ -9734,16 +10339,22 @@ export {
9734
10339
  exampleDiagram,
9735
10340
  exportDiagram,
9736
10341
  generateRoutePoints,
10342
+ getAppearanceDefaults,
10343
+ getAppearanceSchema,
9737
10344
  getAvailableSymbols,
9738
10345
  getPortWorldPosition,
9739
10346
  getSymbolDefinition,
9740
10347
  importDiagram,
9741
10348
  mockSymbolLibrary,
9742
10349
  nodeHasPort,
10350
+ normalizeDeviceStatus,
9743
10351
  overlayStyles,
10352
+ readAppearanceValue,
9744
10353
  registerSymbol,
9745
10354
  registerSymbols,
10355
+ resolvePresetColor,
9746
10356
  resolveStatusColor,
10357
+ useBooleanIndicators,
9747
10358
  useDeviceConfigs,
9748
10359
  useDeviceControls,
9749
10360
  useDrag,
@@ -9754,6 +10365,7 @@ export {
9754
10365
  useTelemetryStatus,
9755
10366
  useTheme,
9756
10367
  useViewBox,
9757
- validateDiagram
10368
+ validateDiagram,
10369
+ writeAppearanceValue
9758
10370
  };
9759
10371
  //# sourceMappingURL=index.js.map