@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.cjs CHANGED
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  DisplayModeToggle: () => DisplayModeToggle,
45
45
  EquipmentIndicator: () => EquipmentIndicator,
46
46
  EquipmentIndicatorWithSparkline: () => EquipmentIndicatorWithSparkline,
47
+ FORCED_MARK_COLOR: () => FORCED_MARK_COLOR,
47
48
  FixedSVGContainer: () => FixedSVGContainer,
48
49
  FooterPanel: () => FooterPanel,
49
50
  FullscreenContainer: () => FullscreenContainer,
@@ -278,6 +279,8 @@ var ValueEntry = ({
278
279
  inputType = "numeric",
279
280
  label,
280
281
  readOnly = false,
282
+ forced = false,
283
+ realValue,
281
284
  className = "",
282
285
  disabled,
283
286
  ...props
@@ -328,9 +331,14 @@ var ValueEntry = ({
328
331
  const unitClass = unit ? "ui-value-entry--with-unit" : "";
329
332
  const currentStatus = disabled || readOnly ? "disabled" : status;
330
333
  const statusVariantClass = currentStatus ? `ui-value-entry--${currentStatus}` : "";
334
+ const forcedClass = forced ? "ui-value-entry--forced" : "";
335
+ const hasRealValue = forced && realValue !== void 0;
331
336
  const displayText = isEditing ? displayValue : inputType === "numeric" ? Number(value).toFixed(precision) : String(value);
332
337
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ui-value-entry-container", children: [
333
- label && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "ui-value-entry-label", children: label }),
338
+ label && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "ui-value-entry-label", children: [
339
+ label,
340
+ forced && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ui-value-entry-forced-badge", "aria-label": "Forced", children: "F" })
341
+ ] }),
334
342
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ui-value-entry-wrapper", children: [
335
343
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
336
344
  "input",
@@ -343,19 +351,57 @@ var ValueEntry = ({
343
351
  onKeyDown: handleKeyDown,
344
352
  disabled,
345
353
  readOnly,
346
- className: `${baseClass} ${statusVariantClass} ${unitClass} ${className}`.trim(),
354
+ className: `${baseClass} ${statusVariantClass} ${forcedClass} ${unitClass} ${className}`.trim(),
347
355
  ...props
348
356
  }
349
357
  ),
350
358
  unit && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ui-value-entry-unit", children: unit }),
351
359
  status === "alarm" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ui-value-entry-badge ui-value-entry-badge--alarm" }),
352
- status === "warning" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ui-value-entry-badge ui-value-entry-badge--warning" })
360
+ status === "warning" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ui-value-entry-badge ui-value-entry-badge--warning" }),
361
+ !label && forced && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ui-value-entry-forced-badge", "aria-label": "Forced", children: "F" })
362
+ ] }),
363
+ hasRealValue && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ui-value-entry-real-value", children: [
364
+ "REAL ",
365
+ String(realValue),
366
+ unit ? ` ${unit}` : ""
353
367
  ] })
354
368
  ] });
355
369
  };
356
370
 
357
371
  // src/components/ValueEntry/LegacyValueEntry.tsx
358
372
  var import_react3 = require("react");
373
+
374
+ // src/theme/statusColors.ts
375
+ var DEFAULT_STATUS_COLORS = {
376
+ normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
377
+ warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
378
+ alarm: { fill: "#ef4444", stroke: "#dc2626", bg: "#FF0000", text: "#ffffff", border: "#dc2626" },
379
+ fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
380
+ off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
381
+ starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
382
+ stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
383
+ };
384
+ var FORCED_MARK_COLOR = {
385
+ fill: "#ede9fe",
386
+ stroke: "#5b21b6",
387
+ bg: "#ede9fe",
388
+ text: "#5b21b6",
389
+ border: "#7c3aed"
390
+ };
391
+ var STATUS_ALIASES = {
392
+ running: "normal",
393
+ stopped: "off"
394
+ };
395
+ function resolveStatusColor(status, customMap) {
396
+ const raw = (status || "off").toLowerCase();
397
+ const key = STATUS_ALIASES[raw] ?? raw;
398
+ const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
399
+ const overrides = customMap?.[key];
400
+ if (!overrides) return defaults;
401
+ return { ...defaults, ...overrides };
402
+ }
403
+
404
+ // src/components/ValueEntry/LegacyValueEntry.tsx
359
405
  var import_jsx_runtime4 = require("react/jsx-runtime");
