@procaaso/alphinity-ui-components 1.12.0 → 1.14.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
@@ -277,6 +277,13 @@ var ISA_UNKNOWN_DARK = {
277
277
  bg: "#23263a",
278
278
  border: "#6f74a3"
279
279
  };
280
+ var FORCED_MARK_COLOR = {
281
+ fill: "#ede9fe",
282
+ stroke: "#5b21b6",
283
+ bg: "#ede9fe",
284
+ text: "#5b21b6",
285
+ border: "#7c3aed"
286
+ };
280
287
  var DEFAULT_STATUS_COLORS = {
281
288
  normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
282
289
  warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
@@ -285,14 +292,8 @@ var DEFAULT_STATUS_COLORS = {
285
292
  off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
286
293
  starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
287
294
  stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
288
- unknown: { fill: ISA_UNKNOWN.bg, stroke: ISA_UNKNOWN.border, bg: ISA_UNKNOWN.bg, text: ISA_UNKNOWN.fg, border: ISA_UNKNOWN.border }
289
- };
290
- var FORCED_MARK_COLOR = {
291
- fill: "#ede9fe",
292
- stroke: "#5b21b6",
293
- bg: "#ede9fe",
294
- text: "#5b21b6",
295
- border: "#7c3aed"
295
+ unknown: { fill: ISA_UNKNOWN.bg, stroke: ISA_UNKNOWN.border, bg: ISA_UNKNOWN.bg, text: ISA_UNKNOWN.fg, border: ISA_UNKNOWN.border },
296
+ forced: FORCED_MARK_COLOR
296
297
  };
297
298
  var STATUS_ALIASES = {
298
299
  running: "normal",
@@ -1138,6 +1139,10 @@ var NodeControlsPanel = ({
1138
1139
  // src/components/DeviceControlPanel/DeviceControlPanel.tsx
1139
1140
  import React6, { useState as useState5, useEffect as useEffect2 } from "react";
1140
1141
  import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1142
+ var DISABLED_BUTTON_BG = "#e5e7eb";
1143
+ var DISABLED_BUTTON_TEXT = "#4b5563";
1144
+ var DISABLED_BUTTON_BORDER = "#6b7280";
1145
+ var DISABLED_BUTTON_HATCH = "repeating-linear-gradient(135deg, rgba(0,0,0,0.10) 0px, rgba(0,0,0,0.10) 4px, transparent 4px, transparent 10px)";
1141
1146
  function DeviceControlPanelImpl({
1142
1147
  binding,
1143
1148
  onClose,
@@ -1218,6 +1223,36 @@ function DeviceControlPanelImpl({
1218
1223
  const statusEntry = resolveStatusColor(binding.state.status, binding.theme?.statusColors);
1219
1224
  const statusColor = statusEntry.bg;
1220
1225
  const isUnknownStatus = normalizeDeviceStatus(binding.state.status) === "unknown";
1226
+ const startStopRowShown = binding.display?.showStartStop !== false && (activeCapabilities?.canStart === true || activeCapabilities?.canStop === true);
1227
+ const startEnabledNow = activeCapabilities?.canStart === true && activeCapabilities?.startEnabled !== false && !startPending;
1228
+ const stopEnabledNow = activeCapabilities?.canStop === true && activeCapabilities?.stopEnabled !== false;
1229
+ const startStopFontPx = Math.max(18, Math.round(18 * fontScale));
1230
+ const startStopButtonStyle = (enabled, enabledBg) => ({
1231
+ flex: 1,
1232
+ minWidth: 0,
1233
+ height: Math.max(touchSize, 56),
1234
+ borderRadius: "6px",
1235
+ fontSize: `${startStopFontPx}px`,
1236
+ fontWeight: 700,
1237
+ fontFamily: "Arial, sans-serif",
1238
+ letterSpacing: "0.5px",
1239
+ ...enabled ? {
1240
+ backgroundColor: enabledBg,
1241
+ color: "#ffffff",
1242
+ border: "2px solid transparent",
1243
+ cursor: "pointer",
1244
+ opacity: 1
1245
+ } : {
1246
+ // Disabled look that does not depend on colour (ISA-101): a dashed
1247
+ // border and a diagonal hatch. A grey fill alone is not enough.
1248
+ backgroundColor: DISABLED_BUTTON_BG,
1249
+ backgroundImage: DISABLED_BUTTON_HATCH,
1250
+ color: DISABLED_BUTTON_TEXT,
1251
+ border: `2px dashed ${DISABLED_BUTTON_BORDER}`,
1252
+ cursor: "not-allowed",
1253
+ opacity: 1
1254
+ }
1255
+ });
1221
1256
  const handleModeChange = (mode) => {
1222
1257
  onModeChange?.(mode);
1223
1258
  };
@@ -1403,6 +1438,7 @@ function DeviceControlPanelImpl({
1403
1438
  /* @__PURE__ */ jsx9(
1404
1439
  "div",
1405
1440
  {
1441
+ "data-testid": "device-status-dot",
1406
1442
  style: {
1407
1443
  width: "10px",
1408
1444
  height: "10px",
@@ -1988,50 +2024,45 @@ function DeviceControlPanelImpl({
1988
2024
  param.id
1989
2025
  ))
1990
2026
  ] }),
1991
- binding.display?.showStartStop !== false && (activeCapabilities?.canStart || activeCapabilities?.canStop) && /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom }, children: [
1992
- activeCapabilities?.canStart && /* @__PURE__ */ jsx9(
1993
- "button",
1994
- {
1995
- onClick: onStart,
1996
- style: {
1997
- flex: 1,
1998
- height: touchSize,
1999
- backgroundColor: "#10b981",
2000
- color: "#ffffff",
2001
- border: "none",
2002
- borderRadius: "6px",
2003
- fontSize: "12px",
2004
- fontWeight: 700,
2005
- fontFamily: "Arial, sans-serif",
2006
- letterSpacing: "0.5px",
2007
- cursor: "pointer",
2008
- opacity: 1
2009
- },
2010
- children: startPending ? "STARTING\u2026" : "START"
2011
- }
2012
- ),
2013
- activeCapabilities?.canStop && /* @__PURE__ */ jsx9(
2014
- "button",
2015
- {
2016
- onClick: onStop,
2017
- style: {
2018
- flex: 1,
2019
- height: touchSize,
2020
- backgroundColor: "#ef4444",
2021
- color: "#ffffff",
2022
- border: "none",
2023
- borderRadius: "6px",
2024
- fontSize: "12px",
2025
- fontWeight: 700,
2026
- fontFamily: "Arial, sans-serif",
2027
- letterSpacing: "0.5px",
2028
- cursor: "pointer",
2029
- opacity: 1
2030
- },
2031
- children: stopPending ? "STOPPING\u2026" : "STOP"
2032
- }
2033
- )
2034
- ] }),
2027
+ startStopRowShown && /* @__PURE__ */ jsxs8(
2028
+ "div",
2029
+ {
2030
+ "data-testid": "start-stop-row",
2031
+ style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom },
2032
+ children: [
2033
+ /* @__PURE__ */ jsx9(
2034
+ "button",
2035
+ {
2036
+ type: "button",
2037
+ "data-control": "start",
2038
+ "data-enabled": startEnabledNow ? "true" : "false",
2039
+ onClick: () => {
2040
+ if (startEnabledNow) onStart?.();
2041
+ },
2042
+ disabled: !startEnabledNow,
2043
+ "aria-busy": startPending || void 0,
2044
+ style: startStopButtonStyle(startEnabledNow, "#10b981"),
2045
+ children: startPending ? "STARTING\u2026" : "START"
2046
+ }
2047
+ ),
2048
+ /* @__PURE__ */ jsx9(
2049
+ "button",
2050
+ {
2051
+ type: "button",
2052
+ "data-control": "stop",
2053
+ "data-enabled": stopEnabledNow ? "true" : "false",
2054
+ onClick: () => {
2055
+ if (stopEnabledNow) onStop?.();
2056
+ },
2057
+ disabled: !stopEnabledNow,
2058
+ "aria-busy": stopPending || void 0,
2059
+ style: startStopButtonStyle(stopEnabledNow, "#ef4444"),
2060
+ children: stopPending ? "STOPPING\u2026" : "STOP"
2061
+ }
2062
+ )
2063
+ ]
2064
+ }
2065
+ ),
2035
2066
  activeCapabilities?.customActions && activeCapabilities.customActions.length > 0 && /* @__PURE__ */ jsx9("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: activeCapabilities.customActions.map((action) => {
2036
2067
  const actionDisabled = action.disabled === true;
2037
2068
  return /* @__PURE__ */ jsxs8(
@@ -5031,6 +5062,32 @@ function writeAppearanceValue(node, field, value) {
5031
5062
  return { metadata: { ...node.metadata, [field.key]: value } };
5032
5063
  }
5033
5064
 
5065
+ // src/diagram/badges/nodeBadgeGlyphs.ts
5066
+ var PROHIBIT_GLYPH = {
5067
+ id: "prohibit",
5068
+ svgContent: '<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2.4"/><line x1="5.6" y1="18.4" x2="18.4" y2="5.6" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/>'
5069
+ };
5070
+ var builtInBadgeGlyphs = {
5071
+ [PROHIBIT_GLYPH.id]: PROHIBIT_GLYPH
5072
+ };
5073
+ var customBadgeGlyphs = {};
5074
+ function registerBadgeGlyph(glyph) {
5075
+ customBadgeGlyphs[glyph.id] = glyph;
5076
+ }
5077
+ function registerBadgeGlyphs(glyphs) {
5078
+ if (Array.isArray(glyphs)) {
5079
+ glyphs.forEach((glyph) => registerBadgeGlyph(glyph));
5080
+ } else {
5081
+ Object.values(glyphs).forEach((glyph) => registerBadgeGlyph(glyph));
5082
+ }
5083
+ }
5084
+ function getBadgeGlyph(glyphId) {
5085
+ return customBadgeGlyphs[glyphId] ?? builtInBadgeGlyphs[glyphId];
5086
+ }
5087
+ function getAvailableBadgeGlyphs() {
5088
+ return [...Object.keys(customBadgeGlyphs), ...Object.keys(builtInBadgeGlyphs)];
5089
+ }
5090
+
5034
5091
  // src/components/PIDCanvas/NodeRenderer.tsx
5035
5092
  import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
5036
5093
  var HATCH_TILE_SIZE = 7;
@@ -5040,6 +5097,75 @@ var HATCH_STRIPE_OPACITY = 85 / 255;
5040
5097
  function isUnknownNode(node) {
5041
5098
  return node.positionUnknown === true || normalizeDeviceStatus(node.status) === "unknown";
5042
5099
  }
5100
+ var DEFAULT_BADGE_CORNER = "top-right";
5101
+ var DEFAULT_BADGE_SIZE = 14;
5102
+ var DEFAULT_BADGE_COLOR = "#111827";
5103
+ var DEFAULT_BADGE_BACKGROUND = "#d1d5db";
5104
+ var BADGE_GLYPH_BOX = 24;
5105
+ var BADGE_BLINK_KEYFRAME_ID = "pid-node-badge-blink-keyframes";
5106
+ var BADGE_BLINK_KEYFRAME_NAME = "pid-node-badge-blink";
5107
+ function ensureBadgeBlinkKeyframes() {
5108
+ if (typeof document === "undefined") return;
5109
+ if (document.getElementById(BADGE_BLINK_KEYFRAME_ID)) return;
5110
+ const style = document.createElement("style");
5111
+ style.id = BADGE_BLINK_KEYFRAME_ID;
5112
+ style.textContent = `@keyframes ${BADGE_BLINK_KEYFRAME_NAME}{0%{opacity:1}50%{opacity:.15}100%{opacity:1}}`;
5113
+ document.head.appendChild(style);
5114
+ }
5115
+ function badgeCenter(corner, vb, size) {
5116
+ const margin = size / 4;
5117
+ switch (corner) {
5118
+ case "top-left":
5119
+ return { x: -margin, y: -margin };
5120
+ case "bottom-left":
5121
+ return { x: -margin, y: vb.height + margin };
5122
+ case "bottom-right":
5123
+ return { x: vb.width + margin, y: vb.height + margin };
5124
+ case "top-right":
5125
+ default:
5126
+ return { x: vb.width + margin, y: -margin };
5127
+ }
5128
+ }
5129
+ function renderNodeBadge(badge, vb, rotation) {
5130
+ const glyphDef = getBadgeGlyph(badge.glyph);
5131
+ const size = badge.size ?? DEFAULT_BADGE_SIZE;
5132
+ const color = badge.color ?? DEFAULT_BADGE_COLOR;
5133
+ const backgroundColor = badge.backgroundColor ?? DEFAULT_BADGE_BACKGROUND;
5134
+ const corner = badge.corner ?? DEFAULT_BADGE_CORNER;
5135
+ const { x: cx, y: cy } = badgeCenter(corner, vb, size);
5136
+ const radius = size / 2;
5137
+ const glyphScale = size / BADGE_GLYPH_BOX;
5138
+ let blinkStyle;
5139
+ if (badge.blink) {
5140
+ ensureBadgeBlinkKeyframes();
5141
+ blinkStyle = {
5142
+ animation: `${BADGE_BLINK_KEYFRAME_NAME} ${badge.blinkPeriodMs}ms ease-in-out infinite`
5143
+ };
5144
+ }
5145
+ return /* @__PURE__ */ jsxs13(
5146
+ "g",
5147
+ {
5148
+ "data-node-badge": badge.glyph,
5149
+ "data-badge-corner": corner,
5150
+ "data-badge-blink": badge.blink ? "true" : "false",
5151
+ "aria-label": badge.label,
5152
+ role: badge.label ? "img" : void 0,
5153
+ transform: rotation ? `translate(${cx}, ${cy}) rotate(${-rotation})` : `translate(${cx}, ${cy})`,
5154
+ style: blinkStyle,
5155
+ children: [
5156
+ /* @__PURE__ */ jsx17("circle", { r: radius, fill: backgroundColor }),
5157
+ glyphDef && /* @__PURE__ */ jsx17(
5158
+ "g",
5159
+ {
5160
+ color,
5161
+ transform: `scale(${glyphScale}) translate(${-BADGE_GLYPH_BOX / 2}, ${-BADGE_GLYPH_BOX / 2})`,
5162
+ dangerouslySetInnerHTML: { __html: glyphDef.svgContent }
5163
+ }
5164
+ )
5165
+ ]
5166
+ }
5167
+ );
5168
+ }
5043
5169
  function NodeRendererImpl({
5044
5170
  nodes: nodes2,
5045
5171
  selectedNodeIds,
@@ -5246,7 +5372,8 @@ function NodeRendererImpl({
5246
5372
  transform: node.transform.rotation ? `rotate(${-node.transform.rotation}, ${labelX}, ${labelY})` : void 0,
5247
5373
  children: node.label || node.id
5248
5374
  }
5249
- )
5375
+ ),
5376
+ node.badge && renderNodeBadge(node.badge, vb, node.transform.rotation)
5250
5377
  ]
5251
5378
  },
5252
5379
  node.id
@@ -10341,7 +10468,9 @@ export {
10341
10468
  generateRoutePoints,
10342
10469
  getAppearanceDefaults,
10343
10470
  getAppearanceSchema,
10471
+ getAvailableBadgeGlyphs,
10344
10472
  getAvailableSymbols,
10473
+ getBadgeGlyph,
10345
10474
  getPortWorldPosition,
10346
10475
  getSymbolDefinition,
10347
10476
  importDiagram,
@@ -10350,6 +10479,8 @@ export {
10350
10479
  normalizeDeviceStatus,
10351
10480
  overlayStyles,
10352
10481
  readAppearanceValue,
10482
+ registerBadgeGlyph,
10483
+ registerBadgeGlyphs,
10353
10484
  registerSymbol,
10354
10485
  registerSymbols,
10355
10486
  resolvePresetColor,