@procaaso/alphinity-ui-components 1.10.0 → 1.11.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,
@@ -60,6 +61,7 @@ __export(index_exports, {
60
61
  PanelTabs: () => PanelTabs,
61
62
  RightPanel: () => RightPanel,
62
63
  RightToggleButton: () => RightToggleButton,
64
+ STATUS_LED_APPEARANCE: () => STATUS_LED_APPEARANCE,
63
65
  SVGArea: () => SVGArea,
64
66
  SVGLockedOverlay: () => SVGLockedOverlay,
65
67
  ScrollableSVGWrapper: () => ScrollableSVGWrapper,
@@ -70,6 +72,8 @@ __export(index_exports, {
70
72
  ThemeProvider: () => ThemeProvider,
71
73
  ThemeToggle: () => ThemeToggle,
72
74
  TrendLine: () => TrendLine,
75
+ UNKNOWN_MARK_COLOR: () => UNKNOWN_MARK_COLOR,
76
+ UNKNOWN_MARK_COLOR_DARK: () => UNKNOWN_MARK_COLOR_DARK,
73
77
  ValueEntry: () => ValueEntry,
74
78
  ZoomButton: () => ZoomButton,
75
79
  ZoomControls: () => ZoomControls,
@@ -79,6 +83,8 @@ __export(index_exports, {
79
83
  exampleDiagram: () => exampleDiagram,
80
84
  exportDiagram: () => exportDiagram,
81
85
  generateRoutePoints: () => generateRoutePoints,
86
+ getAppearanceDefaults: () => getAppearanceDefaults,
87
+ getAppearanceSchema: () => getAppearanceSchema,
82
88
  getAvailableSymbols: () => getAvailableSymbols,
83
89
  getPortWorldPosition: () => getPortWorldPosition,
84
90
  getSymbolDefinition: () => getSymbolDefinition,
@@ -86,9 +92,12 @@ __export(index_exports, {
86
92
  mockSymbolLibrary: () => mockSymbolLibrary,
87
93
  nodeHasPort: () => nodeHasPort,
88
94
  overlayStyles: () => overlayStyles,
95
+ readAppearanceValue: () => readAppearanceValue,
89
96
  registerSymbol: () => registerSymbol,
90
97
  registerSymbols: () => registerSymbols,
98
+ resolvePresetColor: () => resolvePresetColor,
91
99
  resolveStatusColor: () => resolveStatusColor,
100
+ useBooleanIndicators: () => useBooleanIndicators,
92
101
  useDeviceConfigs: () => useDeviceConfigs,
93
102
  useDeviceControls: () => useDeviceControls,
94
103
  useDrag: () => useDrag,
@@ -99,7 +108,8 @@ __export(index_exports, {
99
108
  useTelemetryStatus: () => useTelemetryStatus,
100
109
  useTheme: () => useTheme,
101
110
  useViewBox: () => useViewBox,
102
- validateDiagram: () => validateDiagram
111
+ validateDiagram: () => validateDiagram,
112
+ writeAppearanceValue: () => writeAppearanceValue
103
113
  });
104
114
  module.exports = __toCommonJS(index_exports);
105
115
 
@@ -388,6 +398,20 @@ var FORCED_MARK_COLOR = {
388
398
  text: "#5b21b6",
389
399
  border: "#7c3aed"
390
400
  };
401
+ var UNKNOWN_MARK_COLOR = {
402
+ fill: "#ebebf5",
403
+ stroke: "#8f8fc2",
404
+ bg: "#ebebf5",
405
+ text: "#4f4f8a",
406
+ border: "#8f8fc2"
407
+ };
408
+ var UNKNOWN_MARK_COLOR_DARK = {
409
+ fill: "#23263a",
410
+ stroke: "#6f74a3",
411
+ bg: "#23263a",
412
+ text: "#b9bbdb",
413
+ border: "#6f74a3"
414
+ };
391
415
  var STATUS_ALIASES = {
392
416
  running: "normal",
393
417
  stopped: "off"
@@ -861,11 +885,15 @@ var StatusIndicator = ({
861
885
  labelPosition = "right",
862
886
  shape = "circle",
863
887
  led = true,
888
+ visible = true,
864
889
  forced = false,
865
890
  realValue,
866
891
  className = "",
867
892
  ...props
868
893
  }) => {
894
+ if (!visible) {
895
+ return null;
896
+ }
869
897
  const baseClass = "ui-status-indicator";
870
898
  const statusClass = status ? `ui-status-indicator--${status}` : "";
871
899
  const sizeClass = size ? `ui-status-indicator--${size}` : "";
@@ -3620,7 +3648,7 @@ var ThemeToggle = ({
3620
3648
  };
3621
3649
 
3622
3650
  // src/components/PIDCanvas/PIDCanvas.tsx
3623
- var import_react20 = __toESM(require("react"), 1);
3651
+ var import_react21 = __toESM(require("react"), 1);
3624
3652
 
3625
3653
  // src/components/PIDCanvas/NodeRenderer.tsx
3626
3654
  var import_react10 = __toESM(require("react"), 1);
@@ -3651,6 +3679,25 @@ var mockSymbolLibrary = {
3651
3679
  tags: ["valve", "gate", "powered", "actuator"]
3652
3680
  }
3653
3681
  },
3682
+ "status-indicator": {
3683
+ id: "status-indicator",
3684
+ name: "Status LED",
3685
+ category: "instrument",
3686
+ viewBox: { x: 0, y: 0, width: 24, height: 24 },
3687
+ ports: [],
3688
+ svgContent: `
3689
+ <rect x="2" y="2" width="20" height="20"
3690
+ rx="var(--led-rx, 50%)"
3691
+ fill="var(--symbol-fill, #64748b)"
3692
+ stroke="rgba(255,255,255,0.4)"
3693
+ stroke-width="var(--led-bezel, 0)"
3694
+ style="transform-box: fill-box; transform: rotate(var(--led-rotate, 0deg)); transform-origin: center; pointer-events: all;"/>
3695
+ `,
3696
+ metadata: {
3697
+ description: "Status LED indicator driven by telemetry status color",
3698
+ tags: ["status", "led", "indicator", "instrument"]
3699
+ }
3700
+ },
3654
3701
  "pump-positive-displacement": {
3655
3702
  id: "pump-positive-displacement",
3656
3703
  name: "Positive Displacement Pump",
@@ -4843,8 +4890,144 @@ function getAvailableSymbols() {
4843
4890
  return [...Object.keys(customSymbols), ...Object.keys(mockSymbolLibrary)];
4844
4891
  }
4845
4892
 
4893
+ // src/diagram/symbols/appearanceSchema.ts
4894
+ var COLOR_PRESETS = {
4895
+ green: "#22c55e",
4896
+ red: "#ef4444",
4897
+ amber: "#f59e0b",
4898
+ blue: "#3b82f6",
4899
+ grey: "#64748b"
4900
+ };
4901
+ function resolvePresetColor(name) {
4902
+ return name && COLOR_PRESETS[name] || COLOR_PRESETS.grey;
4903
+ }
4904
+ var COLOR_OPTIONS = Object.keys(COLOR_PRESETS);
4905
+ var STATUS_LED_APPEARANCE = [
4906
+ {
4907
+ key: "shape",
4908
+ kind: "select",
4909
+ label: "Shape",
4910
+ options: ["circle", "square", "diamond"],
4911
+ default: "circle"
4912
+ },
4913
+ {
4914
+ key: "size",
4915
+ kind: "select",
4916
+ label: "Size",
4917
+ target: "scale",
4918
+ options: ["small", "medium", "large"],
4919
+ default: "medium",
4920
+ valueMap: { small: 0.75, medium: 1, large: 1.5 }
4921
+ },
4922
+ { key: "led", kind: "boolean", label: "Bezel ring", default: true },
4923
+ { key: "glow", kind: "boolean", label: "Diffusion (glow)", default: false },
4924
+ {
4925
+ key: "animation",
4926
+ kind: "select",
4927
+ label: "Animation",
4928
+ options: ["none", "pulse", "blink"],
4929
+ default: "none"
4930
+ },
4931
+ {
4932
+ key: "labelPosition",
4933
+ kind: "select",
4934
+ label: "Label position",
4935
+ options: ["bottom", "top", "left", "right"],
4936
+ default: "bottom"
4937
+ },
4938
+ // Color binding: a named signal drives the on/off color.
4939
+ {
4940
+ key: "colorSource",
4941
+ kind: "tag",
4942
+ label: "Color source (tag)",
4943
+ default: "",
4944
+ help: "Signal/tag that drives color. True (non-zero) shows On color; false shows Off color. Blank = unbound (grey)."
4945
+ },
4946
+ {
4947
+ key: "onColor",
4948
+ kind: "select",
4949
+ label: "On color (true)",
4950
+ options: COLOR_OPTIONS,
4951
+ default: "green",
4952
+ help: "Color when the Color source is true."
4953
+ },
4954
+ {
4955
+ key: "offColor",
4956
+ kind: "select",
4957
+ label: "Off color (false)",
4958
+ options: COLOR_OPTIONS,
4959
+ default: "red",
4960
+ help: "Color when the Color source is false."
4961
+ },
4962
+ // Visibility binding: an independent named signal drives show/hide.
4963
+ {
4964
+ key: "visibleSource",
4965
+ kind: "tag",
4966
+ label: "Visibility source (tag)",
4967
+ default: "",
4968
+ help: "Signal/tag that drives visibility. True shows the LED, false hides it. Blank = always visible (use the Visible checkbox)."
4969
+ },
4970
+ {
4971
+ key: "visible",
4972
+ kind: "boolean",
4973
+ label: "Visible",
4974
+ target: "visible",
4975
+ default: true,
4976
+ help: "Static show/hide, used only when no Visibility source is set."
4977
+ }
4978
+ ];
4979
+ var APPEARANCE_REGISTRY = {
4980
+ "status-indicator": STATUS_LED_APPEARANCE
4981
+ };
4982
+ function getAppearanceSchema(symbolId) {
4983
+ return APPEARANCE_REGISTRY[symbolId] ?? [];
4984
+ }
4985
+ function getAppearanceDefaults(symbolId) {
4986
+ const out = {};
4987
+ for (const field of getAppearanceSchema(symbolId)) {
4988
+ if ((field.target ?? "metadata") === "metadata" && field.default !== void 0) {
4989
+ out[field.key] = field.default;
4990
+ }
4991
+ }
4992
+ return out;
4993
+ }
4994
+ function readAppearanceValue(node, field) {
4995
+ if (field.target === "visible") {
4996
+ return node.visible ?? field.default ?? true;
4997
+ }
4998
+ if (field.target === "scale") {
4999
+ const scale = node.transform.scale ?? 1;
5000
+ if (field.valueMap) {
5001
+ let bestOption = field.options?.[0] ?? String(scale);
5002
+ let bestDiff = Infinity;
5003
+ for (const [option, value2] of Object.entries(field.valueMap)) {
5004
+ const diff = Math.abs(value2 - scale);
5005
+ if (diff < bestDiff) {
5006
+ bestDiff = diff;
5007
+ bestOption = option;
5008
+ }
5009
+ }
5010
+ return bestOption;
5011
+ }
5012
+ return scale;
5013
+ }
5014
+ const value = node.metadata?.[field.key];
5015
+ return value ?? field.default;
5016
+ }
5017
+ function writeAppearanceValue(node, field, value) {
5018
+ if (field.target === "visible") {
5019
+ return { visible: Boolean(value) };
5020
+ }
5021
+ if (field.target === "scale") {
5022
+ const scale = field.valueMap ? field.valueMap[String(value)] ?? 1 : Number(value);
5023
+ return { transform: { ...node.transform, scale } };
5024
+ }
5025
+ return { metadata: { ...node.metadata, [field.key]: value } };
5026
+ }
5027
+
4846
5028
  // src/components/PIDCanvas/NodeRenderer.tsx
4847
5029
  var import_jsx_runtime16 = require("react/jsx-runtime");
5030
+ var HATCH_SIZE = 6;
4848
5031
  function NodeRendererImpl({
4849
5032
  nodes: nodes2,
4850
5033
  selectedNodeIds,
@@ -4856,140 +5039,206 @@ function NodeRendererImpl({
4856
5039
  enableDrag = false,
4857
5040
  statusColorMap
4858
5041
  }) {
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",
5042
+ const hatchId = `pid-unknown-hatch-${import_react10.default.useId().replace(/[^A-Za-z0-9_-]/g, "")}`;
5043
+ const hasUnknown = nodes2.some((n) => n.positionUnknown === true && n.visible !== false);
5044
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("g", { id: "layer-nodes", children: [
5045
+ hasUnknown && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5046
+ "pattern",
4921
5047
  {
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
- },
5048
+ id: hatchId,
5049
+ "data-testid": "position-unknown-pattern",
5050
+ patternUnits: "userSpaceOnUse",
5051
+ width: HATCH_SIZE,
5052
+ height: HATCH_SIZE,
5053
+ patternTransform: "rotate(45)",
4951
5054
  children: [
4952
- isSelected && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4953
- "rect",
5055
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("rect", { width: HATCH_SIZE, height: HATCH_SIZE, fill: UNKNOWN_MARK_COLOR.fill }),
5056
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5057
+ "line",
4954
5058
  {
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"
5059
+ x1: "0",
5060
+ y1: "0",
5061
+ x2: "0",
5062
+ y2: HATCH_SIZE,
5063
+ stroke: UNKNOWN_MARK_COLOR.text,
5064
+ strokeWidth: "2"
4965
5065
  }
4966
- ),
5066
+ )
5067
+ ]
5068
+ }
5069
+ ) }),
5070
+ nodes2.filter((node) => node.visible !== false).map((node) => {
5071
+ const symbol = getSymbolDefinition(node.symbolId);
5072
+ const isSelected = selectedNodeIds?.has(node.id) ?? false;
5073
+ const isDragging = draggingNodeId === node.id;
5074
+ if (!symbol) {
5075
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("g", { children: [
4967
5076
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4968
- "g",
5077
+ "circle",
4969
5078
  {
4970
- dangerouslySetInnerHTML: { __html: symbol.svgContent },
4971
- "data-symbol-content": symbol.id,
4972
- style: { pointerEvents: "auto" }
5079
+ cx: node.transform.x,
5080
+ cy: node.transform.y,
5081
+ r: "15",
5082
+ fill: "#ff0000",
5083
+ opacity: "0.3",
5084
+ stroke: "#ff0000",
5085
+ strokeWidth: "2"
4973
5086
  }
4974
5087
  ),
4975
5088
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4976
5089
  "text",
4977
5090
  {
4978
- x: symbol.viewBox.width / 2 + (node.labelOffset?.x ?? 0),
4979
- y: symbol.viewBox.height + 12 + (node.labelOffset?.y ?? 0),
5091
+ x: node.transform.x,
5092
+ y: node.transform.y,
4980
5093
  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
5094
+ dominantBaseline: "middle",
5095
+ fontSize: "10",
5096
+ fill: "#ff0000",
5097
+ children: "?"
4986
5098
  }
4987
5099
  )
4988
- ]
4989
- },
4990
- node.id
4991
- );
4992
- }) });
5100
+ ] }, node.id);
5101
+ }
5102
+ const transforms = [];
5103
+ const effectiveX = isDragging ? node.transform.x + dragOffset.x : node.transform.x;
5104
+ const effectiveY = isDragging ? node.transform.y + dragOffset.y : node.transform.y;
5105
+ transforms.push(`translate(${effectiveX}, ${effectiveY})`);
5106
+ if (node.transform.rotation) {
5107
+ const centerX = symbol.viewBox.width / 2;
5108
+ const centerY = symbol.viewBox.height / 2;
5109
+ transforms.push(`rotate(${node.transform.rotation}, ${centerX}, ${centerY})`);
5110
+ }
5111
+ if (node.transform.scale && node.transform.scale !== 1) {
5112
+ const centerX = symbol.viewBox.width / 2;
5113
+ const centerY = symbol.viewBox.height / 2;
5114
+ transforms.push(
5115
+ `translate(${centerX}, ${centerY}) scale(${node.transform.scale}) translate(${-centerX}, ${-centerY})`
5116
+ );
5117
+ }
5118
+ if (node.transform.flipX || node.transform.flipY) {
5119
+ const scaleX = node.transform.flipX ? -1 : 1;
5120
+ const scaleY = node.transform.flipY ? -1 : 1;
5121
+ const centerX = symbol.viewBox.width / 2;
5122
+ const centerY = symbol.viewBox.height / 2;
5123
+ transforms.push(
5124
+ `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
5125
+ );
5126
+ }
5127
+ const statusEntry = resolveStatusColor(node.status, statusColorMap);
5128
+ const isUnknown = node.positionUnknown === true;
5129
+ const fillColor = isUnknown ? `url(#${hatchId})` : node.customColors?.fill ?? statusEntry.fill;
5130
+ const strokeColor = isUnknown ? UNKNOWN_MARK_COLOR.stroke : node.customColors?.stroke ?? statusEntry.stroke;
5131
+ const appearance = { ...getAppearanceDefaults(node.symbolId), ...node.metadata ?? {} };
5132
+ const ledStyle = {};
5133
+ if (appearance.shape !== void 0) {
5134
+ ledStyle["--led-rx"] = appearance.shape === "circle" ? "50%" : "2";
5135
+ ledStyle["--led-rotate"] = appearance.shape === "diamond" ? "45deg" : "0deg";
5136
+ }
5137
+ if (appearance.led !== void 0) {
5138
+ ledStyle["--led-bezel"] = appearance.led ? "2" : "0";
5139
+ }
5140
+ if (appearance.glow) {
5141
+ ledStyle.filter = `drop-shadow(0 0 4px ${fillColor})`;
5142
+ }
5143
+ const ledAnimation = typeof appearance.animation === "string" && appearance.animation !== "none" ? appearance.animation : void 0;
5144
+ const vb = symbol.viewBox;
5145
+ const ox = node.labelOffset?.x ?? 0;
5146
+ const oy = node.labelOffset?.y ?? 0;
5147
+ const labelPos = typeof appearance.labelPosition === "string" ? appearance.labelPosition : "bottom";
5148
+ let labelX = vb.width / 2 + ox;
5149
+ let labelY = vb.height + 12 + oy;
5150
+ let labelAnchor = "middle";
5151
+ if (labelPos === "top") {
5152
+ labelX = vb.width / 2 + ox;
5153
+ labelY = -6 + oy;
5154
+ labelAnchor = "middle";
5155
+ } else if (labelPos === "left") {
5156
+ labelX = -6 + ox;
5157
+ labelY = vb.height / 2 + oy;
5158
+ labelAnchor = "end";
5159
+ } else if (labelPos === "right") {
5160
+ labelX = vb.width + 6 + ox;
5161
+ labelY = vb.height / 2 + oy;
5162
+ labelAnchor = "start";
5163
+ }
5164
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5165
+ "g",
5166
+ {
5167
+ "data-node-id": node.id,
5168
+ "data-symbol-id": node.symbolId,
5169
+ "data-selected": isSelected,
5170
+ "data-dragging": isDragging,
5171
+ "data-led-animation": ledAnimation,
5172
+ "data-position-unknown": isUnknown ? "true" : void 0,
5173
+ transform: transforms.join(" "),
5174
+ style: {
5175
+ "--symbol-fill": fillColor,
5176
+ "--symbol-stroke": strokeColor,
5177
+ cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default",
5178
+ ...ledStyle
5179
+ },
5180
+ onClick: (e) => {
5181
+ e.stopPropagation();
5182
+ onNodeClick?.(node.id, e);
5183
+ },
5184
+ onDoubleClick: (e) => {
5185
+ e.stopPropagation();
5186
+ onNodeDoubleClick?.(node.id, e);
5187
+ },
5188
+ onMouseDown: (e) => {
5189
+ if (enableDrag) {
5190
+ onNodeDragStart?.(node.id, e);
5191
+ }
5192
+ },
5193
+ onTouchStart: (e) => {
5194
+ if (enableDrag) {
5195
+ e.stopPropagation();
5196
+ onNodeDragStart?.(node.id, e);
5197
+ }
5198
+ },
5199
+ children: [
5200
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5201
+ "rect",
5202
+ {
5203
+ x: "-5",
5204
+ y: "-5",
5205
+ width: symbol.viewBox.width + 10,
5206
+ height: symbol.viewBox.height + 10,
5207
+ fill: "none",
5208
+ stroke: isDragging ? "#10b981" : "#3b82f6",
5209
+ strokeWidth: isDragging ? "3" : "2",
5210
+ strokeDasharray: "4 2",
5211
+ rx: "2",
5212
+ pointerEvents: "none"
5213
+ }
5214
+ ),
5215
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5216
+ "g",
5217
+ {
5218
+ dangerouslySetInnerHTML: { __html: symbol.svgContent },
5219
+ "data-symbol-content": symbol.id,
5220
+ style: { pointerEvents: "auto" }
5221
+ }
5222
+ ),
5223
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5224
+ "text",
5225
+ {
5226
+ x: labelX,
5227
+ y: labelY,
5228
+ textAnchor: labelAnchor,
5229
+ fontSize: node.labelFontSize ?? 10,
5230
+ fontWeight: "600",
5231
+ fill: isSelected ? "#3b82f6" : "#1f2937",
5232
+ transform: node.transform.rotation ? `rotate(${-node.transform.rotation}, ${labelX}, ${labelY})` : void 0,
5233
+ children: node.label || node.id
5234
+ }
5235
+ )
5236
+ ]
5237
+ },
5238
+ node.id
5239
+ );
5240
+ })
5241
+ ] });
4993
5242
  }