360
406
  var LegacyValueEntry = ({
361
407
  value,
@@ -368,6 +414,8 @@ var LegacyValueEntry = ({
368
414
  inputType = "numeric",
369
415
  label,
370
416
  readOnly = false,
417
+ forced = false,
418
+ realValue,
371
419
  className = "",
372
420
  disabled,
373
421
  ...props
@@ -448,6 +496,27 @@ var LegacyValueEntry = ({
448
496
  }
449
497
  };
450
498
  const statusStyles = getStatusStyles();
499
+ const forcedBadgeStyle = {
500
+ display: "inline-flex",
501
+ alignItems: "center",
502
+ justifyContent: "center",
503
+ width: "1.1rem",
504
+ height: "1.1rem",
505
+ fontSize: "0.7rem",
506
+ fontWeight: 700,
507
+ color: FORCED_MARK_COLOR.text,
508
+ backgroundColor: FORCED_MARK_COLOR.bg,
509
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
510
+ borderRadius: "50%",
511
+ marginLeft: "8px",
512
+ lineHeight: 1
513
+ };
514
+ const realValueStyle = {
515
+ fontSize: "0.75rem",
516
+ fontWeight: 500,
517
+ color: "#94a3b8",
518
+ letterSpacing: "0.02em"
519
+ };
451
520
  const inputStyle = {
452
521
  width: "12rem",
453
522
  height: "4rem",
@@ -496,17 +565,22 @@ var LegacyValueEntry = ({
496
565
  gap: "0.75rem"
497
566
  },
498
567
  children: [
499
- label && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
568
+ (label || forced) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
500
569
  "label",
501
570
  {
502
571
  style: {
572
+ display: "flex",
573
+ alignItems: "center",
503
574
  fontSize: "0.875rem",
504
575
  fontWeight: "600",
505
576
  color: "#cbd5e1",
506
577
  textTransform: "uppercase",
507
578
  letterSpacing: "0.05em"
508
579
  },
509
- children: label
580
+ children: [
581
+ label,
582
+ forced && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: forcedBadgeStyle, "aria-label": "Forced", children: "F" })
583
+ ]
510
584
  }
511
585
  ),
512
586
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative", display: "inline-flex", alignItems: "center" }, children: [
@@ -563,6 +637,11 @@ var LegacyValueEntry = ({
563
637
  }
564
638
  }
565
639
  )
640
+ ] }),
641
+ forced && realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: realValueStyle, children: [
642
+ "REAL ",
643
+ String(realValue),
644
+ unit ? ` ${unit}` : ""
566
645
  ] })
567
646
  ]
568
647
  }
@@ -578,6 +657,9 @@ var ControlPanel = ({
578
657
  onModeChange,
579
658
  setpointValue,
580
659
  onSetpointChange,
660
+ currentSetpointValue,
661
+ currentSetpointForced = false,
662
+ currentSetpointRealValue,
581
663
  secondSetpointValue,
582
664
  onSecondSetpointChange,
583
665
  modeConfigs = {},
@@ -674,6 +756,21 @@ var ControlPanel = ({
674
756
  disabled: status === "disabled"
675
757
  }
676
758
  ) }),
759
+ currentSetpointValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
760
+ LegacyValueEntry,
761
+ {
762
+ value: currentSetpointValue,
763
+ onChange: () => {
764
+ },
765
+ label: `${currentLabel} (Current)`,
766
+ unit: currentUnit,
767
+ precision: currentPrecision,
768
+ status,
769
+ readOnly: true,
770
+ forced: currentSetpointForced,
771
+ realValue: currentSetpointRealValue
772
+ }
773
+ ) }),
677
774
  hasSecondSetpoint && onSecondSetpointChange && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
678
775
  LegacyValueEntry,
