@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.57 → 2.0.0-next.59

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.
Files changed (185) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +6585 -4581
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +1047 -83
  4. package/dist/alert-severity.d.ts +38 -0
  5. package/dist/alert-severity.d.ts.map +1 -0
  6. package/dist/alert-severity.js +176 -0
  7. package/dist/alert-severity.js.map +1 -0
  8. package/dist/ar/building-blocks/poi-header/poi-header.css.js +38 -7
  9. package/dist/ar/building-blocks/poi-header/poi-header.css.js.map +1 -1
  10. package/dist/ar/building-blocks/poi-header/poi-header.d.ts +7 -2
  11. package/dist/ar/building-blocks/poi-header/poi-header.d.ts.map +1 -1
  12. package/dist/ar/building-blocks/poi-header/poi-header.js +5 -0
  13. package/dist/ar/building-blocks/poi-header/poi-header.js.map +1 -1
  14. package/dist/ar/poi-button/poi-button.css.js +110 -32
  15. package/dist/ar/poi-button/poi-button.css.js.map +1 -1
  16. package/dist/ar/poi-button/poi-button.d.ts +7 -2
  17. package/dist/ar/poi-button/poi-button.d.ts.map +1 -1
  18. package/dist/ar/poi-button/poi-button.js +15 -0
  19. package/dist/ar/poi-button/poi-button.js.map +1 -1
  20. package/dist/ar/types.d.ts +6 -1
  21. package/dist/ar/types.d.ts.map +1 -1
  22. package/dist/ar/types.js +5 -0
  23. package/dist/ar/types.js.map +1 -1
  24. package/dist/automation/automation-badge/automation-badge.d.ts +9 -1
  25. package/dist/automation/automation-badge/automation-badge.d.ts.map +1 -1
  26. package/dist/automation/automation-badge/automation-badge.js +119 -12
  27. package/dist/automation/automation-badge/automation-badge.js.map +1 -1
  28. package/dist/automation/automation-button/abstract-automation-button-storybook-helpers.d.ts +25 -1
  29. package/dist/automation/automation-button/abstract-automation-button-storybook-helpers.d.ts.map +1 -1
  30. package/dist/automation/automation-button/abstract-automation-button-storybook-helpers.js +7 -2
  31. package/dist/automation/automation-button/abstract-automation-button-storybook-helpers.js.map +1 -1
  32. package/dist/automation/automation-button/abstract-automation-button.d.ts +10 -3
  33. package/dist/automation/automation-button/abstract-automation-button.d.ts.map +1 -1
  34. package/dist/automation/automation-button/abstract-automation-button.js +23 -2
  35. package/dist/automation/automation-button/abstract-automation-button.js.map +1 -1
  36. package/dist/automation/automation-button/automation-button.css.js +9 -4
  37. package/dist/automation/automation-button/automation-button.css.js.map +1 -1
  38. package/dist/automation/automation-button/automation-button.d.ts +4 -2
  39. package/dist/automation/automation-button/automation-button.d.ts.map +1 -1
  40. package/dist/automation/automation-button/automation-button.js +24 -6
  41. package/dist/automation/automation-button/automation-button.js.map +1 -1
  42. package/dist/automation/automation-tank/automation-tank.d.ts +3 -2
  43. package/dist/automation/automation-tank/automation-tank.d.ts.map +1 -1
  44. package/dist/automation/automation-tank/automation-tank.js +13 -2
  45. package/dist/automation/automation-tank/automation-tank.js.map +1 -1
  46. package/dist/building-blocks/instrument-radial/instrument-radial.d.ts +10 -0
  47. package/dist/building-blocks/instrument-radial/instrument-radial.d.ts.map +1 -1
  48. package/dist/building-blocks/instrument-radial/instrument-radial.js +86 -21
  49. package/dist/building-blocks/instrument-radial/instrument-radial.js.map +1 -1
  50. package/dist/components/accordion-card/accordion-card.css.js +34 -0
  51. package/dist/components/accordion-card/accordion-card.css.js.map +1 -1
  52. package/dist/components/accordion-card/accordion-card.d.ts +3 -2
  53. package/dist/components/accordion-card/accordion-card.d.ts.map +1 -1
  54. package/dist/components/accordion-card/accordion-card.js +4 -3
  55. package/dist/components/accordion-card/accordion-card.js.map +1 -1
  56. package/dist/components/alert-button/alert-button.css.js +744 -7
  57. package/dist/components/alert-button/alert-button.css.js.map +1 -1
  58. package/dist/components/alert-button/alert-button.d.ts +3 -1
  59. package/dist/components/alert-button/alert-button.d.ts.map +1 -1
  60. package/dist/components/alert-button/alert-button.js +33 -31
  61. package/dist/components/alert-button/alert-button.js.map +1 -1
  62. package/dist/components/alert-frame/alert-frame.css.js +116 -29
  63. package/dist/components/alert-frame/alert-frame.css.js.map +1 -1
  64. package/dist/components/alert-frame/alert-frame.d.ts +42 -15
  65. package/dist/components/alert-frame/alert-frame.d.ts.map +1 -1
  66. package/dist/components/alert-frame/alert-frame.js +68 -17
  67. package/dist/components/alert-frame/alert-frame.js.map +1 -1
  68. package/dist/components/alert-frame/critical-badge.d.ts +12 -0
  69. package/dist/components/alert-frame/critical-badge.d.ts.map +1 -0
  70. package/dist/components/alert-frame/critical-badge.js +47 -0
  71. package/dist/components/alert-frame/critical-badge.js.map +1 -0
  72. package/dist/components/alert-frame/diagnostic-badge.d.ts +12 -0
  73. package/dist/components/alert-frame/diagnostic-badge.d.ts.map +1 -0
  74. package/dist/components/alert-frame/diagnostic-badge.js +60 -0
  75. package/dist/components/alert-frame/diagnostic-badge.js.map +1 -0
  76. package/dist/components/alert-icon/alert-icon.d.ts +6 -1
  77. package/dist/components/alert-icon/alert-icon.d.ts.map +1 -1
  78. package/dist/components/alert-icon/alert-icon.js +96 -38
  79. package/dist/components/alert-icon/alert-icon.js.map +1 -1
  80. package/dist/components/alert-icon/icons/icon-critical.d.ts +3 -0
  81. package/dist/components/alert-icon/icons/icon-critical.d.ts.map +1 -0
  82. package/dist/components/alert-icon/icons/icon-critical.js +26 -0
  83. package/dist/components/alert-icon/icons/icon-critical.js.map +1 -0
  84. package/dist/components/alert-list-details/alert-list-details.d.ts.map +1 -1
  85. package/dist/components/alert-list-details/alert-list-details.js +6 -7
  86. package/dist/components/alert-list-details/alert-list-details.js.map +1 -1
  87. package/dist/components/badge/badge.css.js +50 -2
  88. package/dist/components/badge/badge.css.js.map +1 -1
  89. package/dist/components/badge/badge.d.ts +13 -2
  90. package/dist/components/badge/badge.d.ts.map +1 -1
  91. package/dist/components/badge/badge.js +54 -0
  92. package/dist/components/badge/badge.js.map +1 -1
  93. package/dist/index.js +2 -1
  94. package/dist/manual-icon/icon-alerts-critical-twotone.d.ts +30 -0
  95. package/dist/manual-icon/icon-alerts-critical-twotone.d.ts.map +1 -0
  96. package/dist/manual-icon/icon-alerts-critical-twotone.js +55 -0
  97. package/dist/manual-icon/icon-alerts-critical-twotone.js.map +1 -0
  98. package/dist/manual-icon/icon-alerts-diagnostic-twotone.d.ts +30 -0
  99. package/dist/manual-icon/icon-alerts-diagnostic-twotone.d.ts.map +1 -0
  100. package/dist/manual-icon/icon-alerts-diagnostic-twotone.js +55 -0
  101. package/dist/manual-icon/icon-alerts-diagnostic-twotone.js.map +1 -0
  102. package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js +99 -0
  103. package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js.map +1 -0
  104. package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts +42 -7
  105. package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts.map +1 -1
  106. package/dist/navigation-instruments/gauge-radial/gauge-radial.js +178 -31
  107. package/dist/navigation-instruments/gauge-radial/gauge-radial.js.map +1 -1
  108. package/dist/navigation-instruments/readout/readout.css.js +7 -18
  109. package/dist/navigation-instruments/readout/readout.css.js.map +1 -1
  110. package/dist/navigation-instruments/readout/readout.d.ts +6 -7
  111. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  112. package/dist/navigation-instruments/readout/readout.js +54 -52
  113. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  114. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +9 -36
  115. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  116. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +8 -8
  117. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  118. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +49 -42
  119. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  120. package/dist/navigation-instruments/watch/tickmark.d.ts +2 -1
  121. package/dist/navigation-instruments/watch/tickmark.d.ts.map +1 -1
  122. package/dist/navigation-instruments/watch/tickmark.js +24 -4
  123. package/dist/navigation-instruments/watch/tickmark.js.map +1 -1
  124. package/dist/navigation-instruments/watch/watch.d.ts +23 -1
  125. package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
  126. package/dist/navigation-instruments/watch/watch.js +48 -20
  127. package/dist/navigation-instruments/watch/watch.js.map +1 -1
  128. package/dist/openbridge.css +155 -0
  129. package/dist/types.d.ts +7 -1
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +18 -7
  132. package/dist/types.js.map +1 -1
  133. package/package.json +1 -1
  134. package/src/alert-severity.ts +225 -0
  135. package/src/ar/building-blocks/poi-header/poi-header.css +38 -7
  136. package/src/ar/building-blocks/poi-header/poi-header.ts +6 -1
  137. package/src/ar/poi-button/poi-button.css +126 -32
  138. package/src/ar/poi-button/poi-button.ts +16 -1
  139. package/src/ar/types.ts +5 -0
  140. package/src/automation/automation-badge/automation-badge.ts +124 -17
  141. package/src/automation/automation-button/abstract-automation-button-storybook-helpers.ts +5 -0
  142. package/src/automation/automation-button/abstract-automation-button.ts +21 -3
  143. package/src/automation/automation-button/automation-button.css +9 -4
  144. package/src/automation/automation-button/automation-button.ts +25 -7
  145. package/src/automation/automation-tank/automation-tank.stories.ts +22 -0
  146. package/src/automation/automation-tank/automation-tank.ts +12 -3
  147. package/src/building-blocks/instrument-radial/instrument-radial.stories.ts +21 -0
  148. package/src/building-blocks/instrument-radial/instrument-radial.ts +118 -21
  149. package/src/components/accordion-card/accordion-card.css +33 -0
  150. package/src/components/accordion-card/accordion-card.stories.ts +2 -1
  151. package/src/components/accordion-card/accordion-card.ts +3 -4
  152. package/src/components/alert-button/alert-button.css +89 -5
  153. package/src/components/alert-button/alert-button.stories.ts +43 -0
  154. package/src/components/alert-button/alert-button.ts +43 -32
  155. package/src/components/alert-frame/alert-frame.css +116 -29
  156. package/src/components/alert-frame/alert-frame.stories.ts +63 -3
  157. package/src/components/alert-frame/alert-frame.ts +114 -23
  158. package/src/components/alert-frame/critical-badge.ts +39 -0
  159. package/src/components/alert-frame/diagnostic-badge.ts +52 -0
  160. package/src/components/alert-icon/alert-icon.stories.ts +41 -0
  161. package/src/components/alert-icon/alert-icon.ts +101 -38
  162. package/src/components/alert-icon/icons/icon-critical.ts +23 -0
  163. package/src/components/alert-list-details/alert-list-details.stories.ts +67 -0
  164. package/src/components/alert-list-details/alert-list-details.ts +11 -10
  165. package/src/components/badge/badge.css +52 -2
  166. package/src/components/badge/badge.stories.ts +75 -0
  167. package/src/components/badge/badge.ts +70 -2
  168. package/src/main.css +1 -0
  169. package/src/manual-icon/icon-alerts-critical-twotone.ts +60 -0
  170. package/src/manual-icon/icon-alerts-diagnostic-twotone.ts +60 -0
  171. package/src/mixins/alert.css +29 -0
  172. package/src/navigation-instruments/gauge-radial/gauge-radial.css +85 -0
  173. package/src/navigation-instruments/gauge-radial/gauge-radial.stories.ts +149 -7
  174. package/src/navigation-instruments/gauge-radial/gauge-radial.ts +212 -33
  175. package/src/navigation-instruments/readout/readout.css +7 -24
  176. package/src/navigation-instruments/readout/readout.stories.ts +57 -4
  177. package/src/navigation-instruments/readout/readout.ts +69 -63
  178. package/src/navigation-instruments/readout-list-item/readout-list-item.css +9 -42
  179. package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +57 -14
  180. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +54 -44
  181. package/src/navigation-instruments/watch/tickmark.ts +33 -6
  182. package/src/navigation-instruments/watch/watch.ts +60 -25
  183. package/src/palettes/critical.md +95 -0
  184. package/src/palettes/manual.css +165 -0
  185. package/src/types.ts +20 -3