4994
5243
  var NodeRenderer = import_react10.default.memo(NodeRendererImpl);
4995
5244
 
@@ -6139,8 +6388,49 @@ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
6139
6388
  }, [telemetry, diagram, setDiagram, enabled, mapStatus]);
6140
6389
  }
6141
6390
 
6142
- // src/diagram/hooks/useDrag.ts
6391
+ // src/diagram/hooks/useBooleanIndicators.ts
6143
6392
  var import_react18 = require("react");
6393
+ function useBooleanIndicators(telemetry, diagram, setDiagram, options = {}) {
6394
+ const { enabled = true, symbolId = "status-indicator" } = options;
6395
+ (0, import_react18.useEffect)(() => {
6396
+ if (!enabled) return;
6397
+ const tagIndex = /* @__PURE__ */ new Map();
6398
+ telemetry.forEach((binding) => {
6399
+ if (binding.tag) tagIndex.set(binding.tag, binding.value.value);
6400
+ });
6401
+ let needsUpdate = false;
6402
+ const updatedNodes = diagram.nodes.map((node) => {
6403
+ if (node.symbolId !== symbolId) return node;
6404
+ const meta = node.metadata ?? {};
6405
+ const colorSource = typeof meta.colorSource === "string" ? meta.colorSource : "";
6406
+ const visibleSource = typeof meta.visibleSource === "string" ? meta.visibleSource : "";
6407
+ let next = node;
6408
+ if (colorSource) {
6409
+ const isTrue = Boolean(tagIndex.get(colorSource));
6410
+ const fill = resolvePresetColor(
6411
+ String(isTrue ? meta.onColor ?? "green" : meta.offColor ?? "red")
6412
+ );
6413
+ if (node.customColors?.fill !== fill) {
6414
+ next = { ...next, customColors: { ...next.customColors, fill } };
6415
+ }
6416
+ }
6417
+ if (visibleSource) {
6418
+ const vis = Boolean(tagIndex.get(visibleSource));
6419
+ if ((next.visible ?? true) !== vis) {
6420
+ next = { ...next, visible: vis };
6421
+ }
6422
+ }
6423
+ if (next !== node) needsUpdate = true;
6424
+ return next;
6425
+ });
6426
+ if (needsUpdate) {
6427
+ setDiagram({ ...diagram, nodes: updatedNodes });
6428
+ }
6429
+ }, [telemetry, diagram, setDiagram, enabled, symbolId]);
6430
+ }
6431
+
6432
+ // src/diagram/hooks/useDrag.ts
6433
+ var import_react19 = require("react");
6144
6434
  function useDrag(options = {}) {
6145
6435
  const {
6146
6436
  onDragStart,
@@ -6150,23 +6440,23 @@ function useDrag(options = {}) {
6150
6440
  dragThreshold = 3,
6151
6441
  screenToWorld = (x, y) => ({ x, y })
6152
6442
  } = options;
6153
- const [dragState, setDragState] = (0, import_react18.useState)({
6443
+ const [dragState, setDragState] = (0, import_react19.useState)({
6154
6444
  isDragging: false,
6155
6445
  startPosition: null,
6156
6446
  offset: { x: 0, y: 0 },
6157
6447
  draggedId: null
6158
6448
  });
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)(
6449
+ const [isListening, setIsListening] = (0, import_react19.useState)(false);
6450
+ const dragStartRef = (0, import_react19.useRef)(null);
6451
+ const hasMovedRef = (0, import_react19.useRef)(false);
6452
+ const applySnap = (0, import_react19.useCallback)(
6163
6453
  (value) => {
6164
6454
  if (snapToGrid <= 0) return value;
6165
6455
  return Math.round(value / snapToGrid) * snapToGrid;
6166
6456
  },
6167
6457
  [snapToGrid]
6168
6458
  );
6169
- const handleMouseMove = (0, import_react18.useCallback)(
6459
+ const handleMouseMove = (0, import_react19.useCallback)(
6170
6460
  (event) => {
6171
6461
  if (!dragStartRef.current) return;
6172
6462
  const { id, screenStart: _screenStart, itemPosition, clickOffset } = dragStartRef.current;
@@ -6207,7 +6497,7 @@ function useDrag(options = {}) {
6207
6497
  },
6208
6498
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
6209
6499
  );
6210
- const handleMouseUp = (0, import_react18.useCallback)(
6500
+ const handleMouseUp = (0, import_react19.useCallback)(
6211
6501
  (event) => {
6212
6502
  if (!dragStartRef.current || !hasMovedRef.current) {
6213
6503
  dragStartRef.current = null;
@@ -6244,7 +6534,7 @@ function useDrag(options = {}) {
6244
6534
  },
6245
6535
  [screenToWorld, applySnap, onDragEnd]
6246
6536
  );
6247
- const handleTouchMove = (0, import_react18.useCallback)(
6537
+ const handleTouchMove = (0, import_react19.useCallback)(
6248
6538
  (event) => {
6249
6539
  if (!dragStartRef.current || event.touches.length !== 1) return;
6250
6540
  const touch = event.touches[0];
@@ -6287,7 +6577,7 @@ function useDrag(options = {}) {
6287
6577
  },
6288
6578
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
6289
6579
  );
6290
- const handleTouchEnd = (0, import_react18.useCallback)(
6580
+ const handleTouchEnd = (0, import_react19.useCallback)(
6291
6581
  (event) => {
6292
6582
  if (!dragStartRef.current || !hasMovedRef.current) {
6293
6583
  dragStartRef.current = null;
@@ -6325,7 +6615,7 @@ function useDrag(options = {}) {
6325
6615
  },
6326
6616
  [screenToWorld, applySnap, onDragEnd]
6327
6617
  );
6328
- (0, import_react18.useEffect)(() => {
6618
+ (0, import_react19.useEffect)(() => {
6329
6619
  if (!isListening) return;
6330
6620
  document.addEventListener("mousemove", handleMouseMove);
6331
6621
  document.addEventListener("mouseup", handleMouseUp);
@@ -6340,7 +6630,7 @@ function useDrag(options = {}) {
6340
6630
  document.removeEventListener("touchcancel", handleTouchEnd);
6341
6631
  };
6342
6632
  }, [isListening, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
6343
- const startDrag = (0, import_react18.useCallback)(
6633
+ const startDrag = (0, import_react19.useCallback)(
6344
6634
  (id, event, itemPosition) => {
6345
6635
  event.stopPropagation();
6346
6636
  let clientX;
@@ -6370,7 +6660,7 @@ function useDrag(options = {}) {
6370
6660
  },
6371
6661
  [screenToWorld]
6372
6662
  );
6373
- const cancelDrag = (0, import_react18.useCallback)(() => {
6663
+ const cancelDrag = (0, import_react19.useCallback)(() => {
6374
6664
  dragStartRef.current = null;
6375
6665
  hasMovedRef.current = false;
6376
6666
  setIsListening(false);
@@ -6381,7 +6671,7 @@ function useDrag(options = {}) {
6381
6671
  draggedId: null
6382
6672
  });
6383
6673
  }, []);
6384
- const isDraggingItem = (0, import_react18.useCallback)(
6674
+ const isDraggingItem = (0, import_react19.useCallback)(
6385
6675
  (id) => dragState.isDragging && dragState.draggedId === id,
6386
6676
  [dragState.isDragging, dragState.draggedId]
6387
6677
  );
@@ -6394,7 +6684,7 @@ function useDrag(options = {}) {
6394
6684
  }
6395
6685
 
6396
6686
  // src/diagram/hooks/useKeyboardShortcuts.ts
6397
- var import_react19 = require("react");
6687
+ var import_react20 = require("react");
6398
6688
  function useKeyboardShortcuts(options) {
6399
6689
  const {
6400
6690
  onDelete,
@@ -6405,7 +6695,7 @@ function useKeyboardShortcuts(options) {
6405
6695
  onSelectAll,
6406
6696
  enabled = true
6407
6697
  } = options;
6408
- (0, import_react19.useEffect)(() => {
6698
+ (0, import_react20.useEffect)(() => {
6409
6699
  if (!enabled) return;
6410
6700
  const handleKeyDown = (event) => {
6411
6701
  const target = event.target;
@@ -6543,21 +6833,21 @@ function PIDCanvasImpl({
6543
6833
  statusColorMap,
6544
6834
  ...props
6545
6835
  }) {
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)(() => {
6836
+ const [localDiagram, setLocalDiagram] = (0, import_react21.useState)(diagram);
6837
+ const [dragOverPosition, setDragOverPosition] = (0, import_react21.useState)(null);
6838
+ const [isConnecting, setIsConnecting] = (0, import_react21.useState)(false);
6839
+ const [connectionSource, setConnectionSource] = (0, import_react21.useState)(null);
6840
+ const [connectionSourcePort, setConnectionSourcePort] = (0, import_react21.useState)(null);
6841
+ const [connectionCursor, setConnectionCursor] = (0, import_react21.useState)(null);
6842
+ const [hoveredNode, setHoveredNode] = (0, import_react21.useState)(null);
6843
+ const [hoveredPort, setHoveredPort] = (0, import_react21.useState)(null);
6844
+ const [selectedWaypoint, setSelectedWaypoint] = (0, import_react21.useState)(null);
6845
+ const historyStack = (0, import_react21.useRef)([]);
6846
+ const redoStack = (0, import_react21.useRef)([]);
6847
+ const isUndoRedoAction = (0, import_react21.useRef)(false);
6848
+ const isInternalChange = (0, import_react21.useRef)(false);
6849
+ const lastInternalDiagram = (0, import_react21.useRef)(null);
6850
+ (0, import_react21.useEffect)(() => {
6561
6851
  const isExternal = !isInternalChange.current && diagram !== lastInternalDiagram.current;
6562
6852
  if (isExternal) {
6563
6853
  setLocalDiagram(diagram);
@@ -6579,7 +6869,7 @@ function PIDCanvasImpl({
6579
6869
  enablePan: features.pan ?? true,
6580
6870
  enableZoom: features.zoom ?? true
6581
6871
  });
6582
- const calculateContentBounds = (0, import_react20.useCallback)(() => {
6872
+ const calculateContentBounds = (0, import_react21.useCallback)(() => {
6583
6873
  const nodes2 = localDiagram.nodes.filter((n) => n.visible !== false);
6584
6874
  if (nodes2.length === 0) {
6585
6875
  return null;
@@ -6602,7 +6892,7 @@ function PIDCanvasImpl({
6602
6892
  });
6603
6893
  return { minX, minY, maxX, maxY };
6604
6894
  }, [localDiagram.nodes]);
6605
- (0, import_react20.useEffect)(() => {
6895
+ (0, import_react21.useEffect)(() => {
6606
6896
  if (onMounted) {
6607
6897
  const controls = {
6608
6898
  fitToContent: (padding = 50) => {
@@ -6643,8 +6933,8 @@ function PIDCanvasImpl({
6643
6933
  });
6644
6934
  const dragEnabled = features.dragNodes ?? false;
6645
6935
  const selectionEnabled = features.selection ?? false;
6646
- const containerRef = (0, import_react20.useRef)(null);
6647
- const pushToHistory = (0, import_react20.useCallback)((currentDiagram) => {
6936
+ const containerRef = (0, import_react21.useRef)(null);
6937
+ const pushToHistory = (0, import_react21.useCallback)((currentDiagram) => {
6648
6938
  if (isUndoRedoAction.current) return;
6649
6939
  const snapshot = JSON.parse(JSON.stringify(currentDiagram));
6650
6940
  historyStack.current.push(snapshot);
@@ -6653,7 +6943,7 @@ function PIDCanvasImpl({
6653
6943
  }
6654
6944
  redoStack.current = [];
6655
6945
  }, []);
6656
- const undo = (0, import_react20.useCallback)(() => {
6946
+ const undo = (0, import_react21.useCallback)(() => {
6657
6947
  if (historyStack.current.length === 0) return;
6658
6948
  isUndoRedoAction.current = true;
6659
6949
  const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
@@ -6667,7 +6957,7 @@ function PIDCanvasImpl({
6667
6957
  isUndoRedoAction.current = false;
6668
6958
  }, 0);
6669
6959
  }, [localDiagram, onDiagramChange]);
6670
- const redo = (0, import_react20.useCallback)(() => {
6960
+ const redo = (0, import_react21.useCallback)(() => {
6671
6961
  if (redoStack.current.length === 0) return;
6672
6962
  isUndoRedoAction.current = true;
6673
6963
  const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
@@ -6681,7 +6971,7 @@ function PIDCanvasImpl({
6681
6971
  isUndoRedoAction.current = false;
6682
6972
  }, 0);
6683
6973
  }, [localDiagram, onDiagramChange]);
6684
- (0, import_react20.useEffect)(() => {
6974
+ (0, import_react21.useEffect)(() => {
6685
6975
  const handleKeyDown = (e) => {
6686
6976
  const isCtrlOrCmd = e.ctrlKey || e.metaKey;
6687
6977
  if (isCtrlOrCmd && e.key === "z" && !e.shiftKey) {
@@ -6698,7 +6988,7 @@ function PIDCanvasImpl({
6698
6988
  window.addEventListener("keydown", handleKeyDown);
6699
6989
  return () => window.removeEventListener("keydown", handleKeyDown);
6700
6990
  }, [undo, redo]);
6701
- (0, import_react20.useEffect)(() => {
6991
+ (0, import_react21.useEffect)(() => {
6702
6992
  if (!features.dragNodes) return;
6703
6993
  const handleKeyDown = (e) => {
6704
6994
  if (selection.selectedNodeIds.size === 0 && !selectedWaypoint) return;
@@ -6783,7 +7073,7 @@ function PIDCanvasImpl({
6783
7073
  onNodeMove,
6784
7074
  pushToHistory
6785
7075
  ]);
6786
- const handlePositionChange = (0, import_react20.useCallback)(
7076
+ const handlePositionChange = (0, import_react21.useCallback)(
6787
7077
  (nodeId, x, y) => {
6788
7078
  pushToHistory(localDiagram);
6789
7079
  const updatedNodes = localDiagram.nodes.map(
@@ -6805,7 +7095,7 @@ function PIDCanvasImpl({
6805
7095
  },
6806
7096
  [localDiagram, onDiagramChange, onNodeMove, pushToHistory]
6807
7097
  );
6808
- const handleWaypointPositionChange = (0, import_react20.useCallback)(
7098
+ const handleWaypointPositionChange = (0, import_react21.useCallback)(
6809
7099
  (pipeId, pointIndex, x, y) => {
6810
7100
  pushToHistory(localDiagram);
6811
7101
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -6823,7 +7113,7 @@ function PIDCanvasImpl({
6823
7113
  },
6824
7114
  [localDiagram, onDiagramChange, pushToHistory]
6825
7115
  );
6826
- const handleDragEnd = (0, import_react20.useCallback)(
7116
+ const handleDragEnd = (0, import_react21.useCallback)(
6827
7117
  (nodeId, offset, finalPosition) => {
6828
7118
  const dragDistance = Math.sqrt(offset.x ** 2 + offset.y ** 2);
6829
7119
  if (dragDistance < 2) {
@@ -6904,7 +7194,7 @@ function PIDCanvasImpl({
6904
7194
  onDiagramChange?.(updatedDiagram);
6905
7195
  }
6906
7196
  });
6907
- const handleNodeDragStart = (0, import_react20.useCallback)(
7197
+ const handleNodeDragStart = (0, import_react21.useCallback)(
6908
7198
  (nodeId, event) => {
6909
7199
  const node = localDiagram.nodes.find((n) => n.id === nodeId);
6910
7200
  if (!node) return;
@@ -6912,7 +7202,7 @@ function PIDCanvasImpl({
6912
7202
  },
6913
7203
  [localDiagram.nodes, startDrag]
6914
7204
  );
6915
- const handleWaypointClick = (0, import_react20.useCallback)(
7205
+ const handleWaypointClick = (0, import_react21.useCallback)(
6916
7206
  (pipeId, pointIndex) => {
6917
7207
  setSelectedWaypoint({ pipeId, pointIndex });
6918
7208
  if (selectionEnabled) {
@@ -6921,7 +7211,7 @@ function PIDCanvasImpl({
6921
7211
  },
6922
7212
  [selectionEnabled, clearSelection]
6923
7213
  );
6924
- const handleWaypointDragStart = (0, import_react20.useCallback)(
7214
+ const handleWaypointDragStart = (0, import_react21.useCallback)(
6925
7215
  (pipeId, pointIndex, event) => {
6926
7216
  const pipe = localDiagram.pipes.find((p) => p.id === pipeId);
6927
7217
  if (!pipe || pointIndex >= pipe.routePoints.length) return;
@@ -6930,7 +7220,7 @@ function PIDCanvasImpl({
6930
7220
  },
6931
7221
  [localDiagram.pipes, startWaypointDrag]
6932
7222
  );
6933
- const handlePipeSegmentClick = (0, import_react20.useCallback)(
7223
+ const handlePipeSegmentClick = (0, import_react21.useCallback)(
6934
7224
  (pipeId, position, segmentIndex) => {
6935
7225
  pushToHistory(localDiagram);
6936
7226
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -6949,7 +7239,7 @@ function PIDCanvasImpl({
6949
7239
  [localDiagram, onDiagramChange, pushToHistory]
6950
7240
  );
6951
7241
  const telemetryEnabled = features.telemetry ?? false;
6952
- const handleDelete = (0, import_react20.useCallback)(() => {
7242
+ const handleDelete = (0, import_react21.useCallback)(() => {
6953
7243
  if (!selectionEnabled) return;
6954
7244
  const { selectedNodeIds, selectedPipeIds } = selection;
6955
7245
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
@@ -6978,17 +7268,17 @@ function PIDCanvasImpl({
6978
7268
  onDelete,
6979
7269
  pushToHistory
6980
7270
  ]);
6981
- const handleCopy = (0, import_react20.useCallback)(() => {
7271
+ const handleCopy = (0, import_react21.useCallback)(() => {
6982
7272
  if (!selectionEnabled) return;
6983
7273
  const { selectedNodeIds, selectedPipeIds } = selection;
6984
7274
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
6985
7275
  onItemsCopied?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
6986
7276
  }, [selection, selectionEnabled, onItemsCopied]);
6987
- const handlePaste = (0, import_react20.useCallback)(() => {
7277
+ const handlePaste = (0, import_react21.useCallback)(() => {
6988
7278
  if (!selectionEnabled) return;
6989
7279
  onPaste?.();
6990
7280
  }, [selectionEnabled, onPaste]);
6991
- const handleNodeDoubleClick = (0, import_react20.useCallback)(
7281
+ const handleNodeDoubleClick = (0, import_react21.useCallback)(
6992
7282
  (nodeId, event) => {
6993
7283
  if (!selectionEnabled) return;
6994
7284
  console.log("Node double-clicked:", nodeId);
@@ -7002,7 +7292,7 @@ function PIDCanvasImpl({
7002
7292
  onCopy: handleCopy,
7003
7293
  onPaste: handlePaste
7004
7294
  });
7005
- const handleNodeClick = (0, import_react20.useCallback)(
7295
+ const handleNodeClick = (0, import_react21.useCallback)(
7006
7296
  (nodeId, event) => {
7007
7297
  const connectionModeEnabled = features.connectionMode ?? false;
7008
7298
  if (connectionModeEnabled && isConnecting) {
@@ -7044,7 +7334,7 @@ function PIDCanvasImpl({
7044
7334
  onNodeClick
7045
7335
  ]
7046
7336
  );
7047
- const handlePipeClick = (0, import_react20.useCallback)(
7337
+ const handlePipeClick = (0, import_react21.useCallback)(
7048
7338
  (pipeId, event) => {
7049
7339
  if (selectionEnabled) {
7050
7340
  selectPipe(pipeId, event.ctrlKey || event.metaKey);
@@ -7063,7 +7353,7 @@ function PIDCanvasImpl({
7063
7353
  }
7064
7354
  setSelectedWaypoint(null);
7065
7355
  };
7066
- const handleDragOver = (0, import_react20.useCallback)(
7356
+ const handleDragOver = (0, import_react21.useCallback)(
7067
7357
  (event) => {
7068
7358
  if (!features.allowSymbolDrop) return;
7069
7359
  event.preventDefault();
@@ -7074,7 +7364,7 @@ function PIDCanvasImpl({
7074
7364
  },
7075
7365
  [features.allowSymbolDrop, screenToWorld, svgRef]
7076
7366
  );
7077
- const handleDrop = (0, import_react20.useCallback)(
7367
+ const handleDrop = (0, import_react21.useCallback)(
7078
7368
  (event) => {
7079
7369
  if (!features.allowSymbolDrop) return;
7080
7370
  event.preventDefault();
@@ -7087,10 +7377,10 @@ function PIDCanvasImpl({
7087
7377
  },
7088
7378
  [features.allowSymbolDrop, screenToWorld, onSymbolDrop, svgRef]
7089
7379
  );
7090
- const handleDragLeave = (0, import_react20.useCallback)(() => {
7380
+ const handleDragLeave = (0, import_react21.useCallback)(() => {
7091
7381
  setDragOverPosition(null);
7092
7382
  }, []);
7093
- const handleMouseMove = (0, import_react20.useCallback)(
7383
+ const handleMouseMove = (0, import_react21.useCallback)(
7094
7384
  (event) => {
7095
7385
  const connectionModeEnabled = features.connectionMode ?? false;
7096
7386
  if (connectionModeEnabled) {
@@ -7235,6 +7525,7 @@ function PIDCanvasImpl({
7235
7525
  }
7236
7526
  ),
7237
7527
  telemetryEnabled && telemetry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
7528
+ if (node.symbolId === "status-indicator") return null;
7238
7529
  const binding = telemetry.get(node.id);
7239
7530
  if (!binding) return null;
7240
7531
  const symbol = getSymbolDefinition(node.symbolId);
@@ -7400,10 +7691,10 @@ function PIDCanvasImpl({
7400
7691
  }
7401
7692
  );
7402
7693
  }
7403
- var PIDCanvas = import_react20.default.memo(PIDCanvasImpl);
7694
+ var PIDCanvas = import_react21.default.memo(PIDCanvasImpl);
7404
7695
 
7405
7696
  // src/components/SymbolLibrary/SymbolLibrary.tsx
7406
- var import_react21 = require("react");
7697
+ var import_react22 = require("react");
7407
7698
  var import_jsx_runtime21 = require("react/jsx-runtime");
7408
7699
  function SymbolLibrary({
7409
7700
  className = "",
@@ -7411,8 +7702,8 @@ function SymbolLibrary({
7411
7702
  showCategories = true,
7412
7703
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
7413
7704
  }) {
7414
- const [selectedCategory, setSelectedCategory] = (0, import_react21.useState)("all");
7415
- const [searchQuery, setSearchQuery] = (0, import_react21.useState)("");
7705
+ const [selectedCategory, setSelectedCategory] = (0, import_react22.useState)("all");
7706
+ const [searchQuery, setSearchQuery] = (0, import_react22.useState)("");
7416
7707
  const symbolIds = getAvailableSymbols();
7417
7708
  const allSymbols = symbolIds.map((id) => getSymbolDefinition(id)).filter((symbol) => symbol !== void 0);
7418
7709
  const categoryMap = {
@@ -7602,6 +7893,8 @@ function NodeConfigPanel({
7602
7893
  onUpdateNode,
7603
7894
  onUpdateTelemetry,
7604
7895
  onAddTelemetry,
7896
+ onRemoveTelemetry,
7897
+ availableTags = [],
7605
7898
  onRemove,
7606
7899
  className = "",
7607
7900
  style = {}
@@ -7614,6 +7907,8 @@ function NodeConfigPanel({
7614
7907
  padding: "20px",
7615
7908
  ...style
7616
7909
  };
7910
+ const appearanceFields = getAppearanceSchema(node.symbolId);
7911
+ const showGenericTelemetry = appearanceFields.length === 0;
7617
7912
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className, style: defaultStyle, children: [
7618
7913
  /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
7619
7914
  "Configure: ",
@@ -7735,7 +8030,136 @@ function NodeConfigPanel({
7735
8030
  )
7736
8031
  ] })
7737
8032
  ] }),
7738
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8033
+ appearanceFields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8034
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Appearance" }),
8035
+ appearanceFields.map((field) => {
8036
+ const value = readAppearanceValue(node, field);
8037
+ const fieldLabel = field.label ?? field.key;
8038
+ const inputStyle = {
8039
+ width: "100%",
8040
+ padding: "6px 8px",
8041
+ borderRadius: "4px",
8042
+ border: "1px solid #cbd5e1",
8043
+ fontSize: "0.875rem"
8044
+ };
8045
+ let control;
8046
+ if (field.kind === "boolean") {
8047
+ control = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { style: { fontSize: "0.75rem", display: "block" }, children: [
8048
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8049
+ "input",
8050
+ {
8051
+ type: "checkbox",
8052
+ checked: Boolean(value),
8053
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.checked)),
8054
+ style: { marginRight: "6px" }
8055
+ }
8056
+ ),
8057
+ fieldLabel
8058
+ ] });
8059
+ } else if (field.kind === "select") {
8060
+ control = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8061
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8062
+ fieldLabel,
8063
+ ":"
8064
+ ] }),
8065
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8066
+ "select",
8067
+ {
8068
+ value: String(value),
8069
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8070
+ style: inputStyle,
8071
+ children: field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: opt, children: opt }, opt))
8072
+ }
8073
+ )
8074
+ ] });
8075
+ } else if (field.kind === "number") {
8076
+ control = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8077
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8078
+ fieldLabel,
8079
+ ":"
8080
+ ] }),
8081
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8082
+ "input",
8083
+ {
8084
+ type: "number",
8085
+ value: Number(value),
8086
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, parseFloat(e.target.value) || 0)),
8087
+ style: inputStyle
8088
+ }
8089
+ )
8090
+ ] });
8091
+ } else if (field.kind === "tag") {
8092
+ const tagValue = String(value);
8093
+ const listId = `tags-${node.id}-${field.key}`;
8094
+ const known = tagValue.length > 0 && availableTags.includes(tagValue);
8095
+ const unknown = tagValue.length > 0 && !known;
8096
+ control = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8097
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8098
+ fieldLabel,
8099
+ ":"
8100
+ ] }),
8101
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8102
+ "input",
8103
+ {
8104
+ type: "text",
8105
+ value: tagValue,
8106
+ list: listId,
8107
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8108
+ placeholder: "type or pick a signal\u2026",
8109
+ style: {
8110
+ ...inputStyle,
8111
+ border: `1px solid ${unknown ? "#f59e0b" : known ? "#22c55e" : "#cbd5e1"}`
8112
+ }
8113
+ }
8114
+ ),
8115
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("datalist", { id: listId, children: availableTags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: t }, t)) }),
8116
+ tagValue.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8117
+ "div",
8118
+ {
8119
+ style: {
8120
+ fontSize: "0.7rem",
8121
+ marginTop: "3px",
8122
+ color: known ? "#16a34a" : "#b45309"
8123
+ },
8124
+ children: known ? "\u2713 matched signal" : "\u26A0 no matching signal (reads as false until published)"
8125
+ }
8126
+ )
8127
+ ] });
8128
+ } else {
8129
+ control = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8130
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
8131
+ fieldLabel,
8132
+ ":"
8133
+ ] }),
8134
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8135
+ "input",
8136
+ {
8137
+ type: "text",
8138
+ value: String(value),
8139
+ onChange: (e) => onUpdateNode(writeAppearanceValue(node, field, e.target.value)),
8140
+ style: inputStyle
8141
+ }
8142
+ )
8143
+ ] });
8144
+ }
8145
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "10px" }, children: [
8146
+ control,
8147
+ field.help && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8148
+ "div",
8149
+ {
8150
+ style: {
8151
+ fontSize: "0.7rem",
8152
+ color: "#64748b",
8153
+ marginTop: "3px",
8154
+ lineHeight: 1.35
8155
+ },
8156
+ children: field.help
8157
+ }
8158
+ )
8159
+ ] }, field.key);
8160
+ })
8161
+ ] }),
8162
+ showGenericTelemetry && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
7739
8163
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
7740
8164
  !binding ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7741
8165
  "button",
@@ -7781,6 +8205,27 @@ function NodeConfigPanel({
7781
8205
  children: "+ Add Telemetry"
7782
8206
  }
7783
8207
  ) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8208
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Tag (attribute):" }),
8209
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8210
+ "input",
8211
+ {
8212
+ type: "text",
8213
+ value: binding.tag || "",
8214
+ onChange: (e) => onUpdateTelemetry({
8215
+ nodeId: node.id,
8216
+ tag: e.target.value
8217
+ }),
8218
+ placeholder: "e.g. pump.running",
8219
+ style: {
8220
+ width: "100%",
8221
+ padding: "6px 8px",
8222
+ borderRadius: "4px",
8223
+ border: "1px solid #cbd5e1",
8224
+ fontSize: "0.875rem",
8225
+ marginBottom: "8px"
8226
+ }
8227
+ }
8228
+ ),
7784
8229
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
7785
8230
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7786
8231
  "input",
@@ -8088,10 +8533,28 @@ function NodeConfigPanel({
8088
8533
  ] })
8089
8534
  ] }),
8090
8535
  " "
8091
- ] })
8536
+ ] }),
8537
+ onRemoveTelemetry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8538
+ "button",
8539
+ {
8540
+ onClick: onRemoveTelemetry,
8541
+ style: {
8542
+ width: "100%",
8543
+ padding: "6px 8px",
8544
+ marginTop: "8px",
8545
+ borderRadius: "4px",
8546
+ border: "1px solid #ef4444",
8547
+ background: "transparent",
8548
+ color: "#ef4444",
8549
+ fontSize: "0.8125rem",
8550
+ cursor: "pointer"
8551
+ },
8552
+ children: "Remove Telemetry"
8553
+ }
8554
+ )
8092
8555
  ] })
8093
8556
  ] }),
8094
- binding && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8557
+ showGenericTelemetry && binding && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8095
8558
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
8096
8559
  ["highHigh", "high", "low", "lowLow"].map((key) => {
8097
8560
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
@@ -8121,7 +8584,7 @@ function NodeConfigPanel({
8121
8584
  ] }, key);
8122
8585
  })
8123
8586
  ] }),
8124
- binding && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8587
+ showGenericTelemetry && binding && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { marginBottom: "20px" }, children: [
8125
8588
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
8126
8589
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
8127
8590
  const defaultColors = {
@@ -8463,13 +8926,13 @@ function createControlBinding(nodeId, deviceConfig, actions) {
8463
8926
  }
8464
8927
 
8465
8928
  // src/hooks/useDeviceControls.ts
8466
- var import_react22 = require("react");
8929
+ var import_react23 = require("react");
8467
8930
  function commandKey(nodeId, kind, suffix) {
8468
8931
  return suffix ? `${nodeId}:${kind}:${suffix}` : `${nodeId}:${kind}`;
8469
8932
  }
8470
8933
  function useDeviceControls(initialBindings, options) {
8471
8934
  const { commandTimeoutMs } = options ?? {};
8472
- const [controls, setControls] = (0, import_react22.useState)(() => {
8935
+ const [controls, setControls] = (0, import_react23.useState)(() => {
8473
8936
  const map = /* @__PURE__ */ new Map();
8474
8937
  if (initialBindings) {
8475
8938
  initialBindings.forEach((binding) => {
@@ -8478,12 +8941,12 @@ function useDeviceControls(initialBindings, options) {
8478
8941
  }
8479
8942
  return map;
8480
8943
  });
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)(() => {
8944
+ const pendingRef = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
8945
+ const [pendingCommands, setPendingCommands] = (0, import_react23.useState)(() => /* @__PURE__ */ new Set());
8946
+ const syncPending = (0, import_react23.useCallback)(() => {
8484
8947
  setPendingCommands(new Set(pendingRef.current.keys()));
8485
8948
  }, []);
8486
- const updateControl = (0, import_react22.useCallback)((nodeId, updates) => {
8949
+ const updateControl = (0, import_react23.useCallback)((nodeId, updates) => {
8487
8950
  setControls((prev) => {
8488
8951
  const newMap = new Map(prev);
8489
8952
  const existing = newMap.get(nodeId);
@@ -8497,7 +8960,7 @@ function useDeviceControls(initialBindings, options) {
8497
8960
  return newMap;
8498
8961
  });
8499
8962
  }, []);
8500
- const updateControlBatch = (0, import_react22.useCallback)((updates) => {
8963
+ const updateControlBatch = (0, import_react23.useCallback)((updates) => {
8501
8964
  setControls((prev) => {
8502
8965
  const newMap = new Map(prev);
8503
8966
  Object.entries(updates).forEach(([nodeId, update]) => {
@@ -8509,7 +8972,7 @@ function useDeviceControls(initialBindings, options) {
8509
8972
  return newMap;
8510
8973
  });
8511
8974
  }, []);
8512
- const updateDeviceState = (0, import_react22.useCallback)(
8975
+ const updateDeviceState = (0, import_react23.useCallback)(
8513
8976
  (nodeId, stateUpdates) => {
8514
8977
  setControls((prev) => {
8515
8978
  const newMap = new Map(prev);
@@ -8525,7 +8988,7 @@ function useDeviceControls(initialBindings, options) {
8525
8988
  },
8526
8989
  []
8527
8990
  );
8528
- const updateParameter = (0, import_react22.useCallback)(
8991
+ const updateParameter = (0, import_react23.useCallback)(
8529
8992
  (nodeId, parameterId, value) => {
8530
8993
  setControls((prev) => {
8531
8994
  const newMap = new Map(prev);
@@ -8552,27 +9015,27 @@ function useDeviceControls(initialBindings, options) {
8552
9015
  },
8553
9016
  []
8554
9017
  );
8555
- const setControlBinding = (0, import_react22.useCallback)((binding) => {
9018
+ const setControlBinding = (0, import_react23.useCallback)((binding) => {
8556
9019
  setControls((prev) => {
8557
9020
  const newMap = new Map(prev);
8558
9021
  newMap.set(binding.nodeId, binding);
8559
9022
  return newMap;
8560
9023
  });
8561
9024
  }, []);
8562
- const removeControlBinding = (0, import_react22.useCallback)((nodeId) => {
9025
+ const removeControlBinding = (0, import_react23.useCallback)((nodeId) => {
8563
9026
  setControls((prev) => {
8564
9027
  const newMap = new Map(prev);
8565
9028
  newMap.delete(nodeId);
8566
9029
  return newMap;
8567
9030
  });
8568
9031
  }, []);
8569
- const getControlBinding = (0, import_react22.useCallback)(
9032
+ const getControlBinding = (0, import_react23.useCallback)(
8570
9033
  (nodeId) => {
8571
9034
  return controls.get(nodeId);
8572
9035
  },
8573
9036
  [controls]
8574
9037
  );
8575
- const isCommandPending = (0, import_react22.useCallback)(
9038
+ const isCommandPending = (0, import_react23.useCallback)(
8576
9039
  (nodeId, kind) => {
8577
9040
  if (kind) {
8578
9041
  const prefix2 = `${nodeId}:${kind}`;
@@ -8589,7 +9052,7 @@ function useDeviceControls(initialBindings, options) {
8589
9052
  },
8590
9053
  [pendingCommands]
8591
9054
  );
8592
- const markPending = (0, import_react22.useCallback)(
9055
+ const markPending = (0, import_react23.useCallback)(
8593
9056
  (key, delta) => {
8594
9057
  const next = (pendingRef.current.get(key) ?? 0) + delta;
8595
9058
  if (next > 0) pendingRef.current.set(key, next);
@@ -8598,7 +9061,7 @@ function useDeviceControls(initialBindings, options) {
8598
9061
  },
8599
9062
  [syncPending]
8600
9063
  );
8601
- const runCommand = (0, import_react22.useCallback)(
9064
+ const runCommand = (0, import_react23.useCallback)(
8602
9065
  async (nodeId, key, invoke, messages) => {
8603
9066
  const binding = controls.get(nodeId);
8604
9067
  if (!binding) return;
@@ -8638,7 +9101,7 @@ function useDeviceControls(initialBindings, options) {
8638
9101
  },
8639
9102
  [controls, updateDeviceState, markPending, commandTimeoutMs]
8640
9103
  );
8641
- const sendModeChange = (0, import_react22.useCallback)(
9104
+ const sendModeChange = (0, import_react23.useCallback)(
8642
9105
  (nodeId, mode) => runCommand(
8643
9106
  nodeId,
8644
9107
  commandKey(nodeId, "mode"),
@@ -8647,7 +9110,7 @@ function useDeviceControls(initialBindings, options) {
8647
9110
  ),
8648
9111
  [runCommand]
8649
9112
  );
8650
- const sendParameterChange = (0, import_react22.useCallback)(
9113
+ const sendParameterChange = (0, import_react23.useCallback)(
8651
9114
  (nodeId, parameterId, value) => runCommand(
8652
9115
  nodeId,
8653
9116
  commandKey(nodeId, "parameter", parameterId),
@@ -8656,21 +9119,21 @@ function useDeviceControls(initialBindings, options) {
8656
9119
  ),
8657
9120
  [runCommand]
8658
9121
  );
8659
- const sendStartCommand = (0, import_react22.useCallback)(
9122
+ const sendStartCommand = (0, import_react23.useCallback)(
8660
9123
  (nodeId) => runCommand(nodeId, commandKey(nodeId, "start"), (binding) => binding.actions.onStart?.(), {
8661
9124
  success: "Start command sent",
8662
9125
  failure: "Start command failed"
8663
9126
  }),
8664
9127
  [runCommand]
8665
9128
  );
8666
- const sendStopCommand = (0, import_react22.useCallback)(
9129
+ const sendStopCommand = (0, import_react23.useCallback)(
8667
9130
  (nodeId) => runCommand(nodeId, commandKey(nodeId, "stop"), (binding) => binding.actions.onStop?.(), {
8668
9131
  success: "Stop command sent",
8669
9132
  failure: "Stop command failed"
8670
9133
  }),
8671
9134
  [runCommand]
8672
9135
  );
8673
- const sendCustomAction = (0, import_react22.useCallback)(
9136
+ const sendCustomAction = (0, import_react23.useCallback)(
8674
9137
  (nodeId, actionId) => runCommand(
8675
9138
  nodeId,
8676
9139
  commandKey(nodeId, "action", actionId),
@@ -8723,9 +9186,9 @@ function createConfigBinding(nodeId, tag, parameters, actions, options) {
8723
9186
  }
8724
9187
 
8725
9188
  // src/hooks/useDeviceConfigs.ts
8726
- var import_react23 = require("react");
9189
+ var import_react24 = require("react");
8727
9190
  function useDeviceConfigs(initialConfigs) {
8728
- const [configs, setConfigs] = (0, import_react23.useState)(() => {
9191
+ const [configs, setConfigs] = (0, import_react24.useState)(() => {
8729
9192
  const map = /* @__PURE__ */ new Map();
8730
9193
  if (initialConfigs) {
8731
9194
  initialConfigs.forEach((config) => {
@@ -8734,13 +9197,13 @@ function useDeviceConfigs(initialConfigs) {
8734
9197
  }
8735
9198
  return map;
8736
9199
  });
8737
- const getConfig = (0, import_react23.useCallback)(
9200
+ const getConfig = (0, import_react24.useCallback)(
8738
9201
  (nodeId) => {
8739
9202
  return configs.get(nodeId);
8740
9203
  },
8741
9204
  [configs]
8742
9205
  );
8743
- const updateConfig = (0, import_react23.useCallback)((nodeId, updates) => {
9206
+ const updateConfig = (0, import_react24.useCallback)((nodeId, updates) => {
8744
9207
  setConfigs((prev) => {
8745
9208
  const newMap = new Map(prev);
8746
9209
  const existing = newMap.get(nodeId);
@@ -8754,7 +9217,7 @@ function useDeviceConfigs(initialConfigs) {
8754
9217
  return newMap;
8755
9218
  });
8756
9219
  }, []);
8757
- const updateConfigState = (0, import_react23.useCallback)((nodeId, stateUpdates) => {
9220
+ const updateConfigState = (0, import_react24.useCallback)((nodeId, stateUpdates) => {
8758
9221
  setConfigs((prev) => {
8759
9222
  const newMap = new Map(prev);
8760
9223
  const existing = newMap.get(nodeId);
@@ -8767,7 +9230,7 @@ function useDeviceConfigs(initialConfigs) {
8767
9230
  return newMap;
8768
9231
  });
8769
9232
  }, []);
8770
- const updateConfigBatch = (0, import_react23.useCallback)((updates) => {
9233
+ const updateConfigBatch = (0, import_react24.useCallback)((updates) => {
8771
9234
  setConfigs((prev) => {
8772
9235
  const newMap = new Map(prev);
8773
9236
  Object.entries(updates).forEach(([nodeId, update]) => {
@@ -8779,7 +9242,7 @@ function useDeviceConfigs(initialConfigs) {
8779
9242
  return newMap;
8780
9243
  });
8781
9244
  }, []);
8782
- const updateParameterValue = (0, import_react23.useCallback)((nodeId, parameterId, value) => {
9245
+ const updateParameterValue = (0, import_react24.useCallback)((nodeId, parameterId, value) => {
8783
9246
  setConfigs((prev) => {
8784
9247
  const newMap = new Map(prev);
8785
9248
  const existing = newMap.get(nodeId);
@@ -8796,7 +9259,7 @@ function useDeviceConfigs(initialConfigs) {
8796
9259
  return newMap;
8797
9260
  });
8798
9261
  }, []);
8799
- const applyConfig = (0, import_react23.useCallback)(
9262
+ const applyConfig = (0, import_react24.useCallback)(
8800
9263
  async (nodeId) => {
8801
9264
  const config = configs.get(nodeId);
8802
9265
  if (!config || !config.actions.onApply) {
@@ -8829,7 +9292,7 @@ function useDeviceConfigs(initialConfigs) {
8829
9292
  },
8830
9293
  [configs, updateConfigState]
8831
9294
  );
8832
- const saveConfig = (0, import_react23.useCallback)(
9295
+ const saveConfig = (0, import_react24.useCallback)(
8833
9296
  async (nodeId) => {
8834
9297
  const config = configs.get(nodeId);
8835
9298
  if (!config || !config.actions.onSave) {
@@ -8863,7 +9326,7 @@ function useDeviceConfigs(initialConfigs) {
8863
9326
  },
8864
9327
  [configs, updateConfigState]
8865
9328
  );
8866
- const resetConfig = (0, import_react23.useCallback)(
9329
+ const resetConfig = (0, import_react24.useCallback)(
8867
9330
  async (nodeId) => {
8868
9331
  const config = configs.get(nodeId);
8869
9332
  if (!config) {
@@ -8893,7 +9356,7 @@ function useDeviceConfigs(initialConfigs) {
8893
9356
  },
8894
9357
  [configs, updateConfig, updateConfigState]
8895
9358
  );
8896
- const cancelConfig = (0, import_react23.useCallback)(
9359
+ const cancelConfig = (0, import_react24.useCallback)(
8897
9360
  (nodeId) => {
8898
9361
  const config = configs.get(nodeId);
8899
9362
  if (config?.actions.onCancel) {
@@ -8902,7 +9365,7 @@ function useDeviceConfigs(initialConfigs) {
8902
9365
  },
8903
9366
  [configs]
8904
9367
  );
8905
- const validateConfig = (0, import_react23.useCallback)(
9368
+ const validateConfig = (0, import_react24.useCallback)(
8906
9369
  (nodeId) => {
8907
9370
  const config = configs.get(nodeId);
8908
9371
  if (!config) {
@@ -8944,21 +9407,21 @@ function useDeviceConfigs(initialConfigs) {
8944
9407
  },
8945
9408
  [configs, updateConfigState]
8946
9409
  );
8947
- const setConfig = (0, import_react23.useCallback)((config) => {
9410
+ const setConfig = (0, import_react24.useCallback)((config) => {
8948
9411
  setConfigs((prev) => {
8949
9412
  const newMap = new Map(prev);
8950
9413
  newMap.set(config.nodeId, config);
8951
9414
  return newMap;
8952
9415
  });
8953
9416
  }, []);
8954
- const removeConfig = (0, import_react23.useCallback)((nodeId) => {
9417
+ const removeConfig = (0, import_react24.useCallback)((nodeId) => {
8955
9418
  setConfigs((prev) => {
8956
9419
  const newMap = new Map(prev);
8957
9420
  newMap.delete(nodeId);
8958
9421
  return newMap;
8959
9422
  });
8960
9423
  }, []);
8961
- const clearConfigs = (0, import_react23.useCallback)(() => {
9424
+ const clearConfigs = (0, import_react24.useCallback)(() => {
8962
9425
  setConfigs(/* @__PURE__ */ new Map());
8963
9426
  }, []);
8964
9427
  return {
@@ -8980,7 +9443,7 @@ function useDeviceConfigs(initialConfigs) {
8980
9443
  }
8981
9444
 
8982
9445
  // src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
8983
- var import_react24 = __toESM(require("react"), 1);
9446
+ var import_react25 = __toESM(require("react"), 1);
8984
9447
  var import_jsx_runtime23 = require("react/jsx-runtime");
8985
9448
  function DeviceConfigPanelImpl({
8986
9449
  binding,
@@ -9000,7 +9463,7 @@ function DeviceConfigPanelImpl({
9000
9463
  showBuiltInToast = true,
9001
9464
  embedded = false
9002
9465
  }) {
9003
- const [localValues, setLocalValues] = (0, import_react24.useState)(() => {
9466
+ const [localValues, setLocalValues] = (0, import_react25.useState)(() => {
9004
9467
  if (binding) {
9005
9468
  const values = {};
9006
9469
  binding.parameters.forEach((param) => {
@@ -9010,7 +9473,7 @@ function DeviceConfigPanelImpl({
9010
9473
  }
9011
9474
  return {};
9012
9475
  });
9013
- const [collapsedSections, setCollapsedSections] = (0, import_react24.useState)(() => {
9476
+ const [collapsedSections, setCollapsedSections] = (0, import_react25.useState)(() => {
9014
9477
  const collapsed = /* @__PURE__ */ new Set();
9015
9478
  if (binding?.sections) {
9016
9479
  binding.sections.forEach((section) => {
@@ -9021,9 +9484,9 @@ function DeviceConfigPanelImpl({
9021
9484
  }
9022
9485
  return collapsed;
9023
9486
  });
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);
9487
+ const [showToast, setShowToast] = (0, import_react25.useState)(true);
9488
+ const [bindingId, setBindingId] = (0, import_react25.useState)(binding?.nodeId || null);
9489
+ const lastResultTimestampRef = (0, import_react25.useRef)(0);
9027
9490
  if (binding && binding.nodeId !== bindingId) {
9028
9491
  const values = {};
9029
9492
  binding.parameters.forEach((param) => {
@@ -9039,7 +9502,7 @@ function DeviceConfigPanelImpl({
9039
9502
  setCollapsedSections(collapsed);
9040
9503
  setBindingId(binding.nodeId);
9041
9504
  }
9042
- (0, import_react24.useEffect)(() => {
9505
+ (0, import_react25.useEffect)(() => {
9043
9506
  const currentTimestamp = binding?.state.lastSaved || 0;
9044
9507
  if (binding?.state.lastSaveResult && currentTimestamp !== lastResultTimestampRef.current) {
9045
9508
  lastResultTimestampRef.current = currentTimestamp;
@@ -9056,7 +9519,7 @@ function DeviceConfigPanelImpl({
9056
9519
  };
9057
9520
  }
9058
9521
  }, [binding?.state.lastSaveResult, binding?.state.lastSaved, toastDuration]);
9059
- const parametersBySection = (0, import_react24.useMemo)(() => {
9522
+ const parametersBySection = (0, import_react25.useMemo)(() => {
9060
9523
  if (!binding) return { _unsectioned: [] };
9061
9524
  const groups = { _unsectioned: [] };
9062
9525
  binding.parameters.forEach((param) => {
@@ -9071,7 +9534,7 @@ function DeviceConfigPanelImpl({
9071
9534
  });
9072
9535
  return groups;
9073
9536
  }, [binding]);
9074
- const sortedSections = (0, import_react24.useMemo)(() => {
9537
+ const sortedSections = (0, import_react25.useMemo)(() => {
9075
9538
  if (!binding?.sections) return [];
9076
9539
  return [...binding.sections].sort((a, b) => (a.order || 0) - (b.order || 0));
9077
9540
  }, [binding]);
@@ -9727,10 +10190,10 @@ function DeviceConfigPanelImpl({
9727
10190
  )
9728
10191
  ] });
9729
10192
  }
9730
- var DeviceConfigPanel = import_react24.default.memo(DeviceConfigPanelImpl);
10193
+ var DeviceConfigPanel = import_react25.default.memo(DeviceConfigPanelImpl);
9731
10194
 
9732
10195
  // src/diagram/hooks/useSimulation.ts
9733
- var import_react25 = require("react");
10196
+ var import_react26 = require("react");
9734
10197
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9735
10198
  const {
9736
10199
  interval = 2e3,
@@ -9741,7 +10204,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9741
10204
  historyLength = 10,
9742
10205
  generateValue
9743
10206
  } = options;
9744
- (0, import_react25.useEffect)(() => {
10207
+ (0, import_react26.useEffect)(() => {
9745
10208
  if (!enabled) return;
9746
10209
  const intervalId = setInterval(() => {
9747
10210
  const updates = [];
@@ -9792,6 +10255,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9792
10255
  // Annotate the CommonJS export names for ESM import in node:
9793
10256
  0 && (module.exports = {
9794
10257
  Button,
10258
+ COLOR_PRESETS,
9795
10259
  CardHeader,
9796
10260
  CardUnit,
9797
10261
  CardValue,
@@ -9821,6 +10285,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9821
10285
  PanelTabs,
9822
10286
  RightPanel,
9823
10287
  RightToggleButton,
10288
+ STATUS_LED_APPEARANCE,
9824
10289
  SVGArea,
9825
10290
  SVGLockedOverlay,
9826
10291
  ScrollableSVGWrapper,
@@ -9831,6 +10296,8 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9831
10296
  ThemeProvider,
9832
10297
  ThemeToggle,
9833
10298
  TrendLine,
10299
+ UNKNOWN_MARK_COLOR,
10300
+ UNKNOWN_MARK_COLOR_DARK,
9834
10301
  ValueEntry,
9835
10302
  ZoomButton,
9836
10303
  ZoomControls,
@@ -9840,6 +10307,8 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9840
10307
  exampleDiagram,
9841
10308
  exportDiagram,
9842
10309
  generateRoutePoints,
10310
+ getAppearanceDefaults,
10311
+ getAppearanceSchema,
9843
10312
  getAvailableSymbols,
9844
10313
  getPortWorldPosition,
9845
10314
  getSymbolDefinition,
@@ -9847,9 +10316,12 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9847
10316
  mockSymbolLibrary,
9848
10317
  nodeHasPort,
9849
10318
  overlayStyles,
10319
+ readAppearanceValue,
9850
10320
  registerSymbol,
9851
10321
  registerSymbols,
10322
+ resolvePresetColor,
9852
10323
  resolveStatusColor,
10324
+ useBooleanIndicators,
9853
10325
  useDeviceConfigs,
9854
10326
  useDeviceControls,
9855
10327
  useDrag,
@@ -9860,6 +10332,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9860
10332
  useTelemetryStatus,
9861
10333
  useTheme,
9862
10334
  useViewBox,
9863
- validateDiagram
10335
+ validateDiagram,
10336
+ writeAppearanceValue
9864
10337
  });
9865
10338
  //# sourceMappingURL=index.cjs.map