679
776
  {
@@ -764,6 +861,8 @@ var StatusIndicator = ({
764
861
  labelPosition = "right",
765
862
  shape = "circle",
766
863
  led = true,
864
+ forced = false,
865
+ realValue,
767
866
  className = "",
768
867
  ...props
769
868
  }) => {
@@ -787,28 +886,43 @@ var StatusIndicator = ({
787
886
  return "ui-status-indicator-container ui-status-indicator-label-right";
788
887
  }
789
888
  };
889
+ const forcedClass = forced ? "ui-status-indicator--forced" : "";
890
+ const hasRealValue = forced && realValue !== void 0;
790
891
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: getContainerLayout(), children: [
791
892
  label && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-label", children: label }),
792
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
793
- "span",
794
- {
795
- role: "status",
796
- "aria-label": label || `Status: ${status}`,
797
- "aria-description": getStatusDescription(status),
798
- "data-status": status,
799
- className: `
800
- ${baseClass}
801
- ${statusClass}
802
- ${sizeClass}
803
- ${shapeClass}
804
- ${animationClass}
805
- ${ledClass}
806
- ${className}
807
- `.replace(/\s+/g, " ").trim(),
808
- ...props
809
- }
810
- ),
811
- label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-label", children: label })
893
+ hasRealValue && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ui-status-indicator-real-value", children: [
894
+ "REAL ",
895
+ realValue
896
+ ] }),
897
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
898
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
899
+ "span",
900
+ {
901
+ role: "status",
902
+ "aria-label": label || `Status: ${status}`,
903
+ "aria-description": getStatusDescription(status),
904
+ "data-status": status,
905
+ "data-forced": forced,
906
+ className: `
907
+ ${baseClass}
908
+ ${statusClass}
909
+ ${sizeClass}
910
+ ${shapeClass}
911
+ ${animationClass}
912
+ ${ledClass}
913
+ ${forcedClass}
914
+ ${className}
915
+ `.replace(/\s+/g, " ").trim(),
916
+ ...props
917
+ }
918
+ ),
919
+ forced && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-forced-badge", "aria-label": "Forced", children: "F" })
920
+ ] }),
921
+ label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-label", children: label }),
922
+ hasRealValue && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ui-status-indicator-real-value", children: [
923
+ "REAL ",
924
+ realValue
925
+ ] })
812
926
  ] });
813
927
  };
814
928
 