@@ -228,58 +228,25 @@
228
228
  }
229
229
  }
230
230
 
231
- &:is(
232
- .alert-low-integrity,
233
- .alert-invalid,
234
- .alert-caution,
235
- .alert-warning,
236
- .alert-alarm
237
- ) {
238
- background: var(--_obc-readout-list-item-alert-background);
239
- border: 1px solid var(--_obc-readout-list-item-alert-border);
231
+ &:is(.data-low-integrity, .data-invalid) {
232
+ background: var(--_obc-readout-list-item-data-background);
233
+ border: 1px solid var(--_obc-readout-list-item-data-border);
240
234
  border-radius: 4px;
241
235
  }
242
236
 
243
- &.alert-low-integrity {
244
- --_obc-readout-list-item-alert-background: var(
237
+ &.data-low-integrity {
238
+ --_obc-readout-list-item-data-background: var(
245
239
  --alert-low-integrity-background-color
246
240
  );
247
- --_obc-readout-list-item-alert-border: var(
241
+ --_obc-readout-list-item-data-border: var(
248
242
  --alert-low-integrity-border-color
249
243
  );
250
244
  }
251
245
 
252
- &.alert-invalid {
253
- --_obc-readout-list-item-alert-background: var(
246
+ &.data-invalid {
247
+ --_obc-readout-list-item-data-background: var(
254
248
  --alert-invalid-background-color
255
249
  );
256
- --_obc-readout-list-item-alert-border: var(--alert-invalid-border-color);
257
- }
258
-
259
- &.alert-caution {
260
- --_obc-readout-list-item-alert-background: var(
261
- --alert-caution-container-background-color
262
- );
263
- --_obc-readout-list-item-alert-border: var(
264
- --alert-caution-container-border-color
265
- );
266
- }
267
-
268
- &.alert-warning {
269
- --_obc-readout-list-item-alert-background: var(
270
- --alert-warning-container-background-color
271
- );
272
- --_obc-readout-list-item-alert-border: var(
273
- --alert-warning-container-border-color
274
- );
275
- }
276
-
277
- &.alert-alarm {
278
- --_obc-readout-list-item-alert-background: var(
279
- --alert-alarm-container-background-color
280
- );
281
- --_obc-readout-list-item-alert-border: var(
282
- --alert-alarm-container-border-color
283
- );
250
+ --_obc-readout-list-item-data-border: var(--alert-invalid-border-color);
284
251
  }
285
252
  }
@@ -4,9 +4,15 @@ import {useArgs} from 'storybook/preview-api';
4
4
  import {
5
5
  ReadoutListItemSize,
6
6
  ReadoutListItemStacking,
7
- ReadoutListItemAlertState,
7
+ ReadoutListItemDataState,
8
8
  ReadoutListItemPriority,
9
9
  } from './readout-list-item.js';
10
+ import type {AlertFrameConfig} from '../../components/alert-frame/alert-frame.js';
11
+ import {
12
+ ObcAlertFrameMode,
13
+ ObcAlertFrameType,
14
+ } from '../../components/alert-frame/alert-frame.js';
15
+ import {AlertType} from '../../types.js';
10
16
  import './readout-list-item.js';
11
17
  import '../../icons/icon-placeholder.js';
12
18
 
@@ -14,7 +20,8 @@ type ReadoutListItemStoryArgs = {
14
20
  size: ReadoutListItemSize;
15
21
  stacking: ReadoutListItemStacking;
16
22
  priority: ReadoutListItemPriority;
17
- alertState: ReadoutListItemAlertState;
23
+ dataState: ReadoutListItemDataState;
24
+ alert: AlertFrameConfig | false;
18
25
  hasSetpoint: boolean;
19
26
  setpointValue: number;
20
27
  hasLabel: boolean;
@@ -132,7 +139,8 @@ const defaultArgs: ReadoutListItemStoryArgs = {
132
139
  size: ReadoutListItemSize.base,
133
140
  stacking: ReadoutListItemStacking.trailingUnit,
134
141
  priority: ReadoutListItemPriority.regular,
135
- alertState: ReadoutListItemAlertState.none,
142
+ dataState: ReadoutListItemDataState.none,
143
+ alert: false,
136
144
  hasSetpoint: false,
137
145
  setpointValue: 123,
138
146
  hasDegree: true,
@@ -149,6 +157,7 @@ const defaultArgs: ReadoutListItemStoryArgs = {
149
157
  value: 123,
150
158
  fractionDigits: 0,
151
159
  showZeroPadding: false,
160
+ labelOnly: false,
152
161
  };
153
162
 
154
163
  function renderReadoutListItem(args: Partial<ReadoutListItemStoryArgs>) {
@@ -158,7 +167,8 @@ function renderReadoutListItem(args: Partial<ReadoutListItemStoryArgs>) {
158
167
  .size=${resolved.size}
159
168
  .stacking=${resolved.stacking}
160
169
  .priority=${resolved.priority}
161
- .alertState=${resolved.alertState}
170
+ .dataState=${resolved.dataState}
171
+ .alert=${resolved.alert}
162
172
  .hasSetpoint=${resolved.hasSetpoint}
163
173
  .setpointValue=${resolved.setpointValue}
164
174
  .hasLabel=${resolved.hasLabel}
@@ -260,10 +270,10 @@ const meta = {
260
270
  options: Object.values(ReadoutListItemPriority),
261
271
  table: {category: 'Readout'},
262
272
  },
263
- alertState: {
264
- name: 'Alert State',
273
+ dataState: {
274
+ name: 'Data State',
265
275
  control: {type: 'select'},
266
- options: Object.values(ReadoutListItemAlertState),
276
+ options: Object.values(ReadoutListItemDataState),
267
277
  table: {category: 'Readout'},
268
278
  },
269
279
  hasSetpoint: {
@@ -386,7 +396,7 @@ function renderStackingMatrix({
386
396
  stacking,
387
397
  priority,
388
398
  hasSetpoint,
389
- alertState: ReadoutListItemAlertState.none,
399
+ dataState: ReadoutListItemDataState.none,
390
400
  hasUnit,
391
401
  hasSource,
392
402
  value: 123,
@@ -429,20 +439,53 @@ export const LeadingSrc: Story = {
429
439
  }),
430
440
  };
431
441
 
432
- export const AlertStates: Story = {
442
+ export const DataStates: Story = {
433
443
  render: () => {
434
- const alerts = Object.values(ReadoutListItemAlertState);
435
- const cases: ReadoutShowcaseCase[] = alerts.map((alertState) => ({
436
- label: `${alertState}`,
444
+ const dataStates = Object.values(ReadoutListItemDataState);
445
+ const cases: ReadoutShowcaseCase[] = dataStates.map((dataState) => ({
446
+ label: `${dataState}`,
437
447
  args: {
438
448
  size: ReadoutListItemSize.base,
439
449
  stacking: ReadoutListItemStacking.trailingUnit,
440
450
  priority: ReadoutListItemPriority.regular,
441
- alertState,
451
+ dataState,
442
452
  value: 123,
443
453
  },
444
454
  }));
445
455
 
446
- return renderShowcaseSections([{title: 'Alert States', cases}]);
456
+ return renderShowcaseSections([{title: 'Data States', cases}]);
457
+ },
458
+ };
459
+
460
+ export const Alarm: Story = {
461
+ args: {
462
+ value: 123,
463
+ alert: {
464
+ status: AlertType.Alarm,
465
+ mode: ObcAlertFrameMode.ackedActive,
466
+ type: ObcAlertFrameType.Regular,
467
+ },
468
+ },
469
+ };
470
+
471
+ export const IsaCritical: Story = {
472
+ args: {
473
+ value: 123,
474
+ alert: {
475
+ status: AlertType.IsaCritical,
476
+ mode: ObcAlertFrameMode.unackedActive,
477
+ type: ObcAlertFrameType.Regular,
478
+ },
479
+ },
480
+ };
481
+
482
+ export const IsaLowUnackedRectified: Story = {
483
+ args: {
484
+ value: 123,
485
+ alert: {
486
+ status: AlertType.IsaLow,
487
+ mode: ObcAlertFrameMode.unackedRectified,
488
+ type: ObcAlertFrameType.Regular,
489
+ },
447
490
  },
448
491
  };
@@ -9,14 +9,15 @@ import {Priority} from '../types.js';
9
9
  import '../readout-setpoint/readout-setpoint.js';
10
10
  import '../../icons/icon-input-right.js';
11
11
  import {ReadoutSetpointMode} from '../readout-setpoint/readout-setpoint.js';
12
+ import {
13
+ type AlertFrameConfig,
14
+ wrapWithAlertFrame,
15
+ } from '../../components/alert-frame/alert-frame.js';
12
16
 
13
- export enum ReadoutListItemAlertState {
17
+ export enum ReadoutListItemDataState {
14
18
  none = 'none',
15
19
  lowIntegrity = 'low-integrity',
16
20
  invalid = 'invalid',
17
- caution = 'caution',
18
- warning = 'warning',
19
- alarm = 'alarm',
20
21
  }
21
22
 
22
23
  export enum ReadoutListItemSize {
@@ -47,7 +48,8 @@ export enum ReadoutListItemPriority {
47
48
  * - **Sizes:** `base`, `priority`, and `enhanced` typography/padding scales.
48
49
  * - **Stacking modes:** `trailing-unit`, `leading-unit`, and `leading-src` control where unit/source appear relative to the label/value.
49
50
  * - **Priority styling:** `priority` controls emphasis and setpoint presentation (`regular`, `enhanced`, `setpoint`, `setpoint-flip-flop`).
50
- * - **Alert states:** Supports `alertState` styling for integrity/invalid and attention states (`caution`, `warning`, `alarm`).
51
+ * - **Data states:** Supports `dataState` styling for `low-integrity` and `invalid` data quality.
52
+ * - **Alert frame:** Optional `alert` wrapper for caution, warning, alarm, and other alert-frame statuses.
51
53
  * - **Formatting:** Supports numeric formatting, fixed-length width templates, hinted zeros, and optional degree suffix (`°`).
52
54
  *
53
55
  * ### Usage Guidelines
@@ -71,7 +73,9 @@ export class ObcReadoutListItem extends LitElement {
71
73
  @property({type: String})
72
74
  priority: ReadoutListItemPriority = ReadoutListItemPriority.regular;
73
75
  @property({type: String})
74
- alertState: ReadoutListItemAlertState = ReadoutListItemAlertState.none;
76
+ dataState: ReadoutListItemDataState = ReadoutListItemDataState.none;
77
+
78
+ @property({type: Object}) alert: AlertFrameConfig | boolean = false;
75
79
 
76
80
  @property({type: String}) label = '';
77
81
  @property({type: String}) unit = '';
@@ -369,46 +373,52 @@ export class ObcReadoutListItem extends LitElement {
369
373
  }
370
374
 
371
375
  override render() {
372
- return html`
373
- <div
374
- class=${classMap({
375
- root: true,
376
- [`size-${this.size}`]: true,
377
- [`stacking-${this.stacking}`]: true,
378
- 'priority-enhanced':
379
- this.priority === ReadoutListItemPriority.enhanced,
380
- 'priority-setpoint':
381
- this.priority === ReadoutListItemPriority.setpoint,
382
- 'priority-setpoint-flip-flop':
383
- this.priority === ReadoutListItemPriority.setpointFlipFlop,
384
- [`alert-${this.alertState}`]: true,
385
- 'has-leading-icon': this.hasLeadingIcon,
386
- 'has-value-icon': this.hasValueIcon,
387
- })}
388
- part="root"
389
- >
390
- <div class="content" part="content">
391
- <div class="label-container" part="label-container">
392
- ${this.hasLeadingIcon
393
- ? html`<span class="leading-icon" aria-hidden="true"
394
- ><slot name="leading-icon"></slot
395
- ></span>`
396
- : nothing}
397
- ${this.renderLabelContainer()}
376
+ return wrapWithAlertFrame(
377
+ this.alert,
378
+ html`
379
+ <div
380
+ class=${classMap({
381
+ root: true,
382
+ [`size-${this.size}`]: true,
383
+ [`stacking-${this.stacking}`]: true,
384
+ 'priority-enhanced':
385
+ this.priority === ReadoutListItemPriority.enhanced,
386
+ 'priority-setpoint':
387
+ this.priority === ReadoutListItemPriority.setpoint,
388
+ 'priority-setpoint-flip-flop':
389
+ this.priority === ReadoutListItemPriority.setpointFlipFlop,
390
+ 'data-none': this.dataState === ReadoutListItemDataState.none,
391
+ 'data-low-integrity':
392
+ this.dataState === ReadoutListItemDataState.lowIntegrity,
393
+ 'data-invalid': this.dataState === ReadoutListItemDataState.invalid,
394
+ 'has-leading-icon': this.hasLeadingIcon,
395
+ 'has-value-icon': this.hasValueIcon,
396
+ })}
397
+ part="root"
398
+ >
399
+ <div class="content" part="content">
400
+ <div class="label-container" part="label-container">
401
+ ${this.hasLeadingIcon
402
+ ? html`<span class="leading-icon" aria-hidden="true"
403
+ ><slot name="leading-icon"></slot
404
+ ></span>`
405
+ : nothing}
406
+ ${this.renderLabelContainer()}
407
+ </div>
408
+
409
+ ${this.labelOnly
410
+ ? nothing
411
+ : html`
412
+ <div class="value-container" part="value-container">
413
+ ${this.renderValue()} ${this.renderTrailingUnit()}
414
+ </div>
415
+
416
+ ${this.renderTrailingSource()}
417
+ `}
398
418
  </div>
399
-
400
- ${this.labelOnly
401
- ? nothing
402
- : html`
403
- <div class="value-container" part="value-container">
404
- ${this.renderValue()} ${this.renderTrailingUnit()}
405
- </div>
406
-
407
- ${this.renderTrailingSource()}
408
- `}
409
419
  </div>
410
- </div>
411
- `;
420
+ `
421
+ );
412
422
  }
413
423
 
414
424
  static override styles = unsafeCSS(componentStyle);
@@ -49,6 +49,7 @@ export function tickmark(
49
49
  maxDigits,
50
50
  color,
51
51
  radiusOffset = 0,
52
+ endLabelsMaxMin = false,
52
53
  }: {
53
54
  size: TickmarkType;
54
55
  style: TickmarkStyle;
@@ -60,6 +61,7 @@ export function tickmark(
60
61
  maxDigits: number;
61
62
  color?: string;
62
63
  radiusOffset?: number;
64
+ endLabelsMaxMin?: boolean;
63
65
  }
64
66
  ): SVGTemplateResult | SVGTemplateResult[] {
65
67
  // check if scale is not infinite
@@ -87,7 +89,9 @@ export function tickmark(
87
89
  innerRadius = 328 / 2 + rOff;
88
90
  outerRadius = 336 / 2 + rOff;
89
91
  } else {
90
- return [textSvg(text ?? '', angle, inside, scale, textRadius)];
92
+ return [
93
+ textSvg(text ?? '', {angle, inside, scale, textRadius, endLabelsMaxMin}),
94
+ ];
91
95
  }
92
96
 
93
97
  // When inside, anchor ticks at the outer ring edge and grow inward,
@@ -115,7 +119,10 @@ export function tickmark(
115
119
  const tick = svg`<line x1=${x1} y1=${y1} x2=${x2} y2=${y2} stroke=${colorName} stroke-width=${strokeWidth} vector-effect="non-scaling-stroke"/>`;
116
120
  if (text) {
117
121
  if (rotation === undefined) {
118
- return [tick, textSvg(text, angle, inside, scale, textRadius)];
122
+ return [
123
+ tick,
124
+ textSvg(text, {angle, inside, scale, textRadius, endLabelsMaxMin}),
125
+ ];
119
126
  } else {
120
127
  const newRadius =
121
128
  textRadius + ((4 / scale + 5) * (inside ? -1 : 1) * maxDigits) / 2;
@@ -132,11 +139,31 @@ export function tickmark(
132
139
 
133
140
  function textSvg(
134
141
  text: string,
135
- angle: number,
136
- inside: boolean,
137
- scale: number,
138
- textRadius: number
142
+ {
143
+ angle,
144
+ inside,
145
+ scale,
146
+ textRadius,
147
+ endLabelsMaxMin = false,
148
+ }: {
149
+ angle: number;
150
+ inside: boolean;
151
+ scale: number;
152
+ textRadius: number;
153
+ endLabelsMaxMin?: boolean;
154
+ }
139
155
  ) {
156
+ const radHoriz = (angle * Math.PI) / 180;
157
+ // "Max-min" placement: horizontal end labels (±90°) sit off the dead-center
158
+ // tick (below outside / lifted inside), inset inward by label width.
159
+ if (endLabelsMaxMin && Math.abs(Math.cos(radHoriz)) < 1e-6) {
160
+ const sin = Math.sin(radHoriz);
161
+ const inward = inside ? (6 + (text.length - 1) * 2.5) / scale : 14 / scale;
162
+ const x = sin * (textRadius - inward);
163
+ const y = inside ? -(6 / scale) : 12 / scale;
164
+ return svg`<text x=${x} y=${y} class="label bottom ${inside ? 'inside' : ''}">${text}</text>`;
165
+ }
166
+
140
167
  let positionClass;
141
168
  if (angle === 0) {
142
169
  positionClass = 'top';
@@ -176,7 +176,7 @@ const RADIAL_SETPOINT_INWARD_ADJUST = 4;
176
176
  * @property {number|undefined} rateOfTurnDegreesPerMinute - Measured rate of turn in degrees per minute (the maritime/AIS convention, see ES-TRIN 2025/1 Art. 3.02 and ITU-R M.1371). Sign controls direction (positive = starboard/clockwise). When defined, this drives both the dot animation (multiplied by `rotDotAnimationFactor`) and the port/starboard direction sign.
177
177
  * @property {number} rotDotAnimationFactor - Visual amplification factor applied only to the spinning-dot animation (not to bar extent). Default `18` keeps the legacy visual feel (≈1 rpm at 20°/min).
178
178
  * @property {number} rotationsPerMinute - **Deprecated.** Spin speed of the ROT dot ring in rotations per minute. Sign controls direction (positive = clockwise). Use `rateOfTurnDegreesPerMinute` instead.
179
- * @property {ZoomToFitArcFrame|undefined} arcFrame - Pre-computed zoom-to-fit arc frame. When set, the watch skips its own `computeZoomToFitArcFrame()` call and uses these values directly. Consumer instruments (e.g. rudder, instrument-radial) should compute the frame once and pass it here to avoid redundant computation.
179
+ * @property {ZoomToFitArcFrame|undefined} arcFrame - Pre-computed zoom-to-fit arc frame. When set, the watch skips its own `computeZoomToFitArcFrame()` call and uses these values directly. Consumer instruments (e.g. rudder, instrument-radial) should compute the frame once and pass it here to avoid redundant computation. If you pass `arcFrame`, you own keeping it in sync with `areas` / `watchCircleType` — obc-watch will NOT recompute it, so a stale frame renders stale geometry.
180
180
  */
181
181
  @customElement('obc-watch')
182
182
  export class ObcWatch extends LitElement {
@@ -231,8 +231,21 @@ export class ObcWatch extends LitElement {
231
231
  @property({type: Number}) currentSymbolRadius: number | null = null;
232
232
  @property({type: String}) currentColor: string | undefined;
233
233
  @property({type: Boolean}) starboardPortIndicator: boolean = false;
234
- @property({type: Number}) clipTop: number = 0; // in percent of height
235
- @property({type: Number}) clipBottom: number = 0; // in percent of height
234
+ /** Top clip, % of height. Ignored when `zoomToFitArc` is true. */
235
+ @property({type: Number}) clipTop: number = 0;
236
+ /** Bottom clip, % of height. Ignored when `zoomToFitArc` is true. */
237
+ @property({type: Number}) clipBottom: number = 0;
238
+ /** Left clip, % of width — horizontal counterpart of clipTop/bottom (90° sectors). Ignored when `zoomToFitArc`. */
239
+ @property({type: Number}) clipLeft: number = 0;
240
+ /** Right clip, % of width — horizontal counterpart of clipTop/bottom (90° sectors). Ignored when `zoomToFitArc`. */
241
+ @property({type: Number}) clipRight: number = 0;
242
+ /**
243
+ * Place the horizontal end labels (±90°, e.g. min/max) below the tick instead
244
+ * of beside it. This is the "Max-min" label placement from the radial label
245
+ * model (External / Internal / Max-min) — see PR #903 / design discussion.
246
+ * Currently only used by the 180° sector of `obc-gauge-radial`.
247
+ */
248
+ @property({type: Boolean}) endLabelsMaxMin: boolean = false;
236
249
  @property({type: Number}) scaleWindIcon: number = 1;
237
250
  @property({type: Number}) rotation: number | undefined;
238
251
  @property({type: Boolean}) zoomToFitArc: boolean = false;
@@ -342,6 +355,18 @@ export class ObcWatch extends LitElement {
342
355
 
343
356
  private _rOff = 0;
344
357
 
358
+ /**
359
+ * Radius for a dial-band edge under zoom: additive (`base + _rOff`), keeping
360
+ * band thickness constant in SVG units. INVARIANT: every band-edge radius
361
+ * (rings, value arc, bars, needles, inside-label `textRadius`) must go through
362
+ * this — mixing additive and multiplicative offsets misaligns ticks, labels,
363
+ * advice masks and arcs. The proportional experiments were removed for this
364
+ * reason (PR #903).
365
+ */
366
+ private _bandRadius(base: number): number {
367
+ return base + this._rOff;
368
+ }
369
+
345
370
  private watchCircle(): SVGTemplateResult | SVGTemplateResult[] {
346
371
  const rings = [];
347
372
  if (this.state !== InstrumentState.off) {
@@ -349,18 +374,19 @@ export class ObcWatch extends LitElement {
349
374
  <circle
350
375
  cx="0"
351
376
  cy="0"
352
- r="${172 + this._rOff}"
377
+ r="${this._bandRadius(172)}"
353
378
  stroke="var(--instrument-frame-primary-color)"
354
379
  fill="none"
355
380
  stroke-width="24"
356
381
  />`);
357
382
 
358
383
  if (this.watchCircleType !== WatchCircleType.single) {
359
- const r1 = RING2_RADIUS + this._rOff;
360
- const r2 =
361
- (this.watchCircleType === WatchCircleType.doubleThin
384
+ const r1 = this._bandRadius(RING2_RADIUS);
385
+ const r2 = this._bandRadius(
386
+ this.watchCircleType === WatchCircleType.doubleThin
362
387
  ? RING3B_RADIUS
363
- : RING3_RADIUS) + this._rOff;
388
+ : RING3_RADIUS
389
+ );
364
390
  const r = (r1 + r2) / 2;
365
391
  const strokeWidth = r1 - r2;
366
392
  rings.push(
@@ -372,8 +398,8 @@ export class ObcWatch extends LitElement {
372
398
  );
373
399
  }
374
400
  if (this.watchCircleType === WatchCircleType.triple) {
375
- const r1 = RING3_RADIUS + this._rOff;
376
- const r2 = RING4_RADIUS + this._rOff;
401
+ const r1 = this._bandRadius(RING3_RADIUS);
402
+ const r2 = this._bandRadius(RING4_RADIUS);
377
403
  const r = (r1 + r2) / 2;
378
404
  const strokeWidth = r1 - r2;
379
405
  rings.push(
@@ -389,8 +415,8 @@ export class ObcWatch extends LitElement {
389
415
  const svgPath = roundedArch({
390
416
  startAngle: area.startAngle,
391
417
  endAngle: area.endAngle,
392
- R: OUTER_RING_RADIUS + this._rOff,
393
- r: this.innerRingRadius + this._rOff,
418
+ R: this._bandRadius(OUTER_RING_RADIUS),
419
+ r: this._bandRadius(this.innerRingRadius),
394
420
  roundOutsideCut: area.roundOutsideCut,
395
421
  roundInsideCut: area.roundInsideCut,
396
422
  });
@@ -422,7 +448,7 @@ export class ObcWatch extends LitElement {
422
448
  if (this.state !== InstrumentState.off) {
423
449
  result.push(
424
450
  circle('outerRing', {
425
- radius: OUTER_RING_RADIUS + this._rOff,
451
+ radius: this._bandRadius(OUTER_RING_RADIUS),
426
452
  strokeWidth: 1,
427
453
  strokeColor: 'var(--instrument-frame-tertiary-color)',
428
454
  strokePosition: 'center',
@@ -432,7 +458,7 @@ export class ObcWatch extends LitElement {
432
458
 
433
459
  result.push(svg`
434
460
  ${circle('innerRing', {
435
- radius: this.innerRingRadius + this._rOff,
461
+ radius: this._bandRadius(this.innerRingRadius),
436
462
  strokeWidth: 1,
437
463
  strokeColor: 'var(--instrument-frame-tertiary-color)',
438
464
  strokePosition: 'center',
@@ -442,7 +468,7 @@ export class ObcWatch extends LitElement {
442
468
  } else {
443
469
  result.push(svg`
444
470
  ${circle('innerRing', {
445
- radius: OUTER_RING_RADIUS + this._rOff,
471
+ radius: this._bandRadius(OUTER_RING_RADIUS),
446
472
  strokeWidth: 1,
447
473
  strokeColor: 'var(--instrument-frame-tertiary-color)',
448
474
  strokePosition: 'center',
@@ -476,7 +502,10 @@ export class ObcWatch extends LitElement {
476
502
  return `M 0 0 L ${x1} ${y1} A ${R} ${R} 0 ${largeArc} 1 ${x2} ${y2} Z`;
477
503
  };
478
504
 
479
- const Rm = (OUTER_RING_RADIUS + this.innerRingRadius) / 2 + this._rOff;
505
+ const Rm =
506
+ (this._bandRadius(OUTER_RING_RADIUS) +
507
+ this._bandRadius(this.innerRingRadius)) /
508
+ 2;
480
509
  const gx = (deg: number) => Rm * Math.sin(toRad(deg));
481
510
  const gy = (deg: number) => -Rm * Math.cos(toRad(deg));
482
511
 
@@ -594,8 +623,8 @@ export class ObcWatch extends LitElement {
594
623
  const startAngle = Math.min(bar.startAngle, bar.endAngle);
595
624
  const endAngle = Math.max(bar.startAngle, bar.endAngle);
596
625
  const arc = roundedArch({
597
- r: RING3_RADIUS + this._rOff,
598
- R: RING2_RADIUS + this._rOff,
626
+ r: this._bandRadius(RING3_RADIUS),
627
+ R: this._bandRadius(RING2_RADIUS),
599
628
  startAngle: startAngle,
600
629
  endAngle: endAngle,
601
630
  roundInsideCut: false,
@@ -638,7 +667,7 @@ export class ObcWatch extends LitElement {
638
667
  return svg`
639
668
  <rect
640
669
  transform="rotate(${needle.angle})"
641
- x="-4" y="${-(RING2_RADIUS + this._rOff)}" width="8" height="48" rx="4"
670
+ x="-4" y="${-this._bandRadius(RING2_RADIUS)}" width="8" height="48" rx="4"
642
671
  fill=${needle.fillColor}
643
672
  stroke=${needle.strokeColor}
644
673
  stroke-width="1"
@@ -705,17 +734,22 @@ export class ObcWatch extends LitElement {
705
734
  viewBox = frame.viewBox;
706
735
  } else {
707
736
  this._rOff = 0;
708
- width = (176 + this.getPadding()) * 2;
709
- height = width * (1 - this.clipTop / 100 - this.clipBottom / 100);
710
- const top = -width / 2 + (width * this.clipTop) / 100;
711
- viewBox = `-${width / 2} ${top} ${width} ${height}`;
737
+ const full = (176 + this.getPadding()) * 2;
738
+ width = full * (1 - this.clipLeft / 100 - this.clipRight / 100);
739
+ height = full * (1 - this.clipTop / 100 - this.clipBottom / 100);
740
+ const left = -full / 2 + (full * this.clipLeft) / 100;
741
+ const top = -full / 2 + (full * this.clipTop) / 100;
742
+ viewBox = `${left} ${top} ${width} ${height}`;
712
743
  }
713
744
 
714
745
  const rOff = this._rOff;
715
746
  const scale = this.getScale({width, height});
716
747
  const angleSetpoint = this.renderSetpoint();
717
- const textRadius =
718
- (this.tickmarksInside ? this.innerRingRadius : OUTER_RING_RADIUS) + rOff;
748
+ // Route through _bandRadius so inside labels track the (zoom-shifted) inner
749
+ // band edge; the coupling is intentional (see _bandRadius INVARIANT).
750
+ const textRadius = this.tickmarksInside
751
+ ? this._bandRadius(this.innerRingRadius)
752
+ : this._bandRadius(OUTER_RING_RADIUS);
719
753
  const maxDigits = Math.max(
720
754
  ...this.tickmarks.map((t) => t.text?.length ?? 0)
721
755
  );
@@ -731,6 +765,7 @@ export class ObcWatch extends LitElement {
731
765
  maxDigits,
732
766
  color: t.color,
733
767
  radiusOffset: rOff,
768
+ endLabelsMaxMin: this.endLabelsMaxMin,
734
769
  })
735
770
  );
736
771
  const advices = this.advices