@procaaso/alphinity-ui-components 1.8.3 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -174,6 +174,8 @@ var ValueEntry = ({
174
174
  inputType = "numeric",
175
175
  label,
176
176
  readOnly = false,
177
+ forced = false,
178
+ realValue,
177
179
  className = "",
178
180
  disabled,
179
181
  ...props
@@ -224,9 +226,14 @@ var ValueEntry = ({
224
226
  const unitClass = unit ? "ui-value-entry--with-unit" : "";
225
227
  const currentStatus = disabled || readOnly ? "disabled" : status;
226
228
  const statusVariantClass = currentStatus ? `ui-value-entry--${currentStatus}` : "";
229
+ const forcedClass = forced ? "ui-value-entry--forced" : "";
230
+ const hasRealValue = forced && realValue !== void 0;
227
231
  const displayText = isEditing ? displayValue : inputType === "numeric" ? Number(value).toFixed(precision) : String(value);
228
232
  return /* @__PURE__ */ jsxs2("div", { className: "ui-value-entry-container", children: [
229
- label && /* @__PURE__ */ jsx3("label", { className: "ui-value-entry-label", children: label }),
233
+ label && /* @__PURE__ */ jsxs2("label", { className: "ui-value-entry-label", children: [
234
+ label,
235
+ forced && /* @__PURE__ */ jsx3("span", { className: "ui-value-entry-forced-badge", "aria-label": "Forced", children: "F" })
236
+ ] }),
230
237
  /* @__PURE__ */ jsxs2("div", { className: "ui-value-entry-wrapper", children: [
231
238
  /* @__PURE__ */ jsx3(
232
239
  "input",
@@ -239,19 +246,57 @@ var ValueEntry = ({
239
246
  onKeyDown: handleKeyDown,
240
247
  disabled,
241
248
  readOnly,
242
- className: `${baseClass} ${statusVariantClass} ${unitClass} ${className}`.trim(),
249
+ className: `${baseClass} ${statusVariantClass} ${forcedClass} ${unitClass} ${className}`.trim(),
243
250
  ...props
244
251
  }
245
252
  ),
246
253
  unit && /* @__PURE__ */ jsx3("span", { className: "ui-value-entry-unit", children: unit }),
247
254
  status === "alarm" && /* @__PURE__ */ jsx3("div", { className: "ui-value-entry-badge ui-value-entry-badge--alarm" }),
248
- status === "warning" && /* @__PURE__ */ jsx3("div", { className: "ui-value-entry-badge ui-value-entry-badge--warning" })
255
+ status === "warning" && /* @__PURE__ */ jsx3("div", { className: "ui-value-entry-badge ui-value-entry-badge--warning" }),
256
+ !label && forced && /* @__PURE__ */ jsx3("span", { className: "ui-value-entry-forced-badge", "aria-label": "Forced", children: "F" })
257
+ ] }),
258
+ hasRealValue && /* @__PURE__ */ jsxs2("div", { className: "ui-value-entry-real-value", children: [
259
+ "REAL ",
260
+ String(realValue),
261
+ unit ? ` ${unit}` : ""
249
262
  ] })
250
263
  ] });
251
264
  };
252
265
 
253
266
  // src/components/ValueEntry/LegacyValueEntry.tsx
254
267
  import { useState as useState3, useCallback as useCallback2 } from "react";
268
+
269
+ // src/theme/statusColors.ts
270
+ var DEFAULT_STATUS_COLORS = {
271
+ normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
272
+ warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
273
+ alarm: { fill: "#ef4444", stroke: "#dc2626", bg: "#FF0000", text: "#ffffff", border: "#dc2626" },
274
+ fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
275
+ off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
276
+ starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
277
+ stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
278
+ };
279
+ var FORCED_MARK_COLOR = {
280
+ fill: "#ede9fe",
281
+ stroke: "#5b21b6",
282
+ bg: "#ede9fe",
283
+ text: "#5b21b6",
284
+ border: "#7c3aed"
285
+ };
286
+ var STATUS_ALIASES = {
287
+ running: "normal",
288
+ stopped: "off"
289
+ };
290
+ function resolveStatusColor(status, customMap) {
291
+ const raw = (status || "off").toLowerCase();
292
+ const key = STATUS_ALIASES[raw] ?? raw;
293
+ const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
294
+ const overrides = customMap?.[key];
295
+ if (!overrides) return defaults;
296
+ return { ...defaults, ...overrides };
297
+ }
298
+
299
+ // src/components/ValueEntry/LegacyValueEntry.tsx
255
300
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
256
301
  var LegacyValueEntry = ({
257
302
  value,
@@ -264,6 +309,8 @@ var LegacyValueEntry = ({
264
309
  inputType = "numeric",
265
310
  label,
266
311
  readOnly = false,
312
+ forced = false,
313
+ realValue,
267
314
  className = "",
268
315
  disabled,
269
316
  ...props
@@ -344,6 +391,27 @@ var LegacyValueEntry = ({
344
391
  }
345
392
  };
346
393
  const statusStyles = getStatusStyles();
394
+ const forcedBadgeStyle = {
395
+ display: "inline-flex",
396
+ alignItems: "center",
397
+ justifyContent: "center",
398
+ width: "1.1rem",
399
+ height: "1.1rem",
400
+ fontSize: "0.7rem",
401
+ fontWeight: 700,
402
+ color: FORCED_MARK_COLOR.text,
403
+ backgroundColor: FORCED_MARK_COLOR.bg,
404
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
405
+ borderRadius: "50%",
406
+ marginLeft: "8px",
407
+ lineHeight: 1
408
+ };
409
+ const realValueStyle = {
410
+ fontSize: "0.75rem",
411
+ fontWeight: 500,
412
+ color: "#94a3b8",
413
+ letterSpacing: "0.02em"
414
+ };
347
415
  const inputStyle = {
348
416
  width: "12rem",
349
417
  height: "4rem",
@@ -392,17 +460,22 @@ var LegacyValueEntry = ({
392
460
  gap: "0.75rem"
393
461
  },
394
462
  children: [
395
- label && /* @__PURE__ */ jsx4(
463
+ (label || forced) && /* @__PURE__ */ jsxs3(
396
464
  "label",
397
465
  {
398
466
  style: {
467
+ display: "flex",
468
+ alignItems: "center",
399
469
  fontSize: "0.875rem",
400
470
  fontWeight: "600",
401
471
  color: "#cbd5e1",
402
472
  textTransform: "uppercase",
403
473
  letterSpacing: "0.05em"
404
474
  },
405
- children: label
475
+ children: [
476
+ label,
477
+ forced && /* @__PURE__ */ jsx4("span", { style: forcedBadgeStyle, "aria-label": "Forced", children: "F" })
478
+ ]
406
479
  }
407
480
  ),
408
481
  /* @__PURE__ */ jsxs3("div", { style: { position: "relative", display: "inline-flex", alignItems: "center" }, children: [
@@ -459,6 +532,11 @@ var LegacyValueEntry = ({
459
532
  }
460
533
  }
461
534
  )
535
+ ] }),
536
+ forced && realValue !== void 0 && /* @__PURE__ */ jsxs3("div", { style: realValueStyle, children: [
537
+ "REAL ",
538
+ String(realValue),
539
+ unit ? ` ${unit}` : ""
462
540
  ] })
463
541
  ]
464
542
  }
@@ -474,6 +552,9 @@ var ControlPanel = ({
474
552
  onModeChange,
475
553
  setpointValue,
476
554
  onSetpointChange,
555
+ currentSetpointValue,
556
+ currentSetpointForced = false,
557
+ currentSetpointRealValue,
477
558
  secondSetpointValue,
478
559
  onSecondSetpointChange,
479
560
  modeConfigs = {},
@@ -570,6 +651,21 @@ var ControlPanel = ({
570
651
  disabled: status === "disabled"
571
652
  }
572
653
  ) }),
654
+ currentSetpointValue !== void 0 && /* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsx5(
655
+ LegacyValueEntry,
656
+ {
657
+ value: currentSetpointValue,
658
+ onChange: () => {
659
+ },
660
+ label: `${currentLabel} (Current)`,
661
+ unit: currentUnit,
662
+ precision: currentPrecision,
663
+ status,
664
+ readOnly: true,
665
+ forced: currentSetpointForced,
666
+ realValue: currentSetpointRealValue
667
+ }
668
+ ) }),
573
669
  hasSecondSetpoint && onSecondSetpointChange && /* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsx5(
574
670
  LegacyValueEntry,
575
671
  {
@@ -660,6 +756,8 @@ var StatusIndicator = ({
660
756
  labelPosition = "right",
661
757
  shape = "circle",
662
758
  led = true,
759
+ forced = false,
760
+ realValue,
663
761
  className = "",
664
762
  ...props
665
763
  }) => {
@@ -683,28 +781,43 @@ var StatusIndicator = ({
683
781
  return "ui-status-indicator-container ui-status-indicator-label-right";
684
782
  }
685
783
  };
784
+ const forcedClass = forced ? "ui-status-indicator--forced" : "";
785
+ const hasRealValue = forced && realValue !== void 0;
686
786
  return /* @__PURE__ */ jsxs5("span", { className: getContainerLayout(), children: [
687
787
  label && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-label", children: label }),
688
- /* @__PURE__ */ jsx6(
689
- "span",
690
- {
691
- role: "status",
692
- "aria-label": label || `Status: ${status}`,
693
- "aria-description": getStatusDescription(status),
694
- "data-status": status,
695
- className: `
696
- ${baseClass}
697
- ${statusClass}
698
- ${sizeClass}
699
- ${shapeClass}
700
- ${animationClass}
701
- ${ledClass}
702
- ${className}
703
- `.replace(/\s+/g, " ").trim(),
704
- ...props
705
- }
706
- ),
707
- label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-label", children: label })
788
+ hasRealValue && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ jsxs5("span", { className: "ui-status-indicator-real-value", children: [
789
+ "REAL ",
790
+ realValue
791
+ ] }),
792
+ /* @__PURE__ */ jsxs5("span", { style: { position: "relative", display: "inline-flex" }, children: [
793
+ /* @__PURE__ */ jsx6(
794
+ "span",
795
+ {
796
+ role: "status",
797
+ "aria-label": label || `Status: ${status}`,
798
+ "aria-description": getStatusDescription(status),
799
+ "data-status": status,
800
+ "data-forced": forced,
801
+ className: `
802
+ ${baseClass}
803
+ ${statusClass}
804
+ ${sizeClass}
805
+ ${shapeClass}
806
+ ${animationClass}
807
+ ${ledClass}
808
+ ${forcedClass}
809
+ ${className}
810
+ `.replace(/\s+/g, " ").trim(),
811
+ ...props
812
+ }
813
+ ),
814
+ forced && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-forced-badge", "aria-label": "Forced", children: "F" })
815
+ ] }),
816
+ label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-label", children: label }),
817
+ hasRealValue && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ jsxs5("span", { className: "ui-status-indicator-real-value", children: [
818
+ "REAL ",
819
+ realValue
820
+ ] })
708
821
  ] });
709
822
  };
710
823
 
@@ -832,6 +945,9 @@ var NodeControlsPanel = ({
832
945
  onModeChange: config.onModeChange,
833
946
  setpointValue: config.setpoint,
834
947
  onSetpointChange: config.onSetpointChange,
948
+ currentSetpointValue: config.currentSetpoint,
949
+ currentSetpointForced: config.currentSetpointForced,
950
+ currentSetpointRealValue: config.currentSetpointRealValue,
835
951
  secondSetpointValue: config.secondSetpoint,
836
952
  onSecondSetpointChange: config.onSecondSetpointChange,
837
953
  modeConfigs: config.modeConfigs,
@@ -896,31 +1012,6 @@ var NodeControlsPanel = ({
896
1012
 
897
1013
  // src/components/DeviceControlPanel/DeviceControlPanel.tsx
898
1014
  import React5, { useState as useState5, useEffect as useEffect2 } from "react";
899
-
900
- // src/theme/statusColors.ts
901
- var DEFAULT_STATUS_COLORS = {
902
- normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
903
- warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
904
- alarm: { fill: "#ef4444", stroke: "#dc2626", bg: "#FF0000", text: "#ffffff", border: "#dc2626" },
905
- fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
906
- off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
907
- starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
908
- stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
909
- };
910
- var STATUS_ALIASES = {
911
- running: "normal",
912
- stopped: "off"
913
- };
914
- function resolveStatusColor(status, customMap) {
915
- const raw = (status || "off").toLowerCase();
916
- const key = STATUS_ALIASES[raw] ?? raw;
917
- const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
918
- const overrides = customMap?.[key];
919
- if (!overrides) return defaults;
920
- return { ...defaults, ...overrides };
921
- }
922
-
923
- // src/components/DeviceControlPanel/DeviceControlPanel.tsx
924
1015
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
925
1016
  function DeviceControlPanelImpl({
926
1017
  binding,
@@ -946,6 +1037,7 @@ function DeviceControlPanelImpl({
946
1037
  onUndock,
947
1038
  undockIcon = "\u21F1",
948
1039
  modeSelectionStyle = "buttons",
1040
+ showBuiltInToast = true,
949
1041
  embedded = false
950
1042
  }) {
951
1043
  const [localParameterValues, setLocalParameterValues] = useState5({});
@@ -1567,10 +1659,13 @@ function DeviceControlPanelImpl({
1567
1659
  marginBottom: spacing.gap
1568
1660
  },
1569
1661
  children: [
1570
- /* @__PURE__ */ jsx8(
1662
+ /* @__PURE__ */ jsxs7(
1571
1663
  "span",
1572
1664
  {
1573
1665
  style: {
1666
+ display: "flex",
1667
+ alignItems: "center",
1668
+ gap: "6px",
1574
1669
  fontSize: `${fontSize.label}px`,
1575
1670
  fontWeight: 600,
1576
1671
  textTransform: "uppercase",
@@ -1578,7 +1673,29 @@ function DeviceControlPanelImpl({
1578
1673
  fontFamily: "Arial, sans-serif",
1579
1674
  color: mutedTextColor
1580
1675
  },
1581
- children: param.label
1676
+ children: [
1677
+ param.label,
1678
+ param.forced?.active && /* @__PURE__ */ jsx8(
1679
+ "span",
1680
+ {
1681
+ "aria-label": "Forced",
1682
+ style: {
1683
+ display: "inline-flex",
1684
+ alignItems: "center",
1685
+ justifyContent: "center",
1686
+ width: "16px",
1687
+ height: "16px",
1688
+ fontSize: "10px",
1689
+ fontWeight: 700,
1690
+ color: FORCED_MARK_COLOR.text,
1691
+ backgroundColor: FORCED_MARK_COLOR.bg,
1692
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
1693
+ borderRadius: "50%"
1694
+ },
1695
+ children: "F"
1696
+ }
1697
+ )
1698
+ ]
1582
1699
  }
1583
1700
  ),
1584
1701
  /* @__PURE__ */ jsxs7(
@@ -1600,6 +1717,27 @@ function DeviceControlPanelImpl({
1600
1717
  ]
1601
1718
  }
1602
1719
  ),
1720
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ jsxs7(
1721
+ "div",
1722
+ {
1723
+ style: {
1724
+ display: "flex",
1725
+ justifyContent: "flex-end",
1726
+ marginTop: "-4px",
1727
+ marginBottom: spacing.gap,
1728
+ fontSize: "10px",
1729
+ fontWeight: 500,
1730
+ fontFamily: "Arial, sans-serif",
1731
+ color: mutedTextColor
1732
+ },
1733
+ children: [
1734
+ "REAL",
1735
+ " ",
1736
+ typeof param.forced.realValue === "boolean" ? param.forced.realValue ? "ON" : "OFF" : String(param.forced.realValue),
1737
+ param.unit ? ` ${param.unit}` : ""
1738
+ ]
1739
+ }
1740
+ ),
1603
1741
  param.type === "boolean" ? /* @__PURE__ */ jsx8(
1604
1742
  "button",
1605
1743
  {
@@ -1791,7 +1929,7 @@ function DeviceControlPanelImpl({
1791
1929
  );
1792
1930
  }) })
1793
1931
  ] }),
1794
- binding.state.lastCommandResult && showToast && /* @__PURE__ */ jsx8(
1932
+ binding.state.lastCommandResult && showToast && showBuiltInToast && /* @__PURE__ */ jsx8(
1795
1933
  "div",
1796
1934
  {
1797
1935
  style: {
@@ -2698,19 +2836,38 @@ var CardUnit = styled2("div")({
2698
2836
 
2699
2837
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.visuals.tsx
2700
2838
  import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
2839
+ var forcedTagStyle = {
2840
+ position: "absolute",
2841
+ top: "-6px",
2842
+ right: "-6px",
2843
+ width: "14px",
2844
+ height: "14px",
2845
+ display: "flex",
2846
+ alignItems: "center",
2847
+ justifyContent: "center",
2848
+ fontSize: "9px",
2849
+ fontWeight: 700,
2850
+ color: FORCED_MARK_COLOR.text,
2851
+ backgroundColor: FORCED_MARK_COLOR.bg,
2852
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
2853
+ borderRadius: "50%",
2854
+ pointerEvents: "none"
2855
+ };
2701
2856
  var TrendLine = ({
2702
2857
  values,
2703
2858
  width = 60,
2704
2859
  height = 20,
2705
2860
  color = "#4fc3f7",
2706
2861
  strokeWidth = 1.5,
2707
- backgroundColor = "rgba(255, 255, 255, 0.1)"
2862
+ backgroundColor = "rgba(255, 255, 255, 0.1)",
2863
+ forced = false
2708
2864
  }) => {
2709
2865
  if (!values || values.length < 2) {
2710
- return /* @__PURE__ */ jsx12(
2866
+ return /* @__PURE__ */ jsxs9(
2711
2867
  "div",
2712
2868
  {
2713
2869
  style: {
2870
+ position: "relative",
2714
2871
  width,
2715
2872
  height,
2716
2873
  backgroundColor,
@@ -2719,7 +2876,10 @@ var TrendLine = ({
2719
2876
  alignItems: "center",
2720
2877
  justifyContent: "center"
2721
2878
  },
2722
- children: /* @__PURE__ */ jsx12("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" })
2879
+ children: [
2880
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" }),
2881
+ forced && /* @__PURE__ */ jsx12("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
2882
+ ]
2723
2883
  }
2724
2884
  );
2725
2885
  }
@@ -2731,7 +2891,7 @@ var TrendLine = ({
2731
2891
  const x = 4 + i / (values.length - 1) * (width - 8);
2732
2892
  return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
2733
2893
  }).join(" ");
2734
- return /* @__PURE__ */ jsx12(
2894
+ return /* @__PURE__ */ jsxs9(
2735
2895
  "div",
2736
2896
  {
2737
2897
  style: {
@@ -2740,30 +2900,41 @@ var TrendLine = ({
2740
2900
  backgroundColor,
2741
2901
  borderRadius: "2px",
2742
2902
  position: "relative",
2743
- overflow: "hidden"
2903
+ overflow: "visible"
2744
2904
  },
2745
- children: /* @__PURE__ */ jsxs9("svg", { width, height, style: { position: "absolute", top: 0, left: 0 }, children: [
2746
- /* @__PURE__ */ jsx12(
2747
- "path",
2905
+ children: [
2906
+ /* @__PURE__ */ jsxs9(
2907
+ "svg",
2748
2908
  {
2749
- d: pathData,
2750
- fill: "none",
2751
- stroke: color,
2752
- strokeWidth,
2753
- strokeLinecap: "round",
2754
- strokeLinejoin: "round"
2909
+ width,
2910
+ height,
2911
+ style: { position: "absolute", top: 0, left: 0, overflow: "hidden", borderRadius: "2px" },
2912
+ children: [
2913
+ /* @__PURE__ */ jsx12(
2914
+ "path",
2915
+ {
2916
+ d: pathData,
2917
+ fill: "none",
2918
+ stroke: color,
2919
+ strokeWidth,
2920
+ strokeLinecap: "round",
2921
+ strokeLinejoin: "round"
2922
+ }
2923
+ ),
2924
+ /* @__PURE__ */ jsx12(
2925
+ "circle",
2926
+ {
2927
+ cx: 4 + (width - 8),
2928
+ cy: normalizedValues[normalizedValues.length - 1],
2929
+ r: 1.5,
2930
+ fill: color
2931
+ }
2932
+ )
2933
+ ]
2755
2934
  }
2756
2935
  ),
2757
- /* @__PURE__ */ jsx12(
2758
- "circle",
2759
- {
2760
- cx: 4 + (width - 8),
2761
- cy: normalizedValues[normalizedValues.length - 1],
2762
- r: 1.5,
2763
- fill: color
2764
- }
2765
- )
2766
- ] })
2936
+ forced && /* @__PURE__ */ jsx12("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
2937
+ ]
2767
2938
  }
2768
2939
  );
2769
2940
  };
@@ -2779,7 +2950,9 @@ var CircularGauge = ({
2779
2950
  unit,
2780
2951
  label,
2781
2952
  size = 60,
2782
- color = "rgba(255, 255, 255, 0.9)"
2953
+ color = "rgba(255, 255, 255, 0.9)",
2954
+ forced = false,
2955
+ realValue
2783
2956
  }) => {
2784
2957
  const percentage = Math.min(value / max * 100, 100);
2785
2958
  const strokeWidth = 4;
@@ -2810,7 +2983,7 @@ var CircularGauge = ({
2810
2983
  height: size,
2811
2984
  borderRadius: "50%",
2812
2985
  backgroundColor,
2813
- border: `1px solid ${borderStrokeColor}`
2986
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : `1px solid ${borderStrokeColor}`
2814
2987
  },
2815
2988
  children: [
2816
2989
  /* @__PURE__ */ jsxs9("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
@@ -2886,12 +3059,39 @@ var CircularGauge = ({
2886
3059
  },
2887
3060
  children: label
2888
3061
  }
2889
- )
3062
+ ),
3063
+ forced && /* @__PURE__ */ jsx12(
3064
+ "span",
3065
+ {
3066
+ "aria-label": "Forced",
3067
+ style: {
3068
+ display: "inline-flex",
3069
+ alignItems: "center",
3070
+ justifyContent: "center",
3071
+ width: "14px",
3072
+ height: "14px",
3073
+ fontSize: "9px",
3074
+ fontWeight: 700,
3075
+ color: FORCED_MARK_COLOR.text,
3076
+ backgroundColor: FORCED_MARK_COLOR.bg,
3077
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
3078
+ borderRadius: "50%",
3079
+ marginTop: "2px"
3080
+ },
3081
+ children: "F"
3082
+ }
3083
+ ),
3084
+ forced && realValue !== void 0 && /* @__PURE__ */ jsxs9("div", { style: { fontSize: "8px", opacity: 0.8, color: "white", marginTop: "1px" }, children: [
3085
+ "REAL ",
3086
+ realValue.toFixed(1),
3087
+ " ",
3088
+ unit
3089
+ ] })
2890
3090
  ]
2891
3091
  }
2892
3092
  );
2893
3093
  };
2894
- var Sparkline = ({ data, width, height, color }) => {
3094
+ var Sparkline = ({ data, width, height, color, forced = false }) => {
2895
3095
  if (!data || data.length < 2) {
2896
3096
  return null;
2897
3097
  }
@@ -2906,24 +3106,52 @@ var Sparkline = ({ data, width, height, color }) => {
2906
3106
  const y = height - 8 - (value - min) / range * (height - 8);
2907
3107
  return `${x + 4},${y + 4}`;
2908
3108
  }).join(" ");
2909
- return /* @__PURE__ */ jsx12(
3109
+ return /* @__PURE__ */ jsxs9(
2910
3110
  "div",
2911
3111
  {
2912
3112
  style: {
3113
+ position: "relative",
2913
3114
  width,
2914
3115
  height,
2915
3116
  backgroundColor: "rgba(0, 0, 0, 0.7)",
2916
- border: "1px solid rgba(255, 255, 255, 0.3)",
3117
+ border: forced ? `1px solid ${FORCED_MARK_COLOR.border}` : "1px solid rgba(255, 255, 255, 0.3)",
2917
3118
  borderRadius: "4px",
2918
3119
  display: "flex",
2919
3120
  alignItems: "center",
2920
3121
  justifyContent: "center",
2921
3122
  boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
2922
3123
  },
2923
- children: /* @__PURE__ */ jsx12("svg", { width, height, children: /* @__PURE__ */ jsx12("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) })
3124
+ children: [
3125
+ /* @__PURE__ */ jsx12("svg", { width, height, children: /* @__PURE__ */ jsx12("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) }),
3126
+ forced && /* @__PURE__ */ jsx12("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3127
+ ]
2924
3128
  }
2925
3129
  );
2926
3130
  };
3131
+ var EquipmentForcedBadge = () => /* @__PURE__ */ jsx12(
3132
+ "span",
3133
+ {
3134
+ "aria-label": "Forced",
3135
+ style: {
3136
+ position: "absolute",
3137
+ top: "-4px",
3138
+ right: "-4px",
3139
+ width: "11px",
3140
+ height: "11px",
3141
+ borderRadius: "50%",
3142
+ backgroundColor: FORCED_MARK_COLOR.bg,
3143
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
3144
+ color: FORCED_MARK_COLOR.text,
3145
+ fontSize: "7px",
3146
+ fontWeight: 700,
3147
+ display: "flex",
3148
+ alignItems: "center",
3149
+ justifyContent: "center",
3150
+ lineHeight: 1
3151
+ },
3152
+ children: "F"
3153
+ }
3154
+ );
2927
3155
  var EquipmentIndicatorWithSparkline = ({
2928
3156
  svgX,
2929
3157
  svgY,
@@ -2931,11 +3159,51 @@ var EquipmentIndicatorWithSparkline = ({
2931
3159
  label,
2932
3160
  size = 30,
2933
3161
  sparklineData,
2934
- sparklineOffset = { x: 40, y: 0 }
3162
+ sparklineOffset = { x: 40, y: 0 },
3163
+ forced = false
2935
3164
  }) => {
2936
3165
  const hasSparkline = sparklineData && sparklineData.length > 1 && Math.max(...sparklineData) !== Math.min(...sparklineData);
2937
3166
  return /* @__PURE__ */ jsxs9(Fragment3, { children: [
2938
- /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ jsx12(
3167
+ /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ jsxs9("div", { style: { position: "relative", width: size, height: size }, children: [
3168
+ /* @__PURE__ */ jsx12(
3169
+ "div",
3170
+ {
3171
+ style: {
3172
+ width: size,
3173
+ height: size,
3174
+ borderRadius: "50%",
3175
+ backgroundColor: color,
3176
+ display: "flex",
3177
+ alignItems: "center",
3178
+ justifyContent: "center",
3179
+ color: "white",
3180
+ fontSize: "7px",
3181
+ fontWeight: "700",
3182
+ textAlign: "center",
3183
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
3184
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : "2px solid rgba(255, 255, 255, 0.3)"
3185
+ },
3186
+ children: label
3187
+ }
3188
+ ),
3189
+ forced && /* @__PURE__ */ jsx12(EquipmentForcedBadge, {})
3190
+ ] }) }),
3191
+ hasSparkline && /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ jsx12(Sparkline, { data: sparklineData, width: 60, height: 20, color, forced }) })
3192
+ ] });
3193
+ };
3194
+ var EquipmentIndicator = ({
3195
+ svgX,
3196
+ svgY,
3197
+ color,
3198
+ label,
3199
+ size = 30,
3200
+ style,
3201
+ className,
3202
+ forced = false
3203
+ }) => {
3204
+ const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
3205
+ return /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ jsxs9("div", { style: { position: "relative", width: size, height: size }, children: [
3206
+ /* @__PURE__ */ jsx12(
2939
3207
  "div",
2940
3208
  {
2941
3209
  style: {
@@ -2951,45 +3219,13 @@ var EquipmentIndicatorWithSparkline = ({
2951
3219
  fontWeight: "700",
2952
3220
  textAlign: "center",
2953
3221
  boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
2954
- border: "2px solid rgba(255, 255, 255, 0.3)"
3222
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : "2px solid rgba(255, 255, 255, 0.3)"
2955
3223
  },
2956
3224
  children: label
2957
3225
  }
2958
- ) }),
2959
- hasSparkline && /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ jsx12(Sparkline, { data: sparklineData, width: 60, height: 20, color }) })
2960
- ] });
2961
- };
2962
- var EquipmentIndicator = ({
2963
- svgX,
2964
- svgY,
2965
- color,
2966
- label,
2967
- size = 30,
2968
- style,
2969
- className
2970
- }) => {
2971
- const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
2972
- return /* @__PURE__ */ jsx12(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ jsx12(
2973
- "div",
2974
- {
2975
- style: {
2976
- width: size,
2977
- height: size,
2978
- borderRadius: "50%",
2979
- backgroundColor: color,
2980
- display: "flex",
2981
- alignItems: "center",
2982
- justifyContent: "center",
2983
- color: "white",
2984
- fontSize: "7px",
2985
- fontWeight: "700",
2986
- textAlign: "center",
2987
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
2988
- border: "2px solid rgba(255, 255, 255, 0.3)"
2989
- },
2990
- children: label
2991
- }
2992
- ) });
3226
+ ),
3227
+ forced && /* @__PURE__ */ jsx12(EquipmentForcedBadge, {})
3228
+ ] }) });
2993
3229
  };
2994
3230
 
2995
3231
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
@@ -4869,7 +5105,9 @@ function DataOverlayImpl({
4869
5105
  scale = 1,
4870
5106
  statusColorMap
4871
5107
  }) {
4872
- const { value, display, history, scaling } = telemetry;
5108
+ const { value, display, history, scaling, forced } = telemetry;
5109
+ const isForced = forced?.active === true;
5110
+ const hasRealValue = isForced && forced?.realValue !== void 0;
4873
5111
  const showValue = display?.showValue ?? true;
4874
5112
  const showStatus = display?.showStatus ?? true;
4875
5113
  const showSparkline = display?.showSparkline ?? false;
@@ -4918,12 +5156,16 @@ function DataOverlayImpl({
4918
5156
  const finalTextColor = textColor || colors.text;
4919
5157
  const formattedValue = formatValue(value.value);
4920
5158
  const displayText = unit ? `${formattedValue} ${unit}` : formattedValue;
5159
+ const realDisplayText = hasRealValue ? unit ? `REAL ${formatValue(forced.realValue)} ${unit}` : `REAL ${formatValue(forced.realValue)}` : void 0;
5160
+ const borderColor = isForced ? FORCED_MARK_COLOR.border : colors.border;
5161
+ const borderWidth = (isForced ? 2 : 1) * scale;
4921
5162
  const hasSparkline = showSparkline && sparklinePath;
4922
- const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale;
5163
+ const extraMarkRows = realDisplayText ? 1 : 0;
5164
+ const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale + extraMarkRows * 9 * scale;
4923
5165
  const overlayWidth = 80 * scale;
4924
5166
  const displayX = x + offsetX;
4925
5167
  const displayY = y + offsetY;
4926
- return /* @__PURE__ */ jsxs14("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
5168
+ return /* @__PURE__ */ jsxs14("g", { "data-overlay-node": nodeId, "data-status": value.status, "data-forced": isForced, children: [
4927
5169
  /* @__PURE__ */ jsx18(
4928
5170
  "rect",
4929
5171
  {
@@ -4933,8 +5175,8 @@ function DataOverlayImpl({
4933
5175
  height: overlayHeight,
4934
5176
  rx: 4 * scale,
4935
5177
  fill: colors.bg,
4936
- stroke: colors.border,
4937
- strokeWidth: 1 * scale,
5178
+ stroke: borderColor,
5179
+ strokeWidth: borderWidth,
4938
5180
  opacity: "0.95",
4939
5181
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
4940
5182
  }
@@ -4983,6 +5225,48 @@ function DataOverlayImpl({
4983
5225
  ]
4984
5226
  }
4985
5227
  ),
5228
+ isForced && /* @__PURE__ */ jsxs14(
5229
+ "g",
5230
+ {
5231
+ transform: `translate(${displayX + overlayWidth / 2}, ${displayY - 25 * scale})`,
5232
+ "aria-label": "Forced",
5233
+ children: [
5234
+ /* @__PURE__ */ jsx18(
5235
+ "circle",
5236
+ {
5237
+ r: 7 * scale,
5238
+ fill: FORCED_MARK_COLOR.bg,
5239
+ stroke: FORCED_MARK_COLOR.border,
5240
+ strokeWidth: 1.5 * scale
5241
+ }
5242
+ ),
5243
+ /* @__PURE__ */ jsx18(
5244
+ "text",
5245
+ {
5246
+ textAnchor: "middle",
5247
+ dominantBaseline: "central",
5248
+ fontSize: 8 * scale,
5249
+ fontWeight: "700",
5250
+ fill: FORCED_MARK_COLOR.text,
5251
+ children: "F"
5252
+ }
5253
+ )
5254
+ ]
5255
+ }
5256
+ ),
5257
+ realDisplayText && /* @__PURE__ */ jsx18(
5258
+ "text",
5259
+ {
5260
+ x: displayX,
5261
+ y: displayY + (value.quality !== void 0 && value.quality < 100 ? 14 : 5) * scale,
5262
+ textAnchor: "middle",
5263
+ fontSize: 7 * scale,
5264
+ fontWeight: "500",
5265
+ fill: finalTextColor,
5266
+ opacity: "0.85",
5267
+ children: realDisplayText
5268
+ }
5269
+ ),
4986
5270
  hasSparkline && /* @__PURE__ */ jsx18("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ jsx18(
4987
5271
  "path",
4988
5272
  {
@@ -8608,6 +8892,7 @@ function DeviceConfigPanelImpl({
8608
8892
  toastDuration = 3e3,
8609
8893
  onOpenControls,
8610
8894
  showControlsButton = true,
8895
+ showBuiltInToast = true,
8611
8896
  embedded = false
8612
8897
  }) {
8613
8898
  const [localValues, setLocalValues] = useState18(() => {
@@ -8784,7 +9069,49 @@ function DeviceConfigPanelImpl({
8784
9069
  " (",
8785
9070
  param.unit,
8786
9071
  ")"
8787
- ] })
9072
+ ] }),
9073
+ param.forced?.active && /* @__PURE__ */ jsx23(
9074
+ "span",
9075
+ {
9076
+ "aria-label": "Forced",
9077
+ style: {
9078
+ display: "inline-flex",
9079
+ alignItems: "center",
9080
+ justifyContent: "center",
9081
+ width: "16px",
9082
+ height: "16px",
9083
+ marginLeft: "6px",
9084
+ fontSize: "10px",
9085
+ fontWeight: 700,
9086
+ textTransform: "none",
9087
+ letterSpacing: "normal",
9088
+ color: FORCED_MARK_COLOR.text,
9089
+ backgroundColor: FORCED_MARK_COLOR.bg,
9090
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
9091
+ borderRadius: "50%",
9092
+ verticalAlign: "middle"
9093
+ },
9094
+ children: "F"
9095
+ }
9096
+ )
9097
+ ]
9098
+ }
9099
+ ),
9100
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ jsxs19(
9101
+ "div",
9102
+ {
9103
+ style: {
9104
+ fontSize: `${fontSize.description}px`,
9105
+ color: mutedTextColor,
9106
+ marginTop: "-2px",
9107
+ marginBottom: "6px",
9108
+ fontFamily: "Arial, sans-serif"
9109
+ },
9110
+ children: [
9111
+ "REAL",
9112
+ " ",
9113
+ typeof param.forced.realValue === "boolean" ? param.forced.realValue ? "ON" : "OFF" : String(param.forced.realValue),
9114
+ param.unit ? ` ${param.unit}` : ""
8788
9115
  ]
8789
9116
  }
8790
9117
  ),
@@ -9273,7 +9600,7 @@ function DeviceConfigPanelImpl({
9273
9600
  }
9274
9601
  )
9275
9602
  ] }),
9276
- binding.state.lastSaveResult && showToast && /* @__PURE__ */ jsx23(
9603
+ binding.state.lastSaveResult && showToast && showBuiltInToast && /* @__PURE__ */ jsx23(
9277
9604
  "div",
9278
9605
  {
9279
9606
  style: {
@@ -9372,6 +9699,7 @@ export {
9372
9699
  DisplayModeToggle,
9373
9700
  EquipmentIndicator,
9374
9701
  EquipmentIndicatorWithSparkline,
9702
+ FORCED_MARK_COLOR,
9375
9703
  FixedSVGContainer,
9376
9704
  FooterPanel,
9377
9705
  FullscreenContainer,