@@ -936,6 +1050,9 @@ var NodeControlsPanel = ({
936
1050
  onModeChange: config.onModeChange,
937
1051
  setpointValue: config.setpoint,
938
1052
  onSetpointChange: config.onSetpointChange,
1053
+ currentSetpointValue: config.currentSetpoint,
1054
+ currentSetpointForced: config.currentSetpointForced,
1055
+ currentSetpointRealValue: config.currentSetpointRealValue,
939
1056
  secondSetpointValue: config.secondSetpoint,
940
1057
  onSecondSetpointChange: config.onSecondSetpointChange,
941
1058
  modeConfigs: config.modeConfigs,
@@ -1000,31 +1117,6 @@ var NodeControlsPanel = ({
1000
1117
 
1001
1118
  // src/components/DeviceControlPanel/DeviceControlPanel.tsx
1002
1119
  var import_react5 = __toESM(require("react"), 1);
1003
-
1004
- // src/theme/statusColors.ts
1005
- var DEFAULT_STATUS_COLORS = {
1006
- normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
1007
- warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
1008
- alarm: { fill: "#ef4444", stroke: "#dc2626", bg: "#FF0000", text: "#ffffff", border: "#dc2626" },
1009
- fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
1010
- off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
1011
- starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
1012
- stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
1013
- };
1014
- var STATUS_ALIASES = {
1015
- running: "normal",
1016
- stopped: "off"
1017
- };
1018
- function resolveStatusColor(status, customMap) {
1019
- const raw = (status || "off").toLowerCase();
1020
- const key = STATUS_ALIASES[raw] ?? raw;
1021
- const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
1022
- const overrides = customMap?.[key];
1023
- if (!overrides) return defaults;
1024
- return { ...defaults, ...overrides };
1025
- }
1026
-
1027
- // src/components/DeviceControlPanel/DeviceControlPanel.tsx
1028
1120
  var import_jsx_runtime8 = require("react/jsx-runtime");
1029
1121
  function DeviceControlPanelImpl({
1030
1122
  binding,
@@ -1050,6 +1142,7 @@ function DeviceControlPanelImpl({
1050
1142
  onUndock,
1051
1143
  undockIcon = "\u21F1",
1052
1144
  modeSelectionStyle = "buttons",
1145
+ showBuiltInToast = true,
1053
1146
  embedded = false
1054
1147
  }) {
1055
1148
  const [localParameterValues, setLocalParameterValues] = (0, import_react5.useState)({});
@@ -1671,10 +1764,13 @@ function DeviceControlPanelImpl({
1671
1764
  marginBottom: spacing.gap
1672
1765
  },
1673
1766
  children: [
1674
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1767
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1675
1768
  "span",
1676
1769
  {
1677
1770
  style: {
1771
+ display: "flex",
1772
+ alignItems: "center",
1773
+ gap: "6px",
1678
1774
  fontSize: `${fontSize.label}px`,
1679
1775
  fontWeight: 600,
1680
1776
  textTransform: "uppercase",
@@ -1682,7 +1778,29 @@ function DeviceControlPanelImpl({
1682
1778
  fontFamily: "Arial, sans-serif",
1683
1779
  color: mutedTextColor
1684
1780
  },
1685
- children: param.label
1781
+ children: [
1782
+ param.label,
1783
+ param.forced?.active && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1784
+ "span",
1785
+ {
1786
+ "aria-label": "Forced",
1787
+ style: {
1788
+ display: "inline-flex",
1789
+ alignItems: "center",
1790
+ justifyContent: "center",
1791
+ width: "16px",
1792
+ height: "16px",
1793
+ fontSize: "10px",
1794
+ fontWeight: 700,
1795
+ color: FORCED_MARK_COLOR.text,
1796
+ backgroundColor: FORCED_MARK_COLOR.bg,
1797
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
1798
+ borderRadius: "50%"
1799
+ },
1800
+ children: "F"
1801
+ }
1802
+ )
1803
+ ]
1686
1804
  }
1687
1805
  ),
1688
1806
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
@@ -1704,6 +1822,27 @@ function DeviceControlPanelImpl({
1704
1822
  ]
1705
1823
  }
1706
1824
  ),
1825
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1826
+ "div",
1827
+ {
1828
+ style: {
1829
+ display: "flex",
1830
+ justifyContent: "flex-end",
1831
+ marginTop: "-4px",
1832
+ marginBottom: spacing.gap,
1833
+ fontSize: "10px",
1834
+ fontWeight: 500,
1835
+ fontFamily: "Arial, sans-serif",
1836
+ color: mutedTextColor
1837
+ },
1838
+ children: [
1839
+ "REAL",
1840
+ " ",
1841
+ typeof param.forced.realValue === "boolean" ? param.forced.realValue ? "ON" : "OFF" : String(param.forced.realValue),
1842
+ param.unit ? ` ${param.unit}` : ""
1843
+ ]
1844
+ }
1845
+ ),
1707
1846
  param.type === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1708
1847
  "button",
1709
1848
  {
@@ -1895,7 +2034,7 @@ function DeviceControlPanelImpl({
1895
2034
  );
1896
2035
  }) })
1897
2036
  ] }),
1898
- binding.state.lastCommandResult && showToast && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2037
+ binding.state.lastCommandResult && showToast && showBuiltInToast && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1899
2038
  "div",
1900
2039
  {
1901
2040
  style: {
@@ -2802,19 +2941,38 @@ var CardUnit = (0, import_styled2.default)("div")({
2802
2941
 
2803
2942
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.visuals.tsx
2804
2943
  var import_jsx_runtime12 = require("react/jsx-runtime");
2944
+ var forcedTagStyle = {
2945
+ position: "absolute",
2946
+ top: "-6px",
2947
+ right: "-6px",
2948
+ width: "14px",
2949
+ height: "14px",
2950
+ display: "flex",
2951
+ alignItems: "center",
2952
+ justifyContent: "center",
2953
+ fontSize: "9px",
2954
+ fontWeight: 700,
2955
+ color: FORCED_MARK_COLOR.text,
2956
+ backgroundColor: FORCED_MARK_COLOR.bg,
2957
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
2958
+ borderRadius: "50%",
2959
+ pointerEvents: "none"
2960
+ };
2805
2961
  var TrendLine = ({
2806
2962
  values,
2807
2963
  width = 60,
2808
2964
  height = 20,
2809
2965
  color = "#4fc3f7",
2810
2966
  strokeWidth = 1.5,
2811
- backgroundColor = "rgba(255, 255, 255, 0.1)"
2967
+ backgroundColor = "rgba(255, 255, 255, 0.1)",
2968
+ forced = false
2812
2969
  }) => {
2813
2970
  if (!values || values.length < 2) {
2814
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2971
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2815
2972
  "div",
2816
2973
  {
2817
2974
  style: {
2975
+ position: "relative",
2818
2976
  width,
2819
2977
  height,
2820
2978
  backgroundColor,
@@ -2823,7 +2981,10 @@ var TrendLine = ({
2823
2981
  alignItems: "center",
2824
2982
  justifyContent: "center"
2825
2983
  },
2826
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" })
2984
+ children: [
2985
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" }),
2986
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
2987
+ ]
2827
2988
  }
2828
2989
  );
2829
2990
  }
@@ -2835,7 +2996,7 @@ var TrendLine = ({
2835
2996
  const x = 4 + i / (values.length - 1) * (width - 8);
2836
2997
  return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
2837
2998
  }).join(" ");
2838
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2999
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2839
3000
  "div",
2840
3001
  {
2841
3002
  style: {
@@ -2844,30 +3005,41 @@ var TrendLine = ({
2844
3005
  backgroundColor,
2845
3006
  borderRadius: "2px",
2846
3007
  position: "relative",
2847
- overflow: "hidden"
3008
+ overflow: "visible"
2848
3009
  },
2849
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width, height, style: { position: "absolute", top: 0, left: 0 }, children: [
2850
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2851
- "path",
3010
+ children: [
3011
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3012
+ "svg",
2852
3013
  {
2853
- d: pathData,
2854
- fill: "none",
2855
- stroke: color,
2856
- strokeWidth,
2857
- strokeLinecap: "round",
2858
- strokeLinejoin: "round"
3014
+ width,
3015
+ height,
3016
+ style: { position: "absolute", top: 0, left: 0, overflow: "hidden", borderRadius: "2px" },
3017
+ children: [
3018
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3019
+ "path",
3020
+ {
3021
+ d: pathData,
3022
+ fill: "none",
3023
+ stroke: color,
3024
+ strokeWidth,
3025
+ strokeLinecap: "round",
3026
+ strokeLinejoin: "round"
3027
+ }
3028
+ ),
3029
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3030
+ "circle",
3031
+ {
3032
+ cx: 4 + (width - 8),
3033
+ cy: normalizedValues[normalizedValues.length - 1],
3034
+ r: 1.5,
3035
+ fill: color
3036
+ }
3037
+ )
3038
+ ]
2859
3039
  }
2860
3040
  ),
2861
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2862
- "circle",
2863
- {
2864
- cx: 4 + (width - 8),
2865
- cy: normalizedValues[normalizedValues.length - 1],
2866
- r: 1.5,
2867
- fill: color
2868
- }
2869
- )
2870
- ] })
3041
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3042
+ ]
2871
3043
  }
2872
3044
  );
2873
3045
  };
@@ -2883,7 +3055,9 @@ var CircularGauge = ({
2883
3055
  unit,
2884
3056
  label,
2885
3057
  size = 60,
2886
- color = "rgba(255, 255, 255, 0.9)"
3058
+ color = "rgba(255, 255, 255, 0.9)",
3059
+ forced = false,
3060
+ realValue
2887
3061
  }) => {
2888
3062
  const percentage = Math.min(value / max * 100, 100);
2889
3063
  const strokeWidth = 4;
@@ -2914,7 +3088,7 @@ var CircularGauge = ({
2914
3088
  height: size,
2915
3089
  borderRadius: "50%",
2916
3090
  backgroundColor,
2917
- border: `1px solid ${borderStrokeColor}`
3091
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : `1px solid ${borderStrokeColor}`
2918
3092
  },
2919
3093
  children: [
2920
3094
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
@@ -2990,12 +3164,39 @@ var CircularGauge = ({
2990
3164
  },
2991
3165
  children: label
2992
3166
  }
2993
- )
3167
+ ),
3168
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3169
+ "span",
3170
+ {
3171
+ "aria-label": "Forced",
3172
+ style: {
3173
+ display: "inline-flex",
3174
+ alignItems: "center",
3175
+ justifyContent: "center",
3176
+ width: "14px",
3177
+ height: "14px",
3178
+ fontSize: "9px",
3179
+ fontWeight: 700,
3180
+ color: FORCED_MARK_COLOR.text,
3181
+ backgroundColor: FORCED_MARK_COLOR.bg,
3182
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
3183
+ borderRadius: "50%",
3184
+ marginTop: "2px"
3185
+ },
3186
+ children: "F"
3187
+ }
3188
+ ),
3189
+ forced && realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { fontSize: "8px", opacity: 0.8, color: "white", marginTop: "1px" }, children: [
3190
+ "REAL ",
3191
+ realValue.toFixed(1),
3192
+ " ",
3193
+ unit
3194
+ ] })
2994
3195
  ]
