@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.151 → 2.0.0-next.152

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 (136) hide show
  1. package/.storybook/vitest.setup.ts +12 -1
  2. package/bundle/openbridge-webcomponents.bundle.js +989 -319
  3. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  4. package/custom-elements.json +1514 -77
  5. package/dist/alert-severity.d.ts +12 -9
  6. package/dist/alert-severity.d.ts.map +1 -1
  7. package/dist/alert-severity.js +30 -38
  8. package/dist/alert-severity.js.map +1 -1
  9. package/dist/building-blocks/bar-horizontal/bar-horizontal.d.ts +6 -0
  10. package/dist/building-blocks/bar-horizontal/bar-horizontal.d.ts.map +1 -1
  11. package/dist/building-blocks/bar-horizontal/bar-horizontal.js +6 -1
  12. package/dist/building-blocks/bar-horizontal/bar-horizontal.js.map +1 -1
  13. package/dist/building-blocks/bar-vertical/bar-vertical.d.ts +6 -0
  14. package/dist/building-blocks/bar-vertical/bar-vertical.d.ts.map +1 -1
  15. package/dist/building-blocks/bar-vertical/bar-vertical.js +6 -1
  16. package/dist/building-blocks/bar-vertical/bar-vertical.js.map +1 -1
  17. package/dist/building-blocks/chart-line/chart-line-base.d.ts +129 -0
  18. package/dist/building-blocks/chart-line/chart-line-base.d.ts.map +1 -1
  19. package/dist/building-blocks/chart-line/chart-line-base.js +464 -79
  20. package/dist/building-blocks/chart-line/chart-line-base.js.map +1 -1
  21. package/dist/building-blocks/external-scale/external-scale.d.ts +11 -0
  22. package/dist/building-blocks/external-scale/external-scale.d.ts.map +1 -1
  23. package/dist/building-blocks/external-scale/external-scale.js +45 -12
  24. package/dist/building-blocks/external-scale/external-scale.js.map +1 -1
  25. package/dist/charthelpers/index.d.ts +1 -0
  26. package/dist/charthelpers/index.d.ts.map +1 -1
  27. package/dist/charthelpers/index.js +7 -1
  28. package/dist/charthelpers/index.js.map +1 -1
  29. package/dist/charthelpers/range-labels.d.ts +55 -0
  30. package/dist/charthelpers/range-labels.d.ts.map +1 -0
  31. package/dist/charthelpers/range-labels.js +36 -0
  32. package/dist/charthelpers/range-labels.js.map +1 -0
  33. package/dist/components/alert-button/alert-button.css.js +13 -21
  34. package/dist/components/alert-button/alert-button.css.js.map +1 -1
  35. package/dist/components/alert-button/alert-button.d.ts +9 -4
  36. package/dist/components/alert-button/alert-button.d.ts.map +1 -1
  37. package/dist/components/alert-button/alert-button.js +24 -15
  38. package/dist/components/alert-button/alert-button.js.map +1 -1
  39. package/dist/components/alert-frame/alert-frame.css.js +109 -33
  40. package/dist/components/alert-frame/alert-frame.css.js.map +1 -1
  41. package/dist/components/alert-frame/alert-frame.d.ts +34 -7
  42. package/dist/components/alert-frame/alert-frame.d.ts.map +1 -1
  43. package/dist/components/alert-frame/alert-frame.js +134 -30
  44. package/dist/components/alert-frame/alert-frame.js.map +1 -1
  45. package/dist/components/alert-icon/alert-icon.d.ts +10 -7
  46. package/dist/components/alert-icon/alert-icon.d.ts.map +1 -1
  47. package/dist/components/alert-icon/alert-icon.js +37 -60
  48. package/dist/components/alert-icon/alert-icon.js.map +1 -1
  49. package/dist/components/alert-menu-item/alert-menu-item.css.js +1 -4
  50. package/dist/components/alert-menu-item/alert-menu-item.css.js.map +1 -1
  51. package/dist/components/date-item/date-item.css.js +3 -3
  52. package/dist/components/keyboard-full/keyboard-full.css.js +4 -4
  53. package/dist/components/message-menu-item/message-menu-item.css.js +2 -6
  54. package/dist/components/message-menu-item/message-menu-item.css.js.map +1 -1
  55. package/dist/components/sequence-toolbar/sequence-toolbar.css.js +1 -2
  56. package/dist/components/sequence-toolbar/sequence-toolbar.css.js.map +1 -1
  57. package/dist/index.js +2 -1
  58. package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts +6 -0
  59. package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts.map +1 -1
  60. package/dist/navigation-instruments/gauge-trend/gauge-trend.js +8 -0
  61. package/dist/navigation-instruments/gauge-trend/gauge-trend.js.map +1 -1
  62. package/dist/navigation-instruments/instrument-field/instrument-field.css.js +2 -2
  63. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  64. package/dist/navigation-instruments/readout/readout.js +2 -1
  65. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  66. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  67. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +2 -1
  68. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  69. package/dist/openbridge.css +19 -4
  70. package/dist/palettes/blinking.d.ts +14 -4
  71. package/dist/palettes/blinking.d.ts.map +1 -1
  72. package/dist/palettes/blinking.js +41 -35
  73. package/dist/palettes/blinking.js.map +1 -1
  74. package/dist/palettes/flashing-controller.d.ts +21 -0
  75. package/dist/palettes/flashing-controller.d.ts.map +1 -0
  76. package/dist/palettes/flashing-controller.js +45 -0
  77. package/dist/palettes/flashing-controller.js.map +1 -0
  78. package/dist/svghelpers/index.d.ts +1 -0
  79. package/dist/svghelpers/index.d.ts.map +1 -1
  80. package/dist/svghelpers/index.js +2 -0
  81. package/dist/svghelpers/index.js.map +1 -1
  82. package/dist/svghelpers/rounded-rect.d.ts +15 -0
  83. package/dist/svghelpers/rounded-rect.d.ts.map +1 -0
  84. package/dist/svghelpers/rounded-rect.js +29 -0
  85. package/dist/svghelpers/rounded-rect.js.map +1 -0
  86. package/dist/types.d.ts +14 -0
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/types.js +9 -0
  89. package/dist/types.js.map +1 -1
  90. package/package.json +1 -1
  91. package/script/check-css-variables.ts +2 -15
  92. package/src/alert-severity.spec.ts +56 -0
  93. package/src/alert-severity.ts +45 -43
  94. package/src/bars-graphs/area-graph/area-graph.stories.ts +235 -1
  95. package/src/bars-graphs/line-graph/line-graph.stories.ts +24 -0
  96. package/src/building-blocks/bar-horizontal/bar-horizontal.ts +7 -0
  97. package/src/building-blocks/bar-vertical/bar-vertical.ts +7 -0
  98. package/src/building-blocks/chart-line/chart-line-base.spec.ts +767 -0
  99. package/src/building-blocks/chart-line/chart-line-base.stories.ts +124 -0
  100. package/src/building-blocks/chart-line/chart-line-base.ts +671 -97
  101. package/src/building-blocks/chart-line/chart-sizing-battleground.stories.ts +17 -0
  102. package/src/building-blocks/external-scale/external-scale.spec.ts +154 -0
  103. package/src/building-blocks/external-scale/external-scale.ts +78 -20
  104. package/src/charthelpers/index.ts +1 -0
  105. package/src/charthelpers/range-labels.spec.ts +46 -0
  106. package/src/charthelpers/range-labels.ts +93 -0
  107. package/src/components/alert-button/alert-button.css +10 -23
  108. package/src/components/alert-button/alert-button.spec.ts +56 -15
  109. package/src/components/alert-button/alert-button.stories.ts +5 -1
  110. package/src/components/alert-button/alert-button.ts +33 -24
  111. package/src/components/alert-frame/alert-flash-scenario.stories.ts +669 -0
  112. package/src/components/alert-frame/alert-frame.css +106 -33
  113. package/src/components/alert-frame/alert-frame.spec.ts +176 -17
  114. package/src/components/alert-frame/alert-frame.stories.ts +61 -0
  115. package/src/components/alert-frame/alert-frame.ts +179 -41
  116. package/src/components/alert-icon/alert-icon.spec.ts +82 -27
  117. package/src/components/alert-icon/alert-icon.stories.ts +7 -1
  118. package/src/components/alert-icon/alert-icon.ts +48 -74
  119. package/src/components/alert-menu-item/alert-menu-item.css +1 -4
  120. package/src/components/date-item/date-item.css +3 -3
  121. package/src/components/keyboard-full/keyboard-full.css +4 -4
  122. package/src/components/message-menu-item/message-menu-item.css +2 -6
  123. package/src/components/sequence-toolbar/sequence-toolbar.css +1 -2
  124. package/src/navigation-instruments/gauge-trend/gauge-trend.stories.ts +29 -0
  125. package/src/navigation-instruments/gauge-trend/gauge-trend.ts +11 -0
  126. package/src/navigation-instruments/instrument-field/instrument-field.css +2 -2
  127. package/src/navigation-instruments/readout/readout.ts +2 -1
  128. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +2 -1
  129. package/src/palettes/blinking.spec.ts +83 -0
  130. package/src/palettes/blinking.ts +47 -35
  131. package/src/palettes/flashing-controller.ts +62 -0
  132. package/src/palettes/manual.css +18 -4
  133. package/src/svghelpers/index.ts +1 -0
  134. package/src/svghelpers/rounded-rect.spec.ts +47 -0
  135. package/src/svghelpers/rounded-rect.ts +42 -0
  136. package/src/types.ts +20 -0
