@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.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Button: () => Button,
34
+ COLOR_PRESETS: () => COLOR_PRESETS,
34
35
  CardHeader: () => CardHeader,
35
36
  CardUnit: () => CardUnit,
36
37
  CardValue: () => CardValue,
@@ -49,6 +50,8 @@ __export(index_exports, {
49
50
  FooterPanel: () => FooterPanel,
50
51
  FullscreenContainer: () => FullscreenContainer,
51
52
  FullscreenWorkspace: () => FullscreenWorkspace,
53
+ ISA_UNKNOWN: () => ISA_UNKNOWN,
54
+ ISA_UNKNOWN_DARK: () => ISA_UNKNOWN_DARK,
52
55
  LeftPanel: () => LeftPanel,
53
56
  LeftToggleButton: () => LeftToggleButton,
54
57
  LegacyValueEntry: () => LegacyValueEntry,
@@ -60,6 +63,7 @@ __export(index_exports, {
60
63
  PanelTabs: () => PanelTabs,
61
64
  RightPanel: () => RightPanel,
62
65
  RightToggleButton: () => RightToggleButton,
66
+ STATUS_LED_APPEARANCE: () => STATUS_LED_APPEARANCE,
63
67
  SVGArea: () => SVGArea,
64
68
  SVGLockedOverlay: () => SVGLockedOverlay,
65
69
  ScrollableSVGWrapper: () => ScrollableSVGWrapper,
@@ -70,6 +74,9 @@ __export(index_exports, {
70
74
  ThemeProvider: () => ThemeProvider,
71
75
  ThemeToggle: () => ThemeToggle,
72
76
  TrendLine: () => TrendLine,
77
+ UNKNOWN_GLYPH_RING_DASH: () => UNKNOWN_GLYPH_RING_DASH,
78
+ UNKNOWN_STATUS_GLYPH_COLOR_VAR: () => UNKNOWN_STATUS_GLYPH_COLOR_VAR,
79
+ UnknownStatusGlyph: () => UnknownStatusGlyph,
73
80
  ValueEntry: () => ValueEntry,
74
81
  ZoomButton: () => ZoomButton,
75
82
  ZoomControls: () => ZoomControls,
@@ -79,16 +86,22 @@ __export(index_exports, {
79
86
  exampleDiagram: () => exampleDiagram,
80
87
  exportDiagram: () => exportDiagram,
81
88
  generateRoutePoints: () => generateRoutePoints,
89
+ getAppearanceDefaults: () => getAppearanceDefaults,
90
+ getAppearanceSchema: () => getAppearanceSchema,
82
91
  getAvailableSymbols: () => getAvailableSymbols,
83
92
  getPortWorldPosition: () => getPortWorldPosition,
84
93
  getSymbolDefinition: () => getSymbolDefinition,
85
94
  importDiagram: () => importDiagram,
86
95
  mockSymbolLibrary: () => mockSymbolLibrary,
87
96
  nodeHasPort: () => nodeHasPort,
97
+ normalizeDeviceStatus: () => normalizeDeviceStatus,
88
98
  overlayStyles: () => overlayStyles,
99
+ readAppearanceValue: () => readAppearanceValue,
89
100
  registerSymbol: () => registerSymbol,
90
101
  registerSymbols: () => registerSymbols,
102
+ resolvePresetColor: () => resolvePresetColor,
91
103
  resolveStatusColor: () => resolveStatusColor,
104
+ useBooleanIndicators: () => useBooleanIndicators,
92
105
  useDeviceConfigs: () => useDeviceConfigs,
93
106
  useDeviceControls: () => useDeviceControls,
94
107
  useDrag: () => useDrag,
@@ -99,7 +112,8 @@ __export(index_exports, {
99
112
  useTelemetryStatus: () => useTelemetryStatus,
100
113
  useTheme: () => useTheme,
101
114
  useViewBox: () => useViewBox,
102
- validateDiagram: () => validateDiagram
115
+ validateDiagram: () => validateDiagram,
116
+ writeAppearanceValue: () => writeAppearanceValue
103
117
  });
104
118
  module.exports = __toCommonJS(index_exports);
105
119
 
@@ -372,6 +386,16 @@ var ValueEntry = ({
372
386
  var import_react3 = require("react");
373
387
 
374
388
  // src/theme/statusColors.ts
389
+ var ISA_UNKNOWN = {
390
+ fg: "#4f4f8a",
391
+ bg: "#ebebf5",
392
+ border: "#8f8fc2"
393
+ };
394
+ var ISA_UNKNOWN_DARK = {
395
+ fg: "#b9bbdb",
396
+ bg: "#23263a",
397
+ border: "#6f74a3"
398
+ };
375
399
  var DEFAULT_STATUS_COLORS = {
376
400
  normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
377
401
  warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
@@ -379,7 +403,8 @@ var DEFAULT_STATUS_COLORS = {
379
403
  fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
380
404
  off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
381
405
  starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
382
- stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
406
+ stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
407
+ unknown: { fill: ISA_UNKNOWN.bg, stroke: ISA_UNKNOWN.border, bg: ISA_UNKNOWN.bg, text: ISA_UNKNOWN.fg, border: ISA_UNKNOWN.border }
383
408
  };
384
409
  var FORCED_MARK_COLOR = {
385
410
  fill: "#ede9fe",
@@ -392,9 +417,12 @@ var STATUS_ALIASES = {
392
417
  running: "normal",
393
418
  stopped: "off"
394
419
  };
395
- function resolveStatusColor(status, customMap) {
420
+ function normalizeDeviceStatus(status) {
396
421
  const raw = (status || "off").toLowerCase();
397
- const key = STATUS_ALIASES[raw] ?? raw;
422
+ return STATUS_ALIASES[raw] ?? raw;
423
+ }
424
+ function resolveStatusColor(status, customMap) {
425
+ const key = normalizeDeviceStatus(status);
398
426
  const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
399
427
  const overrides = customMap?.[key];
400
428
  if (!overrides) return defaults;
@@ -834,7 +862,79 @@ var ControlPanel = ({
834
862
  };
835
863
 
836
864
  // src/components/StatusIndicator/StatusIndicator.tsx
865
+ var import_react5 = __toESM(require("react"), 1);
866
+
867
+ // src/theme/unknownStatusGlyph.tsx
837
868
  var import_jsx_runtime6 = require("react/jsx-runtime");
869
+ var UNKNOWN_STATUS_GLYPH_COLOR_VAR = "--ui-status-indicator-unknown-glyph-color";
870
+ var UNKNOWN_GLYPH_STROKE_WEIGHTS = {
871
+ small: { ring: 4.5, ringDash: "7 6", mark: 5, dot: 2.9 },
872
+ medium: { ring: 4, ringDash: "6.5 5.5", mark: 4.5, dot: 2.7 },
873
+ large: { ring: 3.5, ringDash: "6.5 5.5", mark: 4, dot: 2.5 }
874
+ };
875
+ var UNKNOWN_GLYPH_RING_DASH = UNKNOWN_GLYPH_STROKE_WEIGHTS.medium.ringDash;
876
+ var UNKNOWN_GLYPH_FILL_STYLE = {
877
+ position: "absolute",
878
+ inset: 0,
879
+ width: "100%",
880
+ height: "100%"
881
+ };
882
+ function unknownGlyphHostStyle(isUnknown, style) {
883
+ return isUnknown ? { position: "relative", ...style } : style;
884
+ }
885
+ function UnknownStatusGlyph({
886
+ size = "medium",
887
+ counterRotate = false,
888
+ style,
889
+ ...rest
890
+ }) {
891
+ const weights = UNKNOWN_GLYPH_STROKE_WEIGHTS[size];
892
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
893
+ "svg",
894
+ {
895
+ viewBox: "0 0 48 48",
896
+ "aria-hidden": "true",
897
+ focusable: "false",
898
+ style: {
899
+ display: "block",
900
+ color: `var(${UNKNOWN_STATUS_GLYPH_COLOR_VAR}, currentColor)`,
901
+ transform: counterRotate ? "rotate(-45deg)" : void 0,
902
+ ...style
903
+ },
904
+ ...rest,
905
+ children: [
906
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
907
+ "circle",
908
+ {
909
+ cx: 24,
910
+ cy: 24,
911
+ r: 20,
912
+ fill: "none",
913
+ stroke: "currentColor",
914
+ strokeDasharray: weights.ringDash,
915
+ strokeLinecap: "round",
916
+ strokeWidth: weights.ring
917
+ }
918
+ ),
919
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
920
+ "path",
921
+ {
922
+ d: "M18.4 18.6a5.8 5.8 0 1 1 6.4 8.1v3.1",
923
+ fill: "none",
924
+ stroke: "currentColor",
925
+ strokeLinecap: "round",
926
+ strokeLinejoin: "round",
927
+ strokeWidth: weights.mark
928
+ }
929
+ ),
930
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: 24.6, cy: 35.4, r: weights.dot, fill: "currentColor" })
931
+ ]
932
+ }
933
+ );
934
+ }
935
+
936
+ // src/components/StatusIndicator/StatusIndicator.tsx
937
+ var import_jsx_runtime7 = require("react/jsx-runtime");
838
938
  var getStatusDescription = (status) => {
839
939
  switch (status) {
840
940
  case "alarm":
@@ -849,10 +949,23 @@ var getStatusDescription = (status) => {
849
949
  return "Operating normally";
850
950
  case "off":
851
951
  return "Equipment is off";
952
+ case "unknown":
953
+ return "Device state cannot be confirmed. Check the link or the wiring.";
852
954
  default:
853
955
  return "";
854
956
  }
855
957
  };
958
+ var visuallyHiddenStyle = {
959
+ position: "absolute",
960
+ width: "1px",
961
+ height: "1px",
962
+ padding: 0,
963
+ margin: "-1px",
964
+ overflow: "hidden",
965
+ clip: "rect(0, 0, 0, 0)",
966
+ whiteSpace: "nowrap",
967
+ border: 0
968
+ };
856
969
  var StatusIndicator = ({
857
970
  status,
858
971
  size = "medium",
@@ -861,17 +974,29 @@ var StatusIndicator = ({
861
974
  labelPosition = "right",
862
975
  shape = "circle",
863
976
  led = true,
977
+ visible = true,
864
978
  forced = false,
865
979
  realValue,
866
980
  className = "",
981
+ style,
982
+ "aria-describedby": consumerDescribedBy,
983
+ children,
867
984
  ...props
868
985
  }) => {
986
+ const generatedDescriptionId = `${import_react5.default.useId()}-status-description`;
987
+ if (!visible) {
988
+ return null;
989
+ }
990
+ const describedBy = consumerDescribedBy ? `${consumerDescribedBy} ${generatedDescriptionId}` : generatedDescriptionId;
991
+ const description = getStatusDescription(status);
869
992
  const baseClass = "ui-status-indicator";
870
993
  const statusClass = status ? `ui-status-indicator--${status}` : "";
871
994
  const sizeClass = size ? `ui-status-indicator--${size}` : "";
872
995
  const shapeClass = shape ? `ui-status-indicator--${shape}` : "";
873
996
  const animationClass = animation ? `ui-status-indicator--${animation}` : "";
874
997
  const ledClass = led ? "ui-status-indicator--led" : "";
998
+ const isUnknown = status === "unknown";
999
+ const indicatorStyle = unknownGlyphHostStyle(isUnknown, style);
875
1000
  const getContainerLayout = () => {
876
1001
  switch (labelPosition) {
877
1002
  case "left":
@@ -888,19 +1013,20 @@ var StatusIndicator = ({
888
1013
  };
889
1014
  const forcedClass = forced ? "ui-status-indicator--forced" : "";
890
1015
  const hasRealValue = forced && realValue !== void 0;
891
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: getContainerLayout(), children: [
892
- label && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-label", children: label }),
893
- hasRealValue && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ui-status-indicator-real-value", children: [
1016
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: getContainerLayout(), children: [
1017
+ label && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ui-status-indicator-label", children: label }),
1018
+ hasRealValue && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ui-status-indicator-real-value", children: [
894
1019
  "REAL ",
895
1020
  realValue
896
1021
  ] }),
897
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
898
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1022
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
1023
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
899
1024
  "span",
900
1025
  {
901
1026
  role: "status",
902
1027
  "aria-label": label || `Status: ${status}`,
903
- "aria-description": getStatusDescription(status),
1028
+ "aria-description": description,
1029
+ "aria-describedby": describedBy,
904
1030
  "data-status": status,
905
1031
  "data-forced": forced,
906
1032
  className: `
@@ -913,13 +1039,26 @@ var StatusIndicator = ({
913
1039
  ${forcedClass}
914
1040
  ${className}
915
1041
  `.replace(/\s+/g, " ").trim(),
916
- ...props
1042
+ style: indicatorStyle,
1043
+ ...props,
1044
+ children: [
1045
+ isUnknown && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1046
+ UnknownStatusGlyph,
1047
+ {
1048
+ size,
1049
+ counterRotate: shape === "diamond",
1050
+ style: UNKNOWN_GLYPH_FILL_STYLE
1051
+ }
1052
+ ),
1053
+ children,
1054
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { id: generatedDescriptionId, style: visuallyHiddenStyle, children: description })
1055
+ ]
917
1056
  }
918
1057
  ),
919
- forced && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-forced-badge", "aria-label": "Forced", children: "F" })
1058
+ forced && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ui-status-indicator-forced-badge", "aria-label": "Forced", children: "F" })
920
1059
  ] }),
921
- label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-label", children: label }),
922
- hasRealValue && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ui-status-indicator-real-value", children: [
1060
+ label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ui-status-indicator-label", children: label }),
1061
+ hasRealValue && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ui-status-indicator-real-value", children: [
923
1062
  "REAL ",
924
1063
  realValue
925
1064
  ] })
@@ -927,7 +1066,7 @@ var StatusIndicator = ({
927
1066
  };
928
1067
 
929
1068
  // src/components/NodeControlsPanel/NodeControlsPanel.tsx
930
- var import_jsx_runtime7 = require("react/jsx-runtime");
1069
+ var import_jsx_runtime8 = require("react/jsx-runtime");
931
1070
  var NodeControlsPanel = ({
932
1071
  config,
933
1072
  onClose,
@@ -1000,8 +1139,8 @@ var NodeControlsPanel = ({
1000
1139
  overflowY: "auto",
1001
1140
  flex: 1
1002
1141
  };
1003
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1004
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1142
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1143
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1005
1144
  "div",
1006
1145
  {
1007
1146
  style: {
@@ -1014,16 +1153,16 @@ var NodeControlsPanel = ({
1014
1153
  onClick: onClose
1015
1154
  }
1016
1155
  ),
1017
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1156
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1018
1157
  "div",
1019
1158
  {
1020
1159
  className: `node-controls-panel ${touchClass} ${className}`.trim(),
1021
1160
  style: containerStyle,
1022
1161
  ...props,
1023
1162
  children: [
1024
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: headerStyle, children: [
1025
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: titleStyle, children: config.title }),
1026
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1163
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: headerStyle, children: [
1164
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { style: titleStyle, children: config.title }),
1165
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1027
1166
  "button",
1028
1167
  {
1029
1168
  style: closeButtonStyle,
@@ -1041,7 +1180,7 @@ var NodeControlsPanel = ({
1041
1180
  }
1042
1181
  )
1043
1182
  ] }),
1044
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: contentStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1183
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: contentStyle, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1045
1184
  ControlPanel,
1046
1185
  {
1047
1186
  title: config.nodeId,
@@ -1070,7 +1209,7 @@ var NodeControlsPanel = ({
1070
1209
  ]
1071
1210
  }
1072
1211
  ),
1073
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
1212
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `
1074
1213
  @keyframes slideUp {
1075
1214
  from {
1076
1215
  transform: translateY(100%);
@@ -1116,8 +1255,8 @@ var NodeControlsPanel = ({
1116
1255
  };
1117
1256
 
1118
1257
  // src/components/DeviceControlPanel/DeviceControlPanel.tsx
1119
- var import_react5 = __toESM(require("react"), 1);
1120
- var import_jsx_runtime8 = require("react/jsx-runtime");
1258
+ var import_react6 = __toESM(require("react"), 1);
1259
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1121
1260
  function DeviceControlPanelImpl({
1122
1261
  binding,
1123
1262
  onClose,
@@ -1145,11 +1284,11 @@ function DeviceControlPanelImpl({
1145
1284
  showBuiltInToast = true,
1146
1285
  embedded = false
1147
1286
  }) {
1148
- const [localParameterValues, setLocalParameterValues] = (0, import_react5.useState)({});
1149
- const [showModeModal, setShowModeModal] = (0, import_react5.useState)(false);
1150
- const [pendingMode, setPendingMode] = (0, import_react5.useState)(null);
1151
- const [showToast, setShowToast] = (0, import_react5.useState)(true);
1152
- (0, import_react5.useEffect)(() => {
1287
+ const [localParameterValues, setLocalParameterValues] = (0, import_react6.useState)({});
1288
+ const [showModeModal, setShowModeModal] = (0, import_react6.useState)(false);
1289
+ const [pendingMode, setPendingMode] = (0, import_react6.useState)(null);
1290
+ const [showToast, setShowToast] = (0, import_react6.useState)(true);
1291
+ (0, import_react6.useEffect)(() => {
1153
1292
  if (binding) {
1154
1293
  const currentParams2 = binding.parameters[binding.state.currentMode] || [];
1155
1294
  const values = {};
@@ -1159,7 +1298,7 @@ function DeviceControlPanelImpl({
1159
1298
  setLocalParameterValues(values);
1160
1299
  }
1161
1300
  }, [binding, binding?.state.currentMode]);
1162
- (0, import_react5.useEffect)(() => {
1301
+ (0, import_react6.useEffect)(() => {
1163
1302
  if (binding?.state.lastCommandResult) {
1164
1303
  setShowToast(true);
1165
1304
  const timer = setTimeout(() => {
@@ -1197,6 +1336,7 @@ function DeviceControlPanelImpl({
1197
1336
  const selectedColor = "#b4d0fe";
1198
1337
  const statusEntry = resolveStatusColor(binding.state.status, binding.theme?.statusColors);
1199
1338
  const statusColor = statusEntry.bg;
1339
+ const isUnknownStatus = normalizeDeviceStatus(binding.state.status) === "unknown";
1200
1340
  const handleModeChange = (mode) => {
1201
1341
  onModeChange?.(mode);
1202
1342
  };
@@ -1251,9 +1391,9 @@ function DeviceControlPanelImpl({
1251
1391
  ...binding.display?.style
1252
1392
  };
1253
1393
  const titleText = binding.display?.title || binding.deviceName || binding.tag || binding.nodeId;
1254
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1255
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: panelStyle, className: binding.display?.className, children: [
1256
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1394
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1395
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: panelStyle, className: binding.display?.className, children: [
1396
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1257
1397
  "div",
1258
1398
  {
1259
1399
  style: {
@@ -1265,8 +1405,8 @@ function DeviceControlPanelImpl({
1265
1405
  borderBottom: `2px solid ${borderColor}`
1266
1406
  },
1267
1407
  children: [
1268
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
1269
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1408
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
1409
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1270
1410
  "h3",
1271
1411
  {
1272
1412
  style: {
@@ -1286,7 +1426,7 @@ function DeviceControlPanelImpl({
1286
1426
  children: titleText.length > 25 ? titleText.slice(0, 25) + "..." : titleText
1287
1427
  }
1288
1428
  ),
1289
- binding.deviceType && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1429
+ binding.deviceType && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1290
1430
  "div",
1291
1431
  {
1292
1432
  style: {
@@ -1299,8 +1439,8 @@ function DeviceControlPanelImpl({
1299
1439
  }
1300
1440
  )
1301
1441
  ] }),
1302
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1303
- showConfigButton && onOpenConfig && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1442
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1443
+ showConfigButton && onOpenConfig && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1304
1444
  "button",
1305
1445
  {
1306
1446
  onClick: onOpenConfig,
@@ -1321,7 +1461,7 @@ function DeviceControlPanelImpl({
1321
1461
  children: "\u2699"
1322
1462
  }
1323
1463
  ),
1324
- onUndock && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1464
+ onUndock && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1325
1465
  "button",
1326
1466
  {
1327
1467
  onClick: onUndock,
@@ -1342,7 +1482,7 @@ function DeviceControlPanelImpl({
1342
1482
  children: undockIcon
1343
1483
  }
1344
1484
  ),
1345
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1485
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1346
1486
  "button",
1347
1487
  {
1348
1488
  onClick: onClose,
@@ -1365,7 +1505,7 @@ function DeviceControlPanelImpl({
1365
1505
  ]
1366
1506
  }
1367
1507
  ),
1368
- binding.state.status && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1508
+ binding.state.status && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1369
1509
  "div",
1370
1510
  {
1371
1511
  style: {
@@ -1379,7 +1519,7 @@ function DeviceControlPanelImpl({
1379
1519
  marginBottom: spacing.marginBottom
1380
1520
  },
1381
1521
  children: [
1382
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1522
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1383
1523
  "div",
1384
1524
  {
1385
1525
  style: {
@@ -1387,12 +1527,17 @@ function DeviceControlPanelImpl({
1387
1527
  height: "10px",
1388
1528
  borderRadius: "50%",
1389
1529
  backgroundColor: statusColor,
1390
- border: "1px solid #333"
1391
- }
1530
+ border: "1px solid #333",
1531
+ // The shared helper positions this dot only while it shows
1532
+ // the unknown marker. The glyph then fills the dot with an
1533
+ // absolute inset, the same pattern StatusIndicator uses.
1534
+ ...unknownGlyphHostStyle(isUnknownStatus)
1535
+ },
1536
+ children: isUnknownStatus && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(UnknownStatusGlyph, { size: "small", style: UNKNOWN_GLYPH_FILL_STYLE })
1392
1537
  }
1393
1538
  ),
1394
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { flex: 1 }, children: [
1395
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1539
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: 1 }, children: [
1540
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1396
1541
  "div",
1397
1542
  {
1398
1543
  style: {
@@ -1406,7 +1551,7 @@ function DeviceControlPanelImpl({
1406
1551
  children: binding.state.status
1407
1552
  }
1408
1553
  ),
1409
- binding.state.statusMessage && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1554
+ binding.state.statusMessage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1410
1555
  "div",
1411
1556
  {
1412
1557
  style: {
@@ -1419,28 +1564,36 @@ function DeviceControlPanelImpl({
1419
1564
  }
1420
1565
  )
1421
1566
  ] }),
1422
- binding.state.isRunning !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1567
+ binding.state.isRunning !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1423
1568
  "div",
1424
1569
  {
1425
1570
  style: {
1426
1571
  padding: "4px 10px",
1427
- backgroundColor: binding.state.isRunning ? selectedColor : surfaceColor,
1428
- border: `1px solid ${borderColor}`,
1572
+ // This badge shows the last published isRunning value. It
1573
+ // never re-derives that value here. When status is
1574
+ // unknown, the value may be stale. Muted colors carry that
1575
+ // warning alongside the word, so color is not the only
1576
+ // channel and the wording is not the only channel either.
1577
+ backgroundColor: isUnknownStatus ? statusEntry.bg : binding.state.isRunning ? selectedColor : surfaceColor,
1578
+ border: `1px solid ${isUnknownStatus ? statusEntry.border : borderColor}`,
1429
1579
  borderRadius: "2px",
1430
1580
  fontSize: "10px",
1431
1581
  fontWeight: 600,
1432
1582
  fontFamily: "Arial, sans-serif",
1433
1583
  letterSpacing: "0.5px",
1434
- color: textColor
1584
+ color: isUnknownStatus ? statusEntry.text : textColor
1435
1585
  },
1436
- children: binding.state.isRunning ? "RUNNING" : "STOPPED"
1586
+ children: [
1587
+ binding.state.isRunning ? "RUNNING" : "STOPPED",
1588
+ isUnknownStatus && " (unconfirmed)"
1589
+ ]
1437
1590
  }
1438
1591
  )
1439
1592
  ]
1440
1593
  }
1441
1594
  ),
1442
- binding.display?.showModeSelector !== false && binding.modes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginBottom: "16px" }, children: [
1443
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1595
+ binding.display?.showModeSelector !== false && binding.modes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { marginBottom: "16px" }, children: [
1596
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1444
1597
  "label",
1445
1598
  {
1446
1599
  style: {
@@ -1456,7 +1609,7 @@ function DeviceControlPanelImpl({
1456
1609
  children: "Mode"
1457
1610
  }
1458
1611
  ),
1459
- modeSelectionStyle === "dropdown" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1612
+ modeSelectionStyle === "dropdown" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1460
1613
  "select",
1461
1614
  {
1462
1615
  value: currentMode,
@@ -1477,10 +1630,10 @@ function DeviceControlPanelImpl({
1477
1630
  paddingRight: "12px",
1478
1631
  cursor: "pointer"
1479
1632
  },
1480
- children: binding.modes.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: mode.value, disabled: mode.enabled === false, children: mode.label }, mode.value))
1633
+ children: binding.modes.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: mode.value, disabled: mode.enabled === false, children: mode.label }, mode.value))
1481
1634
  }
1482
- ) : modeSelectionStyle === "modal" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1483
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1635
+ ) : modeSelectionStyle === "modal" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1636
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1484
1637
  "div",
1485
1638
  {
1486
1639
  onClick: () => {
@@ -1503,12 +1656,12 @@ function DeviceControlPanelImpl({
1503
1656
  alignItems: "center"
1504
1657
  },
1505
1658
  children: [
1506
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: binding.modes.find((m) => m.value === currentMode)?.label || currentMode }),
1507
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "20px", marginLeft: "8px" }, children: "\u25BC" })
1659
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: binding.modes.find((m) => m.value === currentMode)?.label || currentMode }),
1660
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "20px", marginLeft: "8px" }, children: "\u25BC" })
1508
1661
  ]
1509
1662
  }
1510
1663
  ),
1511
- showModeModal && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1664
+ showModeModal && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1512
1665
  "div",
1513
1666
  {
1514
1667
  style: {
@@ -1527,7 +1680,7 @@ function DeviceControlPanelImpl({
1527
1680
  setShowModeModal(false);
1528
1681
  setPendingMode(null);
1529
1682
  },
1530
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1683
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1531
1684
  "div",
1532
1685
  {
1533
1686
  onClick: (e) => e.stopPropagation(),
@@ -1540,7 +1693,7 @@ function DeviceControlPanelImpl({
1540
1693
  border: `2px solid ${borderColor}`
1541
1694
  },
1542
1695
  children: [
1543
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1696
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1544
1697
  "h3",
1545
1698
  {
1546
1699
  style: {
@@ -1553,7 +1706,7 @@ function DeviceControlPanelImpl({
1553
1706
  children: "Select Mode"
1554
1707
  }
1555
1708
  ),
1556
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1709
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1557
1710
  "div",
1558
1711
  {
1559
1712
  style: {
@@ -1567,7 +1720,7 @@ function DeviceControlPanelImpl({
1567
1720
  children: binding.display?.title || binding.deviceName || binding.tag || binding.nodeId
1568
1721
  }
1569
1722
  ),
1570
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1723
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1571
1724
  "select",
1572
1725
  {
1573
1726
  value: pendingMode || currentMode,
@@ -1589,7 +1742,7 @@ function DeviceControlPanelImpl({
1589
1742
  paddingRight: "12px",
1590
1743
  cursor: "pointer"
1591
1744
  },
1592
- children: binding.modes.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1745
+ children: binding.modes.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1593
1746
  "option",
1594
1747
  {
1595
1748
  value: mode.value,
@@ -1604,7 +1757,7 @@ function DeviceControlPanelImpl({
1604
1757
  const selectedMode = binding.modes.find(
1605
1758
  (m) => m.value === (pendingMode || currentMode)
1606
1759
  );
1607
- return selectedMode?.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1760
+ return selectedMode?.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1608
1761
  "div",
1609
1762
  {
1610
1763
  style: {
@@ -1622,8 +1775,8 @@ function DeviceControlPanelImpl({
1622
1775
  }
1623
1776
  ) : null;
1624
1777
  })(),
1625
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "12px" }, children: [
1626
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1778
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: "12px" }, children: [
1779
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1627
1780
  "button",
1628
1781
  {
1629
1782
  onClick: () => {
@@ -1651,7 +1804,7 @@ function DeviceControlPanelImpl({
1651
1804
  children: "Confirm"
1652
1805
  }
1653
1806
  ),
1654
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1807
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1655
1808
  "button",
1656
1809
  {
1657
1810
  onClick: () => {
@@ -1681,7 +1834,7 @@ function DeviceControlPanelImpl({
1681
1834
  )
1682
1835
  }
1683
1836
  )
1684
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1837
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1685
1838
  "div",
1686
1839
  {
1687
1840
  style: {
@@ -1697,7 +1850,7 @@ function DeviceControlPanelImpl({
1697
1850
  children: binding.modes.map((mode) => {
1698
1851
  const isSelected = mode.value === currentMode;
1699
1852
  const isEnabled = mode.enabled !== false;
1700
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1853
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1701
1854
  "button",
1702
1855
  {
1703
1856
  onClick: () => isEnabled && handleModeChange(mode.value),
@@ -1727,8 +1880,8 @@ function DeviceControlPanelImpl({
1727
1880
  }
1728
1881
  )
1729
1882
  ] }),
1730
- currentParams.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginBottom: spacing.marginBottom }, children: [
1731
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1883
+ currentParams.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { marginBottom: spacing.marginBottom }, children: [
1884
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1732
1885
  "label",
1733
1886
  {
1734
1887
  style: {
@@ -1744,7 +1897,7 @@ function DeviceControlPanelImpl({
1744
1897
  children: "Parameters"
1745
1898
  }
1746
1899
  ),
1747
- currentParams.map((param) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1900
+ currentParams.map((param) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1748
1901
  "div",
1749
1902
  {
1750
1903
  style: {
@@ -1755,7 +1908,7 @@ function DeviceControlPanelImpl({
1755
1908
  borderRadius: "6px"
1756
1909
  },
1757
1910
  children: [
1758
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1911
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1759
1912
  "div",
1760
1913
  {
1761
1914
  style: {
@@ -1764,7 +1917,7 @@ function DeviceControlPanelImpl({
1764
1917
  marginBottom: spacing.gap
1765
1918
  },
1766
1919
  children: [
1767
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1920
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1768
1921
  "span",
1769
1922
  {
1770
1923
  style: {
@@ -1780,7 +1933,7 @@ function DeviceControlPanelImpl({
1780
1933
  },
1781
1934
  children: [
1782
1935
  param.label,
1783
- param.forced?.active && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1936
+ param.forced?.active && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1784
1937
  "span",
1785
1938
  {
1786
1939
  "aria-label": "Forced",
@@ -1803,7 +1956,7 @@ function DeviceControlPanelImpl({
1803
1956
  ]
1804
1957
  }
1805
1958
  ),
1806
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1959
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1807
1960
  "span",
1808
1961
  {
1809
1962
  style: {
@@ -1815,14 +1968,14 @@ function DeviceControlPanelImpl({
1815
1968
  children: [
1816
1969
  localParameterValues[param.id] ?? param.value,
1817
1970
  " ",
1818
- param.unit && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { color: mutedTextColor, fontSize: "10px", fontWeight: 600 }, children: param.unit })
1971
+ param.unit && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: mutedTextColor, fontSize: "10px", fontWeight: 600 }, children: param.unit })
1819
1972
  ]
1820
1973
  }
1821
1974
  )
1822
1975
  ]
1823
1976
  }
1824
1977
  ),
1825
- param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1978
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1826
1979
  "div",
1827
1980
  {
1828
1981
  style: {
@@ -1843,7 +1996,7 @@ function DeviceControlPanelImpl({
1843
1996
  ]
1844
1997
  }
1845
1998
  ),
1846
- param.type === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1999
+ param.type === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1847
2000
  "button",
1848
2001
  {
1849
2002
  onClick: () => {
@@ -1868,7 +2021,7 @@ function DeviceControlPanelImpl({
1868
2021
  },
1869
2022
  children: localParameterValues[param.id] ? "ON" : "OFF"
1870
2023
  }
1871
- ) : param.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2024
+ ) : param.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1872
2025
  "select",
1873
2026
  {
1874
2027
  value: localParameterValues[param.id] || param.value,
@@ -1889,12 +2042,12 @@ function DeviceControlPanelImpl({
1889
2042
  fontFamily: "monospace",
1890
2043
  cursor: "pointer"
1891
2044
  },
1892
- children: param.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
2045
+ children: param.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
1893
2046
  }
1894
2047
  ) : onParameterActivate && !param.readOnly ? (
1895
2048
  // Tappable value: defer editing to the consumer's editor (e.g. a
1896
2049
  // numpad dialog). Commit happens via onParameterChange.
1897
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2050
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1898
2051
  "button",
1899
2052
  {
1900
2053
  type: "button",
@@ -1921,7 +2074,7 @@ function DeviceControlPanelImpl({
1921
2074
  ]
1922
2075
  }
1923
2076
  )
1924
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2077
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1925
2078
  "input",
1926
2079
  {
1927
2080
  type: param.type === "number" ? "number" : "text",
@@ -1954,8 +2107,8 @@ function DeviceControlPanelImpl({
1954
2107
  param.id
1955
2108
  ))
1956
2109
  ] }),
1957
- binding.display?.showStartStop !== false && (activeCapabilities?.canStart || activeCapabilities?.canStop) && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom }, children: [
1958
- activeCapabilities?.canStart && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2110
+ binding.display?.showStartStop !== false && (activeCapabilities?.canStart || activeCapabilities?.canStop) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom }, children: [
2111
+ activeCapabilities?.canStart && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1959
2112
  "button",
1960
2113
  {
1961
2114
  onClick: onStart,
@@ -1976,7 +2129,7 @@ function DeviceControlPanelImpl({
1976
2129
  children: startPending ? "STARTING\u2026" : "START"
1977
2130
  }
1978
2131
  ),
1979
- activeCapabilities?.canStop && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2132
+ activeCapabilities?.canStop && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1980
2133
  "button",
1981
2134
  {
1982
2135
  onClick: onStop,
@@ -1998,9 +2151,9 @@ function DeviceControlPanelImpl({
1998
2151
  }
1999
2152
  )
2000
2153
  ] }),
2001
- activeCapabilities?.customActions && activeCapabilities.customActions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: activeCapabilities.customActions.map((action) => {
2154
+ activeCapabilities?.customActions && activeCapabilities.customActions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: activeCapabilities.customActions.map((action) => {
2002
2155
  const actionDisabled = action.disabled === true;
2003
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2156
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2004
2157
  "button",
2005
2158
  {
2006
2159
  onClick: () => {
@@ -2026,7 +2179,7 @@ function DeviceControlPanelImpl({
2026
2179
  transition: "all 0.15s"
2027
2180
  },
2028
2181
  children: [
2029
- action.icon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { marginRight: "6px" }, children: action.icon }),
2182
+ action.icon && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { marginRight: "6px" }, children: action.icon }),
2030
2183
  action.label
2031
2184
  ]
2032
2185
  },
@@ -2034,7 +2187,7 @@ function DeviceControlPanelImpl({
2034
2187
  );
2035
2188
  }) })
2036
2189
  ] }),
2037
- binding.state.lastCommandResult && showToast && showBuiltInToast && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2190
+ binding.state.lastCommandResult && showToast && showBuiltInToast && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2038
2191
  "div",
2039
2192
  {
2040
2193
  style: {
@@ -2058,7 +2211,7 @@ function DeviceControlPanelImpl({
2058
2211
  children: binding.state.lastCommandResult.message
2059
2212
  }
2060
2213
  ),
2061
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `
2214
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("style", { children: `
2062
2215
  @keyframes slideInToast {
2063
2216
  from {
2064
2217
  opacity: 0;
@@ -2090,11 +2243,11 @@ function DeviceControlPanelImpl({
2090
2243
  ` })
2091
2244
  ] });
2092
2245
  }
2093
- var DeviceControlPanel = import_react5.default.memo(DeviceControlPanelImpl);
2246
+ var DeviceControlPanel = import_react6.default.memo(DeviceControlPanelImpl);
2094
2247
 
2095
2248
  // src/components/NumpadDialog/NumpadDialog.tsx
2096
- var import_react6 = require("react");
2097
- var import_jsx_runtime9 = require("react/jsx-runtime");
2249
+ var import_react7 = require("react");
2250
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2098
2251
  var NumpadDialog = ({
2099
2252
  isOpen,
2100
2253
  onClose,
@@ -2108,24 +2261,24 @@ var NumpadDialog = ({
2108
2261
  currentValue = 0,
2109
2262
  touchOptimized = true
2110
2263
  }) => {
2111
- const [inputValue, setInputValue] = (0, import_react6.useState)("");
2112
- const [pristine, setPristine] = (0, import_react6.useState)(true);
2113
- const dialogRef = (0, import_react6.useRef)(null);
2114
- const titleId = (0, import_react6.useId)();
2264
+ const [inputValue, setInputValue] = (0, import_react7.useState)("");
2265
+ const [pristine, setPristine] = (0, import_react7.useState)(true);
2266
+ const dialogRef = (0, import_react7.useRef)(null);
2267
+ const titleId = (0, import_react7.useId)();
2115
2268
  const decimals = precision ?? 1;
2116
- (0, import_react6.useEffect)(() => {
2269
+ (0, import_react7.useEffect)(() => {
2117
2270
  if (isOpen) {
2118
2271
  setInputValue(Number(currentValue).toFixed(decimals));
2119
2272
  setPristine(true);
2120
2273
  }
2121
2274
  }, [isOpen, currentValue, decimals]);
2122
- (0, import_react6.useEffect)(() => {
2275
+ (0, import_react7.useEffect)(() => {
2123
2276
  if (!isOpen) return;
2124
2277
  const previouslyFocused = document.activeElement;
2125
2278
  dialogRef.current?.focus();
2126
2279
  return () => previouslyFocused?.focus?.();
2127
2280
  }, [isOpen]);
2128
- (0, import_react6.useEffect)(() => {
2281
+ (0, import_react7.useEffect)(() => {
2129
2282
  if (!isOpen) return;
2130
2283
  const prevOverflow = document.body.style.overflow;
2131
2284
  document.body.style.overflow = "hidden";
@@ -2133,13 +2286,13 @@ var NumpadDialog = ({
2133
2286
  document.body.style.overflow = prevOverflow;
2134
2287
  };
2135
2288
  }, [isOpen]);
2136
- const handleApply = (0, import_react6.useCallback)(() => {
2289
+ const handleApply = (0, import_react7.useCallback)(() => {
2137
2290
  const n = Number(inputValue);
2138
2291
  if (inputValue === "" || inputValue === "-" || inputValue === "." || inputValue === "-.") return;
2139
2292
  if (isNaN(n) || n < min || n > max) return;
2140
2293
  onApply(Number(n.toFixed(decimals)));
2141
2294
  }, [inputValue, min, max, onApply, decimals]);
2142
- const appendDigit = (0, import_react6.useCallback)(
2295
+ const appendDigit = (0, import_react7.useCallback)(
2143
2296
  (digit) => {
2144
2297
  setInputValue((prev) => {
2145
2298
  const base = pristine ? "" : prev;
@@ -2159,15 +2312,15 @@ var NumpadDialog = ({
2159
2312
  },
2160
2313
  [pristine, decimals]
2161
2314
  );
2162
- const handleBackspace = (0, import_react6.useCallback)(() => {
2315
+ const handleBackspace = (0, import_react7.useCallback)(() => {
2163
2316
  setPristine(false);
2164
2317
  setInputValue((prev) => prev.slice(0, -1));
2165
2318
  }, []);
2166
- const handleClear = (0, import_react6.useCallback)(() => {
2319
+ const handleClear = (0, import_react7.useCallback)(() => {
2167
2320
  setPristine(false);
2168
2321
  setInputValue("");
2169
2322
  }, []);
2170
- const handleToggleSign = (0, import_react6.useCallback)(() => {
2323
+ const handleToggleSign = (0, import_react7.useCallback)(() => {
2171
2324
  if (min >= 0) return;
2172
2325
  setPristine(false);
2173
2326
  setInputValue((prev) => {
@@ -2176,8 +2329,8 @@ var NumpadDialog = ({
2176
2329
  return "-" + prev;
2177
2330
  });
2178
2331
  }, [min]);
2179
- const bsTimer = (0, import_react6.useRef)(null);
2180
- const bsLongFired = (0, import_react6.useRef)(false);
2332
+ const bsTimer = (0, import_react7.useRef)(null);
2333
+ const bsLongFired = (0, import_react7.useRef)(false);
2181
2334
  const clearBsTimer = () => {
2182
2335
  if (bsTimer.current !== null) {
2183
2336
  clearTimeout(bsTimer.current);
@@ -2199,8 +2352,8 @@ var NumpadDialog = ({
2199
2352
  }
2200
2353
  handleBackspace();
2201
2354
  };
2202
- (0, import_react6.useEffect)(() => () => clearBsTimer(), []);
2203
- (0, import_react6.useEffect)(() => {
2355
+ (0, import_react7.useEffect)(() => () => clearBsTimer(), []);
2356
+ (0, import_react7.useEffect)(() => {
2204
2357
  if (!isOpen) return;
2205
2358
  const handler = (e) => {
2206
2359
  if (e.key === "Tab") {
@@ -2298,7 +2451,7 @@ var NumpadDialog = ({
2298
2451
  };
2299
2452
  const reason = belowMin ? `Below minimum of ${min} ${unit}` : aboveMax ? `Above maximum of ${max} ${unit}` : "Type or tap to change the value";
2300
2453
  const backspaceInGrid = min >= 0;
2301
- const BackspaceButton = ({ style, label }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2454
+ const BackspaceButton = ({ style, label }) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2302
2455
  "button",
2303
2456
  {
2304
2457
  type: "button",
@@ -2315,7 +2468,7 @@ var NumpadDialog = ({
2315
2468
  );
2316
2469
  return (
2317
2470
  // Backdrop
2318
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2471
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2319
2472
  "div",
2320
2473
  {
2321
2474
  style: {
@@ -2332,14 +2485,14 @@ var NumpadDialog = ({
2332
2485
  },
2333
2486
  onClick: onClose,
2334
2487
  children: [
2335
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("style", { children: `
2488
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `
2336
2489
  .np-key { transition: background .1s, transform .05s; touch-action: manipulation; }
2337
2490
  .np-key:active:not(:disabled) { background:#e2e8f0; transform: translateY(1px); }
2338
2491
  .np-key:focus-visible, .np-action:focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; }
2339
2492
  @keyframes np-blink { 0%,49%{opacity:1} 50%,100%{opacity:0} }
2340
2493
  .np-caret { display:inline-block; width:2px; height:1.1em; background:#2563eb; animation: np-blink 1s step-end infinite; }
2341
2494
  ` }),
2342
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2495
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2343
2496
  "div",
2344
2497
  {
2345
2498
  ref: dialogRef,
@@ -2360,9 +2513,9 @@ var NumpadDialog = ({
2360
2513
  },
2361
2514
  onClick: (e) => e.stopPropagation(),
2362
2515
  children: [
2363
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "14px 16px 0 16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { id: titleId, style: { margin: 0, fontSize: 15, fontWeight: 700, color: "#0f172a" }, children: deviceName ? `${deviceName} - ${parameterLabel}` : parameterLabel }) }),
2364
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { padding: 16, display: "flex", flexDirection: "column", gap: 10 }, children: [
2365
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2516
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { padding: "14px 16px 0 16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { id: titleId, style: { margin: 0, fontSize: 15, fontWeight: 700, color: "#0f172a" }, children: deviceName ? `${deviceName} - ${parameterLabel}` : parameterLabel }) }),
2517
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { padding: 16, display: "flex", flexDirection: "column", gap: 10 }, children: [
2518
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2366
2519
  "div",
2367
2520
  {
2368
2521
  style: {
@@ -2372,24 +2525,24 @@ var NumpadDialog = ({
2372
2525
  color: "#64748b"
2373
2526
  },
2374
2527
  children: [
2375
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
2528
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
2376
2529
  "Current: ",
2377
2530
  currentValue,
2378
2531
  " ",
2379
2532
  unit
2380
2533
  ] }),
2381
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
2534
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
2382
2535
  "Range: ",
2383
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: limitStyle(belowMin), children: min }),
2536
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: limitStyle(belowMin), children: min }),
2384
2537
  "-",
2385
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: limitStyle(aboveMax), children: max }),
2538
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: limitStyle(aboveMax), children: max }),
2386
2539
  " ",
2387
2540
  unit
2388
2541
  ] })
2389
2542
  ]
2390
2543
  }
2391
2544
  ),
2392
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2545
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2393
2546
  "div",
2394
2547
  {
2395
2548
  "aria-live": "polite",
@@ -2411,13 +2564,13 @@ var NumpadDialog = ({
2411
2564
  border: outOfRange ? "1.5px solid #fca5a5" : "1.5px solid #e2e8f0"
2412
2565
  },
2413
2566
  children: [
2414
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: inputValue || "0" }),
2415
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "np-caret", "aria-hidden": "true" }),
2416
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: 14, color: "#94a3b8", fontWeight: 500 }, children: unit })
2567
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: inputValue || "0" }),
2568
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "np-caret", "aria-hidden": "true" }),
2569
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontSize: 14, color: "#94a3b8", fontWeight: 500 }, children: unit })
2417
2570
  ]
2418
2571
  }
2419
2572
  ),
2420
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2573
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2421
2574
  "div",
2422
2575
  {
2423
2576
  role: "status",
@@ -2432,8 +2585,8 @@ var NumpadDialog = ({
2432
2585
  children: reason
2433
2586
  }
2434
2587
  ),
2435
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 5 }, children: [
2436
- ["7", "8", "9", "4", "5", "6", "1", "2", "3"].map((digit) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2588
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 5 }, children: [
2589
+ ["7", "8", "9", "4", "5", "6", "1", "2", "3"].map((digit) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2437
2590
  "button",
2438
2591
  {
2439
2592
  type: "button",
@@ -2445,7 +2598,7 @@ var NumpadDialog = ({
2445
2598
  },
2446
2599
  digit
2447
2600
  )),
2448
- min < 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2601
+ min < 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2449
2602
  "button",
2450
2603
  {
2451
2604
  type: "button",
@@ -2456,9 +2609,9 @@ var NumpadDialog = ({
2456
2609
  onClick: handleToggleSign,
2457
2610
  children: "+/\u2212"
2458
2611
  }
2459
- ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BackspaceButton, { style: { ...keyStyle, fontSize: 20, background: "#f8fafc", color: "#64748b" }, label: "\u232B" }),
2460
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "np-key", tabIndex: -1, style: keyStyle, onClick: () => appendDigit("0"), children: "0" }),
2461
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2612
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BackspaceButton, { style: { ...keyStyle, fontSize: 20, background: "#f8fafc", color: "#64748b" }, label: "\u232B" }),
2613
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "np-key", tabIndex: -1, style: keyStyle, onClick: () => appendDigit("0"), children: "0" }),
2614
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2462
2615
  "button",
2463
2616
  {
2464
2617
  type: "button",
@@ -2476,12 +2629,12 @@ var NumpadDialog = ({
2476
2629
  }
2477
2630
  )
2478
2631
  ] }),
2479
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: 5 }, children: [
2480
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "np-key", tabIndex: -1, style: utilKeyStyle, onClick: handleClear, children: "Clear" }),
2481
- !backspaceInGrid && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BackspaceButton, { style: utilKeyStyle, label: "Backspace" })
2632
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", gap: 5 }, children: [
2633
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "np-key", tabIndex: -1, style: utilKeyStyle, onClick: handleClear, children: "Clear" }),
2634
+ !backspaceInGrid && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BackspaceButton, { style: utilKeyStyle, label: "Backspace" })
2482
2635
  ] }),
2483
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 2 }, children: [
2484
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2636
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 2 }, children: [
2637
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2485
2638
  "button",
2486
2639
  {
2487
2640
  type: "button",
@@ -2501,7 +2654,7 @@ var NumpadDialog = ({
2501
2654
  children: "Cancel"
2502
2655
  }
2503
2656
  ),
2504
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2657
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2505
2658
  "button",
2506
2659
  {
2507
2660
  type: "button",
@@ -2536,13 +2689,13 @@ var NumpadDialog = ({
2536
2689
  };
2537
2690
 
2538
2691
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
2539
- var import_react8 = __toESM(require("react"), 1);
2692
+ var import_react9 = __toESM(require("react"), 1);
2540
2693
  var import_material3 = require("@mui/material");
2541
2694
 
2542
2695
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.styles.tsx
2543
2696
  var import_material = require("@mui/material");
2544
2697
  var import_styled = __toESM(require("@emotion/styled"), 1);
2545
- var import_jsx_runtime10 = require("react/jsx-runtime");
2698
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2546
2699
  var FullscreenContainer = (0, import_styled.default)(import_material.Box)(
2547
2700
  ({ leftCollapsed, rightCollapsed }) => {
2548
2701
  const getGridTemplateAreas = () => {
@@ -2667,7 +2820,7 @@ var DisplayModeToggle = (0, import_styled.default)(import_material.IconButton)((
2667
2820
  transform: "translateX(-50%) scale(1.05)"
2668
2821
  }
2669
2822
  }));
2670
- var ChevronLeft = () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2823
+ var ChevronLeft = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2671
2824
  "div",
2672
2825
  {
2673
2826
  style: {
@@ -2679,7 +2832,7 @@ var ChevronLeft = () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2679
2832
  }
2680
2833
  }
2681
2834
  );
2682
- var ChevronRight = () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2835
+ var ChevronRight = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2683
2836
  "div",
2684
2837
  {
2685
2838
  style: {
@@ -2790,10 +2943,10 @@ var ScrollableSVGWrapper = (0, import_styled.default)("div")({
2790
2943
  });
2791
2944
 
2792
2945
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.overlays.tsx
2793
- var import_react7 = require("react");
2946
+ var import_react8 = require("react");
2794
2947
  var import_material2 = require("@mui/material");
2795
2948
  var import_styled2 = __toESM(require("@emotion/styled"), 1);
2796
- var import_jsx_runtime11 = require("react/jsx-runtime");
2949
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2797
2950
  var baseOverlayStyles = {
2798
2951
  tank: {
2799
2952
  background: "linear-gradient(135deg, rgba(69, 90, 120, 0.95) 0%, rgba(52, 73, 94, 0.95) 100%)",
@@ -2855,8 +3008,8 @@ var SVGLockedOverlay = ({
2855
3008
  onClick,
2856
3009
  containerSelector = ".tff-svg-container"
2857
3010
  }) => {
2858
- const [svgOffset, setSvgOffset] = (0, import_react7.useState)({ x: 0, y: 0 });
2859
- (0, import_react7.useEffect)(() => {
3011
+ const [svgOffset, setSvgOffset] = (0, import_react8.useState)({ x: 0, y: 0 });
3012
+ (0, import_react8.useEffect)(() => {
2860
3013
  const updateSvgOffset = () => {
2861
3014
  const containerElement = document.querySelector(containerSelector);
2862
3015
  const svgElement = containerElement?.querySelector("svg");
@@ -2876,7 +3029,7 @@ var SVGLockedOverlay = ({
2876
3029
  const pixelX = svgX + svgOffset.x;
2877
3030
  const pixelY = svgY + svgOffset.y;
2878
3031
  const appliedStyle = theme ? { ...overlayStyles[theme], ...style } : style;
2879
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3032
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2880
3033
  import_material2.Box,
2881
3034
  {
2882
3035
  className: `svg-locked-overlay ${className}`.trim(),
@@ -2940,7 +3093,7 @@ var CardUnit = (0, import_styled2.default)("div")({
2940
3093
  });
2941
3094
 
2942
3095
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.visuals.tsx
2943
- var import_jsx_runtime12 = require("react/jsx-runtime");
3096
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2944
3097
  var forcedTagStyle = {
2945
3098
  position: "absolute",
2946
3099
  top: "-6px",
@@ -2968,7 +3121,7 @@ var TrendLine = ({
2968
3121
  forced = false
2969
3122
  }) => {
2970
3123
  if (!values || values.length < 2) {
2971
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3124
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2972
3125
  "div",
2973
3126
  {
2974
3127
  style: {
@@ -2982,8 +3135,8 @@ var TrendLine = ({
2982
3135
  justifyContent: "center"
2983
3136
  },
2984
3137
  children: [
2985
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" }),
2986
- forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3138
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" }),
3139
+ forced && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
2987
3140
  ]
2988
3141
  }
2989
3142
  );
@@ -2996,7 +3149,7 @@ var TrendLine = ({
2996
3149
  const x = 4 + i / (values.length - 1) * (width - 8);
2997
3150
  return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
2998
3151
  }).join(" ");
2999
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3152
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3000
3153
  "div",
3001
3154
  {
3002
3155
  style: {
@@ -3008,14 +3161,14 @@ var TrendLine = ({
3008
3161
  overflow: "visible"
3009
3162
  },
3010
3163
  children: [
3011
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3164
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3012
3165
  "svg",
3013
3166
  {
3014
3167
  width,
3015
3168
  height,
3016
3169
  style: { position: "absolute", top: 0, left: 0, overflow: "hidden", borderRadius: "2px" },
3017
3170
  children: [
3018
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3171
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3019
3172
  "path",
3020
3173
  {
3021
3174
  d: pathData,
@@ -3026,7 +3179,7 @@ var TrendLine = ({
3026
3179
  strokeLinejoin: "round"
3027
3180
  }
3028
3181
  ),
3029
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3182
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3030
3183
  "circle",
3031
3184
  {
3032
3185
  cx: 4 + (width - 8),
@@ -3038,7 +3191,7 @@ var TrendLine = ({
3038
3191
  ]
3039
3192
  }
3040
3193
  ),
3041
- forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3194
+ forced && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3042
3195
  ]
3043
3196
  }
3044
3197
  );
@@ -3070,7 +3223,7 @@ var CircularGauge = ({
3070
3223
  const borderColor = isHex ? hexToRgba(color, 0.9) : color;
3071
3224
  const backgroundColor = isHex ? hexToRgba(color, 0.15) : color;
3072
3225
  const borderStrokeColor = isHex ? hexToRgba(color, 0.4) : color;
3073
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3226
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3074
3227
  "div",
3075
3228
  {
3076
3229
  style: {
@@ -3079,7 +3232,7 @@ var CircularGauge = ({
3079
3232
  alignItems: "center"
3080
3233
  },
3081
3234
  children: [
3082
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3235
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3083
3236
  "div",
3084
3237
  {
3085
3238
  style: {
@@ -3091,8 +3244,8 @@ var CircularGauge = ({
3091
3244
  border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : `1px solid ${borderStrokeColor}`
3092
3245
  },
3093
3246
  children: [
3094
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
3095
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3247
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
3248
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3096
3249
  "circle",
3097
3250
  {
3098
3251
  cx: size / 2,
@@ -3103,7 +3256,7 @@ var CircularGauge = ({
3103
3256
  strokeWidth: 1
3104
3257
  }
3105
3258
  ),
3106
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3259
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3107
3260
  "circle",
3108
3261
  {
3109
3262
  cx: size / 2,
@@ -3114,7 +3267,7 @@ var CircularGauge = ({
3114
3267
  fill: "transparent"
3115
3268
  }
3116
3269
  ),
3117
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3270
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3118
3271
  "circle",
3119
3272
  {
3120
3273
  cx: size / 2,
@@ -3130,7 +3283,7 @@ var CircularGauge = ({
3130
3283
  }
3131
3284
  )
3132
3285
  ] }),
3133
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3286
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3134
3287
  "div",
3135
3288
  {
3136
3289
  style: {
@@ -3146,14 +3299,14 @@ var CircularGauge = ({
3146
3299
  },
3147
3300
  children: [
3148
3301
  value.toFixed(1),
3149
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { fontSize: "8px", opacity: 0.9, color: "white" }, children: unit })
3302
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { fontSize: "8px", opacity: 0.9, color: "white" }, children: unit })
3150
3303
  ]
3151
3304
  }
3152
3305
  )
3153
3306
  ]
3154
3307
  }
3155
3308
  ),
3156
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3309
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3157
3310
  "div",
3158
3311
  {
3159
3312
  style: {
@@ -3165,7 +3318,7 @@ var CircularGauge = ({
3165
3318
  children: label
3166
3319
  }
3167
3320
  ),
3168
- forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3321
+ forced && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3169
3322
  "span",
3170
3323
  {
3171
3324
  "aria-label": "Forced",
@@ -3186,7 +3339,7 @@ var CircularGauge = ({
3186
3339
  children: "F"
3187
3340
  }
3188
3341
  ),
3189
- forced && realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { fontSize: "8px", opacity: 0.8, color: "white", marginTop: "1px" }, children: [
3342
+ forced && realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { fontSize: "8px", opacity: 0.8, color: "white", marginTop: "1px" }, children: [
3190
3343
  "REAL ",
3191
3344
  realValue.toFixed(1),
3192
3345
  " ",
@@ -3211,7 +3364,7 @@ var Sparkline = ({ data, width, height, color, forced = false }) => {
3211
3364
  const y = height - 8 - (value - min) / range * (height - 8);
3212
3365
  return `${x + 4},${y + 4}`;
3213
3366
  }).join(" ");
3214
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3367
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3215
3368
  "div",
3216
3369
  {
3217
3370
  style: {
@@ -3227,13 +3380,13 @@ var Sparkline = ({ data, width, height, color, forced = false }) => {
3227
3380
  boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
3228
3381
  },
3229
3382
  children: [
3230
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { width, height, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) }),
3231
- forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3383
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { width, height, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) }),
3384
+ forced && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3232
3385
  ]
3233
3386
  }
3234
3387
  );
3235
3388
  };
3236
- var EquipmentForcedBadge = () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3389
+ var EquipmentForcedBadge = () => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3237
3390
  "span",
3238
3391
  {
3239
3392
  "aria-label": "Forced",
@@ -3268,9 +3421,9 @@ var EquipmentIndicatorWithSparkline = ({
3268
3421
  forced = false
3269
3422
  }) => {
3270
3423
  const hasSparkline = sparklineData && sparklineData.length > 1 && Math.max(...sparklineData) !== Math.min(...sparklineData);
3271
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
3272
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { position: "relative", width: size, height: size }, children: [
3273
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3424
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
3425
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { position: "relative", width: size, height: size }, children: [
3426
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3274
3427
  "div",
3275
3428
  {
3276
3429
  style: {
@@ -3291,9 +3444,9 @@ var EquipmentIndicatorWithSparkline = ({
3291
3444
  children: label
3292
3445
  }
3293
3446
  ),
3294
- forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EquipmentForcedBadge, {})
3447
+ forced && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(EquipmentForcedBadge, {})
3295
3448
  ] }) }),
3296
- hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Sparkline, { data: sparklineData, width: 60, height: 20, color, forced }) })
3449
+ hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Sparkline, { data: sparklineData, width: 60, height: 20, color, forced }) })
3297
3450
  ] });
3298
3451
  };
3299
3452
  var EquipmentIndicator = ({
@@ -3307,8 +3460,8 @@ var EquipmentIndicator = ({
3307
3460
  forced = false
3308
3461
  }) => {
3309
3462
  const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
3310
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { position: "relative", width: size, height: size }, children: [
3311
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3463
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { position: "relative", width: size, height: size }, children: [
3464
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3312
3465
  "div",
3313
3466
  {
3314
3467
  style: {
@@ -3329,12 +3482,12 @@ var EquipmentIndicator = ({
3329
3482
  children: label
3330
3483
  }
3331
3484
  ),
3332
- forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EquipmentForcedBadge, {})
3485
+ forced && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(EquipmentForcedBadge, {})
3333
3486
  ] }) });
3334
3487
  };
3335
3488
 
3336
3489
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
3337
- var import_jsx_runtime13 = require("react/jsx-runtime");
3490
+ var import_jsx_runtime14 = require("react/jsx-runtime");
3338
3491
  function FullscreenWorkspace({
3339
3492
  svgContent,
3340
3493
  overlays,
@@ -3362,10 +3515,10 @@ function FullscreenWorkspace({
3362
3515
  className = "",
3363
3516
  ...rest
3364
3517
  }) {
3365
- const [leftCollapsedState, setLeftCollapsedState] = import_react8.default.useState(defaultLeftCollapsed);
3366
- const [rightCollapsedState, setRightCollapsedState] = import_react8.default.useState(defaultRightCollapsed);
3367
- const [internalDisplayMode, setInternalDisplayMode] = import_react8.default.useState(displayMode ?? defaultDisplayMode);
3368
- const [internalTab, setInternalTab] = import_react8.default.useState(
3518
+ const [leftCollapsedState, setLeftCollapsedState] = import_react9.default.useState(defaultLeftCollapsed);
3519
+ const [rightCollapsedState, setRightCollapsedState] = import_react9.default.useState(defaultRightCollapsed);
3520
+ const [internalDisplayMode, setInternalDisplayMode] = import_react9.default.useState(displayMode ?? defaultDisplayMode);
3521
+ const [internalTab, setInternalTab] = import_react9.default.useState(
3369
3522
  tabs && tabs.length > 0 ? tabs[0].value : ""
3370
3523
  );
3371
3524
  const leftCollapsed = leftCollapsedProp ?? leftCollapsedState;
@@ -3373,10 +3526,10 @@ function FullscreenWorkspace({
3373
3526
  const isDisplayModeControlled = displayMode !== void 0;
3374
3527
  const currentDisplayMode = isDisplayModeControlled ? displayMode : internalDisplayMode;
3375
3528
  const isTabControlled = activeTab !== void 0 && activeTab !== null;
3376
- const resolvedTabs = import_react8.default.useMemo(() => tabs ?? [], [tabs]);
3529
+ const resolvedTabs = import_react9.default.useMemo(() => tabs ?? [], [tabs]);
3377
3530
  const defaultTabValue = resolvedTabs.length > 0 ? resolvedTabs[0].value : "";
3378
3531
  const currentTab = isTabControlled ? activeTab : internalTab || defaultTabValue;
3379
- import_react8.default.useEffect(() => {
3532
+ import_react9.default.useEffect(() => {
3380
3533
  if (resolvedTabs.length > 0) {
3381
3534
  const values = resolvedTabs.map((tab) => tab.value);
3382
3535
  if (!values.includes(currentTab)) {
@@ -3385,7 +3538,7 @@ function FullscreenWorkspace({
3385
3538
  }
3386
3539
  }
3387
3540
  }, [resolvedTabs, activeTab, currentTab, defaultTabValue]);
3388
- import_react8.default.useEffect(() => {
3541
+ import_react9.default.useEffect(() => {
3389
3542
  if (displayMode !== void 0) {
3390
3543
  setInternalDisplayMode(displayMode);
3391
3544
  }
@@ -3420,7 +3573,7 @@ function FullscreenWorkspace({
3420
3573
  const currentTabContent = resolvedTabs.find((tab) => tab.value === currentTab)?.content ?? null;
3421
3574
  const showLeftToggle = Boolean(leftPanel);
3422
3575
  const showRightToggle = Boolean(rightPanel);
3423
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3576
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3424
3577
  FullscreenContainer,
3425
3578
  {
3426
3579
  leftCollapsed,
@@ -3428,25 +3581,25 @@ function FullscreenWorkspace({
3428
3581
  className: `tff-svg-container ${className}`.trim(),
3429
3582
  ...rest,
3430
3583
  children: [
3431
- showLeftToggle && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3584
+ showLeftToggle && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3432
3585
  LeftToggleButton,
3433
3586
  {
3434
3587
  collapsed: leftCollapsed,
3435
3588
  onClick: handleLeftToggle,
3436
3589
  "aria-label": leftCollapsed ? "Expand left panel" : "Collapse left panel",
3437
- children: leftCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronRight, {}) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronLeft, {})
3590
+ children: leftCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronRight, {}) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronLeft, {})
3438
3591
  }
3439
3592
  ),
3440
- showRightToggle && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3593
+ showRightToggle && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3441
3594
  RightToggleButton,
3442
3595
  {
3443
3596
  collapsed: rightCollapsed,
3444
3597
  onClick: handleRightToggle,
3445
3598
  "aria-label": rightCollapsed ? "Expand right panel" : "Collapse right panel",
3446
- children: rightCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronLeft, {}) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronRight, {})
3599
+ children: rightCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronLeft, {}) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronRight, {})
3447
3600
  }
3448
3601
  ),
3449
- showDisplayModeToggle && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3602
+ showDisplayModeToggle && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3450
3603
  DisplayModeToggle,
3451
3604
  {
3452
3605
  mode: currentDisplayMode,
@@ -3455,9 +3608,9 @@ function FullscreenWorkspace({
3455
3608
  children: currentDisplayMode === "dashboard" ? "Dashboard Mode" : "Standard Mode"
3456
3609
  }
3457
3610
  ),
3458
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(SVGArea, { children: [
3459
- showZoomControls && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ZoomControls, { children: [
3460
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3611
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(SVGArea, { children: [
3612
+ showZoomControls && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(ZoomControls, { children: [
3613
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3461
3614
  ZoomButton,
3462
3615
  {
3463
3616
  onClick: onZoomIn,
@@ -3467,7 +3620,7 @@ function FullscreenWorkspace({
3467
3620
  children: "+"
3468
3621
  }
3469
3622
  ),
3470
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3623
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3471
3624
  ZoomButton,
3472
3625
  {
3473
3626
  onClick: onZoomOut,
@@ -3477,7 +3630,7 @@ function FullscreenWorkspace({
3477
3630
  children: "-"
3478
3631
  }
3479
3632
  ),
3480
- onResetZoom && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3633
+ onResetZoom && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3481
3634
  ZoomButton,
3482
3635
  {
3483
3636
  onClick: onResetZoom,
@@ -3488,44 +3641,44 @@ function FullscreenWorkspace({
3488
3641
  }
3489
3642
  )
3490
3643
  ] }),
3491
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ScrollableSVGWrapper, { className: "svg-scroll-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(FixedSVGContainer, { className: "svg-container", children: [
3644
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ScrollableSVGWrapper, { className: "svg-scroll-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(FixedSVGContainer, { className: "svg-container", children: [
3492
3645
  svgContent,
3493
3646
  overlays
3494
3647
  ] }) })
3495
3648
  ] }),
3496
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(LeftPanel, { collapsed: leftCollapsed, children: resolvedTabs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
3497
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3649
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LeftPanel, { collapsed: leftCollapsed, children: resolvedTabs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
3650
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3498
3651
  PanelTabs,
3499
3652
  {
3500
3653
  value: currentTab,
3501
3654
  onChange: handleTabChangeInternal,
3502
3655
  "aria-label": "Left panel tabs",
3503
- children: resolvedTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_material3.Tab, { label: tab.label, value: tab.value }, tab.value))
3656
+ children: resolvedTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material3.Tab, { label: tab.label, value: tab.value }, tab.value))
3504
3657
  }
3505
3658
  ),
3506
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PanelContent, { children: currentTabContent })
3659
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PanelContent, { children: currentTabContent })
3507
3660
  ] }) : leftPanel }),
3508
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RightPanel, { collapsed: rightCollapsed, children: rightPanel }),
3509
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FooterPanel, { children: footer })
3661
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RightPanel, { collapsed: rightCollapsed, children: rightPanel }),
3662
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(FooterPanel, { children: footer })
3510
3663
  ]
3511
3664
  }
3512
3665
  );
3513
3666
  }
3514
3667
 
3515
3668
  // src/theme/ThemeContext.tsx
3516
- var import_react9 = require("react");
3517
- var import_jsx_runtime14 = require("react/jsx-runtime");
3518
- var ThemeContext = (0, import_react9.createContext)(void 0);
3669
+ var import_react10 = require("react");
3670
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3671
+ var ThemeContext = (0, import_react10.createContext)(void 0);
3519
3672
  var useTheme = () => {
3520
- const context = (0, import_react9.useContext)(ThemeContext);
3673
+ const context = (0, import_react10.useContext)(ThemeContext);
3521
3674
  if (!context) {
3522
3675
  throw new Error("useTheme must be used within a ThemeProvider");
3523
3676
  }
3524
3677
  return context;
3525
3678
  };
3526
3679
  var ThemeProvider = ({ children }) => {
3527
- const [theme, setThemeState] = (0, import_react9.useState)("modern");
3528
- (0, import_react9.useEffect)(() => {
3680
+ const [theme, setThemeState] = (0, import_react10.useState)("modern");
3681
+ (0, import_react10.useEffect)(() => {
3529
3682
  document.body.className = "";
3530
3683
  document.body.classList.add(`theme-${theme}`);
3531
3684
  document.documentElement.className = "";
@@ -3537,11 +3690,11 @@ var ThemeProvider = ({ children }) => {
3537
3690
  const setTheme = (newTheme) => {
3538
3691
  setThemeState(newTheme);
3539
3692
  };
3540
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ThemeContext.Provider, { value: { theme, toggleTheme, setTheme }, children });
3693
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ThemeContext.Provider, { value: { theme, toggleTheme, setTheme }, children });
3541
3694
  };
3542
3695
 
3543
3696
  // src/theme/ThemeToggle.tsx
3544
- var import_jsx_runtime15 = require("react/jsx-runtime");
3697
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3545
3698
  var ThemeToggle = ({
3546
3699
  size = "medium",
3547
3700
  position = "top-right",
@@ -3612,18 +3765,18 @@ var ThemeToggle = ({
3612
3765
  transition: "all 0.2s ease",
3613
3766
  boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)"
3614
3767
  };
3615
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: currentStyle, onClick: toggleTheme, className, ...props, children: [
3616
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: theme === "modern" ? "\u{1F3A8} Modern" : "\u{1F3ED} HMI" }),
3617
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: toggleStyle, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: thumbStyle }) }),
3618
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: { fontSize: "0.75rem", opacity: 0.8 }, children: theme === "modern" ? "Industrial Design" : "High Performance" })
3768
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: currentStyle, onClick: toggleTheme, className, ...props, children: [
3769
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: theme === "modern" ? "\u{1F3A8} Modern" : "\u{1F3ED} HMI" }),
3770
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: toggleStyle, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: thumbStyle }) }),
3771
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontSize: "0.75rem", opacity: 0.8 }, children: theme === "modern" ? "Industrial Design" : "High Performance" })
3619
3772
  ] });
3620
3773
  };
3621
3774
 
3622
3775
  // src/components/PIDCanvas/PIDCanvas.tsx
3623
- var import_react20 = __toESM(require("react"), 1);
3776
+ var import_react22 = __toESM(require("react"), 1);
3624
3777
 
3625
3778
  // src/components/PIDCanvas/NodeRenderer.tsx
3626
- var import_react10 = __toESM(require("react"), 1);
3779
+ var import_react11 = __toESM(require("react"), 1);
3627
3780
 
3628
3781
  // src/diagram/symbols/mockSymbolLibrary.ts
3629
3782
  var mockSymbolLibrary = {
@@ -3651,6 +3804,25 @@ var mockSymbolLibrary = {
3651
3804
  tags: ["valve", "gate", "powered", "actuator"]
3652
3805
  }
3653
3806
  },
3807
+ "status-indicator": {
3808
+ id: "status-indicator",
3809
+ name: "Status LED",
3810
+ category: "instrument",
3811
+ viewBox: { x: 0, y: 0, width: 24, height: 24 },
3812
+ ports: [],
3813
+ svgContent: `
3814
+ <rect x="2" y="2" width="20" height="20"
3815
+ rx="var(--led-rx, 50%)"
3816
+ fill="var(--symbol-fill, #64748b)"
3817
+ stroke="rgba(255,255,255,0.4)"
3818
+ stroke-width="var(--led-bezel, 0)"
3819
+ style="transform-box: fill-box; transform: rotate(var(--led-rotate, 0deg)); transform-origin: center; pointer-events: all;"/>
3820
+ `,
3821
+ metadata: {
3822
+ description: "Status LED indicator driven by telemetry status color",
3823
+ tags: ["status", "led", "indicator", "instrument"]
3824
+ }
3825
+ },
3654
3826
  "pump-positive-displacement": {
3655
3827
  id: "pump-positive-displacement",
3656
3828
  name: "Positive Displacement Pump",
@@ -4843,8 +5015,150 @@ function getAvailableSymbols() {
4843
5015
  return [...Object.keys(customSymbols), ...Object.keys(mockSymbolLibrary)];
4844
5016
  }
4845
5017
 
5018
+ // src/diagram/symbols/appearanceSchema.ts
5019
+ var COLOR_PRESETS = {
5020
+ green: "#22c55e",
5021
+ red: "#ef4444",
5022
+ amber: "#f59e0b",
5023
+ blue: "#3b82f6",
5024
+ grey: "#64748b"
5025
+ };
5026
+ function resolvePresetColor(name) {
5027
+ return name && COLOR_PRESETS[name] || COLOR_PRESETS.grey;
5028
+ }
5029
+ var COLOR_OPTIONS = Object.keys(COLOR_PRESETS);
5030
+ var STATUS_LED_APPEARANCE = [
5031
+ {
5032
+ key: "shape",
5033
+ kind: "select",
5034
+ label: "Shape",
5035
+ options: ["circle", "square", "diamond"],
5036
+ default: "circle"
5037
+ },
5038
+ {
5039
+ key: "size",
5040
+ kind: "select",
5041
+ label: "Size",
5042
+ target: "scale",
5043
+ options: ["small", "medium", "large"],
5044
+ default: "medium",
5045
+ valueMap: { small: 0.75, medium: 1, large: 1.5 }
5046
+ },
5047
+ { key: "led", kind: "boolean", label: "Bezel ring", default: true },
5048
+ { key: "glow", kind: "boolean", label: "Diffusion (glow)", default: false },
5049
+ {
5050
+ key: "animation",
5051
+ kind: "select",
5052
+ label: "Animation",
5053
+ options: ["none", "pulse", "blink"],
5054
+ default: "none"
5055
+ },
5056
+ {
5057
+ key: "labelPosition",
5058
+ kind: "select",
5059
+ label: "Label position",
5060
+ options: ["bottom", "top", "left", "right"],
5061
+ default: "bottom"
5062
+ },
5063
+ // Color binding: a named signal drives the on/off color.
5064
+ {
5065
+ key: "colorSource",
5066
+ kind: "tag",
5067
+ label: "Color source (tag)",
5068
+ default: "",
5069
+ help: "Signal/tag that drives color. True (non-zero) shows On color; false shows Off color. Blank = unbound (grey)."
5070
+ },
5071
+ {
5072
+ key: "onColor",
5073
+ kind: "select",
5074
+ label: "On color (true)",
5075
+ options: COLOR_OPTIONS,
5076
+ default: "green",
5077
+ help: "Color when the Color source is true."
5078
+ },
5079
+ {
5080
+ key: "offColor",
5081
+ kind: "select",
5082
+ label: "Off color (false)",
5083
+ options: COLOR_OPTIONS,
5084
+ default: "red",
5085
+ help: "Color when the Color source is false."
5086
+ },
5087
+ // Visibility binding: an independent named signal drives show/hide.
5088
+ {
5089
+ key: "visibleSource",
5090
+ kind: "tag",
5091
+ label: "Visibility source (tag)",
5092
+ default: "",
5093
+ help: "Signal/tag that drives visibility. True shows the LED, false hides it. Blank = always visible (use the Visible checkbox)."
5094
+ },
5095
+ {
5096
+ key: "visible",
5097
+ kind: "boolean",
5098
+ label: "Visible",
5099
+ target: "visible",
5100
+ default: true,
5101
+ help: "Static show/hide, used only when no Visibility source is set."
5102
+ }
5103
+ ];
5104
+ var APPEARANCE_REGISTRY = {
5105
+ "status-indicator": STATUS_LED_APPEARANCE
5106
+ };
5107
+ function getAppearanceSchema(symbolId) {
5108
+ return APPEARANCE_REGISTRY[symbolId] ?? [];
5109
+ }
5110
+ function getAppearanceDefaults(symbolId) {
5111
+ const out = {};
5112
+ for (const field of getAppearanceSchema(symbolId)) {
5113
+ if ((field.target ?? "metadata") === "metadata" && field.default !== void 0) {
5114
+ out[field.key] = field.default;
5115
+ }
5116
+ }
5117
+ return out;
5118
+ }
5119
+ function readAppearanceValue(node, field) {
5120
+ if (field.target === "visible") {
5121
+ return node.visible ?? field.default ?? true;
5122
+ }
5123
+ if (field.target === "scale") {
5124
+ const scale = node.transform.scale ?? 1;
5125
+ if (field.valueMap) {
5126
+ let bestOption = field.options?.[0] ?? String(scale);
5127
+ let bestDiff = Infinity;
5128
+ for (const [option, value2] of Object.entries(field.valueMap)) {
5129
+ const diff = Math.abs(value2 - scale);
5130
+ if (diff < bestDiff) {
5131
+ bestDiff = diff;
5132
+ bestOption = option;
5133
+ }
5134
+ }
5135
+ return bestOption;
5136
+ }
5137
+ return scale;
5138
+ }
5139
+ const value = node.metadata?.[field.key];
5140
+ return value ?? field.default;
5141
+ }
5142
+ function writeAppearanceValue(node, field, value) {
5143
+ if (field.target === "visible") {
5144
+ return { visible: Boolean(value) };
5145
+ }
5146
+ if (field.target === "scale") {
5147
+ const scale = field.valueMap ? field.valueMap[String(value)] ?? 1 : Number(value);
5148
+ return { transform: { ...node.transform, scale } };
5149
+ }
5150
+ return { metadata: { ...node.metadata, [field.key]: value } };
5151
+ }
5152
+
4846
5153
  // src/components/PIDCanvas/NodeRenderer.tsx
4847
- var import_jsx_runtime16 = require("react/jsx-runtime");
5154
+ var import_jsx_runtime17 = require("react/jsx-runtime");
5155
+ var HATCH_TILE_SIZE = 7;
5156
+ var HATCH_STRIPE_WIDTH = 2;
5157
+ var HATCH_STRIPE_ANGLE = 135;
5158
+ var HATCH_STRIPE_OPACITY = 85 / 255;
5159
+ function isUnknownNode(node) {
5160
+ return node.positionUnknown === true || normalizeDeviceStatus(node.status) === "unknown";
5161
+ }
4848
5162
  function NodeRendererImpl({
4849
5163
  nodes: nodes2,
4850
5164
  selectedNodeIds,
@@ -4856,146 +5170,214 @@ function NodeRendererImpl({
4856
5170
  enableDrag = false,
4857
5171
  statusColorMap
4858
5172
  }) {
4859
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "layer-nodes", children: nodes2.filter((node) => node.visible !== false).map((node) => {
4860
- const symbol = getSymbolDefinition(node.symbolId);
4861
- const isSelected = selectedNodeIds?.has(node.id) ?? false;
4862
- const isDragging = draggingNodeId === node.id;
4863
- if (!symbol) {
4864
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("g", { children: [
4865
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4866
- "circle",
4867
- {
4868
- cx: node.transform.x,
4869
- cy: node.transform.y,
4870
- r: "15",
4871
- fill: "#ff0000",
4872
- opacity: "0.3",
4873
- stroke: "#ff0000",
4874
- strokeWidth: "2"
4875
- }
4876
- ),
4877
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4878
- "text",
4879
- {
4880
- x: node.transform.x,
4881
- y: node.transform.y,
4882
- textAnchor: "middle",
4883
- dominantBaseline: "middle",
4884
- fontSize: "10",
4885
- fill: "#ff0000",
4886
- children: "?"
4887
- }
4888
- )
4889
- ] }, node.id);
4890
- }
4891
- const transforms = [];
4892
- const effectiveX = isDragging ? node.transform.x + dragOffset.x : node.transform.x;
4893
- const effectiveY = isDragging ? node.transform.y + dragOffset.y : node.transform.y;
4894
- transforms.push(`translate(${effectiveX}, ${effectiveY})`);
4895
- if (node.transform.rotation) {
4896
- const centerX = symbol.viewBox.width / 2;
4897
- const centerY = symbol.viewBox.height / 2;
4898
- transforms.push(`rotate(${node.transform.rotation}, ${centerX}, ${centerY})`);
4899
- }
4900
- if (node.transform.scale && node.transform.scale !== 1) {
4901
- const centerX = symbol.viewBox.width / 2;
4902
- const centerY = symbol.viewBox.height / 2;
4903
- transforms.push(
4904
- `translate(${centerX}, ${centerY}) scale(${node.transform.scale}) translate(${-centerX}, ${-centerY})`
4905
- );
4906
- }
4907
- if (node.transform.flipX || node.transform.flipY) {
4908
- const scaleX = node.transform.flipX ? -1 : 1;
4909
- const scaleY = node.transform.flipY ? -1 : 1;
4910
- const centerX = symbol.viewBox.width / 2;
4911
- const centerY = symbol.viewBox.height / 2;
4912
- transforms.push(
4913
- `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
4914
- );
4915
- }
4916
- const statusEntry = resolveStatusColor(node.status, statusColorMap);
4917
- const fillColor = node.customColors?.fill ?? statusEntry.fill;
4918
- const strokeColor = node.customColors?.stroke ?? statusEntry.stroke;
4919
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4920
- "g",
5173
+ const hatchPatternId = `node-unknown-hatch-${import_react11.default.useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
5174
+ const visibleNodes = nodes2.filter((node) => node.visible !== false);
5175
+ const hasUnknownNode = visibleNodes.some(isUnknownNode);
5176
+ const unknownEntry = resolveStatusColor("unknown", statusColorMap);
5177
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("g", { id: "layer-nodes", children: [
5178
+ hasUnknownNode && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5179
+ "pattern",
4921
5180
  {
4922
- "data-node-id": node.id,
4923
- "data-symbol-id": node.symbolId,
4924
- "data-selected": isSelected,
4925
- "data-dragging": isDragging,
4926
- transform: transforms.join(" "),
4927
- style: {
4928
- "--symbol-fill": fillColor,
4929
- "--symbol-stroke": strokeColor,
4930
- cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
4931
- },
4932
- onClick: (e) => {
4933
- e.stopPropagation();
4934
- onNodeClick?.(node.id, e);
4935
- },
4936
- onDoubleClick: (e) => {
4937
- e.stopPropagation();
4938
- onNodeDoubleClick?.(node.id, e);
4939
- },
4940
- onMouseDown: (e) => {
4941
- if (enableDrag) {
4942
- onNodeDragStart?.(node.id, e);
4943
- }
4944
- },
4945
- onTouchStart: (e) => {
4946
- if (enableDrag) {
4947
- e.stopPropagation();
4948
- onNodeDragStart?.(node.id, e);
4949
- }
4950
- },
5181
+ id: hatchPatternId,
5182
+ "data-testid": "unknown-hatch-pattern",
5183
+ patternUnits: "userSpaceOnUse",
5184
+ width: HATCH_TILE_SIZE,
5185
+ height: HATCH_TILE_SIZE,
5186
+ patternTransform: `rotate(${HATCH_STRIPE_ANGLE})`,
4951
5187
  children: [
4952
- isSelected && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5188
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("rect", { width: HATCH_TILE_SIZE, height: HATCH_TILE_SIZE, fill: unknownEntry.fill }),
5189
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4953
5190
  "rect",
4954
5191
  {
4955
- x: "-5",
4956
- y: "-5",
4957
- width: symbol.viewBox.width + 10,
4958
- height: symbol.viewBox.height + 10,
4959
- fill: "none",
4960
- stroke: isDragging ? "#10b981" : "#3b82f6",
4961
- strokeWidth: isDragging ? "3" : "2",
4962
- strokeDasharray: "4 2",
4963
- rx: "2",
4964
- pointerEvents: "none"
5192
+ x: HATCH_TILE_SIZE - HATCH_STRIPE_WIDTH,
5193
+ width: HATCH_STRIPE_WIDTH,
5194
+ height: HATCH_TILE_SIZE,
5195
+ fill: unknownEntry.stroke,
5196
+ fillOpacity: HATCH_STRIPE_OPACITY
4965
5197
  }
4966
- ),
4967
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4968
- "g",
5198
+ )
5199
+ ]
5200
+ }
5201
+ ) }),
5202
+ visibleNodes.map((node) => {
5203
+ const symbol = getSymbolDefinition(node.symbolId);
5204
+ const isSelected = selectedNodeIds?.has(node.id) ?? false;
5205
+ const isDragging = draggingNodeId === node.id;
5206
+ if (!symbol) {
5207
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("g", { children: [
5208
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5209
+ "circle",
4969
5210
  {
4970
- dangerouslySetInnerHTML: { __html: symbol.svgContent },
4971
- "data-symbol-content": symbol.id,
4972
- style: { pointerEvents: "auto" }
5211
+ cx: node.transform.x,
5212
+ cy: node.transform.y,
5213
+ r: "15",
5214
+ fill: "#ff0000",
5215
+ opacity: "0.3",
5216
+ stroke: "#ff0000",
5217
+ strokeWidth: "2"
4973
5218
  }
4974
5219
  ),
4975
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5220
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4976
5221
  "text",
4977
5222
  {
4978
- x: symbol.viewBox.width / 2 + (node.labelOffset?.x ?? 0),
4979
- y: symbol.viewBox.height + 12 + (node.labelOffset?.y ?? 0),
5223
+ x: node.transform.x,
5224
+ y: node.transform.y,
4980
5225
  textAnchor: "middle",
4981
- fontSize: node.labelFontSize ?? 10,
4982
- fontWeight: "600",
4983
- fill: isSelected ? "#3b82f6" : "#1f2937",
4984
- 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,
4985
- children: node.label || node.id
5226
+ dominantBaseline: "middle",
5227
+ fontSize: "10",
5228
+ fill: "#ff0000",
5229
+ children: "?"
4986
5230
  }
4987
5231
  )
4988
- ]
4989
- },
4990
- node.id
4991
- );
4992
- }) });
5232
+ ] }, node.id);
5233
+ }
5234
+ const transforms = [];
5235
+ const effectiveX = isDragging ? node.transform.x + dragOffset.x : node.transform.x;
5236
+ const effectiveY = isDragging ? node.transform.y + dragOffset.y : node.transform.y;
5237
+ transforms.push(`translate(${effectiveX}, ${effectiveY})`);
5238
+ if (node.transform.rotation) {
5239
+ const centerX = symbol.viewBox.width / 2;
5240
+ const centerY = symbol.viewBox.height / 2;
5241
+ transforms.push(`rotate(${node.transform.rotation}, ${centerX}, ${centerY})`);
5242
+ }
5243
+ if (node.transform.scale && node.transform.scale !== 1) {
5244
+ const centerX = symbol.viewBox.width / 2;
5245
+ const centerY = symbol.viewBox.height / 2;
5246
+ transforms.push(
5247
+ `translate(${centerX}, ${centerY}) scale(${node.transform.scale}) translate(${-centerX}, ${-centerY})`
5248
+ );
5249
+ }
5250
+ if (node.transform.flipX || node.transform.flipY) {
5251
+ const scaleX = node.transform.flipX ? -1 : 1;
5252
+ const scaleY = node.transform.flipY ? -1 : 1;
5253
+ const centerX = symbol.viewBox.width / 2;
5254
+ const centerY = symbol.viewBox.height / 2;
5255
+ transforms.push(
5256
+ `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
5257
+ );
5258
+ }
5259
+ const statusEntry = resolveStatusColor(node.status, statusColorMap);
5260
+ const isUnknown = isUnknownNode(node);
5261
+ const fillColor = isUnknown ? `url(#${hatchPatternId})` : node.customColors?.fill ?? statusEntry.fill;
5262
+ const strokeColor = isUnknown ? unknownEntry.stroke : node.customColors?.stroke ?? statusEntry.stroke;
5263
+ const appearance = { ...getAppearanceDefaults(node.symbolId), ...node.metadata ?? {} };
5264
+ const ledStyle = {};
5265
+ if (appearance.shape !== void 0) {
5266
+ ledStyle["--led-rx"] = appearance.shape === "circle" ? "50%" : "2";
5267
+ ledStyle["--led-rotate"] = appearance.shape === "diamond" ? "45deg" : "0deg";
5268
+ }
5269
+ if (appearance.led !== void 0) {
5270
+ ledStyle["--led-bezel"] = appearance.led ? "2" : "0";
5271
+ }
5272
+ if (appearance.glow) {
5273
+ const glowColor = isUnknown ? unknownEntry.stroke : fillColor;
5274
+ ledStyle.filter = `drop-shadow(0 0 4px ${glowColor})`;
5275
+ }
5276
+ const ledAnimation = typeof appearance.animation === "string" && appearance.animation !== "none" ? appearance.animation : void 0;
5277
+ const vb = symbol.viewBox;
5278
+ const ox = node.labelOffset?.x ?? 0;
5279
+ const oy = node.labelOffset?.y ?? 0;
5280
+ const labelPos = typeof appearance.labelPosition === "string" ? appearance.labelPosition : "bottom";
5281
+ let labelX = vb.width / 2 + ox;
5282
+ let labelY = vb.height + 12 + oy;
5283
+ let labelAnchor = "middle";
5284
+ if (labelPos === "top") {
5285
+ labelX = vb.width / 2 + ox;
5286
+ labelY = -6 + oy;
5287
+ labelAnchor = "middle";
5288
+ } else if (labelPos === "left") {
5289
+ labelX = -6 + ox;
5290
+ labelY = vb.height / 2 + oy;
5291
+ labelAnchor = "end";
5292
+ } else if (labelPos === "right") {
5293
+ labelX = vb.width + 6 + ox;
5294
+ labelY = vb.height / 2 + oy;
5295
+ labelAnchor = "start";
5296
+ }
5297
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5298
+ "g",
5299
+ {
5300
+ "data-node-id": node.id,
5301
+ "data-symbol-id": node.symbolId,
5302
+ "data-selected": isSelected,
5303
+ "data-dragging": isDragging,
5304
+ "data-led-animation": ledAnimation,
5305
+ "data-position-unknown": node.positionUnknown === true ? "true" : void 0,
5306
+ transform: transforms.join(" "),
5307
+ style: {
5308
+ "--symbol-fill": fillColor,
5309
+ "--symbol-stroke": strokeColor,
5310
+ cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default",
5311
+ ...ledStyle
5312
+ },
5313
+ onClick: (e) => {
5314
+ e.stopPropagation();
5315
+ onNodeClick?.(node.id, e);
5316
+ },
5317
+ onDoubleClick: (e) => {
5318
+ e.stopPropagation();
5319
+ onNodeDoubleClick?.(node.id, e);
5320
+ },
5321
+ onMouseDown: (e) => {
5322
+ if (enableDrag) {
5323
+ onNodeDragStart?.(node.id, e);
5324
+ }
5325
+ },
5326
+ onTouchStart: (e) => {
5327
+ if (enableDrag) {
5328
+ e.stopPropagation();
5329
+ onNodeDragStart?.(node.id, e);
5330
+ }
5331
+ },
5332
+ children: [
5333
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5334
+ "rect",
5335
+ {
5336
+ x: "-5",
5337
+ y: "-5",
5338
+ width: symbol.viewBox.width + 10,
5339
+ height: symbol.viewBox.height + 10,
5340
+ fill: "none",
5341
+ stroke: isDragging ? "#10b981" : "#3b82f6",
5342
+ strokeWidth: isDragging ? "3" : "2",
5343
+ strokeDasharray: "4 2",
5344
+ rx: "2",
5345
+ pointerEvents: "none"
5346
+ }
5347
+ ),
5348
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5349
+ "g",
5350
+ {
5351
+ dangerouslySetInnerHTML: { __html: symbol.svgContent },
5352
+ "data-symbol-content": symbol.id,
5353
+ style: { pointerEvents: "auto" }
5354
+ }
5355
+ ),
5356
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5357
+ "text",
5358
+ {
5359
+ x: labelX,
5360
+ y: labelY,
5361
+ textAnchor: labelAnchor,
5362
+ fontSize: node.labelFontSize ?? 10,
5363
+ fontWeight: "600",
5364
+ fill: isSelected ? "#3b82f6" : "#1f2937",
5365
+ transform: node.transform.rotation ? `rotate(${-node.transform.rotation}, ${labelX}, ${labelY})` : void 0,
5366
+ children: node.label || node.id
5367
+ }
5368
+ )
5369
+ ]
5370
+ },
5371
+ node.id
5372
+ );
5373
+ })
5374
+ ] });
4993
5375
  }
4994
- var NodeRenderer = import_react10.default.memo(NodeRendererImpl);
5376
+ var NodeRenderer = import_react11.default.memo(NodeRendererImpl);
4995
5377
 
4996
5378
  // src/components/PIDCanvas/PipeRenderer.tsx
4997
- var import_react11 = __toESM(require("react"), 1);
4998
- var import_jsx_runtime17 = require("react/jsx-runtime");
5379
+ var import_react12 = __toESM(require("react"), 1);
5380
+ var import_jsx_runtime18 = require("react/jsx-runtime");
4999
5381
  function PipeRendererImpl({
5000
5382
  pipes,
5001
5383
  selectedPipeIds,
@@ -5010,7 +5392,7 @@ function PipeRendererImpl({
5010
5392
  waypointDragOffset,
5011
5393
  onPipeSegmentClick
5012
5394
  }) {
5013
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { id: "layer-pipes", children: pipes.filter((pipe) => pipe.visible !== false).map((pipe) => {
5395
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("g", { id: "layer-pipes", children: pipes.filter((pipe) => pipe.visible !== false).map((pipe) => {
5014
5396
  const isSelected = selectedPipeIds?.has(pipe.id) ?? false;
5015
5397
  const isEditing = enableWaypointEditing && editingPipeId === pipe.id;
5016
5398
  const showEditingControls = enableWaypointEditing && isSelected;
@@ -5029,7 +5411,7 @@ function PipeRendererImpl({
5029
5411
  const strokeWidth = isSelected ? (pipeStyle.strokeWidth || 3) + 2 : pipeStyle.strokeWidth || 3;
5030
5412
  const strokeDasharray = pipeStyle.strokeDasharray;
5031
5413
  const opacity = pipeStyle.opacity !== void 0 ? pipeStyle.opacity : 1;
5032
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5414
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5033
5415
  "g",
5034
5416
  {
5035
5417
  "data-pipe-id": pipe.id,
@@ -5042,8 +5424,8 @@ function PipeRendererImpl({
5042
5424
  cursor: onPipeClick ? "pointer" : "default"
5043
5425
  },
5044
5426
  children: [
5045
- !enableWaypointEditing && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
5046
- onPipeClick && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5427
+ !enableWaypointEditing && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5428
+ onPipeClick && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5047
5429
  "polyline",
5048
5430
  {
5049
5431
  points: pointsString,
@@ -5055,7 +5437,7 @@ function PipeRendererImpl({
5055
5437
  pointerEvents: "stroke"
5056
5438
  }
5057
5439
  ),
5058
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5440
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5059
5441
  "polyline",
5060
5442
  {
5061
5443
  points: pointsString,
@@ -5079,7 +5461,7 @@ function PipeRendererImpl({
5079
5461
  const waypointSize = isSelectedWaypoint ? 8 : isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
5080
5462
  const waypointOpacity = isSelectedWaypoint ? 1 : isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
5081
5463
  const waypointColor = isSelectedWaypoint ? "#3b82f6" : isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
5082
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5464
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5083
5465
  "circle",
5084
5466
  {
5085
5467
  cx: point.x,
@@ -5116,12 +5498,12 @@ function PipeRendererImpl({
5116
5498
  `${pipe.id}-point-${idx}`
5117
5499
  );
5118
5500
  }),
5119
- showEditingControls && onPipeSegmentClick && displayPoints.length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children: displayPoints.slice(0, -1).map((point, idx) => {
5501
+ showEditingControls && onPipeSegmentClick && displayPoints.length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: displayPoints.slice(0, -1).map((point, idx) => {
5120
5502
  const nextPoint = displayPoints[idx + 1];
5121
5503
  const midX = (point.x + nextPoint.x) / 2;
5122
5504
  const midY = (point.y + nextPoint.y) / 2;
5123
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("g", { children: [
5124
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5505
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("g", { children: [
5506
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5125
5507
  "circle",
5126
5508
  {
5127
5509
  cx: midX,
@@ -5137,7 +5519,7 @@ function PipeRendererImpl({
5137
5519
  }
5138
5520
  }
5139
5521
  ),
5140
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5522
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5141
5523
  "circle",
5142
5524
  {
5143
5525
  cx: midX,
@@ -5153,7 +5535,7 @@ function PipeRendererImpl({
5153
5535
  )
5154
5536
  ] }, `${pipe.id}-segment-${idx}`);
5155
5537
  }) }),
5156
- pipe.showLabel !== false && pipe.label && pipe.routePoints.length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5538
+ pipe.showLabel !== false && pipe.label && pipe.routePoints.length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5157
5539
  "text",
5158
5540
  {
5159
5541
  x: pipe.routePoints[Math.floor(pipe.routePoints.length / 2)].x + (pipe.labelOffset?.x ?? 0),
@@ -5173,11 +5555,11 @@ function PipeRendererImpl({
5173
5555
  );
5174
5556
  }) });
5175
5557
  }
5176
- var PipeRenderer = import_react11.default.memo(PipeRendererImpl);
5558
+ var PipeRenderer = import_react12.default.memo(PipeRendererImpl);
5177
5559
 
5178
5560
  // src/components/PIDCanvas/DataOverlay.tsx
5179
- var import_react12 = __toESM(require("react"), 1);
5180
- var import_jsx_runtime18 = require("react/jsx-runtime");
5561
+ var import_react13 = __toESM(require("react"), 1);
5562
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5181
5563
  var PULSE_KEYFRAME_ID = "pid-data-overlay-pulse-keyframes";
5182
5564
  var PULSE_ANIMATION = "pid-data-overlay-pulse 1.5s ease-in-out infinite";
5183
5565
  function ensurePulseKeyframes() {
@@ -5188,9 +5570,10 @@ function ensurePulseKeyframes() {
5188
5570
  style.textContent = "@keyframes pid-data-overlay-pulse{0%{opacity:.9}50%{opacity:.3}100%{opacity:.9}}";
5189
5571
  document.head.appendChild(style);
5190
5572
  }
5191
- function renderStatusDot(cx, cy, scale, fill) {
5192
- ensurePulseKeyframes();
5193
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5573
+ function renderStatusDot(cx, cy, scale, fill, status) {
5574
+ const isUnknown = normalizeDeviceStatus(status) === "unknown";
5575
+ if (!isUnknown) ensurePulseKeyframes();
5576
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5194
5577
  "circle",
5195
5578
  {
5196
5579
  className: "pid-data-overlay-status-dot",
@@ -5198,7 +5581,7 @@ function renderStatusDot(cx, cy, scale, fill) {
5198
5581
  cy: cy - 18 * scale,
5199
5582
  r: 3 * scale,
5200
5583
  fill,
5201
- style: { animation: PULSE_ANIMATION }
5584
+ style: isUnknown ? void 0 : { animation: PULSE_ANIMATION }
5202
5585
  }
5203
5586
  );
5204
5587
  }
@@ -5224,7 +5607,7 @@ function DataOverlayImpl({
5224
5607
  const backgroundColor = display?.backgroundColor;
5225
5608
  const offsetX = display?.offsetX ?? 0;
5226
5609
  const offsetY = display?.offsetY ?? 0;
5227
- const sparklinePath = import_react12.default.useMemo(() => {
5610
+ const sparklinePath = import_react13.default.useMemo(() => {
5228
5611
  if (!showSparkline || !history || history.length < 2) return "";
5229
5612
  const width = 70 * scale;
5230
5613
  const height = 20 * scale;
@@ -5243,7 +5626,7 @@ function DataOverlayImpl({
5243
5626
  return `M ${points.join(" L ")}`;
5244
5627
  }, [showSparkline, history, scaling, scale]);
5245
5628
  if (!showValue) {
5246
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, {});
5629
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, {});
5247
5630
  }
5248
5631
  const formatValue = (val) => {
5249
5632
  if (typeof val === "number") {
@@ -5268,10 +5651,11 @@ function DataOverlayImpl({
5268
5651
  const extraMarkRows = realDisplayText ? 1 : 0;
5269
5652
  const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale + extraMarkRows * 9 * scale;
5270
5653
  const overlayWidth = 80 * scale;
5654
+ const isUnknownValue = normalizeDeviceStatus(value.status) === "unknown";
5271
5655
  const displayX = x + offsetX;
5272
5656
  const displayY = y + offsetY;
5273
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("g", { "data-overlay-node": nodeId, "data-status": value.status, "data-forced": isForced, children: [
5274
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5657
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("g", { "data-overlay-node": nodeId, "data-status": value.status, "data-forced": isForced, children: [
5658
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5275
5659
  "rect",
5276
5660
  {
5277
5661
  x: displayX - overlayWidth / 2,
@@ -5286,7 +5670,7 @@ function DataOverlayImpl({
5286
5670
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
5287
5671
  }
5288
5672
  ),
5289
- label && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5673
+ label && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5290
5674
  "text",
5291
5675
  {
5292
5676
  x: displayX,
@@ -5299,7 +5683,7 @@ function DataOverlayImpl({
5299
5683
  children: label
5300
5684
  }
5301
5685
  ),
5302
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5686
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5303
5687
  "text",
5304
5688
  {
5305
5689
  x: displayX,
@@ -5312,8 +5696,17 @@ function DataOverlayImpl({
5312
5696
  children: displayText
5313
5697
  }
5314
5698
  ),
5315
- showStatus && value.status !== "normal" && renderStatusDot(displayX, displayY, scale, finalTextColor),
5316
- value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5699
+ showStatus && value.status !== "normal" && renderStatusDot(displayX, displayY, scale, finalTextColor, value.status),
5700
+ showStatus && isUnknownValue && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5701
+ UnknownStatusGlyph,
5702
+ {
5703
+ x: displayX - overlayWidth / 2 + 2 * scale,
5704
+ y: displayY - 25 * scale - 6 * scale,
5705
+ width: 12 * scale,
5706
+ height: 12 * scale
5707
+ }
5708
+ ),
5709
+ value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5317
5710
  "text",
5318
5711
  {
5319
5712
  x: displayX,
@@ -5330,13 +5723,13 @@ function DataOverlayImpl({
5330
5723
  ]
5331
5724
  }
5332
5725
  ),
5333
- isForced && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5726
+ isForced && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5334
5727
  "g",
5335
5728
  {
5336
5729
  transform: `translate(${displayX + overlayWidth / 2}, ${displayY - 25 * scale})`,
5337
5730
  "aria-label": "Forced",
5338
5731
  children: [
5339
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5732
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5340
5733
  "circle",
5341
5734
  {
5342
5735
  r: 7 * scale,
@@ -5345,7 +5738,7 @@ function DataOverlayImpl({
5345
5738
  strokeWidth: 1.5 * scale
5346
5739
  }
5347
5740
  ),
5348
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5741
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5349
5742
  "text",
5350
5743
  {
5351
5744
  textAnchor: "middle",
@@ -5359,7 +5752,7 @@ function DataOverlayImpl({
5359
5752
  ]
5360
5753
  }
5361
5754
  ),
5362
- realDisplayText && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5755
+ realDisplayText && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5363
5756
  "text",
5364
5757
  {
5365
5758
  x: displayX,
@@ -5372,7 +5765,7 @@ function DataOverlayImpl({
5372
5765
  children: realDisplayText
5373
5766
  }
5374
5767
  ),
5375
- hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5768
+ hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5376
5769
  "path",
5377
5770
  {
5378
5771
  d: sparklinePath,
@@ -5386,11 +5779,11 @@ function DataOverlayImpl({
5386
5779
  ) })
5387
5780
  ] });
5388
5781
  }
5389
- var DataOverlay = import_react12.default.memo(DataOverlayImpl);
5782
+ var DataOverlay = import_react13.default.memo(DataOverlayImpl);
5390
5783
 
5391
5784
  // src/components/PIDCanvas/PositionPanel.tsx
5392
- var import_react13 = require("react");
5393
- var import_jsx_runtime19 = require("react/jsx-runtime");
5785
+ var import_react14 = require("react");
5786
+ var import_jsx_runtime20 = require("react/jsx-runtime");
5394
5787
  function PositionPanel({
5395
5788
  selectedNode,
5396
5789
  selectedWaypoint,
@@ -5398,9 +5791,9 @@ function PositionPanel({
5398
5791
  onWaypointPositionChange,
5399
5792
  position = "top-right"
5400
5793
  }) {
5401
- const [x, setX] = (0, import_react13.useState)("");
5402
- const [y, setY] = (0, import_react13.useState)("");
5403
- (0, import_react13.useEffect)(() => {
5794
+ const [x, setX] = (0, import_react14.useState)("");
5795
+ const [y, setY] = (0, import_react14.useState)("");
5796
+ (0, import_react14.useEffect)(() => {
5404
5797
  if (selectedNode) {
5405
5798
  setX(selectedNode.transform.x.toFixed(2));
5406
5799
  setY(selectedNode.transform.y.toFixed(2));
@@ -5459,7 +5852,7 @@ function PositionPanel({
5459
5852
  "bottom-left": { bottom: 10, left: 10 },
5460
5853
  "bottom-right": { bottom: 10, right: 10 }
5461
5854
  };
5462
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5855
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
5463
5856
  "div",
5464
5857
  {
5465
5858
  style: {
@@ -5476,11 +5869,11 @@ function PositionPanel({
5476
5869
  fontSize: "13px"
5477
5870
  },
5478
5871
  children: [
5479
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
5480
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
5481
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
5482
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
5483
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5872
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
5873
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
5874
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
5875
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
5876
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5484
5877
  "input",
5485
5878
  {
5486
5879
  type: "number",
@@ -5498,9 +5891,9 @@ function PositionPanel({
5498
5891
  }
5499
5892
  )
5500
5893
  ] }),
5501
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
5502
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
5503
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5894
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
5895
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
5896
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5504
5897
  "input",
5505
5898
  {
5506
5899
  type: "number",
@@ -5519,14 +5912,14 @@ function PositionPanel({
5519
5912
  )
5520
5913
  ] })
5521
5914
  ] }),
5522
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
5915
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
5523
5916
  ]
5524
5917
  }
5525
5918
  );
5526
5919
  }
5527
5920
 
5528
5921
  // src/diagram/hooks/useViewBox.ts
5529
- var import_react14 = require("react");
5922
+ var import_react15 = require("react");
5530
5923
  function useViewBox(options) {
5531
5924
  const {
5532
5925
  initialViewBox,
@@ -5537,16 +5930,16 @@ function useViewBox(options) {
5537
5930
  zoomSensitivity = 1e-3,
5538
5931
  allowLeftClickPan = true
5539
5932
  } = options;
5540
- const [viewBox, setViewBox] = (0, import_react14.useState)(initialViewBox);
5541
- const svgRef = (0, import_react14.useRef)(null);
5542
- const isPanningRef = (0, import_react14.useRef)(false);
5543
- const lastMousePosRef = (0, import_react14.useRef)({ x: 0, y: 0 });
5544
- const spaceKeyDownRef = (0, import_react14.useRef)(false);
5545
- const lastTouchDistanceRef = (0, import_react14.useRef)(null);
5546
- const resetViewBox = (0, import_react14.useCallback)(() => {
5933
+ const [viewBox, setViewBox] = (0, import_react15.useState)(initialViewBox);
5934
+ const svgRef = (0, import_react15.useRef)(null);
5935
+ const isPanningRef = (0, import_react15.useRef)(false);
5936
+ const lastMousePosRef = (0, import_react15.useRef)({ x: 0, y: 0 });
5937
+ const spaceKeyDownRef = (0, import_react15.useRef)(false);
5938
+ const lastTouchDistanceRef = (0, import_react15.useRef)(null);
5939
+ const resetViewBox = (0, import_react15.useCallback)(() => {
5547
5940
  setViewBox(initialViewBox);
5548
5941
  }, [initialViewBox]);
5549
- const zoomIn = (0, import_react14.useCallback)(() => {
5942
+ const zoomIn = (0, import_react15.useCallback)(() => {
5550
5943
  setViewBox((prev) => {
5551
5944
  const currentZoom = initialViewBox.width / prev.width;
5552
5945
  const newZoom = Math.min(1 / minZoom, currentZoom * 1.25);
@@ -5563,7 +5956,7 @@ function useViewBox(options) {
5563
5956
  };
5564
5957
  });
5565
5958
  }, [initialViewBox, minZoom]);
5566
- const zoomOut = (0, import_react14.useCallback)(() => {
5959
+ const zoomOut = (0, import_react15.useCallback)(() => {
5567
5960
  setViewBox((prev) => {
5568
5961
  const currentZoom = initialViewBox.width / prev.width;
5569
5962
  const newZoom = Math.max(1 / maxZoom, currentZoom * 0.8);
@@ -5580,7 +5973,7 @@ function useViewBox(options) {
5580
5973
  };
5581
5974
  });
5582
5975
  }, [initialViewBox, maxZoom]);
5583
- const fitToContent = (0, import_react14.useCallback)((bounds, padding = 50) => {
5976
+ const fitToContent = (0, import_react15.useCallback)((bounds, padding = 50) => {
5584
5977
  const contentWidth = bounds.maxX - bounds.minX;
5585
5978
  const contentHeight = bounds.maxY - bounds.minY;
5586
5979
  if (contentWidth === 0 || contentHeight === 0) {
@@ -5608,7 +6001,7 @@ function useViewBox(options) {
5608
6001
  height: newHeight
5609
6002
  });
5610
6003
  }, [initialViewBox]);
5611
- (0, import_react14.useEffect)(() => {
6004
+ (0, import_react15.useEffect)(() => {
5612
6005
  if (!enableZoom || !svgRef.current) return;
5613
6006
  const svg = svgRef.current;
5614
6007
  const handleWheel = (e) => {
@@ -5641,7 +6034,7 @@ function useViewBox(options) {
5641
6034
  svg.addEventListener("wheel", handleWheel, { passive: false });
5642
6035
  return () => svg.removeEventListener("wheel", handleWheel);
5643
6036
  }, [enableZoom, zoomSensitivity, minZoom, initialViewBox]);
5644
- (0, import_react14.useEffect)(() => {
6037
+ (0, import_react15.useEffect)(() => {
5645
6038
  if (!enablePan || !svgRef.current) return;
5646
6039
  const svg = svgRef.current;
5647
6040
  const handleMouseDown = (e) => {
@@ -5717,7 +6110,7 @@ function useViewBox(options) {
5717
6110
  window.removeEventListener("keyup", handleKeyUp);
5718
6111
  };
5719
6112
  }, [enablePan, allowLeftClickPan]);
5720
- (0, import_react14.useEffect)(() => {
6113
+ (0, import_react15.useEffect)(() => {
5721
6114
  if (!svgRef.current) return;
5722
6115
  if (!enablePan && !enableZoom) return;
5723
6116
  const svg = svgRef.current;
@@ -5808,7 +6201,7 @@ function useViewBox(options) {
5808
6201
  svg.removeEventListener("touchend", handleTouchEnd);
5809
6202
  };
5810
6203
  }, [enablePan, enableZoom, minZoom, initialViewBox]);
5811
- const screenToWorld = (0, import_react14.useCallback)(
6204
+ const screenToWorld = (0, import_react15.useCallback)(
5812
6205
  (screenX, screenY) => {
5813
6206
  const svg = svgRef.current;
5814
6207
  if (!svg) return { x: screenX, y: screenY };
@@ -5847,15 +6240,15 @@ function useViewBox(options) {
5847
6240
  }
5848
6241
 
5849
6242
  // src/diagram/hooks/useSelection.ts
5850
- var import_react15 = require("react");
6243
+ var import_react16 = require("react");
5851
6244
  function useSelection(options = {}) {
5852
6245
  const {
5853
6246
  multiSelect = true,
5854
6247
  initialSelection = { selectedNodeIds: /* @__PURE__ */ new Set(), selectedPipeIds: /* @__PURE__ */ new Set() },
5855
6248
  onSelectionChange
5856
6249
  } = options;
5857
- const [selection, setSelection] = (0, import_react15.useState)(initialSelection);
5858
- const notifyChange = (0, import_react15.useCallback)(
6250
+ const [selection, setSelection] = (0, import_react16.useState)(initialSelection);
6251
+ const notifyChange = (0, import_react16.useCallback)(
5859
6252
  (newSelection) => {
5860
6253
  if (onSelectionChange) {
5861
6254
  onSelectionChange(newSelection);
@@ -5863,15 +6256,15 @@ function useSelection(options = {}) {
5863
6256
  },
5864
6257
  [onSelectionChange]
5865
6258
  );
5866
- const isNodeSelected = (0, import_react15.useCallback)(
6259
+ const isNodeSelected = (0, import_react16.useCallback)(
5867
6260
  (nodeId) => selection.selectedNodeIds.has(nodeId),
5868
6261
  [selection.selectedNodeIds]
5869
6262
  );
5870
- const isPipeSelected = (0, import_react15.useCallback)(
6263
+ const isPipeSelected = (0, import_react16.useCallback)(
5871
6264
  (pipeId) => selection.selectedPipeIds.has(pipeId),
5872
6265
  [selection.selectedPipeIds]
5873
6266
  );
5874
- const selectNode = (0, import_react15.useCallback)(
6267
+ const selectNode = (0, import_react16.useCallback)(
5875
6268
  (nodeId, isMultiSelect = false) => {
5876
6269
  setSelection((prev) => {
5877
6270
  const newNodeIds = new Set(isMultiSelect && multiSelect ? prev.selectedNodeIds : []);
@@ -5890,7 +6283,7 @@ function useSelection(options = {}) {
5890
6283
  },
5891
6284
  [multiSelect, notifyChange]
5892
6285
  );
5893
- const selectPipe = (0, import_react15.useCallback)(
6286
+ const selectPipe = (0, import_react16.useCallback)(
5894
6287
  (pipeId, isMultiSelect = false) => {
5895
6288
  setSelection((prev) => {
5896
6289
  const newPipeIds = new Set(isMultiSelect && multiSelect ? prev.selectedPipeIds : []);
@@ -5909,7 +6302,7 @@ function useSelection(options = {}) {
5909
6302
  },
5910
6303
  [multiSelect, notifyChange]
5911
6304
  );
5912
- const clearSelection = (0, import_react15.useCallback)(() => {
6305
+ const clearSelection = (0, import_react16.useCallback)(() => {
5913
6306
  const newSelection = {
5914
6307
  selectedNodeIds: /* @__PURE__ */ new Set(),
5915
6308
  selectedPipeIds: /* @__PURE__ */ new Set()
@@ -5917,7 +6310,7 @@ function useSelection(options = {}) {
5917
6310
  setSelection(newSelection);
5918
6311
  notifyChange(newSelection);
5919
6312
  }, [notifyChange]);
5920
- const selectNodes = (0, import_react15.useCallback)(
6313
+ const selectNodes = (0, import_react16.useCallback)(
5921
6314
  (nodeIds) => {
5922
6315
  const newSelection = {
5923
6316
  selectedNodeIds: new Set(nodeIds),
@@ -5928,7 +6321,7 @@ function useSelection(options = {}) {
5928
6321
  },
5929
6322
  [notifyChange]
5930
6323
  );
5931
- const selectPipes = (0, import_react15.useCallback)(
6324
+ const selectPipes = (0, import_react16.useCallback)(
5932
6325
  (pipeIds) => {
5933
6326
  const newSelection = {
5934
6327
  selectedNodeIds: /* @__PURE__ */ new Set(),
@@ -5952,23 +6345,23 @@ function useSelection(options = {}) {
5952
6345
  }
5953
6346
 
5954
6347
  // src/diagram/hooks/useTelemetry.ts
5955
- var import_react16 = require("react");
6348
+ var import_react17 = require("react");
5956
6349
  function useTelemetry(options = {}) {
5957
6350
  const {
5958
6351
  initialBindings = [],
5959
6352
  onTelemetryChange,
5960
6353
  refreshInterval = 0
5961
6354
  } = options;
5962
- const [telemetry, setTelemetry] = (0, import_react16.useState)(() => {
6355
+ const [telemetry, setTelemetry] = (0, import_react17.useState)(() => {
5963
6356
  const map = /* @__PURE__ */ new Map();
5964
6357
  initialBindings.forEach((binding) => {
5965
6358
  map.set(binding.nodeId, binding);
5966
6359
  });
5967
6360
  return map;
5968
6361
  });
5969
- const telemetryRef = (0, import_react16.useRef)(telemetry);
6362
+ const telemetryRef = (0, import_react17.useRef)(telemetry);
5970
6363
  telemetryRef.current = telemetry;
5971
- const notifyChange = (0, import_react16.useCallback)(
6364
+ const notifyChange = (0, import_react17.useCallback)(
5972
6365
  (newTelemetry) => {
5973
6366
  if (onTelemetryChange) {
5974
6367
  onTelemetryChange(newTelemetry);
@@ -5976,7 +6369,7 @@ function useTelemetry(options = {}) {
5976
6369
  },
5977
6370
  [onTelemetryChange]
5978
6371
  );
5979
- const updateTelemetry = (0, import_react16.useCallback)(
6372
+ const updateTelemetry = (0, import_react17.useCallback)(
5980
6373
  (nodeId, value) => {
5981
6374
  setTelemetry((prev) => {
5982
6375
  const newMap = new Map(prev);
@@ -6006,7 +6399,7 @@ function useTelemetry(options = {}) {
6006
6399
  },
6007
6400
  [notifyChange]
6008
6401
  );
6009
- const updateTelemetryBatch = (0, import_react16.useCallback)(
6402
+ const updateTelemetryBatch = (0, import_react17.useCallback)(
6010
6403
  (updates) => {
6011
6404
  setTelemetry((prev) => {
6012
6405
  const newMap = new Map(prev);
@@ -6060,16 +6453,16 @@ function useTelemetry(options = {}) {
6060
6453
  },
6061
6454
  [notifyChange]
6062
6455
  );
6063
- const getTelemetry = (0, import_react16.useCallback)(
6456
+ const getTelemetry = (0, import_react17.useCallback)(
6064
6457
  (nodeId) => telemetryRef.current.get(nodeId),
6065
6458
  []
6066
6459
  );
6067
- const clearTelemetry = (0, import_react16.useCallback)(() => {
6460
+ const clearTelemetry = (0, import_react17.useCallback)(() => {
6068
6461
  const newMap = /* @__PURE__ */ new Map();
6069
6462
  setTelemetry(newMap);
6070
6463
  notifyChange(newMap);
6071
6464
  }, [notifyChange]);
6072
- const setBindings = (0, import_react16.useCallback)(
6465
+ const setBindings = (0, import_react17.useCallback)(
6073
6466
  (bindings) => {
6074
6467
  const newMap = /* @__PURE__ */ new Map();
6075
6468
  bindings.forEach((binding) => {
@@ -6080,7 +6473,7 @@ function useTelemetry(options = {}) {
6080
6473
  },
6081
6474
  [notifyChange]
6082
6475
  );
6083
- (0, import_react16.useEffect)(() => {
6476
+ (0, import_react17.useEffect)(() => {
6084
6477
  if (refreshInterval <= 0) return;
6085
6478
  const intervalId = setInterval(() => {
6086
6479
  notifyChange(telemetryRef.current);
@@ -6098,10 +6491,10 @@ function useTelemetry(options = {}) {
6098
6491
  }
6099
6492
 
6100
6493
  // src/diagram/hooks/useTelemetryStatus.ts
6101
- var import_react17 = require("react");
6494
+ var import_react18 = require("react");
6102
6495
  function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
6103
6496
  const { enabled = true, mapStatus } = options;
6104
- (0, import_react17.useEffect)(() => {
6497
+ (0, import_react18.useEffect)(() => {
6105
6498
  if (!enabled) return;
6106
6499
  const defaultMapStatus = (telemetryStatus) => {
6107
6500
  switch (telemetryStatus) {
@@ -6114,8 +6507,13 @@ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
6114
6507
  return "alarm";
6115
6508
  case "off":
6116
6509
  return "stopped";
6117
- default:
6510
+ case "unknown":
6511
+ return "unknown";
6512
+ default: {
6513
+ const exhaustive = telemetryStatus;
6514
+ void exhaustive;
6118
6515
  return "stopped";
6516
+ }
6119
6517
  }
6120
6518
  };
6121
6519
  const statusMapper = mapStatus || defaultMapStatus;
@@ -6139,8 +6537,49 @@ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
6139
6537
  }, [telemetry, diagram, setDiagram, enabled, mapStatus]);
6140
6538
  }
6141
6539
 
6540
+ // src/diagram/hooks/useBooleanIndicators.ts
6541
+ var import_react19 = require("react");
6542
+ function useBooleanIndicators(telemetry, diagram, setDiagram, options = {}) {
6543
+ const { enabled = true, symbolId = "status-indicator" } = options;
6544
+ (0, import_react19.useEffect)(() => {
6545
+ if (!enabled) return;
6546
+ const tagIndex = /* @__PURE__ */ new Map();
6547
+ telemetry.forEach((binding) => {
6548
+ if (binding.tag) tagIndex.set(binding.tag, binding.value.value);
6549
+ });
6550
+ let needsUpdate = false;
6551
+ const updatedNodes = diagram.nodes.map((node) => {
6552
+ if (node.symbolId !== symbolId) return node;
6553
+ const meta = node.metadata ?? {};
6554
+ const colorSource = typeof meta.colorSource === "string" ? meta.colorSource : "";
6555
+ const visibleSource = typeof meta.visibleSource === "string" ? meta.visibleSource : "";
6556
+ let next = node;
6557
+ if (colorSource) {
6558
+ const isTrue = Boolean(tagIndex.get(colorSource));
6559
+ const fill = resolvePresetColor(
6560
+ String(isTrue ? meta.onColor ?? "green" : meta.offColor ?? "red")
6561
+ );
6562
+ if (node.customColors?.fill !== fill) {
6563
+ next = { ...next, customColors: { ...next.customColors, fill } };
6564
+ }
6565
+ }
6566
+ if (visibleSource) {
6567
+ const vis = Boolean(tagIndex.get(visibleSource));
6568
+ if ((next.visible ?? true) !== vis) {
6569
+ next = { ...next, visible: vis };
6570
+ }
6571
+ }
6572
+ if (next !== node) needsUpdate = true;
6573
+ return next;
6574
+ });
6575
+ if (needsUpdate) {
6576
+ setDiagram({ ...diagram, nodes: updatedNodes });
6577
+ }
6578
+ }, [telemetry, diagram, setDiagram, enabled, symbolId]);
6579
+ }
6580
+
6142
6581
  // src/diagram/hooks/useDrag.ts
6143
- var import_react18 = require("react");
6582
+ var import_react20 = require("react");
6144
6583
  function useDrag(options = {}) {
6145
6584
  const {
6146
6585
  onDragStart,
@@ -6150,23 +6589,23 @@ function useDrag(options = {}) {
6150
6589
  dragThreshold = 3,
6151
6590
  screenToWorld = (x, y) => ({ x, y })
6152
6591
  } = options;
6153
- const [dragState, setDragState] = (0, import_react18.useState)({
6592
+ const [dragState, setDragState] = (0, import_react20.useState)({
6154
6593
  isDragging: false,
6155
6594
  startPosition: null,
6156
6595
  offset: { x: 0, y: 0 },
6157
6596
  draggedId: null
6158
6597
  });
6159
- const [isListening, setIsListening] = (0, import_react18.useState)(false);
6160
- const dragStartRef = (0, import_react18.useRef)(null);
6161
- const hasMovedRef = (0, import_react18.useRef)(false);
6162
- const applySnap = (0, import_react18.useCallback)(
6598
+ const [isListening, setIsListening] = (0, import_react20.useState)(false);
6599
+ const dragStartRef = (0, import_react20.useRef)(null);
6600
+ const hasMovedRef = (0, import_react20.useRef)(false);
6601
+ const applySnap = (0, import_react20.useCallback)(
6163
6602
  (value) => {
6164
6603
  if (snapToGrid <= 0) return value;
6165
6604
  return Math.round(value / snapToGrid) * snapToGrid;
6166
6605
  },
6167
6606
  [snapToGrid]
6168
6607
  );
6169
- const handleMouseMove = (0, import_react18.useCallback)(
6608
+ const handleMouseMove = (0, import_react20.useCallback)(
6170
6609
  (event) => {
6171
6610
  if (!dragStartRef.current) return;
6172
6611
  const { id, screenStart: _screenStart, itemPosition, clickOffset } = dragStartRef.current;
@@ -6207,7 +6646,7 @@ function useDrag(options = {}) {
6207
6646
  },
6208
6647
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
6209
6648
  );
6210
- const handleMouseUp = (0, import_react18.useCallback)(
6649
+ const handleMouseUp = (0, import_react20.useCallback)(
6211
6650
  (event) => {
6212
6651
  if (!dragStartRef.current || !hasMovedRef.current) {
6213
6652
  dragStartRef.current = null;
@@ -6244,7 +6683,7 @@ function useDrag(options = {}) {
6244
6683
  },
6245
6684
  [screenToWorld, applySnap, onDragEnd]
6246
6685
  );
6247
- const handleTouchMove = (0, import_react18.useCallback)(
6686
+ const handleTouchMove = (0, import_react20.useCallback)(
6248
6687
  (event) => {
6249
6688
  if (!dragStartRef.current || event.touches.length !== 1) return;
6250
6689
  const touch = event.touches[0];
@@ -6287,7 +6726,7 @@ function useDrag(options = {}) {
6287
6726
  },
6288
6727
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
6289
6728
  );
6290
- const handleTouchEnd = (0, import_react18.useCallback)(
6729
+ const handleTouchEnd = (0, import_react20.useCallback)(
6291
6730
  (event) => {
6292
6731
  if (!dragStartRef.current || !hasMovedRef.current) {
6293
6732
  dragStartRef.current = null;
@@ -6325,7 +6764,7 @@ function useDrag(options = {}) {
6325
6764
  },
6326
6765
  [screenToWorld, applySnap, onDragEnd]
6327
6766
  );
6328
- (0, import_react18.useEffect)(() => {
6767
+ (0, import_react20.useEffect)(() => {
6329
6768
  if (!isListening) return;
6330
6769
  document.addEventListener("mousemove", handleMouseMove);
6331
6770
  document.addEventListener("mouseup", handleMouseUp);
@@ -6340,7 +6779,7 @@ function useDrag(options = {}) {
6340
6779
  document.removeEventListener("touchcancel", handleTouchEnd);
6341
6780
  };
6342
6781
  }, [isListening, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
6343
- const startDrag = (0, import_react18.useCallback)(
6782
+ const startDrag = (0, import_react20.useCallback)(
6344
6783
  (id, event, itemPosition) => {
6345
6784
  event.stopPropagation();
6346
6785
  let clientX;
@@ -6370,7 +6809,7 @@ function useDrag(options = {}) {
6370
6809
  },
6371
6810
  [screenToWorld]
6372
6811
  );
6373
- const cancelDrag = (0, import_react18.useCallback)(() => {
6812
+ const cancelDrag = (0, import_react20.useCallback)(() => {
6374
6813
  dragStartRef.current = null;
6375
6814
  hasMovedRef.current = false;
6376
6815
  setIsListening(false);
@@ -6381,7 +6820,7 @@ function useDrag(options = {}) {
6381
6820
  draggedId: null
6382
6821
  });
6383
6822
  }, []);
6384
- const isDraggingItem = (0, import_react18.useCallback)(
6823
+ const isDraggingItem = (0, import_react20.useCallback)(
6385
6824
  (id) => dragState.isDragging && dragState.draggedId === id,
6386
6825
  [dragState.isDragging, dragState.draggedId]
6387
6826
  );
@@ -6394,7 +6833,7 @@ function useDrag(options = {}) {
6394
6833
  }
6395
6834
 
6396
6835
  // src/diagram/hooks/useKeyboardShortcuts.ts
6397
- var import_react19 = require("react");
6836
+ var import_react21 = require("react");
6398
6837
  function useKeyboardShortcuts(options) {
6399
6838
  const {
6400
6839
  onDelete,
@@ -6405,7 +6844,7 @@ function useKeyboardShortcuts(options) {
6405
6844
  onSelectAll,
6406
6845
  enabled = true
6407
6846
  } = options;
6408
- (0, import_react19.useEffect)(() => {
6847
+ (0, import_react21.useEffect)(() => {
6409
6848
  if (!enabled) return;
6410
6849
  const handleKeyDown = (event) => {
6411
6850
  const target = event.target;
@@ -6514,7 +6953,7 @@ function nodeHasPort(node, portId) {
6514
6953
  }
6515
6954
 
6516
6955
  // src/components/PIDCanvas/PIDCanvas.tsx
6517
- var import_jsx_runtime20 = require("react/jsx-runtime");
6956
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6518
6957
  var DEFAULT_FEATURES = {
6519
6958
  pan: true,
6520
6959
  zoom: true,
@@ -6543,21 +6982,21 @@ function PIDCanvasImpl({
6543
6982
  statusColorMap,
6544
6983
  ...props
6545
6984
  }) {
6546
- const [localDiagram, setLocalDiagram] = (0, import_react20.useState)(diagram);
6547
- const [dragOverPosition, setDragOverPosition] = (0, import_react20.useState)(null);
6548
- const [isConnecting, setIsConnecting] = (0, import_react20.useState)(false);
6549
- const [connectionSource, setConnectionSource] = (0, import_react20.useState)(null);
6550
- const [connectionSourcePort, setConnectionSourcePort] = (0, import_react20.useState)(null);
6551
- const [connectionCursor, setConnectionCursor] = (0, import_react20.useState)(null);
6552
- const [hoveredNode, setHoveredNode] = (0, import_react20.useState)(null);
6553
- const [hoveredPort, setHoveredPort] = (0, import_react20.useState)(null);
6554
- const [selectedWaypoint, setSelectedWaypoint] = (0, import_react20.useState)(null);
6555
- const historyStack = (0, import_react20.useRef)([]);
6556
- const redoStack = (0, import_react20.useRef)([]);
6557
- const isUndoRedoAction = (0, import_react20.useRef)(false);
6558
- const isInternalChange = (0, import_react20.useRef)(false);
6559
- const lastInternalDiagram = (0, import_react20.useRef)(null);
6560
- (0, import_react20.useEffect)(() => {
6985
+ const [localDiagram, setLocalDiagram] = (0, import_react22.useState)(diagram);
6986
+ const [dragOverPosition, setDragOverPosition] = (0, import_react22.useState)(null);
6987
+ const [isConnecting, setIsConnecting] = (0, import_react22.useState)(false);
6988
+ const [connectionSource, setConnectionSource] = (0, import_react22.useState)(null);
6989
+ const [connectionSourcePort, setConnectionSourcePort] = (0, import_react22.useState)(null);
6990
+ const [connectionCursor, setConnectionCursor] = (0, import_react22.useState)(null);
6991
+ const [hoveredNode, setHoveredNode] = (0, import_react22.useState)(null);
6992
+ const [hoveredPort, setHoveredPort] = (0, import_react22.useState)(null);
6993
+ const [selectedWaypoint, setSelectedWaypoint] = (0, import_react22.useState)(null);
6994
+ const historyStack = (0, import_react22.useRef)([]);
6995
+ const redoStack = (0, import_react22.useRef)([]);
6996
+ const isUndoRedoAction = (0, import_react22.useRef)(false);
6997
+ const isInternalChange = (0, import_react22.useRef)(false);
6998
+ const lastInternalDiagram = (0, import_react22.useRef)(null);
6999
+ (0, import_react22.useEffect)(() => {
6561
7000
  const isExternal = !isInternalChange.current && diagram !== lastInternalDiagram.current;
6562
7001
  if (isExternal) {
6563
7002
  setLocalDiagram(diagram);
@@ -6579,7 +7018,7 @@ function PIDCanvasImpl({
6579
7018
  enablePan: features.pan ?? true,
6580
7019
  enableZoom: features.zoom ?? true
6581
7020
  });
6582
- const calculateContentBounds = (0, import_react20.useCallback)(() => {
7021
+ const calculateContentBounds = (0, import_react22.useCallback)(() => {
6583
7022
  const nodes2 = localDiagram.nodes.filter((n) => n.visible !== false);
6584
7023
  if (nodes2.length === 0) {
6585
7024
  return null;
@@ -6602,7 +7041,7 @@ function PIDCanvasImpl({
6602
7041
  });
6603
7042
  return { minX, minY, maxX, maxY };
6604
7043
  }, [localDiagram.nodes]);
6605
- (0, import_react20.useEffect)(() => {
7044
+ (0, import_react22.useEffect)(() => {
6606
7045
  if (onMounted) {
6607
7046
  const controls = {
6608
7047
  fitToContent: (padding = 50) => {
@@ -6643,8 +7082,8 @@ function PIDCanvasImpl({
6643
7082
  });
6644
7083
  const dragEnabled = features.dragNodes ?? false;
6645
7084
  const selectionEnabled = features.selection ?? false;
6646
- const containerRef = (0, import_react20.useRef)(null);
6647
- const pushToHistory = (0, import_react20.useCallback)((currentDiagram) => {
7085
+ const containerRef = (0, import_react22.useRef)(null);
7086
+ const pushToHistory = (0, import_react22.useCallback)((currentDiagram) => {
6648
7087
  if (isUndoRedoAction.current) return;
6649
7088
  const snapshot = JSON.parse(JSON.stringify(currentDiagram));
6650
7089
  historyStack.current.push(snapshot);
@@ -6653,7 +7092,7 @@ function PIDCanvasImpl({
6653
7092
  }
6654
7093
  redoStack.current = [];
6655
7094
  }, []);
6656
- const undo = (0, import_react20.useCallback)(() => {
7095
+ const undo = (0, import_react22.useCallback)(() => {
6657
7096
  if (historyStack.current.length === 0) return;
6658
7097
  isUndoRedoAction.current = true;
6659
7098
  const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
@@ -6667,7 +7106,7 @@ function PIDCanvasImpl({
6667
7106
  isUndoRedoAction.current = false;
6668
7107
  }, 0);
6669
7108
  }, [localDiagram, onDiagramChange]);
6670
- const redo = (0, import_react20.useCallback)(() => {
7109
+ const redo = (0, import_react22.useCallback)(() => {
6671
7110
  if (redoStack.current.length === 0) return;
6672
7111
  isUndoRedoAction.current = true;
6673
7112
  const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
@@ -6681,7 +7120,7 @@ function PIDCanvasImpl({
6681
7120
  isUndoRedoAction.current = false;
6682
7121
  }, 0);
6683
7122
  }, [localDiagram, onDiagramChange]);
6684
- (0, import_react20.useEffect)(() => {
7123
+ (0, import_react22.useEffect)(() => {
6685
7124
  const handleKeyDown = (e) => {
6686
7125
  const isCtrlOrCmd = e.ctrlKey || e.metaKey;
6687
7126
  if (isCtrlOrCmd && e.key === "z" && !e.shiftKey) {
@@ -6698,7 +7137,7 @@ function PIDCanvasImpl({
6698
7137
  window.addEventListener("keydown", handleKeyDown);
6699
7138
  return () => window.removeEventListener("keydown", handleKeyDown);
6700
7139
  }, [undo, redo]);
6701
- (0, import_react20.useEffect)(() => {
7140
+ (0, import_react22.useEffect)(() => {
6702
7141
  if (!features.dragNodes) return;
6703
7142
  const handleKeyDown = (e) => {
6704
7143
  if (selection.selectedNodeIds.size === 0 && !selectedWaypoint) return;
@@ -6783,7 +7222,7 @@ function PIDCanvasImpl({
6783
7222
  onNodeMove,
6784
7223
  pushToHistory
6785
7224
  ]);
6786
- const handlePositionChange = (0, import_react20.useCallback)(
7225
+ const handlePositionChange = (0, import_react22.useCallback)(
6787
7226
  (nodeId, x, y) => {
6788
7227
  pushToHistory(localDiagram);
6789
7228
  const updatedNodes = localDiagram.nodes.map(
@@ -6805,7 +7244,7 @@ function PIDCanvasImpl({
6805
7244
  },
6806
7245
  [localDiagram, onDiagramChange, onNodeMove, pushToHistory]
6807
7246
  );
6808
- const handleWaypointPositionChange = (0, import_react20.useCallback)(
7247
+ const handleWaypointPositionChange = (0, import_react22.useCallback)(
6809
7248
  (pipeId, pointIndex, x, y) => {
6810
7249
  pushToHistory(localDiagram);
6811
7250
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -6823,7 +7262,7 @@ function PIDCanvasImpl({
6823
7262
  },
6824
7263
  [localDiagram, onDiagramChange, pushToHistory]
6825
7264
  );
6826
- const handleDragEnd = (0, import_react20.useCallback)(
7265
+ const handleDragEnd = (0, import_react22.useCallback)(
6827
7266
  (nodeId, offset, finalPosition) => {
6828
7267
  const dragDistance = Math.sqrt(offset.x ** 2 + offset.y ** 2);
6829
7268
  if (dragDistance < 2) {
@@ -6904,7 +7343,7 @@ function PIDCanvasImpl({
6904
7343
  onDiagramChange?.(updatedDiagram);
6905
7344
  }
6906
7345
  });
6907
- const handleNodeDragStart = (0, import_react20.useCallback)(
7346
+ const handleNodeDragStart = (0, import_react22.useCallback)(
6908
7347
  (nodeId, event) => {
6909
7348
  const node = localDiagram.nodes.find((n) => n.id === nodeId);
6910
7349
  if (!node) return;
@@ -6912,7 +7351,7 @@ function PIDCanvasImpl({
6912
7351
  },
6913
7352
  [localDiagram.nodes, startDrag]
6914
7353
  );
6915
- const handleWaypointClick = (0, import_react20.useCallback)(
7354
+ const handleWaypointClick = (0, import_react22.useCallback)(
6916
7355
  (pipeId, pointIndex) => {
6917
7356
  setSelectedWaypoint({ pipeId, pointIndex });
6918
7357
  if (selectionEnabled) {
@@ -6921,7 +7360,7 @@ function PIDCanvasImpl({
6921
7360
  },
6922
7361
  [selectionEnabled, clearSelection]
6923
7362
  );
6924
- const handleWaypointDragStart = (0, import_react20.useCallback)(
7363
+ const handleWaypointDragStart = (0, import_react22.useCallback)(
6925
7364
  (pipeId, pointIndex, event) => {
6926
7365
  const pipe = localDiagram.pipes.find((p) => p.id === pipeId);
6927
7366
  if (!pipe || pointIndex >= pipe.routePoints.length) return;
@@ -6930,7 +7369,7 @@ function PIDCanvasImpl({
6930
7369
  },
6931
7370
  [localDiagram.pipes, startWaypointDrag]
6932
7371
  );
6933
- const handlePipeSegmentClick = (0, import_react20.useCallback)(
7372
+ const handlePipeSegmentClick = (0, import_react22.useCallback)(
6934
7373
  (pipeId, position, segmentIndex) => {
6935
7374
  pushToHistory(localDiagram);
6936
7375
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -6949,7 +7388,7 @@ function PIDCanvasImpl({
6949
7388
  [localDiagram, onDiagramChange, pushToHistory]
6950
7389
  );
6951
7390
  const telemetryEnabled = features.telemetry ?? false;
6952
- const handleDelete = (0, import_react20.useCallback)(() => {
7391
+ const handleDelete = (0, import_react22.useCallback)(() => {
6953
7392
  if (!selectionEnabled) return;
6954
7393
  const { selectedNodeIds, selectedPipeIds } = selection;
6955
7394
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
@@ -6978,17 +7417,17 @@ function PIDCanvasImpl({
6978
7417
  onDelete,
6979
7418
  pushToHistory
6980
7419
  ]);
6981
- const handleCopy = (0, import_react20.useCallback)(() => {
7420
+ const handleCopy = (0, import_react22.useCallback)(() => {
6982
7421
  if (!selectionEnabled) return;
6983
7422
  const { selectedNodeIds, selectedPipeIds } = selection;
6984
7423
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
6985
7424
  onItemsCopied?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
6986
7425
  }, [selection, selectionEnabled, onItemsCopied]);
6987
- const handlePaste = (0, import_react20.useCallback)(() => {
7426
+ const handlePaste = (0, import_react22.useCallback)(() => {
6988
7427
  if (!selectionEnabled) return;
6989
7428
  onPaste?.();
6990
7429
  }, [selectionEnabled, onPaste]);
6991
- const handleNodeDoubleClick = (0, import_react20.useCallback)(
7430
+ const handleNodeDoubleClick = (0, import_react22.useCallback)(
6992
7431
  (nodeId, event) => {
6993
7432
  if (!selectionEnabled) return;
6994
7433
  console.log("Node double-clicked:", nodeId);
@@ -7002,7 +7441,7 @@ function PIDCanvasImpl({
7002
7441
  onCopy: handleCopy,
7003
7442
  onPaste: handlePaste
7004
7443
  });
7005
- const handleNodeClick = (0, import_react20.useCallback)(
7444
+ const handleNodeClick = (0, import_react22.useCallback)(
7006
7445
  (nodeId, event) => {
7007
7446
  const connectionModeEnabled = features.connectionMode ?? false;
7008
7447
  if (connectionModeEnabled && isConnecting) {
@@ -7044,7 +7483,7 @@ function PIDCanvasImpl({
7044
7483
  onNodeClick
7045
7484
  ]
7046
7485
  );
7047
- const handlePipeClick = (0, import_react20.useCallback)(
7486
+ const handlePipeClick = (0, import_react22.useCallback)(
7048
7487
  (pipeId, event) => {
7049
7488
  if (selectionEnabled) {
7050
7489
  selectPipe(pipeId, event.ctrlKey || event.metaKey);
@@ -7063,7 +7502,7 @@ function PIDCanvasImpl({
7063
7502
  }
7064
7503
  setSelectedWaypoint(null);
7065
7504
  };
7066
- const handleDragOver = (0, import_react20.useCallback)(
7505
+ const handleDragOver = (0, import_react22.useCallback)(
7067
7506
  (event) => {
7068
7507
  if (!features.allowSymbolDrop) return;
7069
7508
  event.preventDefault();
@@ -7074,7 +7513,7 @@ function PIDCanvasImpl({
7074
7513
  },
7075
7514
  [features.allowSymbolDrop, screenToWorld, svgRef]
7076
7515
  );
7077
- const handleDrop = (0, import_react20.useCallback)(
7516
+ const handleDrop = (0, import_react22.useCallback)(
7078
7517
  (event) => {
7079
7518
  if (!features.allowSymbolDrop) return;
7080
7519
  event.preventDefault();
@@ -7087,10 +7526,10 @@ function PIDCanvasImpl({
7087
7526
  },
7088
7527
  [features.allowSymbolDrop, screenToWorld, onSymbolDrop, svgRef]
7089
7528
  );
7090
- const handleDragLeave = (0, import_react20.useCallback)(() => {
7529
+ const handleDragLeave = (0, import_react22.useCallback)(() => {
7091
7530
  setDragOverPosition(null);
7092
7531
  }, []);
7093
- const handleMouseMove = (0, import_react20.useCallback)(
7532
+ const handleMouseMove = (0, import_react22.useCallback)(
7094
7533
  (event) => {
7095
7534
  const connectionModeEnabled = features.connectionMode ?? false;
7096
7535
  if (connectionModeEnabled) {
@@ -7127,7 +7566,7 @@ function PIDCanvasImpl({
7127
7566
  const viewBox = controlledViewBox;
7128
7567
  const displayDiagram = localDiagram;
7129
7568
  const selectedNode = selectionEnabled && selection.selectedNodeIds.size === 1 ? localDiagram.nodes.find((n) => selection.selectedNodeIds.has(n.id)) || null : null;
7130
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
7569
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
7131
7570
  "div",
7132
7571
  {
7133
7572
  ref: containerRef,
@@ -7142,7 +7581,7 @@ function PIDCanvasImpl({
7142
7581
  },
7143
7582
  ...props,
7144
7583
  children: [
7145
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
7584
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
7146
7585
  "svg",
7147
7586
  {
7148
7587
  ref: svgRef,
@@ -7161,15 +7600,15 @@ function PIDCanvasImpl({
7161
7600
  onDrop: handleDrop,
7162
7601
  onDragLeave: handleDragLeave,
7163
7602
  children: [
7164
- features.showGrid !== false ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
7165
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("defs", { children: [
7166
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
7167
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
7168
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
7169
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
7603
+ features.showGrid !== false ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
7604
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("defs", { children: [
7605
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
7606
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
7607
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
7608
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
7170
7609
  ] })
7171
7610
  ] }),
7172
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7611
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7173
7612
  "rect",
7174
7613
  {
7175
7614
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
@@ -7179,7 +7618,7 @@ function PIDCanvasImpl({
7179
7618
  fill: "url(#grid-major)"
7180
7619
  }
7181
7620
  )
7182
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7621
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7183
7622
  "rect",
7184
7623
  {
7185
7624
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
@@ -7189,7 +7628,7 @@ function PIDCanvasImpl({
7189
7628
  fill: features.backgroundColor ?? "#ffffff"
7190
7629
  }
7191
7630
  ),
7192
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7631
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7193
7632
  PipeRenderer,
7194
7633
  {
7195
7634
  pipes: displayDiagram.pipes,
@@ -7203,7 +7642,7 @@ function PIDCanvasImpl({
7203
7642
  onPipeSegmentClick: void 0
7204
7643
  }
7205
7644
  ),
7206
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7645
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7207
7646
  NodeRenderer,
7208
7647
  {
7209
7648
  nodes: displayDiagram.nodes,
@@ -7217,7 +7656,7 @@ function PIDCanvasImpl({
7217
7656
  statusColorMap
7218
7657
  }
7219
7658
  ),
7220
- (features.editPipeRoutes ?? false) && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7659
+ (features.editPipeRoutes ?? false) && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7221
7660
  PipeRenderer,
7222
7661
  {
7223
7662
  pipes: displayDiagram.pipes,
@@ -7234,14 +7673,15 @@ function PIDCanvasImpl({
7234
7673
  onPipeSegmentClick: handlePipeSegmentClick
7235
7674
  }
7236
7675
  ),
7237
- telemetryEnabled && telemetry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
7676
+ telemetryEnabled && telemetry && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
7677
+ if (node.symbolId === "status-indicator") return null;
7238
7678
  const binding = telemetry.get(node.id);
7239
7679
  if (!binding) return null;
7240
7680
  const symbol = getSymbolDefinition(node.symbolId);
7241
7681
  if (!symbol) return null;
7242
7682
  const overlayX = node.transform.x + symbol.viewBox.width / 2;
7243
7683
  const overlayY = node.transform.y + symbol.viewBox.height + 50;
7244
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7684
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7245
7685
  DataOverlay,
7246
7686
  {
7247
7687
  nodeId: node.id,
@@ -7254,7 +7694,7 @@ function PIDCanvasImpl({
7254
7694
  `telemetry-${node.id}`
7255
7695
  );
7256
7696
  }) }),
7257
- dragOverPosition && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7697
+ dragOverPosition && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7258
7698
  "circle",
7259
7699
  {
7260
7700
  cx: dragOverPosition.x,
@@ -7267,7 +7707,7 @@ function PIDCanvasImpl({
7267
7707
  pointerEvents: "none"
7268
7708
  }
7269
7709
  ),
7270
- isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
7710
+ isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
7271
7711
  const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
7272
7712
  if (!sourceNode) return null;
7273
7713
  const symbol = getSymbolDefinition(sourceNode.symbolId);
@@ -7283,8 +7723,8 @@ function PIDCanvasImpl({
7283
7723
  }
7284
7724
  }
7285
7725
  }
7286
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
7287
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7726
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
7727
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7288
7728
  "circle",
7289
7729
  {
7290
7730
  cx: lineStartX,
@@ -7296,7 +7736,7 @@ function PIDCanvasImpl({
7296
7736
  opacity: 0.6
7297
7737
  }
7298
7738
  ),
7299
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7739
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7300
7740
  "line",
7301
7741
  {
7302
7742
  x1: lineStartX,
@@ -7309,7 +7749,7 @@ function PIDCanvasImpl({
7309
7749
  opacity: 0.8
7310
7750
  }
7311
7751
  ),
7312
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7752
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7313
7753
  "circle",
7314
7754
  {
7315
7755
  cx: connectionCursor.x,
@@ -7321,7 +7761,7 @@ function PIDCanvasImpl({
7321
7761
  )
7322
7762
  ] });
7323
7763
  })() }),
7324
- features.connectionMode && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
7764
+ features.connectionMode && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
7325
7765
  const symbol = getSymbolDefinition(node.symbolId);
7326
7766
  if (!symbol?.ports) return null;
7327
7767
  return symbol.ports.map((port) => {
@@ -7329,8 +7769,8 @@ function PIDCanvasImpl({
7329
7769
  if (!portPos) return null;
7330
7770
  const isHovered = hoveredPort === port.id && hoveredNode === node.id;
7331
7771
  const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
7332
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("g", { children: [
7333
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7772
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("g", { children: [
7773
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7334
7774
  "circle",
7335
7775
  {
7336
7776
  cx: portPos.x,
@@ -7363,7 +7803,7 @@ function PIDCanvasImpl({
7363
7803
  }
7364
7804
  }
7365
7805
  ),
7366
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7806
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7367
7807
  "circle",
7368
7808
  {
7369
7809
  cx: portPos.x,
@@ -7382,7 +7822,7 @@ function PIDCanvasImpl({
7382
7822
  ]
7383
7823
  }
7384
7824
  ),
7385
- features.dragNodes && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7825
+ features.dragNodes && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7386
7826
  PositionPanel,
7387
7827
  {
7388
7828
  selectedNode,
@@ -7400,19 +7840,19 @@ function PIDCanvasImpl({
7400
7840
  }
7401
7841
  );
7402
7842
  }
7403
- var PIDCanvas = import_react20.default.memo(PIDCanvasImpl);
7843
+ var PIDCanvas = import_react22.default.memo(PIDCanvasImpl);
7404
7844
 
7405
7845
  // src/components/SymbolLibrary/SymbolLibrary.tsx
7406
- var import_react21 = require("react");
7407
- var import_jsx_runtime21 = require("react/jsx-runtime");
7846
+ var import_react23 = require("react");
7847
+ var import_jsx_runtime22 = require("react/jsx-runtime");
7408
7848
  function SymbolLibrary({
7409
7849
  className = "",
7410
7850
  onSymbolDragStart,
7411
7851
  showCategories = true,
7412
7852
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
7413
7853
  }) {
7414
- const [selectedCategory, setSelectedCategory] = (0, import_react21.useState)("all");
7415
- const [searchQuery, setSearchQuery] = (0, import_react21.useState)("");
7854
+ const [selectedCategory, setSelectedCategory] = (0, import_react23.useState)("all");
7855
+ const [searchQuery, setSearchQuery] = (0, import_react23.useState)("");
7416
7856
  const symbolIds = getAvailableSymbols();
7417
7857
  const allSymbols = symbolIds.map((id) => getSymbolDefinition(id)).filter((symbol) => symbol !== void 0);
7418
7858
  const categoryMap = {
@@ -7433,9 +7873,9 @@ function SymbolLibrary({
7433
7873
  event.dataTransfer.effectAllowed = "copy";
7434
7874
  onSymbolDragStart?.(symbol.id, event);
7435
7875
  };
7436
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
7437
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { style: styles.title, children: "Symbol Library" }) }),
7438
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: styles.searchContainer, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7876
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
7877
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: styles.title, children: "Symbol Library" }) }),
7878
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: styles.searchContainer, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7439
7879
  "input",
7440
7880
  {
7441
7881
  type: "text",
@@ -7445,8 +7885,8 @@ function SymbolLibrary({
7445
7885
  style: styles.searchInput
7446
7886
  }
7447
7887
  ) }),
7448
- showCategories && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: styles.categories, children: [
7449
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7888
+ showCategories && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: styles.categories, children: [
7889
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7450
7890
  "button",
7451
7891
  {
7452
7892
  onClick: () => setSelectedCategory("all"),
@@ -7457,7 +7897,7 @@ function SymbolLibrary({
7457
7897
  children: "All"
7458
7898
  }
7459
7899
  ),
7460
- categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7900
+ categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7461
7901
  "button",
7462
7902
  {
7463
7903
  onClick: () => setSelectedCategory(category),
@@ -7470,7 +7910,7 @@ function SymbolLibrary({
7470
7910
  category
7471
7911
  ))
7472
7912
  ] }),
7473
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
7913
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
7474
7914
  "div",
7475
7915
  {
7476
7916
  draggable: true,
@@ -7478,15 +7918,15 @@ function SymbolLibrary({
7478
7918
  style: styles.symbolCard,
7479
7919
  title: symbol.metadata?.description || symbol.name,
7480
7920
  children: [
7481
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: styles.symbolPreview, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7921
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: styles.symbolPreview, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7482
7922
  "svg",
7483
7923
  {
7484
7924
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
7485
7925
  style: styles.symbolSvg,
7486
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
7926
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
7487
7927
  }
7488
7928
  ) }),
7489
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: styles.symbolName, children: symbol.name })
7929
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: styles.symbolName, children: symbol.name })
7490
7930
  ]
7491
7931
  },
7492
7932
  symbol.id
@@ -7595,13 +8035,15 @@ var styles = {
7595
8035
  };
7596
8036
 
7597
8037
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
7598
- var import_jsx_runtime22 = require("react/jsx-runtime");
8038
+ var import_jsx_runtime23 = require("react/jsx-runtime");
7599
8039
  function NodeConfigPanel({
7600
8040
  node,
7601
8041
  binding,
7602
8042
  onUpdateNode,
7603
8043
  onUpdateTelemetry,
7604
8044
  onAddTelemetry,
8045
+ onRemoveTelemetry,
8046
+ availableTags = [],
7605
8047
  onRemove,
7606
8048
  className = "",
7607
8049
  style = {}
@@ -7614,15 +8056,17 @@ function NodeConfigPanel({
7614
8056
  padding: "20px",
7615
8057
  ...style
7616
8058
  };
7617
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className, style: defaultStyle, children: [
7618
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
8059
+ const appearanceFields = getAppearanceSchema(node.symbolId);
8060
+ const showGenericTelemetry = appearanceFields.length === 0;
8061
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className, style: defaultStyle, children: [
8062
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
7619
8063
  "Configure: ",
7620
8064
  node.id
7621
8065
  ] }),
7622
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
7623
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
7624
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
7625
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8066
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8067
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
8068
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
8069
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7626
8070
  "input",
7627
8071
  {
7628
8072
  type: "text",
@@ -7638,10 +8082,10 @@ function NodeConfigPanel({
7638
8082
  }
7639
8083
  }
7640
8084
  ),
7641
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
7642
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
7643
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
7644
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8085
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
8086
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
8087
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
8088
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7645
8089
  "input",
7646
8090
  {
7647
8091
  type: "number",
@@ -7662,9 +8106,9 @@ function NodeConfigPanel({
7662
8106
  }
7663
8107
  )
7664
8108
  ] }),
7665
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
7666
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
7667
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8109
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
8110
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
8111
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7668
8112
  "input",
7669
8113
  {
7670
8114
  type: "number",
@@ -7686,9 +8130,9 @@ function NodeConfigPanel({
7686
8130
  )
7687
8131
  ] })
7688
8132
  ] }),
7689
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
7690
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
7691
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8133
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
8134
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
8135
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7692
8136
  "input",
7693
8137
  {
7694
8138
  type: "number",
@@ -7708,9 +8152,9 @@ function NodeConfigPanel({
7708
8152
  }
7709
8153
  )
7710
8154
  ] }),
7711
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
7712
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
7713
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8155
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
8156
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
8157
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7714
8158
  "input",
7715
8159
  {
7716
8160
  type: "number",
@@ -7735,9 +8179,138 @@ function NodeConfigPanel({
7735
8179
  )
7736
8180
  ] })
7737
8181
  ] }),
7738
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
7739
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
7740
- !binding ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8182
+ appearanceFields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8183
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Appearance" }),
8184
+ appearanceFields.map((field) => {
8185
+ const value = readAppearanceValue(node, field);
8186
+ const fieldLabel = field.label ?? field.key;
8187
+ const inputStyle = {
8188
+ width: "100%",
8189
+ padding: "6px 8px",
8190
+ borderRadius: "4px",
8191
+ border: "1px solid #cbd5e1",
8192
+ fontSize: "0.875rem"
8193
+ };
8194
+ let control;
8195
+ if (field.kind === "boolean") {
8196
+ control = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { fontSize: "0.75rem", display: "block" }, children: [
8197
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8198
+ "input",
8199
+ {
8200
+ type: "checkbox",
8201
+ checked: Boolean(value),
8202
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.checked)),
8203
+ style: { marginRight: "6px" }
8204
+ }
8205
+ ),
8206
+ fieldLabel
8207
+ ] });
8208
+ } else if (field.kind === "select") {
8209
+ control = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
8210
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8211
+ fieldLabel,
8212
+ ":"
8213
+ ] }),
8214
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8215
+ "select",
8216
+ {
8217
+ value: String(value),
8218
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8219
+ style: inputStyle,
8220
+ children: field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: opt, children: opt }, opt))
8221
+ }
8222
+ )
8223
+ ] });
8224
+ } else if (field.kind === "number") {
8225
+ control = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
8226
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8227
+ fieldLabel,
8228
+ ":"
8229
+ ] }),
8230
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8231
+ "input",
8232
+ {
8233
+ type: "number",
8234
+ value: Number(value),
8235
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, parseFloat(e.target.value) || 0)),
8236
+ style: inputStyle
8237
+ }
8238
+ )
8239
+ ] });
8240
+ } else if (field.kind === "tag") {
8241
+ const tagValue = String(value);
8242
+ const listId = `tags-${node.id}-${field.key}`;
8243
+ const known = tagValue.length > 0 && availableTags.includes(tagValue);
8244
+ const unknown = tagValue.length > 0 && !known;
8245
+ control = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
8246
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8247
+ fieldLabel,
8248
+ ":"
8249
+ ] }),
8250
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8251
+ "input",
8252
+ {
8253
+ type: "text",
8254
+ value: tagValue,
8255
+ list: listId,
8256
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8257
+ placeholder: "type or pick a signal\u2026",
8258
+ style: {
8259
+ ...inputStyle,
8260
+ border: `1px solid ${unknown ? "#f59e0b" : known ? "#22c55e" : "#cbd5e1"}`
8261
+ }
8262
+ }
8263
+ ),
8264
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("datalist", { id: listId, children: availableTags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: t }, t)) }),
8265
+ tagValue.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8266
+ "div",
8267
+ {
8268
+ style: {
8269
+ fontSize: "0.7rem",
8270
+ marginTop: "3px",
8271
+ color: known ? "#16a34a" : "#b45309"
8272
+ },
8273
+ children: known ? "\u2713 matched signal" : "\u26A0 no matching signal (reads as false until published)"
8274
+ }
8275
+ )
8276
+ ] });
8277
+ } else {
8278
+ control = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
8279
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8280
+ fieldLabel,
8281
+ ":"
8282
+ ] }),
8283
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8284
+ "input",
8285
+ {
8286
+ type: "text",
8287
+ value: String(value),
8288
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8289
+ style: inputStyle
8290
+ }
8291
+ )
8292
+ ] });
8293
+ }
8294
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "10px" }, children: [
8295
+ control,
8296
+ field.help && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8297
+ "div",
8298
+ {
8299
+ style: {
8300
+ fontSize: "0.7rem",
8301
+ color: "#64748b",
8302
+ marginTop: "3px",
8303
+ lineHeight: 1.35
8304
+ },
8305
+ children: field.help
8306
+ }
8307
+ )
8308
+ ] }, field.key);
8309
+ })
8310
+ ] }),
8311
+ showGenericTelemetry && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8312
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
8313
+ !binding ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7741
8314
  "button",
7742
8315
  {
7743
8316
  onClick: () => {
@@ -7780,9 +8353,30 @@ function NodeConfigPanel({
7780
8353
  },
7781
8354
  children: "+ Add Telemetry"
7782
8355
  }
7783
- ) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
7784
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
7785
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8356
+ ) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
8357
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Tag (attribute):" }),
8358
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8359
+ "input",
8360
+ {
8361
+ type: "text",
8362
+ value: binding.tag || "",
8363
+ onChange: (e) => onUpdateTelemetry({
8364
+ nodeId: node.id,
8365
+ tag: e.target.value
8366
+ }),
8367
+ placeholder: "e.g. pump.running",
8368
+ style: {
8369
+ width: "100%",
8370
+ padding: "6px 8px",
8371
+ borderRadius: "4px",
8372
+ border: "1px solid #cbd5e1",
8373
+ fontSize: "0.875rem",
8374
+ marginBottom: "8px"
8375
+ }
8376
+ }
8377
+ ),
8378
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
8379
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7786
8380
  "input",
7787
8381
  {
7788
8382
  type: "number",
@@ -7804,8 +8398,8 @@ function NodeConfigPanel({
7804
8398
  }
7805
8399
  }
7806
8400
  ),
7807
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
7808
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8401
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
8402
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7809
8403
  "input",
7810
8404
  {
7811
8405
  type: "text",
@@ -7827,8 +8421,8 @@ function NodeConfigPanel({
7827
8421
  }
7828
8422
  }
7829
8423
  ),
7830
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
7831
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8424
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8425
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7832
8426
  "input",
7833
8427
  {
7834
8428
  type: "checkbox",
@@ -7850,8 +8444,8 @@ function NodeConfigPanel({
7850
8444
  ),
7851
8445
  "Show Sparkline"
7852
8446
  ] }),
7853
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginTop: "12px" }, children: [
7854
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8447
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginTop: "12px" }, children: [
8448
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7855
8449
  "label",
7856
8450
  {
7857
8451
  style: {
@@ -7863,10 +8457,10 @@ function NodeConfigPanel({
7863
8457
  children: "Telemetry Position Offset:"
7864
8458
  }
7865
8459
  ),
7866
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
7867
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
7868
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
7869
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8460
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
8461
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
8462
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
8463
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7870
8464
  "input",
7871
8465
  {
7872
8466
  type: "number",
@@ -7893,9 +8487,9 @@ function NodeConfigPanel({
7893
8487
  }
7894
8488
  )
7895
8489
  ] }),
7896
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
7897
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
7898
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8490
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
8491
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
8492
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7899
8493
  "input",
7900
8494
  {
7901
8495
  type: "number",
@@ -7923,8 +8517,8 @@ function NodeConfigPanel({
7923
8517
  )
7924
8518
  ] })
7925
8519
  ] }),
7926
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginTop: "16px" }, children: [
7927
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8520
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginTop: "16px" }, children: [
8521
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7928
8522
  "label",
7929
8523
  {
7930
8524
  style: {
@@ -7936,10 +8530,10 @@ function NodeConfigPanel({
7936
8530
  children: "Display Colors:"
7937
8531
  }
7938
8532
  ),
7939
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "8px" }, children: [
7940
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
7941
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
7942
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8533
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "8px" }, children: [
8534
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
8535
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8536
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7943
8537
  "input",
7944
8538
  {
7945
8539
  type: "color",
@@ -7965,7 +8559,7 @@ function NodeConfigPanel({
7965
8559
  }
7966
8560
  }
7967
8561
  ),
7968
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8562
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7969
8563
  "input",
7970
8564
  {
7971
8565
  type: "text",
@@ -7995,10 +8589,10 @@ function NodeConfigPanel({
7995
8589
  )
7996
8590
  ] })
7997
8591
  ] }),
7998
- binding.display?.showSparkline && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "8px" }, children: [
7999
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
8000
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8001
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8592
+ binding.display?.showSparkline && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "8px" }, children: [
8593
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
8594
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8595
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8002
8596
  "input",
8003
8597
  {
8004
8598
  type: "color",
@@ -8024,7 +8618,7 @@ function NodeConfigPanel({
8024
8618
  }
8025
8619
  }
8026
8620
  ),
8027
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8621
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8028
8622
  "input",
8029
8623
  {
8030
8624
  type: "text",
@@ -8054,9 +8648,9 @@ function NodeConfigPanel({
8054
8648
  )
8055
8649
  ] })
8056
8650
  ] }),
8057
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "8px" }, children: [
8058
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
8059
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8651
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "8px" }, children: [
8652
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
8653
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8060
8654
  "input",
8061
8655
  {
8062
8656
  type: "text",
@@ -8084,20 +8678,38 @@ function NodeConfigPanel({
8084
8678
  }
8085
8679
  }
8086
8680
  ),
8087
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
8681
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
8088
8682
  ] })
8089
8683
  ] }),
8090
8684
  " "
8091
- ] })
8685
+ ] }),
8686
+ onRemoveTelemetry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8687
+ "button",
8688
+ {
8689
+ onClick: onRemoveTelemetry,
8690
+ style: {
8691
+ width: "100%",
8692
+ padding: "6px 8px",
8693
+ marginTop: "8px",
8694
+ borderRadius: "4px",
8695
+ border: "1px solid #ef4444",
8696
+ background: "transparent",
8697
+ color: "#ef4444",
8698
+ fontSize: "0.8125rem",
8699
+ cursor: "pointer"
8700
+ },
8701
+ children: "Remove Telemetry"
8702
+ }
8703
+ )
8092
8704
  ] })
8093
8705
  ] }),
8094
- binding && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8095
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
8706
+ showGenericTelemetry && binding && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8707
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
8096
8708
  ["highHigh", "high", "low", "lowLow"].map((key) => {
8097
8709
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
8098
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "8px" }, children: [
8099
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
8100
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8710
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "8px" }, children: [
8711
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
8712
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8101
8713
  "input",
8102
8714
  {
8103
8715
  type: "number",
@@ -8121,8 +8733,8 @@ function NodeConfigPanel({
8121
8733
  ] }, key);
8122
8734
  })
8123
8735
  ] }),
8124
- binding && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8125
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
8736
+ showGenericTelemetry && binding && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8737
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
8126
8738
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
8127
8739
  const defaultColors = {
8128
8740
  normal: "#10b981",
@@ -8132,13 +8744,13 @@ function NodeConfigPanel({
8132
8744
  off: "#6b7280"
8133
8745
  };
8134
8746
  const label = status === "normal" ? "Normal" : status === "warning" ? "Warning" : status === "alarm" ? "Alarm" : status === "fault" ? "Fault" : "Off";
8135
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "8px" }, children: [
8136
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8747
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { marginBottom: "8px" }, children: [
8748
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8137
8749
  label,
8138
8750
  ":"
8139
8751
  ] }),
8140
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8141
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8752
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
8753
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8142
8754
  "input",
8143
8755
  {
8144
8756
  type: "color",
@@ -8159,7 +8771,7 @@ function NodeConfigPanel({
8159
8771
  }
8160
8772
  }
8161
8773
  ),
8162
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8774
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8163
8775
  "input",
8164
8776
  {
8165
8777
  type: "text",
@@ -8186,7 +8798,7 @@ function NodeConfigPanel({
8186
8798
  ] }, status);
8187
8799
  })
8188
8800
  ] }),
8189
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8801
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8190
8802
  "button",
8191
8803
  {
8192
8804
  onClick: onRemove,
@@ -8463,13 +9075,13 @@ function createControlBinding(nodeId, deviceConfig, actions) {
8463
9075
  }
8464
9076
 
8465
9077
  // src/hooks/useDeviceControls.ts
8466
- var import_react22 = require("react");
9078
+ var import_react24 = require("react");
8467
9079
  function commandKey(nodeId, kind, suffix) {
8468
9080
  return suffix ? `${nodeId}:${kind}:${suffix}` : `${nodeId}:${kind}`;
8469
9081
  }
8470
9082
  function useDeviceControls(initialBindings, options) {
8471
9083
  const { commandTimeoutMs } = options ?? {};
8472
- const [controls, setControls] = (0, import_react22.useState)(() => {
9084
+ const [controls, setControls] = (0, import_react24.useState)(() => {
8473
9085
  const map = /* @__PURE__ */ new Map();
8474
9086
  if (initialBindings) {
8475
9087
  initialBindings.forEach((binding) => {
@@ -8478,12 +9090,12 @@ function useDeviceControls(initialBindings, options) {
8478
9090
  }
8479
9091
  return map;
8480
9092
  });
8481
- const pendingRef = (0, import_react22.useRef)(/* @__PURE__ */ new Map());
8482
- const [pendingCommands, setPendingCommands] = (0, import_react22.useState)(() => /* @__PURE__ */ new Set());
8483
- const syncPending = (0, import_react22.useCallback)(() => {
9093
+ const pendingRef = (0, import_react24.useRef)(/* @__PURE__ */ new Map());
9094
+ const [pendingCommands, setPendingCommands] = (0, import_react24.useState)(() => /* @__PURE__ */ new Set());
9095
+ const syncPending = (0, import_react24.useCallback)(() => {
8484
9096
  setPendingCommands(new Set(pendingRef.current.keys()));
8485
9097
  }, []);
8486
- const updateControl = (0, import_react22.useCallback)((nodeId, updates) => {
9098
+ const updateControl = (0, import_react24.useCallback)((nodeId, updates) => {
8487
9099
  setControls((prev) => {
8488
9100
  const newMap = new Map(prev);
8489
9101
  const existing = newMap.get(nodeId);
@@ -8497,7 +9109,7 @@ function useDeviceControls(initialBindings, options) {
8497
9109
  return newMap;
8498
9110
  });
8499
9111
  }, []);
8500
- const updateControlBatch = (0, import_react22.useCallback)((updates) => {
9112
+ const updateControlBatch = (0, import_react24.useCallback)((updates) => {
8501
9113
  setControls((prev) => {
8502
9114
  const newMap = new Map(prev);
8503
9115
  Object.entries(updates).forEach(([nodeId, update]) => {
@@ -8509,7 +9121,7 @@ function useDeviceControls(initialBindings, options) {
8509
9121
  return newMap;
8510
9122
  });
8511
9123
  }, []);
8512
- const updateDeviceState = (0, import_react22.useCallback)(
9124
+ const updateDeviceState = (0, import_react24.useCallback)(
8513
9125
  (nodeId, stateUpdates) => {
8514
9126
  setControls((prev) => {
8515
9127
  const newMap = new Map(prev);
@@ -8525,7 +9137,7 @@ function useDeviceControls(initialBindings, options) {
8525
9137
  },
8526
9138
  []
8527
9139
  );
8528
- const updateParameter = (0, import_react22.useCallback)(
9140
+ const updateParameter = (0, import_react24.useCallback)(
8529
9141
  (nodeId, parameterId, value) => {
8530
9142
  setControls((prev) => {
8531
9143
  const newMap = new Map(prev);
@@ -8552,27 +9164,27 @@ function useDeviceControls(initialBindings, options) {
8552
9164
  },
8553
9165
  []
8554
9166
  );
8555
- const setControlBinding = (0, import_react22.useCallback)((binding) => {
9167
+ const setControlBinding = (0, import_react24.useCallback)((binding) => {
8556
9168
  setControls((prev) => {
8557
9169
  const newMap = new Map(prev);
8558
9170
  newMap.set(binding.nodeId, binding);
8559
9171
  return newMap;
8560
9172
  });
8561
9173
  }, []);
8562
- const removeControlBinding = (0, import_react22.useCallback)((nodeId) => {
9174
+ const removeControlBinding = (0, import_react24.useCallback)((nodeId) => {
8563
9175
  setControls((prev) => {
8564
9176
  const newMap = new Map(prev);
8565
9177
  newMap.delete(nodeId);
8566
9178
  return newMap;
8567
9179
  });
8568
9180
  }, []);
8569
- const getControlBinding = (0, import_react22.useCallback)(
9181
+ const getControlBinding = (0, import_react24.useCallback)(
8570
9182
  (nodeId) => {
8571
9183
  return controls.get(nodeId);
8572
9184
  },
8573
9185
  [controls]
8574
9186
  );
8575
- const isCommandPending = (0, import_react22.useCallback)(
9187
+ const isCommandPending = (0, import_react24.useCallback)(
8576
9188
  (nodeId, kind) => {
8577
9189
  if (kind) {
8578
9190
  const prefix2 = `${nodeId}:${kind}`;
@@ -8589,7 +9201,7 @@ function useDeviceControls(initialBindings, options) {
8589
9201
  },
8590
9202
  [pendingCommands]
8591
9203
  );
8592
- const markPending = (0, import_react22.useCallback)(
9204
+ const markPending = (0, import_react24.useCallback)(
8593
9205
  (key, delta) => {
8594
9206
  const next = (pendingRef.current.get(key) ?? 0) + delta;
8595
9207
  if (next > 0) pendingRef.current.set(key, next);
@@ -8598,7 +9210,7 @@ function useDeviceControls(initialBindings, options) {
8598
9210
  },
8599
9211
  [syncPending]
8600
9212
  );
8601
- const runCommand = (0, import_react22.useCallback)(
9213
+ const runCommand = (0, import_react24.useCallback)(
8602
9214
  async (nodeId, key, invoke, messages) => {
8603
9215
  const binding = controls.get(nodeId);
8604
9216
  if (!binding) return;
@@ -8638,7 +9250,7 @@ function useDeviceControls(initialBindings, options) {
8638
9250
  },
8639
9251
  [controls, updateDeviceState, markPending, commandTimeoutMs]
8640
9252
  );
8641
- const sendModeChange = (0, import_react22.useCallback)(
9253
+ const sendModeChange = (0, import_react24.useCallback)(
8642
9254
  (nodeId, mode) => runCommand(
8643
9255
  nodeId,
8644
9256
  commandKey(nodeId, "mode"),
@@ -8647,7 +9259,7 @@ function useDeviceControls(initialBindings, options) {
8647
9259
  ),
8648
9260
  [runCommand]
8649
9261
  );
8650
- const sendParameterChange = (0, import_react22.useCallback)(
9262
+ const sendParameterChange = (0, import_react24.useCallback)(
8651
9263
  (nodeId, parameterId, value) => runCommand(
8652
9264
  nodeId,
8653
9265
  commandKey(nodeId, "parameter", parameterId),
@@ -8656,21 +9268,21 @@ function useDeviceControls(initialBindings, options) {
8656
9268
  ),
8657
9269
  [runCommand]
8658
9270
  );
8659
- const sendStartCommand = (0, import_react22.useCallback)(
9271
+ const sendStartCommand = (0, import_react24.useCallback)(
8660
9272
  (nodeId) => runCommand(nodeId, commandKey(nodeId, "start"), (binding) => binding.actions.onStart?.(), {
8661
9273
  success: "Start command sent",
8662
9274
  failure: "Start command failed"
8663
9275
  }),
8664
9276
  [runCommand]
8665
9277
  );
8666
- const sendStopCommand = (0, import_react22.useCallback)(
9278
+ const sendStopCommand = (0, import_react24.useCallback)(
8667
9279
  (nodeId) => runCommand(nodeId, commandKey(nodeId, "stop"), (binding) => binding.actions.onStop?.(), {
8668
9280
  success: "Stop command sent",
8669
9281
  failure: "Stop command failed"
8670
9282
  }),
8671
9283
  [runCommand]
8672
9284
  );
8673
- const sendCustomAction = (0, import_react22.useCallback)(
9285
+ const sendCustomAction = (0, import_react24.useCallback)(
8674
9286
  (nodeId, actionId) => runCommand(
8675
9287
  nodeId,
8676
9288
  commandKey(nodeId, "action", actionId),
@@ -8723,9 +9335,9 @@ function createConfigBinding(nodeId, tag, parameters, actions, options) {
8723
9335
  }
8724
9336
 
8725
9337
  // src/hooks/useDeviceConfigs.ts
8726
- var import_react23 = require("react");
9338
+ var import_react25 = require("react");
8727
9339
  function useDeviceConfigs(initialConfigs) {
8728
- const [configs, setConfigs] = (0, import_react23.useState)(() => {
9340
+ const [configs, setConfigs] = (0, import_react25.useState)(() => {
8729
9341
  const map = /* @__PURE__ */ new Map();
8730
9342
  if (initialConfigs) {
8731
9343
  initialConfigs.forEach((config) => {
@@ -8734,13 +9346,13 @@ function useDeviceConfigs(initialConfigs) {
8734
9346
  }
8735
9347
  return map;
8736
9348
  });
8737
- const getConfig = (0, import_react23.useCallback)(
9349
+ const getConfig = (0, import_react25.useCallback)(
8738
9350
  (nodeId) => {
8739
9351
  return configs.get(nodeId);
8740
9352
  },
8741
9353
  [configs]
8742
9354
  );
8743
- const updateConfig = (0, import_react23.useCallback)((nodeId, updates) => {
9355
+ const updateConfig = (0, import_react25.useCallback)((nodeId, updates) => {
8744
9356
  setConfigs((prev) => {
8745
9357
  const newMap = new Map(prev);
8746
9358
  const existing = newMap.get(nodeId);
@@ -8754,7 +9366,7 @@ function useDeviceConfigs(initialConfigs) {
8754
9366
  return newMap;
8755
9367
  });
8756
9368
  }, []);
8757
- const updateConfigState = (0, import_react23.useCallback)((nodeId, stateUpdates) => {
9369
+ const updateConfigState = (0, import_react25.useCallback)((nodeId, stateUpdates) => {
8758
9370
  setConfigs((prev) => {
8759
9371
  const newMap = new Map(prev);
8760
9372
  const existing = newMap.get(nodeId);
@@ -8767,7 +9379,7 @@ function useDeviceConfigs(initialConfigs) {
8767
9379
  return newMap;
8768
9380
  });
8769
9381
  }, []);
8770
- const updateConfigBatch = (0, import_react23.useCallback)((updates) => {
9382
+ const updateConfigBatch = (0, import_react25.useCallback)((updates) => {
8771
9383
  setConfigs((prev) => {
8772
9384
  const newMap = new Map(prev);
8773
9385
  Object.entries(updates).forEach(([nodeId, update]) => {
@@ -8779,7 +9391,7 @@ function useDeviceConfigs(initialConfigs) {
8779
9391
  return newMap;
8780
9392
  });
8781
9393
  }, []);
8782
- const updateParameterValue = (0, import_react23.useCallback)((nodeId, parameterId, value) => {
9394
+ const updateParameterValue = (0, import_react25.useCallback)((nodeId, parameterId, value) => {
8783
9395
  setConfigs((prev) => {
8784
9396
  const newMap = new Map(prev);
8785
9397
  const existing = newMap.get(nodeId);
@@ -8796,7 +9408,7 @@ function useDeviceConfigs(initialConfigs) {
8796
9408
  return newMap;
8797
9409
  });
8798
9410
  }, []);
8799
- const applyConfig = (0, import_react23.useCallback)(
9411
+ const applyConfig = (0, import_react25.useCallback)(
8800
9412
  async (nodeId) => {
8801
9413
  const config = configs.get(nodeId);
8802
9414
  if (!config || !config.actions.onApply) {
@@ -8829,7 +9441,7 @@ function useDeviceConfigs(initialConfigs) {
8829
9441
  },
8830
9442
  [configs, updateConfigState]
8831
9443
  );
8832
- const saveConfig = (0, import_react23.useCallback)(
9444
+ const saveConfig = (0, import_react25.useCallback)(
8833
9445
  async (nodeId) => {
8834
9446
  const config = configs.get(nodeId);
8835
9447
  if (!config || !config.actions.onSave) {
@@ -8863,7 +9475,7 @@ function useDeviceConfigs(initialConfigs) {
8863
9475
  },
8864
9476
  [configs, updateConfigState]
8865
9477
  );
8866
- const resetConfig = (0, import_react23.useCallback)(
9478
+ const resetConfig = (0, import_react25.useCallback)(
8867
9479
  async (nodeId) => {
8868
9480
  const config = configs.get(nodeId);
8869
9481
  if (!config) {
@@ -8893,7 +9505,7 @@ function useDeviceConfigs(initialConfigs) {
8893
9505
  },
8894
9506
  [configs, updateConfig, updateConfigState]
8895
9507
  );
8896
- const cancelConfig = (0, import_react23.useCallback)(
9508
+ const cancelConfig = (0, import_react25.useCallback)(
8897
9509
  (nodeId) => {
8898
9510
  const config = configs.get(nodeId);
8899
9511
  if (config?.actions.onCancel) {
@@ -8902,7 +9514,7 @@ function useDeviceConfigs(initialConfigs) {
8902
9514
  },
8903
9515
  [configs]
8904
9516
  );
8905
- const validateConfig = (0, import_react23.useCallback)(
9517
+ const validateConfig = (0, import_react25.useCallback)(
8906
9518
  (nodeId) => {
8907
9519
  const config = configs.get(nodeId);
8908
9520
  if (!config) {
@@ -8944,21 +9556,21 @@ function useDeviceConfigs(initialConfigs) {
8944
9556
  },
8945
9557
  [configs, updateConfigState]
8946
9558
  );
8947
- const setConfig = (0, import_react23.useCallback)((config) => {
9559
+ const setConfig = (0, import_react25.useCallback)((config) => {
8948
9560
  setConfigs((prev) => {
8949
9561
  const newMap = new Map(prev);
8950
9562
  newMap.set(config.nodeId, config);
8951
9563
  return newMap;
8952
9564
  });
8953
9565
  }, []);
8954
- const removeConfig = (0, import_react23.useCallback)((nodeId) => {
9566
+ const removeConfig = (0, import_react25.useCallback)((nodeId) => {
8955
9567
  setConfigs((prev) => {
8956
9568
  const newMap = new Map(prev);
8957
9569
  newMap.delete(nodeId);
8958
9570
  return newMap;
8959
9571
  });
8960
9572
  }, []);
8961
- const clearConfigs = (0, import_react23.useCallback)(() => {
9573
+ const clearConfigs = (0, import_react25.useCallback)(() => {
8962
9574
  setConfigs(/* @__PURE__ */ new Map());
8963
9575
  }, []);
8964
9576
  return {
@@ -8980,8 +9592,8 @@ function useDeviceConfigs(initialConfigs) {
8980
9592
  }
8981
9593
 
8982
9594
  // src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
8983
- var import_react24 = __toESM(require("react"), 1);
8984
- var import_jsx_runtime23 = require("react/jsx-runtime");
9595
+ var import_react26 = __toESM(require("react"), 1);
9596
+ var import_jsx_runtime24 = require("react/jsx-runtime");
8985
9597
  function DeviceConfigPanelImpl({
8986
9598
  binding,
8987
9599
  onClose,
@@ -9000,7 +9612,7 @@ function DeviceConfigPanelImpl({
9000
9612
  showBuiltInToast = true,
9001
9613
  embedded = false
9002
9614
  }) {
9003
- const [localValues, setLocalValues] = (0, import_react24.useState)(() => {
9615
+ const [localValues, setLocalValues] = (0, import_react26.useState)(() => {
9004
9616
  if (binding) {
9005
9617
  const values = {};
9006
9618
  binding.parameters.forEach((param) => {
@@ -9010,7 +9622,7 @@ function DeviceConfigPanelImpl({
9010
9622
  }
9011
9623
  return {};
9012
9624
  });
9013
- const [collapsedSections, setCollapsedSections] = (0, import_react24.useState)(() => {
9625
+ const [collapsedSections, setCollapsedSections] = (0, import_react26.useState)(() => {
9014
9626
  const collapsed = /* @__PURE__ */ new Set();
9015
9627
  if (binding?.sections) {
9016
9628
  binding.sections.forEach((section) => {
@@ -9021,9 +9633,9 @@ function DeviceConfigPanelImpl({
9021
9633
  }
9022
9634
  return collapsed;
9023
9635
  });
9024
- const [showToast, setShowToast] = (0, import_react24.useState)(true);
9025
- const [bindingId, setBindingId] = (0, import_react24.useState)(binding?.nodeId || null);
9026
- const lastResultTimestampRef = (0, import_react24.useRef)(0);
9636
+ const [showToast, setShowToast] = (0, import_react26.useState)(true);
9637
+ const [bindingId, setBindingId] = (0, import_react26.useState)(binding?.nodeId || null);
9638
+ const lastResultTimestampRef = (0, import_react26.useRef)(0);
9027
9639
  if (binding && binding.nodeId !== bindingId) {
9028
9640
  const values = {};
9029
9641
  binding.parameters.forEach((param) => {
@@ -9039,7 +9651,7 @@ function DeviceConfigPanelImpl({
9039
9651
  setCollapsedSections(collapsed);
9040
9652
  setBindingId(binding.nodeId);
9041
9653
  }
9042
- (0, import_react24.useEffect)(() => {
9654
+ (0, import_react26.useEffect)(() => {
9043
9655
  const currentTimestamp = binding?.state.lastSaved || 0;
9044
9656
  if (binding?.state.lastSaveResult && currentTimestamp !== lastResultTimestampRef.current) {
9045
9657
  lastResultTimestampRef.current = currentTimestamp;
@@ -9056,7 +9668,7 @@ function DeviceConfigPanelImpl({
9056
9668
  };
9057
9669
  }
9058
9670
  }, [binding?.state.lastSaveResult, binding?.state.lastSaved, toastDuration]);
9059
- const parametersBySection = (0, import_react24.useMemo)(() => {
9671
+ const parametersBySection = (0, import_react26.useMemo)(() => {
9060
9672
  if (!binding) return { _unsectioned: [] };
9061
9673
  const groups = { _unsectioned: [] };
9062
9674
  binding.parameters.forEach((param) => {
@@ -9071,7 +9683,7 @@ function DeviceConfigPanelImpl({
9071
9683
  });
9072
9684
  return groups;
9073
9685
  }, [binding]);
9074
- const sortedSections = (0, import_react24.useMemo)(() => {
9686
+ const sortedSections = (0, import_react26.useMemo)(() => {
9075
9687
  if (!binding?.sections) return [];
9076
9688
  return [...binding.sections].sort((a, b) => (a.order || 0) - (b.order || 0));
9077
9689
  }, [binding]);
@@ -9147,14 +9759,14 @@ function DeviceConfigPanelImpl({
9147
9759
  const value = localValues[param.id];
9148
9760
  const error = binding.state.errors?.[param.id];
9149
9761
  const isDisabled = param.readOnly || param.enabled === false;
9150
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9762
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9151
9763
  "div",
9152
9764
  {
9153
9765
  style: {
9154
9766
  marginBottom: spacing.marginBottom
9155
9767
  },
9156
9768
  children: [
9157
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9769
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9158
9770
  "label",
9159
9771
  {
9160
9772
  style: {
@@ -9169,13 +9781,13 @@ function DeviceConfigPanelImpl({
9169
9781
  },
9170
9782
  children: [
9171
9783
  param.label,
9172
- param.required && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { style: { color: errorColor }, children: " *" }),
9173
- param.unit && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { style: { color: mutedTextColor, fontWeight: "normal" }, children: [
9784
+ param.required && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { color: errorColor }, children: " *" }),
9785
+ param.unit && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { style: { color: mutedTextColor, fontWeight: "normal" }, children: [
9174
9786
  " (",
9175
9787
  param.unit,
9176
9788
  ")"
9177
9789
  ] }),
9178
- param.forced?.active && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9790
+ param.forced?.active && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9179
9791
  "span",
9180
9792
  {
9181
9793
  "aria-label": "Forced",
@@ -9202,7 +9814,7 @@ function DeviceConfigPanelImpl({
9202
9814
  ]
9203
9815
  }
9204
9816
  ),
9205
- param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9817
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9206
9818
  "div",
9207
9819
  {
9208
9820
  style: {
@@ -9220,7 +9832,7 @@ function DeviceConfigPanelImpl({
9220
9832
  ]
9221
9833
  }
9222
9834
  ),
9223
- param.description && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9835
+ param.description && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9224
9836
  "div",
9225
9837
  {
9226
9838
  style: {
@@ -9232,7 +9844,7 @@ function DeviceConfigPanelImpl({
9232
9844
  children: param.description
9233
9845
  }
9234
9846
  ),
9235
- param.type === "number" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9847
+ param.type === "number" && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9236
9848
  "input",
9237
9849
  {
9238
9850
  type: "number",
@@ -9256,7 +9868,7 @@ function DeviceConfigPanelImpl({
9256
9868
  }
9257
9869
  }
9258
9870
  ),
9259
- param.type === "string" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9871
+ param.type === "string" && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9260
9872
  "input",
9261
9873
  {
9262
9874
  type: "text",
@@ -9277,7 +9889,7 @@ function DeviceConfigPanelImpl({
9277
9889
  }
9278
9890
  }
9279
9891
  ),
9280
- param.type === "boolean" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9892
+ param.type === "boolean" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9281
9893
  "label",
9282
9894
  {
9283
9895
  style: {
@@ -9287,7 +9899,7 @@ function DeviceConfigPanelImpl({
9287
9899
  cursor: isDisabled ? "not-allowed" : "pointer"
9288
9900
  },
9289
9901
  children: [
9290
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9902
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9291
9903
  "input",
9292
9904
  {
9293
9905
  type: "checkbox",
@@ -9301,11 +9913,11 @@ function DeviceConfigPanelImpl({
9301
9913
  }
9302
9914
  }
9303
9915
  ),
9304
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { style: { fontSize: `${fontSize.value}px`, color: textColor }, children: value ? "Enabled" : "Disabled" })
9916
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { fontSize: `${fontSize.value}px`, color: textColor }, children: value ? "Enabled" : "Disabled" })
9305
9917
  ]
9306
9918
  }
9307
9919
  ),
9308
- param.type === "select" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9920
+ param.type === "select" && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9309
9921
  "select",
9310
9922
  {
9311
9923
  value: value ?? "",
@@ -9323,10 +9935,10 @@ function DeviceConfigPanelImpl({
9323
9935
  borderRadius: "2px",
9324
9936
  outline: "none"
9325
9937
  },
9326
- children: param.options?.map((option) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: option.value, children: option.label }, option.value))
9938
+ children: param.options?.map((option) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("option", { value: option.value, children: option.label }, option.value))
9327
9939
  }
9328
9940
  ),
9329
- error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9941
+ error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9330
9942
  "div",
9331
9943
  {
9332
9944
  style: {
@@ -9346,7 +9958,7 @@ function DeviceConfigPanelImpl({
9346
9958
  const renderSection = (section) => {
9347
9959
  const params = parametersBySection[section.id] || [];
9348
9960
  const isCollapsed = collapsedSections.has(section.id);
9349
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9961
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9350
9962
  "div",
9351
9963
  {
9352
9964
  style: {
@@ -9356,7 +9968,7 @@ function DeviceConfigPanelImpl({
9356
9968
  backgroundColor: surfaceColor
9357
9969
  },
9358
9970
  children: [
9359
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9971
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9360
9972
  "div",
9361
9973
  {
9362
9974
  onClick: () => section.collapsible && toggleSection(section.id),
@@ -9369,8 +9981,8 @@ function DeviceConfigPanelImpl({
9369
9981
  alignItems: "center"
9370
9982
  },
9371
9983
  children: [
9372
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
9373
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9984
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { children: [
9985
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9374
9986
  "div",
9375
9987
  {
9376
9988
  style: {
@@ -9382,12 +9994,12 @@ function DeviceConfigPanelImpl({
9382
9994
  letterSpacing: "0.5px"
9383
9995
  },
9384
9996
  children: [
9385
- section.icon && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { style: { marginRight: "8px" }, children: section.icon }),
9997
+ section.icon && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { marginRight: "8px" }, children: section.icon }),
9386
9998
  section.title
9387
9999
  ]
9388
10000
  }
9389
10001
  ),
9390
- section.description && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10002
+ section.description && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9391
10003
  "div",
9392
10004
  {
9393
10005
  style: {
@@ -9400,11 +10012,11 @@ function DeviceConfigPanelImpl({
9400
10012
  }
9401
10013
  )
9402
10014
  ] }),
9403
- section.collapsible && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { style: { fontSize: "16px", color: mutedTextColor }, children: isCollapsed ? "\u25BC" : "\u25B2" })
10015
+ section.collapsible && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { fontSize: "16px", color: mutedTextColor }, children: isCollapsed ? "\u25BC" : "\u25B2" })
9404
10016
  ]
9405
10017
  }
9406
10018
  ),
9407
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { padding: spacing.padding }, children: params.map(renderParameter) })
10019
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { padding: spacing.padding }, children: params.map(renderParameter) })
9408
10020
  ]
9409
10021
  },
9410
10022
  section.id
@@ -9463,9 +10075,9 @@ function DeviceConfigPanelImpl({
9463
10075
  const showSave = binding.display?.showSave !== false && binding.actions.onSave;
9464
10076
  const showReset = binding.display?.showReset !== false && binding.actions.onReset;
9465
10077
  const showCancel = binding.display?.showCancel !== false;
9466
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9467
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: panelStyle, className: binding.display?.className, children: [
9468
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
10078
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
10079
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: panelStyle, className: binding.display?.className, children: [
10080
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9469
10081
  "div",
9470
10082
  {
9471
10083
  style: {
@@ -9477,8 +10089,8 @@ function DeviceConfigPanelImpl({
9477
10089
  borderBottom: `2px solid ${borderColor}`
9478
10090
  },
9479
10091
  children: [
9480
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
9481
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10092
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { children: [
10093
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9482
10094
  "h3",
9483
10095
  {
9484
10096
  style: {
@@ -9498,7 +10110,7 @@ function DeviceConfigPanelImpl({
9498
10110
  children: titleText.length > 25 ? titleText.slice(0, 25) + "..." : titleText
9499
10111
  }
9500
10112
  ),
9501
- binding.display?.subtitle && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10113
+ binding.display?.subtitle && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9502
10114
  "div",
9503
10115
  {
9504
10116
  style: {
@@ -9511,8 +10123,8 @@ function DeviceConfigPanelImpl({
9511
10123
  }
9512
10124
  )
9513
10125
  ] }),
9514
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
9515
- showControlsButton && onOpenControls && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10126
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
10127
+ showControlsButton && onOpenControls && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9516
10128
  "button",
9517
10129
  {
9518
10130
  onClick: onOpenControls,
@@ -9535,7 +10147,7 @@ function DeviceConfigPanelImpl({
9535
10147
  children: "CTRL"
9536
10148
  }
9537
10149
  ),
9538
- onUndock && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10150
+ onUndock && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9539
10151
  "button",
9540
10152
  {
9541
10153
  onClick: onUndock,
@@ -9556,7 +10168,7 @@ function DeviceConfigPanelImpl({
9556
10168
  children: undockIcon
9557
10169
  }
9558
10170
  ),
9559
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10171
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9560
10172
  "button",
9561
10173
  {
9562
10174
  onClick: onClose,
@@ -9579,7 +10191,7 @@ function DeviceConfigPanelImpl({
9579
10191
  ]
9580
10192
  }
9581
10193
  ),
9582
- binding.state.isDirty && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10194
+ binding.state.isDirty && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9583
10195
  "div",
9584
10196
  {
9585
10197
  style: {
@@ -9594,11 +10206,11 @@ function DeviceConfigPanelImpl({
9594
10206
  children: "Unsaved changes"
9595
10207
  }
9596
10208
  ),
9597
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { flex: 1, overflowY: "auto", marginBottom: spacing.marginBottom }, children: sortedSections.length > 0 ? sortedSections.map(renderSection) : (
10209
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { flex: 1, overflowY: "auto", marginBottom: spacing.marginBottom }, children: sortedSections.length > 0 ? sortedSections.map(renderSection) : (
9598
10210
  /* Render unsectioned parameters */
9599
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { children: parametersBySection._unsectioned?.map(renderParameter) })
10211
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { children: parametersBySection._unsectioned?.map(renderParameter) })
9600
10212
  ) }),
9601
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
10213
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9602
10214
  "div",
9603
10215
  {
9604
10216
  style: {
@@ -9609,7 +10221,7 @@ function DeviceConfigPanelImpl({
9609
10221
  flexWrap: "wrap"
9610
10222
  },
9611
10223
  children: [
9612
- showApply && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10224
+ showApply && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9613
10225
  "button",
9614
10226
  {
9615
10227
  onClick: handleApply,
@@ -9632,7 +10244,7 @@ function DeviceConfigPanelImpl({
9632
10244
  children: binding.state.isSaving ? "Applying..." : "Apply"
9633
10245
  }
9634
10246
  ),
9635
- showSave && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10247
+ showSave && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9636
10248
  "button",
9637
10249
  {
9638
10250
  onClick: handleSave,
@@ -9655,7 +10267,7 @@ function DeviceConfigPanelImpl({
9655
10267
  children: binding.state.isSaving ? "Saving..." : "Save"
9656
10268
  }
9657
10269
  ),
9658
- showReset && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10270
+ showReset && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9659
10271
  "button",
9660
10272
  {
9661
10273
  onClick: handleReset,
@@ -9678,7 +10290,7 @@ function DeviceConfigPanelImpl({
9678
10290
  children: "Reset"
9679
10291
  }
9680
10292
  ),
9681
- showCancel && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10293
+ showCancel && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9682
10294
  "button",
9683
10295
  {
9684
10296
  onClick: handleCancel,
@@ -9705,7 +10317,7 @@ function DeviceConfigPanelImpl({
9705
10317
  }
9706
10318
  )
9707
10319
  ] }),
9708
- binding.state.lastSaveResult && showToast && showBuiltInToast && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10320
+ binding.state.lastSaveResult && showToast && showBuiltInToast && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9709
10321
  "div",
9710
10322
  {
9711
10323
  style: {
@@ -9727,10 +10339,10 @@ function DeviceConfigPanelImpl({
9727
10339
  )
9728
10340
  ] });
9729
10341
  }
9730
- var DeviceConfigPanel = import_react24.default.memo(DeviceConfigPanelImpl);
10342
+ var DeviceConfigPanel = import_react26.default.memo(DeviceConfigPanelImpl);
9731
10343
 
9732
10344
  // src/diagram/hooks/useSimulation.ts
9733
- var import_react25 = require("react");
10345
+ var import_react27 = require("react");
9734
10346
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9735
10347
  const {
9736
10348
  interval = 2e3,
@@ -9741,7 +10353,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9741
10353
  historyLength = 10,
9742
10354
  generateValue
9743
10355
  } = options;
9744
- (0, import_react25.useEffect)(() => {
10356
+ (0, import_react27.useEffect)(() => {
9745
10357
  if (!enabled) return;
9746
10358
  const intervalId = setInterval(() => {
9747
10359
  const updates = [];
@@ -9792,6 +10404,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9792
10404
  // Annotate the CommonJS export names for ESM import in node:
9793
10405
  0 && (module.exports = {
9794
10406
  Button,
10407
+ COLOR_PRESETS,
9795
10408
  CardHeader,
9796
10409
  CardUnit,
9797
10410
  CardValue,
@@ -9810,6 +10423,8 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9810
10423
  FooterPanel,
9811
10424
  FullscreenContainer,
9812
10425
  FullscreenWorkspace,
10426
+ ISA_UNKNOWN,
10427
+ ISA_UNKNOWN_DARK,
9813
10428
  LeftPanel,
9814
10429
  LeftToggleButton,
9815
10430
  LegacyValueEntry,
@@ -9821,6 +10436,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9821
10436
  PanelTabs,
9822
10437
  RightPanel,
9823
10438
  RightToggleButton,
10439
+ STATUS_LED_APPEARANCE,
9824
10440
  SVGArea,
9825
10441
  SVGLockedOverlay,
9826
10442
  ScrollableSVGWrapper,
@@ -9831,6 +10447,9 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9831
10447
  ThemeProvider,
9832
10448
  ThemeToggle,
9833
10449
  TrendLine,
10450
+ UNKNOWN_GLYPH_RING_DASH,
10451
+ UNKNOWN_STATUS_GLYPH_COLOR_VAR,
10452
+ UnknownStatusGlyph,
9834
10453
  ValueEntry,
9835
10454
  ZoomButton,
9836
10455
  ZoomControls,
@@ -9840,16 +10459,22 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9840
10459
  exampleDiagram,
9841
10460
  exportDiagram,
9842
10461
  generateRoutePoints,
10462
+ getAppearanceDefaults,
10463
+ getAppearanceSchema,
9843
10464
  getAvailableSymbols,
9844
10465
  getPortWorldPosition,
9845
10466
  getSymbolDefinition,
9846
10467
  importDiagram,
9847
10468
  mockSymbolLibrary,
9848
10469
  nodeHasPort,
10470
+ normalizeDeviceStatus,
9849
10471
  overlayStyles,
10472
+ readAppearanceValue,
9850
10473
  registerSymbol,
9851
10474
  registerSymbols,
10475
+ resolvePresetColor,
9852
10476
  resolveStatusColor,
10477
+ useBooleanIndicators,
9853
10478
  useDeviceConfigs,
9854
10479
  useDeviceControls,
9855
10480
  useDrag,
@@ -9860,6 +10485,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9860
10485
  useTelemetryStatus,
9861
10486
  useTheme,
9862
10487
  useViewBox,
9863
- validateDiagram
10488
+ validateDiagram,
10489
+ writeAppearanceValue
9864
10490
  });
9865
10491
  //# sourceMappingURL=index.cjs.map