2995
3196
  }
2996
3197
  );
2997
3198
  };
2998
- var Sparkline = ({ data, width, height, color }) => {
3199
+ var Sparkline = ({ data, width, height, color, forced = false }) => {
2999
3200
  if (!data || data.length < 2) {
3000
3201
  return null;
3001
3202
  }
@@ -3010,24 +3211,52 @@ var Sparkline = ({ data, width, height, color }) => {
3010
3211
  const y = height - 8 - (value - min) / range * (height - 8);
3011
3212
  return `${x + 4},${y + 4}`;
3012
3213
  }).join(" ");
3013
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3214
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3014
3215
  "div",
3015
3216
  {
3016
3217
  style: {
3218
+ position: "relative",
3017
3219
  width,
3018
3220
  height,
3019
3221
  backgroundColor: "rgba(0, 0, 0, 0.7)",
3020
- border: "1px solid rgba(255, 255, 255, 0.3)",
3222
+ border: forced ? `1px solid ${FORCED_MARK_COLOR.border}` : "1px solid rgba(255, 255, 255, 0.3)",
3021
3223
  borderRadius: "4px",
3022
3224
  display: "flex",
3023
3225
  alignItems: "center",
3024
3226
  justifyContent: "center",
3025
3227
  boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
3026
3228
  },
3027
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { width, height, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) })
3229
+ children: [
3230
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { width, height, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) }),
3231
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3232
+ ]
3028
3233
  }