@@ -17,7 +17,9 @@
17
17
  .wrapper {
18
18
  --bg-color: var(--alert-alarm-color);
19
19
  --thickness: var(--global-size-spacing-border-weight-alertframe);
20
- --blink-on: var(--alarm-blink-on);
20
+ /* 0/1 from the flash-<tempo> classes below; steady when no class applies. */
21
+ --blink-on: 0;
22
+ --blink-off: 1;
21
23
  color: var(--on-alarm-active-color);
22
24
  position: relative;
23
25
  width: 100%;
@@ -27,10 +29,10 @@
27
29
  );
28
30
  outline: var(--thickness) solid var(--bg-color);
29
31
  outline-offset: 0;
32
+ /* +2 px outward while on, both thicknesses (Figma Flash=yes: 2 → 4 px). */
30
33
  --blink-width: 2px;
31
34
  &.thickness-small {
32
35
  --thickness: 2px;
33
- --blink-width: 1px;
34
36
  }
35
37
 
36
38
  &.wrap-content {
@@ -42,14 +44,64 @@
42
44
  width: 100%;
43
45
  }
44
46
 
47
+ &.flash-fast {
48
+ --blink-on: var(--flash-fast-on);
49
+ --blink-off: var(--flash-fast-off);
50
+ }
51
+
52
+ &.flash-slow {
53
+ --blink-on: var(--flash-slow-on);
54
+ --blink-off: var(--flash-slow-off);
55
+ }
56
+
57
+ &.flash-very-slow {
58
+ --blink-on: var(--flash-very-slow-on);
59
+ --blink-off: var(--flash-very-slow-off);
60
+ }
61
+
62
+ --flash-transition: 50ms linear;
63
+
64
+ &.flash-effect-outline-eased {
65
+ transition: outline-width var(--flash-transition);
66
+
67
+ .flap {
68
+ transition: box-shadow var(--flash-transition);
69
+ }
70
+
71
+ .mask {
72
+ transition:
73
+ top var(--flash-transition),
74
+ bottom var(--flash-transition),
75
+ left var(--flash-transition),
76
+ right var(--flash-transition);
77
+ }
78
+
79
+ .dash path {
80
+ transition: stroke-width var(--flash-transition);
81
+ }
82
+ }
83
+
45
84
  --blink-width-dynamic: calc(var(--blink-width) * var(--blink-on));