3029
3234
  );
3030
3235
  };
3236
+ var EquipmentForcedBadge = () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3237
+ "span",
3238
+ {
3239
+ "aria-label": "Forced",
3240
+ style: {
3241
+ position: "absolute",
3242
+ top: "-4px",
3243
+ right: "-4px",
3244
+ width: "11px",
3245
+ height: "11px",
3246
+ borderRadius: "50%",
3247
+ backgroundColor: FORCED_MARK_COLOR.bg,
3248
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
3249
+ color: FORCED_MARK_COLOR.text,
3250
+ fontSize: "7px",
3251
+ fontWeight: 700,
3252
+ display: "flex",
3253
+ alignItems: "center",
3254
+ justifyContent: "center",
3255
+ lineHeight: 1
3256
+ },
3257
+ children: "F"
3258
+ }
3259
+ );
3031
3260
  var EquipmentIndicatorWithSparkline = ({
3032
3261
  svgX,
3033
3262
  svgY,
@@ -3035,11 +3264,51 @@ var EquipmentIndicatorWithSparkline = ({
3035
3264
  label,
3036
3265
  size = 30,
3037
3266
  sparklineData,
3038
- sparklineOffset = { x: 40, y: 0 }
3267
+ sparklineOffset = { x: 40, y: 0 },
3268
+ forced = false
3039
3269
  }) => {
3040
3270
  const hasSparkline = sparklineData && sparklineData.length > 1 && Math.max(...sparklineData) !== Math.min(...sparklineData);
3041
3271
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
3042
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3272
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { position: "relative", width: size, height: size }, children: [
3273
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3274
+ "div",
3275
+ {
3276
+ style: {
3277
+ width: size,
3278
+ height: size,
3279
+ borderRadius: "50%",
3280
+ backgroundColor: color,
3281
+ display: "flex",
3282
+ alignItems: "center",
3283
+ justifyContent: "center",
3284
+ color: "white",
3285
+ fontSize: "7px",
3286
+ fontWeight: "700",
3287
+ textAlign: "center",
3288
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
3289
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : "2px solid rgba(255, 255, 255, 0.3)"
3290
+ },
3291
+ children: label
3292
+ }
3293
+ ),
3294
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EquipmentForcedBadge, {})
3295
+ ] }) }),
3296
+ hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Sparkline, { data: sparklineData, width: 60, height: 20, color, forced }) })
3297
+ ] });
3298
+ };
3299
+ var EquipmentIndicator = ({
3300
+ svgX,
3301
+ svgY,
3302
+ color,
3303
+ label,
3304
+ size = 30,
3305
+ style,
3306
+ className,
3307
+ forced = false
3308
+ }) => {
3309
+ const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
3310
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { position: "relative", width: size, height: size }, children: [
3311
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3043
3312
  "div",
3044
3313
  {
3045
3314
  style: {
@@ -3055,45 +3324,13 @@ var EquipmentIndicatorWithSparkline = ({
3055
3324
  fontWeight: "700",
3056
3325
  textAlign: "center",
3057
3326
  boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
3058
- border: "2px solid rgba(255, 255, 255, 0.3)"
3327
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : "2px solid rgba(255, 255, 255, 0.3)"
3059
3328
  },
3060
3329
  children: label
3061
3330
  }
3062
- ) }),
3063
- hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Sparkline, { data: sparklineData, width: 60, height: 20, color }) })
3064
- ] });
3065
- };
3066
- var EquipmentIndicator = ({
3067
- svgX,
3068
- svgY,
3069
- color,
3070
- label,
3071
- size = 30,
3072
- style,
3073
- className
3074
- }) => {
3075
- const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
3076
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3077
- "div",
3078
- {
3079
- style: {
3080
- width: size,
3081
- height: size,
3082
- borderRadius: "50%",
3083
- backgroundColor: color,
3084
- display: "flex",
3085
- alignItems: "center",
3086
- justifyContent: "center",
3087
- color: "white",
3088
- fontSize: "7px",
3089
- fontWeight: "700",
3090
- textAlign: "center",
3091
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
3092
- border: "2px solid rgba(255, 255, 255, 0.3)"
3093
- },
3094
- children: label
3095
- }
3096
- ) });
3331
+ ),
3332
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EquipmentForcedBadge, {})
3333
+ ] }) });
3097
3334
  };
3098
3335
 
3099
3336
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
@@ -4973,7 +5210,9 @@ function DataOverlayImpl({
4973
5210
  scale = 1,
4974
5211
  statusColorMap
4975
5212
  }) {
4976
- const { value, display, history, scaling } = telemetry;
5213
+ const { value, display, history, scaling, forced } = telemetry;
5214
+ const isForced = forced?.active === true;
5215
+ const hasRealValue = isForced && forced?.realValue !== void 0;
4977
5216
  const showValue = display?.showValue ?? true;
4978
5217
  const showStatus = display?.showStatus ?? true;
4979
5218
  const showSparkline = display?.showSparkline ?? false;
@@ -5022,12 +5261,16 @@ function DataOverlayImpl({
5022
5261
  const finalTextColor = textColor || colors.text;
5023
5262
  const formattedValue = formatValue(value.value);
5024
5263
  const displayText = unit ? `${formattedValue} ${unit}` : formattedValue;
5264
+ const realDisplayText = hasRealValue ? unit ? `REAL ${formatValue(forced.realValue)} ${unit}` : `REAL ${formatValue(forced.realValue)}` : void 0;
5265
+ const borderColor = isForced ? FORCED_MARK_COLOR.border : colors.border;
5266
+ const borderWidth = (isForced ? 2 : 1) * scale;
5025
5267
  const hasSparkline = showSparkline && sparklinePath;
5026
- const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale;
5268
+ const extraMarkRows = realDisplayText ? 1 : 0;
5269
+ const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale + extraMarkRows * 9 * scale;
5027
5270
  const overlayWidth = 80 * scale;
5028
5271
  const displayX = x + offsetX;
5029
5272
  const displayY = y + offsetY;
5030
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
5273
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("g", { "data-overlay-node": nodeId, "data-status": value.status, "data-forced": isForced, children: [
5031
5274
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5032
5275
  "rect",
5033
5276
  {
@@ -5037,8 +5280,8 @@ function DataOverlayImpl({
5037
5280
  height: overlayHeight,
5038
5281
  rx: 4 * scale,
5039
5282
  fill: colors.bg,
5040
- stroke: colors.border,
5041
- strokeWidth: 1 * scale,
5283
+ stroke: borderColor,
5284
+ strokeWidth: borderWidth,
5042
5285
  opacity: "0.95",
5043
5286
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
5044
5287
  }
@@ -5087,6 +5330,48 @@ function DataOverlayImpl({
5087
5330
  ]
5088
5331
  }
5089
5332
  ),
5333
+ isForced && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5334
+ "g",
5335
+ {
5336
+ transform: `translate(${displayX + overlayWidth / 2}, ${displayY - 25 * scale})`,
5337
+ "aria-label": "Forced",
5338
+ children: [
5339
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5340
+ "circle",
5341
+ {
5342
+ r: 7 * scale,
5343
+ fill: FORCED_MARK_COLOR.bg,
5344
+ stroke: FORCED_MARK_COLOR.border,
5345
+ strokeWidth: 1.5 * scale
5346
+ }
5347
+ ),
5348
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5349
+ "text",
5350
+ {
5351
+ textAnchor: "middle",
5352
+ dominantBaseline: "central",
5353
+ fontSize: 8 * scale,
5354
+ fontWeight: "700",
5355
+ fill: FORCED_MARK_COLOR.text,
5356
+ children: "F"
5357
+ }
5358
+ )
5359
+ ]
5360
+ }
5361
+ ),
5362
+ realDisplayText && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5363
+ "text",
5364
+ {
5365
+ x: displayX,
5366
+ y: displayY + (value.quality !== void 0 && value.quality < 100 ? 14 : 5) * scale,
5367
+ textAnchor: "middle",
5368
+ fontSize: 7 * scale,
5369
+ fontWeight: "500",
5370
+ fill: finalTextColor,
5371
+ opacity: "0.85",
5372
+ children: realDisplayText
5373
+ }
5374
+ ),
5090
5375
  hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5091
5376
  "path",
5092
5377
  {
@@ -8712,6 +8997,7 @@ function DeviceConfigPanelImpl({
8712
8997
  toastDuration = 3e3,
8713
8998
  onOpenControls,
8714
8999
  showControlsButton = true,
9000
+ showBuiltInToast = true,
8715
9001
  embedded = false
8716
9002
  }) {
8717
9003
  const [localValues, setLocalValues] = (0, import_react24.useState)(() => {
@@ -8888,7 +9174,49 @@ function DeviceConfigPanelImpl({
8888
9174
  " (",
8889
9175
  param.unit,
8890
9176
  ")"
8891
- ] })
9177
+ ] }),
9178
+ param.forced?.active && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9179
+ "span",
9180
+ {
9181
+ "aria-label": "Forced",
9182
+ style: {
9183
+ display: "inline-flex",
9184
+ alignItems: "center",
9185
+ justifyContent: "center",
9186
+ width: "16px",
9187
+ height: "16px",
9188
+ marginLeft: "6px",
9189
+ fontSize: "10px",
9190
+ fontWeight: 700,
9191
+ textTransform: "none",
9192
+ letterSpacing: "normal",
9193
+ color: FORCED_MARK_COLOR.text,
9194
+ backgroundColor: FORCED_MARK_COLOR.bg,
9195
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
9196
+ borderRadius: "50%",
9197
+ verticalAlign: "middle"
9198
+ },
9199
+ children: "F"
9200
+ }
9201
+ )
9202
+ ]
9203
+ }
9204
+ ),
9205
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9206
+ "div",
9207
+ {
9208
+ style: {
9209
+ fontSize: `${fontSize.description}px`,
9210
+ color: mutedTextColor,
9211
+ marginTop: "-2px",
9212
+ marginBottom: "6px",
9213
+ fontFamily: "Arial, sans-serif"
9214
+ },
9215
+ children: [
9216
+ "REAL",
9217
+ " ",
9218
+ typeof param.forced.realValue === "boolean" ? param.forced.realValue ? "ON" : "OFF" : String(param.forced.realValue),
9219
+ param.unit ? ` ${param.unit}` : ""
8892
9220
  ]
8893
9221
  }
8894
9222
  ),
@@ -9377,7 +9705,7 @@ function DeviceConfigPanelImpl({
9377
9705
  }
9378
9706
  )
9379
9707
  ] }),
9380
- binding.state.lastSaveResult && showToast && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9708
+ binding.state.lastSaveResult && showToast && showBuiltInToast && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9381
9709
  "div",
9382
9710
  {
9383
9711
  style: {
@@ -9477,6 +9805,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9477
9805
  DisplayModeToggle,
9478
9806
  EquipmentIndicator,
9479
9807
  EquipmentIndicatorWithSparkline,
9808
+ FORCED_MARK_COLOR,
9480
9809
  FixedSVGContainer,
9481
9810
  FooterPanel,
9482
9811
  FullscreenContainer,