85
+ /* One knob for flap and masks, so they cannot drift apart from the outline. */
86
+ --flap-grow: 0px;
46
87
 
47
- &.unacked-active {
88
+ &.unacked-active,
89
+ &.unacked-rectified {
48
90
  outline-width: calc(var(--thickness) + var(--blink-width-dynamic));
49
91
  }
50
92
 
93
+ &.unacked-active {
94
+ --flap-grow: var(--blink-width-dynamic);
95
+ }
96
+
51
97
  &.unacked-rectified {
52
- outline-style: dashed;
98
+ /* The dashed stroke is centred, so it moves out by half its growth. */
99
+ --flap-grow: calc(var(--blink-width-dynamic) / 2);
100
+ }
101
+
102
+ &.unacked-rectified {
103
+ /* The dash pattern (12/6) needs an SVG stroke; outlines have no dash array. */
104
+ outline-style: none;
53
105
  }
54
106
 
55
107
  &.alarm,
@@ -61,24 +113,17 @@
61
113
  &.warning,
62
114
  &.level-medium {
63
115
  --bg-color: var(--alert-warning-color);
64
- --blink-on: var(--warning-blink-on);
65
116
  color: var(--on-warning-active-color);
66
117
  }
67
118
 
68
119
  &.caution,
69
120
  &.level-low {
70
121
  --bg-color: var(--alert-caution-color);
71
- --blink-on: 1;
72
122
  color: var(--on-caution-active-color);
73
123
  }
74
124
 
75
- &.level-low {
76
- --blink-on: var(--low-blink-on);
77
- }
78
-
79
125
  &.level-critical {
80
126
  --bg-color: var(--critical-enabled-background-color);
81
- --blink-on: var(--critical-blink-on);
82
127
  color: var(--on-critical-active-color);
83
128
  }
84
129
 
@@ -132,7 +177,7 @@
132
177
  width: calc(
133
178
  var(--ui-components-button-border-radius) + var(--thickness) + 0.5px
134
179
  );
135
- top: 0;
180
+ top: calc(-1 * var(--flap-grow));
136
181
  left: calc(
137
182
  -1 * var(--ui-components-button-border-radius) - var(--thickness)
138
183
  );
@@ -143,17 +188,13 @@
143
188
  calc(100% - var(--thickness)) 50%,
144
189
  50% var(--thickness)
145
190
  );
146
-
147
- .unacked-active & {
148
- top: calc(-1 * var(--blink-width-dynamic));
149
- }
150
191
  }
151
192
 
152
193
  &.down {
153
194
  width: calc(
154
195
  var(--ui-components-button-border-radius) + var(--thickness) + 0.5px
155
196
  );
156
- bottom: 0;
197
+ bottom: calc(-1 * var(--flap-grow));
157
198
  left: calc(
158
199
  -1 * var(--ui-components-button-border-radius) - var(--thickness)
159
200
  );
@@ -164,10 +205,6 @@
164
205
  50% calc(100% - var(--thickness)),
165
206
  calc(100% - var(--thickness)) 50%
166
207
  );
167
-
168
- .unacked-active & {
169
- bottom: calc(-1 * var(--blink-width-dynamic));
170
- }
171
208
  }
172
209
 
173
210
  &.left {
@@ -177,7 +214,7 @@
177
214
  top: calc(
178
215
  -1 * var(--ui-components-button-border-radius) - var(--thickness)
179
216
  );
180
- left: 0;
217
+ left: calc(-1 * var(--flap-grow));
181
218
  clip-path: polygon(
182
219
  0 0,
183
220
  0 100%,
@@ -186,10 +223,6 @@
186
223
  var(--thickness) 50%
187
224
  );
188
225
 
189
- .unacked-active & {
190
- left: calc(-1 * var(--blink-width-dynamic));
191
- }
192
-
193
226
  .top & {
194
227
  top: unset;
195
228
  bottom: calc(
@@ -212,7 +245,7 @@
212
245
  top: calc(
213
246
  -1 * var(--ui-components-button-border-radius) - var(--thickness)
214
247
  );
215
- right: 0;
248
+ right: calc(-1 * var(--flap-grow));
216
249
  clip-path: polygon(
217
250
  100% 0,
218
251
  100% 100%,
@@ -234,10 +267,6 @@
234
267
  calc(100% - var(--thickness)) 50%
235
268
  );
236
269
  }
237
-
238
- .unacked-active & {
239
- right: calc(-1 * var(--blink-width-dynamic));
240
- }
241
270
  }
242
271
  }
243
272
 
@@ -315,8 +344,34 @@
315
344
  }
316
345
  }
317
346
 
318
- .unacked-active .flap {
319
- outline: var(--bg-color) solid var(--blink-width-dynamic);
347
+ /* Grows the flap outward with the outline, never toward the content: shifted
348
+ copies leave the inner edge where it is (an outline would cross it). */
349
+ .flap.small,
350
+ .flap.large {
351
+ box-shadow:
352
+ var(--flap-grow) 0 0 0 var(--bg-color),
353
+ 0 calc(-1 * var(--flap-grow)) 0 0 var(--bg-color),
354
+ 0 var(--flap-grow) 0 0 var(--bg-color),
355
+ var(--flap-grow) calc(-1 * var(--flap-grow)) 0 0 var(--bg-color),
356
+ var(--flap-grow) var(--flap-grow) 0 0 var(--bg-color);
357
+ }
358
+
359
+ .flap.bottom {
360
+ box-shadow:
361
+ 0 var(--flap-grow) 0 0 var(--bg-color),
362
+ calc(-1 * var(--flap-grow)) 0 0 0 var(--bg-color),
363
+ var(--flap-grow) 0 0 0 var(--bg-color),
364
+ calc(-1 * var(--flap-grow)) var(--flap-grow) 0 0 var(--bg-color),
365
+ var(--flap-grow) var(--flap-grow) 0 0 var(--bg-color);
366
+ }
367
+
368
+ .flap.top {
369
+ box-shadow:
370
+ 0 calc(-1 * var(--flap-grow)) 0 0 var(--bg-color),
371
+ calc(-1 * var(--flap-grow)) 0 0 0 var(--bg-color),
372
+ var(--flap-grow) 0 0 0 var(--bg-color),
373
+ calc(-1 * var(--flap-grow)) calc(-1 * var(--flap-grow)) 0 0 var(--bg-color),
374
+ var(--flap-grow) calc(-1 * var(--flap-grow)) 0 0 var(--bg-color);
320
375
  }
321
376
 
322
377
  ::slotted([slot="label"]) {
@@ -331,3 +386,21 @@
331
386
  display: block;
332
387
  flex-shrink: 0;
333
388
  }
389
+
390
+ .dash {
391
+ /* Overridden inline per render from the measured thickness. */
392
+ --dash-pad: 0px;
393
+ position: absolute;
394
+ top: calc(-1 * var(--dash-pad));
395
+ left: calc(-1 * var(--dash-pad));
396
+ overflow: visible;
397
+ pointer-events: none;
398
+ fill: none;
399
+ stroke: var(--bg-color);
400
+ stroke-dasharray: 12 6;
401
+ }
402
+
403
+ .dash path {
404
+ /* Animated on the one path: a wider second path would drift its dashes. */
405
+ stroke-width: calc(var(--thickness) + var(--blink-width-dynamic));
406
+ }
@@ -1,45 +1,114 @@
1
1
  import {describe, expect, it} from 'vitest';
2
2
  import './alert-frame.js';
3
- import {ObcAlertFrame, ObcAlertFrameMode} from './alert-frame.js';
3
+ import {
4
+ ObcAlertFrame,
5
+ ObcAlertFrameFlashEffect,
6
+ ObcAlertFrameMode,
7
+ } from './alert-frame.js';
4
8
  import {render} from 'vitest-browser-lit';
5
9
  import {html} from 'lit';
10
+ import {AlertType, FlashingSpeed} from '../../types.js';
6
11
 
7
- describe('obc-alert-frame blinking lifecycle', () => {
8
- async function setup(mode: ObcAlertFrameMode) {
12
+ function durations(el: HTMLElement): number[] {
13
+ return el
14
+ .getAnimations()
15
+ .map((a) => (a.effect as KeyframeEffect).getTiming().duration as number);
16
+ }
17
+
18
+ describe('obc-alert-frame flashing lifecycle', () => {
19
+ async function setup(
20
+ mode: ObcAlertFrameMode,
21
+ status: AlertType = AlertType.Alarm,
22
+ flashingSpeed: FlashingSpeed = FlashingSpeed.Default
23
+ ) {
9
24
  const screen = render(
10
- html`<obc-alert-frame .mode=${mode}></obc-alert-frame>`
25
+ html`<obc-alert-frame
26
+ .mode=${mode}
27
+ .status=${status}
28
+ .flashingSpeed=${flashingSpeed}
29
+ ></obc-alert-frame>`
11
30
  );
12
- const el = screen.baseElement.querySelector(
31
+ const el = screen.container.querySelector(
13
32
  'obc-alert-frame'
14
33
  ) as ObcAlertFrame;
15
34
  await el.updateComplete;
16
35
  return el;
17
36
  }
18
37
 
19
- it('blinks while mode is unacked-active', async () => {
38
+ it('flashes fast while an alarm is unacked-active', async () => {
20
39
  const el = await setup(ObcAlertFrameMode.unackedActive);
21
40
 
22
- expect(el.getAnimations().length).toBeGreaterThan(0);
41
+ expect(durations(el)).toEqual([800]);
42
+ expect(el.resolvedFlashingSpeed).toBe(FlashingSpeed.Fast);
43
+ });
44
+
45
+ it('flashes slow for a warning and very slow for level-low', async () => {
46
+ expect(
47
+ durations(await setup(ObcAlertFrameMode.unackedActive, AlertType.Warning))
48
+ ).toEqual([1600]);
49
+ expect(
50
+ durations(
51
+ await setup(ObcAlertFrameMode.unackedActive, AlertType.LevelLow)
52
+ )
53
+ ).toEqual([3200]);
54
+ });
55
+
56
+ it('never flashes a caution', async () => {
57
+ expect(
58
+ durations(await setup(ObcAlertFrameMode.unackedActive, AlertType.Caution))
59
+ ).toEqual([]);
60
+ expect(
61
+ durations(
62
+ await setup(ObcAlertFrameMode.unackedRectified, AlertType.Caution)
63
+ )
64
+ ).toEqual([]);
65
+ });
66
+
67
+ it('flashes very slow while rectified', async () => {
68
+ const el = await setup(ObcAlertFrameMode.unackedRectified);
69
+
70
+ expect(durations(el)).toEqual([3200]);
71
+ });
72
+
73
+ it('honours an explicit flashingSpeed', async () => {
74
+ expect(
75
+ durations(
76
+ await setup(
77
+ ObcAlertFrameMode.unackedActive,
78
+ AlertType.Alarm,
79
+ FlashingSpeed.Slow
80
+ )
81
+ )
82
+ ).toEqual([1600]);
83
+ expect(
84
+ durations(
85
+ await setup(
86
+ ObcAlertFrameMode.unackedActive,
87
+ AlertType.Alarm,
88
+ FlashingSpeed.Fixed
89
+ )
90
+ )
91
+ ).toEqual([]);
23
92
  });
24
93
 
25
- it('does not blink in other modes', async () => {
94
+ it('does not flash in acked-active', async () => {
26
95
  const el = await setup(ObcAlertFrameMode.ackedActive);
27
96
 
28
97
  expect(el.getAnimations()).toHaveLength(0);
29
98
  });
30
99
 
31
100
  describe('mode transitions', () => {
32
- it('starts blinking when mode becomes unacked-active', async () => {
101
+ it('starts flashing when mode becomes unacked-active', async () => {
33
102
  const el = await setup(ObcAlertFrameMode.ackedActive);
34
103
  expect(el.getAnimations()).toHaveLength(0);
35
104
 
36
105
  el.mode = ObcAlertFrameMode.unackedActive;
37
106
  await el.updateComplete;
38
107
 
39
- expect(el.getAnimations().length).toBeGreaterThan(0);
108
+ expect(durations(el)).toEqual([800]);
40
109
  });
41
110
 
42
- it('stops blinking when mode leaves unacked-active', async () => {
111
+ it('stops flashing when mode leaves unacked-active', async () => {
43
112
  const el = await setup(ObcAlertFrameMode.unackedActive);
44
113
  expect(el.getAnimations().length).toBeGreaterThan(0);
45
114
 
@@ -49,13 +118,13 @@ describe('obc-alert-frame blinking lifecycle', () => {
49
118
  expect(el.getAnimations()).toHaveLength(0);
50
119
  });
51
120
 
52
- it('stops blinking when mode becomes unacked-rectified', async () => {
121
+ it('switches to the very-slow tempo when mode becomes unacked-rectified', async () => {
53
122
  const el = await setup(ObcAlertFrameMode.unackedActive);
54
123
 
55
124
  el.mode = ObcAlertFrameMode.unackedRectified;
56
125
  await el.updateComplete;
57
126
 
58
- expect(el.getAnimations()).toHaveLength(0);
127
+ expect(durations(el)).toEqual([3200]);
59
128
  });
60
129
 
61
130
  it('does not accumulate animations across repeated updates', async () => {
@@ -72,7 +141,7 @@ describe('obc-alert-frame blinking lifecycle', () => {
72
141
  });
73
142
 
74
143
  describe('reconnection', () => {
75
- it('resumes blinking after disconnect and reconnect', async () => {
144
+ it('resumes flashing after disconnect and reconnect', async () => {
76
145
  const el = await setup(ObcAlertFrameMode.unackedActive);
77
146
  const parent = el.parentElement!;
78
147
  expect(el.getAnimations().length).toBeGreaterThan(0);
@@ -81,14 +150,14 @@ describe('obc-alert-frame blinking lifecycle', () => {
81
150
  expect(el.getAnimations()).toHaveLength(0);
82
151
 
83
152
  // Reconnect without touching any property. firstUpdated() will not run
84
- // again, so this only passes if blinking is reinstalled on update.
153
+ // again, so this only passes if the controller re-syncs on connect.
85
154
  parent.appendChild(el);
86
155
  await el.updateComplete;
87
156
 
88
157
  expect(el.getAnimations().length).toBeGreaterThan(0);
89
158
  });
90
159
 
91
- it('does not resume blinking on reconnect when mode is not unacked-active', async () => {
160
+ it('does not resume flashing on reconnect when mode is not unacked-active', async () => {
92
161
  const el = await setup(ObcAlertFrameMode.ackedActive);
93
162
  const parent = el.parentElement!;
94
163
 
@@ -99,7 +168,7 @@ describe('obc-alert-frame blinking lifecycle', () => {
99
168
  expect(el.getAnimations()).toHaveLength(0);
100
169
  });
101
170
 
102
- it('does not blink after disconnection when mode became unacked-active while detached', async () => {
171
+ it('does not flash after disconnection when mode became unacked-active while detached', async () => {
103
172
  const el = await setup(ObcAlertFrameMode.ackedActive);
104
173
  const parent = el.parentElement!;
105
174
 
@@ -119,4 +188,94 @@ describe('obc-alert-frame blinking lifecycle', () => {
119
188
  expect(el.getAnimations().length).toBeGreaterThan(0);
120
189
  });
121
190
  });
191
+
192
+ it('eases the outline width between phases in the outline-eased effect', async () => {
193
+ const el = await setup(ObcAlertFrameMode.unackedActive);
194
+ el.flashEffect = ObcAlertFrameFlashEffect.OutlineEased;
195
+ await el.updateComplete;
196
+ const wrapper = el.shadowRoot!.querySelector('.wrapper') as HTMLElement;
197
+ const [flash] = el.getAnimations();
198
+ flash.pause();
199
+ flash.currentTime = 100;
200
+ await new Promise((r) => requestAnimationFrame(r));
201
+ await Promise.all(wrapper.getAnimations().map((a) => a.finished));
202
+ expect(getComputedStyle(wrapper).outlineWidth).toBe('4px');
203
+
204
+ flash.currentTime = 500;
205
+ // A style read starts the transition synchronously; waiting a frame lets
206
+ // a slow runner finish the 50 ms and drop it from getAnimations().
207
+ void getComputedStyle(wrapper).outlineWidth;
208
+ const transition = wrapper
209
+ .getAnimations()
210
+ .find(
211
+ (a) => (a as CSSTransition).transitionProperty === 'outline-width'
212
+ ) as CSSTransition | undefined;
213
+ expect(transition).toBeDefined();
214
+ expect((transition!.effect as KeyframeEffect).getTiming().duration).toBe(
215
+ 50
216
+ );
217
+ transition!.pause();
218
+ transition!.currentTime = 25;
219
+ const midway = parseFloat(getComputedStyle(wrapper).outlineWidth);
220
+ expect(midway).toBeGreaterThan(2);
221
+ expect(midway).toBeLessThan(4);
222
+ });
223
+
224
+ describe('rectified dash', () => {
225
+ it('draws the 12/6 dash as one svg path whose stroke width flashes', async () => {
226
+ const el = await setup(ObcAlertFrameMode.unackedRectified);
227
+ await new Promise((r) => requestAnimationFrame(r));
228
+ await el.updateComplete;
229
+
230
+ const root = el.shadowRoot!;
231
+ const wrapper = root.querySelector('.wrapper') as HTMLElement;
232
+ expect(getComputedStyle(wrapper).outlineStyle).toBe('none');
233
+ const paths = root.querySelectorAll('svg.dash path');
234
+ expect(paths).toHaveLength(1);
235
+ const path = paths[0];
236
+ expect(getComputedStyle(path).strokeDasharray).toBe('12px, 6px');
237
+ const d = path.getAttribute('d');
238
+ expect(d).toMatch(/^M[\d.-]+ [\d.-]+ H/);
239
+
240
+ // The geometry must not change between phases, or the dashes drift.
241
+ const [anim] = el.getAnimations();
242
+ anim.pause();
243
+ anim.currentTime = 100;
244
+ expect(getComputedStyle(path).strokeWidth).toBe('4px');
245
+ anim.currentTime = 3000;
246
+ expect(getComputedStyle(path).strokeWidth).toBe('2px');
247
+ expect(path.getAttribute('d')).toBe(d);
248
+ });
249
+
250
+ it('re-measures when a sharp edge or the thickness changes', async () => {
251
+ const el = await setup(ObcAlertFrameMode.unackedRectified);
252
+ // The palette is not loaded here; give the corners a radius to lose.
253
+ el.style.setProperty('--ui-components-button-border-radius', '6px');
254
+ await new Promise((r) => requestAnimationFrame(r));
255
+ await el.updateComplete;
256
+ const path = () => el.shadowRoot!.querySelector('svg.dash path')!;
257
+ const rounded = path().getAttribute('d')!;
258
+ expect(rounded).toContain('A');
259
+
260
+ el.sharpEdgeTopLeft = true;
261
+ await el.updateComplete;
262
+ await el.updateComplete;
263
+
264
+ const sharp = path().getAttribute('d')!;
265
+ expect(sharp).not.toBe(rounded);
266
+ expect(sharp.startsWith('M3 3 H')).toBe(true);
267
+ });
268
+
269
+ it('removes the svg when the mode leaves unacked-rectified', async () => {
270
+ const el = await setup(ObcAlertFrameMode.unackedRectified);
271
+ await new Promise((r) => requestAnimationFrame(r));
272
+ await el.updateComplete;
273
+ expect(el.shadowRoot!.querySelector('svg.dash')).not.toBeNull();
274
+
275
+ el.mode = ObcAlertFrameMode.ackedActive;
276
+ await el.updateComplete;
277
+
278
+ expect(el.shadowRoot!.querySelector('svg.dash')).toBeNull();
279
+ });
280
+ });
122
281
  });
@@ -6,7 +6,9 @@ import {
6
6
  ObcAlertFrameStatus,
7
7
  AlertFrameTextSize,
8
8
  ObcAlertFrameMode,
9
+ ObcAlertFrameFlashEffect,
9
10
  } from './alert-frame.js';
11
+ import {AlertType, FlashingSpeed} from '../../types.js';
10
12
  import './alert-frame.js';
11
13
  import '../../icons/icon-placeholder.js';
12
14
  import {html} from 'lit';
@@ -20,6 +22,8 @@ const meta: Meta<typeof ObcAlertFrame> = {
20
22
  thickness: ObcAlertFrameThickness.Small,
21
23
  status: ObcAlertFrameStatus.Alarm,
22
24
  mode: ObcAlertFrameMode.ackedActive,
25
+ flashingSpeed: FlashingSpeed.Default,
26
+ flashEffect: ObcAlertFrameFlashEffect.Outline,
23
27
  demoWidth: 200,
24
28
  showIcon: true,
25
29
  showAlertCategoryIcon: true,
@@ -37,6 +41,16 @@ const meta: Meta<typeof ObcAlertFrame> = {
37
41
  type: 'select',
38
42
  },
39
43
  },
44
+ flashingSpeed: {
45
+ options: Object.values(FlashingSpeed),
46
+ control: {type: 'select'},
47
+ if: {arg: 'mode', neq: ObcAlertFrameMode.ackedActive},
48
+ },
49
+ flashEffect: {
50
+ options: Object.values(ObcAlertFrameFlashEffect),
51
+ control: {type: 'select'},
52
+ if: {arg: 'mode', neq: ObcAlertFrameMode.ackedActive},
53
+ },
40
54
  thickness: {
41
55
  options: Object.values(ObcAlertFrameThickness),
42
56
  control: {
@@ -64,6 +78,8 @@ const meta: Meta<typeof ObcAlertFrame> = {
64
78
  .type=${args.type}
65
79
  .thickness=${args.thickness}
66
80
  .mode=${args.mode}
81
+ .flashingSpeed=${args.flashingSpeed}
82
+ .flashEffect=${args.flashEffect}
67
83
  .status=${args.status}
68
84
  .textSize=${args.textSize}
69
85
  .showIcon=${args.showIcon}
@@ -273,3 +289,48 @@ export const CriticalUnacked: Story = {
273
289
  showAlertCategoryIcon: true,
274
290
  },
275
291
  };
292
+
293
+ export const FlashComparison: Story = {
294
+ tags: ['skip-test'],
295
+ render: () => {
296
+ const statuses = [
297
+ AlertType.LevelCritical,
298
+ AlertType.Alarm,
299
+ AlertType.Warning,
300
+ AlertType.Caution,
301
+ AlertType.LevelLow,
302
+ ];
303
+ const modes = [
304
+ ObcAlertFrameMode.unackedActive,
305
+ ObcAlertFrameMode.unackedRectified,
306
+ ];
307
+ return html`<style>
308
+ .flash-grid {
309
+ display: grid;
310
+ grid-template-columns: repeat(2, 200px);
311
+ gap: 24px;
312
+ padding: 16px;
313
+ }
314
+ </style>
315
+ ${Object.values(ObcAlertFrameFlashEffect).map(
316
+ (effect) =>
317
+ html`<h4>${effect}</h4>
318
+ <div class="flash-grid">
319
+ ${statuses.flatMap((status) =>
320
+ modes.map(
321
+ (mode) =>
322
+ html`<obc-alert-frame
323
+ .type=${ObcAlertFrameType.Regular}
324
+ .status=${status}
325
+ .mode=${mode}
326
+ .flashEffect=${effect}
327
+ wrapContent
328
+ >
329
+ <div style="width: 200px; height: 48px"></div>
330
+ </obc-alert-frame>`
331
+ )
332
+ )}
333
+ </div>`
334
+ )}`;
335
+ },
336
+ };