@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.119 → 2.0.0-next.120

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 (34) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +96 -0
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +44 -0
  4. package/dist/components/alert-button/alert-button.d.ts +3 -0
  5. package/dist/components/alert-button/alert-button.d.ts.map +1 -1
  6. package/dist/components/alert-button/alert-button.js +12 -0
  7. package/dist/components/alert-button/alert-button.js.map +1 -1
  8. package/dist/components/alert-frame/alert-frame.css.js +1 -0
  9. package/dist/components/alert-frame/alert-frame.css.js.map +1 -1
  10. package/dist/components/alert-frame/alert-frame.d.ts +5 -0
  11. package/dist/components/alert-frame/alert-frame.d.ts.map +1 -1
  12. package/dist/components/alert-frame/alert-frame.js +24 -0
  13. package/dist/components/alert-frame/alert-frame.js.map +1 -1
  14. package/dist/components/alert-icon/alert-icon.d.ts +5 -0
  15. package/dist/components/alert-icon/alert-icon.d.ts.map +1 -1
  16. package/dist/components/alert-icon/alert-icon.js +26 -0
  17. package/dist/components/alert-icon/alert-icon.js.map +1 -1
  18. package/dist/openbridge.css +0 -73
  19. package/dist/palettes/blinking.d.ts +6 -0
  20. package/dist/palettes/blinking.d.ts.map +1 -0
  21. package/dist/palettes/blinking.js +44 -0
  22. package/dist/palettes/blinking.js.map +1 -0
  23. package/package.json +1 -1
  24. package/src/components/alert-button/alert-button.spec.ts +58 -0
  25. package/src/components/alert-button/alert-button.ts +16 -0
  26. package/src/components/alert-frame/alert-frame.css +1 -0
  27. package/src/components/alert-frame/alert-frame.spec.ts +122 -0
  28. package/src/components/alert-frame/alert-frame.stories.ts +12 -0
  29. package/src/components/alert-frame/alert-frame.ts +37 -0
  30. package/src/components/alert-icon/alert-icon.spec.ts +76 -0
  31. package/src/components/alert-icon/alert-icon.ts +38 -0
  32. package/src/palettes/blinking.ts +46 -0
  33. package/src/palettes/manual.css +0 -42
  34. package/src/palettes/variables.css +0 -36
@@ -14739,36 +14739,6 @@
14739
14739
  inherits: true;
14740
14740
  initial-value: 0;
14741
14741
  }
14742
- @keyframes warning-blink {
14743
- 0% {
14744
- --warning-blink-on: 1;
14745
- --warning-blink-off: 0;
14746
- --alarm-blink-on: 1;
14747
- --alarm-blink-off: 0;
14748
- }
14749
-
14750
- 25% {
14751
- --alarm-blink-on: 0;
14752
- --alarm-blink-off: 1;
14753
- }
14754
-
14755
- 50% {
14756
- --warning-blink-on: 0;
14757
- --warning-blink-off: 1;
14758
- --alarm-blink-on: 1;
14759
- --alarm-blink-off: 0;
14760
- }
14761
-
14762
- 75% {
14763
- --alarm-blink-on: 0;
14764
- --alarm-blink-off: 1;
14765
- }
14766
-
14767
- 100% {
14768
- --warning-blink-on: 1;
14769
- --warning-blink-off: 0;
14770
- }
14771
- }
14772
14742
  @property --warning-blink-on {
14773
14743
  syntax: "<number>";
14774
14744
  inherits: true;
@@ -14779,10 +14749,6 @@
14779
14749
  inherits: true;
14780
14750
  initial-value: 0;
14781
14751
  }
14782
- :root {
14783
- animation: warning-blink 4s infinite;
14784
- animation-timing-function: steps(1);
14785
- }
14786
14752
  :root[data-obc-theme="day"] {
14787
14753
  --critical-enabled-background-color: rgb(196, 36, 183);
14788
14754
  --critical-enabled-border-color: rgb(153, 0, 143);
@@ -14899,45 +14865,6 @@
14899
14865
  inherits: true;
14900
14866
  initial-value: 0;
14901
14867
  }
14902
- @keyframes critical-blink {
14903
- 0% {
14904
- --critical-blink-on: 1;
14905
- --critical-blink-off: 0;
14906
- }
14907
-
14908
- 50% {
14909
- --critical-blink-on: 0;
14910
- --critical-blink-off: 1;
14911
- }
14912
-
14913
- 100% {
14914
- --critical-blink-on: 1;
14915
- --critical-blink-off: 0;
14916
- }
14917
- }
14918
- @keyframes low-blink {
14919
- 0% {
14920
- --low-blink-on: 1;
14921
- --low-blink-off: 0;
14922
- }
14923
-
14924
- 50% {
14925
- --low-blink-on: 0;
14926
- --low-blink-off: 1;
14927
- }
14928
-
14929
- 100% {
14930
- --low-blink-on: 1;
14931
- --low-blink-off: 0;
14932
- }
14933
- }
14934
- :root {
14935
- animation:
14936
- warning-blink 4s infinite,
14937
- critical-blink 1s infinite,
14938
- low-blink 8s infinite;
14939
- animation-timing-function: steps(1);
14940
- }
14941
14868
  .obc-radio-button {
14942
14869
  user-select: none;
14943
14870
 
@@ -0,0 +1,6 @@
1
+ export declare function blinkingCritical(el: HTMLElement): () => void;
2
+ export declare function blinkingAlarm(el: HTMLElement): () => void;
3
+ export declare function blinkingWarning(el: HTMLElement): () => void;
4
+ export declare function blinkingLow(el: HTMLElement): () => void;
5
+ export declare function blinkingAll(el: HTMLElement): () => void;
6
+ //# sourceMappingURL=blinking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blinking.d.ts","sourceRoot":"","sources":["../../src/palettes/blinking.ts"],"names":[],"mappings":"AAqBA,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,IAAI,CAE5D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,IAAI,CAEzD;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,IAAI,CAE3D;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,IAAI,CAEvD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,IAAI,CAQvD"}
@@ -0,0 +1,44 @@
1
+ const CRITICAL_PERIOD = 1e3;
2
+ const ALARM_PERIOD = 2e3;
3
+ const WARNING_PERIOD = 4e3;
4
+ const LOW_PERIOD = 8e3;
5
+ function blinkingInstall(el, period, label) {
6
+ const anim = el.animate(
7
+ [
8
+ { [label + "-on"]: 1, [label + "-off"]: 0 },
9
+ { [label + "-on"]: 0, [label + "-off"]: 1 }
10
+ ],
11
+ { duration: period, iterations: Infinity, easing: "steps(2, jump-none)" }
12
+ );
13
+ anim.startTime = 0;
14
+ return () => anim.cancel();
15
+ }
16
+ function blinkingCritical(el) {
17
+ return blinkingInstall(el, CRITICAL_PERIOD, "--critical-blink");
18
+ }
19
+ function blinkingAlarm(el) {
20
+ return blinkingInstall(el, ALARM_PERIOD, "--alarm-blink");
21
+ }
22
+ function blinkingWarning(el) {
23
+ return blinkingInstall(el, WARNING_PERIOD, "--warning-blink");
24
+ }
25
+ function blinkingLow(el) {
26
+ return blinkingInstall(el, LOW_PERIOD, "--low-blink");
27
+ }
28
+ function blinkingAll(el) {
29
+ const animes = [
30
+ blinkingCritical(el),
31
+ blinkingAlarm(el),
32
+ blinkingWarning(el),
33
+ blinkingLow(el)
34
+ ];
35
+ return () => animes.forEach((anim) => anim());
36
+ }
37
+ export {
38
+ blinkingAlarm,
39
+ blinkingAll,
40
+ blinkingCritical,
41
+ blinkingLow,
42
+ blinkingWarning
43
+ };
44
+ //# sourceMappingURL=blinking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blinking.js","sources":["../../src/palettes/blinking.ts"],"sourcesContent":["const CRITICAL_PERIOD = 1000; // ms\nconst ALARM_PERIOD = 2000; // ms\nconst WARNING_PERIOD = 4000; // ms\nconst LOW_PERIOD = 8000; // ms\n\nfunction blinkingInstall(\n el: HTMLElement,\n period: number,\n label: string\n): () => void {\n const anim = el.animate(\n [\n {[label + '-on']: 1, [label + '-off']: 0},\n {[label + '-on']: 0, [label + '-off']: 1},\n ],\n {duration: period, iterations: Infinity, easing: 'steps(2, jump-none)'}\n );\n anim.startTime = 0; // Common time origin for all animations, so they blink in sync.\n return () => anim.cancel();\n}\n\nexport function blinkingCritical(el: HTMLElement): () => void {\n return blinkingInstall(el, CRITICAL_PERIOD, '--critical-blink');\n}\n\nexport function blinkingAlarm(el: HTMLElement): () => void {\n return blinkingInstall(el, ALARM_PERIOD, '--alarm-blink');\n}\n\nexport function blinkingWarning(el: HTMLElement): () => void {\n return blinkingInstall(el, WARNING_PERIOD, '--warning-blink');\n}\n\nexport function blinkingLow(el: HTMLElement): () => void {\n return blinkingInstall(el, LOW_PERIOD, '--low-blink');\n}\n\nexport function blinkingAll(el: HTMLElement): () => void {\n const animes = [\n blinkingCritical(el),\n blinkingAlarm(el),\n blinkingWarning(el),\n blinkingLow(el),\n ];\n return () => animes.forEach((anim) => anim());\n}\n"],"names":[],"mappings":"AAAA,MAAM,kBAAkB;AACxB,MAAM,eAAe;AACrB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AAEnB,SAAS,gBACP,IACA,QACA,OACY;AACZ,QAAM,OAAO,GAAG;AAAA,IACd;AAAA,MACE,EAAC,CAAC,QAAQ,KAAK,GAAG,GAAG,CAAC,QAAQ,MAAM,GAAG,EAAA;AAAA,MACvC,EAAC,CAAC,QAAQ,KAAK,GAAG,GAAG,CAAC,QAAQ,MAAM,GAAG,EAAA;AAAA,IAAC;AAAA,IAE1C,EAAC,UAAU,QAAQ,YAAY,UAAU,QAAQ,sBAAA;AAAA,EAAqB;AAExE,OAAK,YAAY;AACjB,SAAO,MAAM,KAAK,OAAA;AACpB;AAEO,SAAS,iBAAiB,IAA6B;AAC5D,SAAO,gBAAgB,IAAI,iBAAiB,kBAAkB;AAChE;AAEO,SAAS,cAAc,IAA6B;AACzD,SAAO,gBAAgB,IAAI,cAAc,eAAe;AAC1D;AAEO,SAAS,gBAAgB,IAA6B;AAC3D,SAAO,gBAAgB,IAAI,gBAAgB,iBAAiB;AAC9D;AAEO,SAAS,YAAY,IAA6B;AACvD,SAAO,gBAAgB,IAAI,YAAY,aAAa;AACtD;AAEO,SAAS,YAAY,IAA6B;AACvD,QAAM,SAAS;AAAA,IACb,iBAAiB,EAAE;AAAA,IACnB,cAAc,EAAE;AAAA,IAChB,gBAAgB,EAAE;AAAA,IAClB,YAAY,EAAE;AAAA,EAAA;AAEhB,SAAO,MAAM,OAAO,QAAQ,CAAC,SAAS,MAAM;AAC9C;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oicl/openbridge-webcomponents-full-bundle",
3
- "version": "2.0.0-next.119",
3
+ "version": "2.0.0-next.120",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,58 @@
1
+ import {describe, expect, it} from 'vitest';
2
+ import './alert-button.js';
3
+ import {ObcAlertButton} from './alert-button.js';
4
+ import {render} from 'vitest-browser-lit';
5
+ import {html} from 'lit';
6
+
7
+ describe('obc-alert-button blinking lifecycle', () => {
8
+ async function setup() {
9
+ const screen = render(html`<obc-alert-button></obc-alert-button>`);
10
+ const el = screen.baseElement.querySelector(
11
+ 'obc-alert-button'
12
+ ) as ObcAlertButton;
13
+ await el.updateComplete;
14
+ return el;
15
+ }
16
+
17
+ it('installs the blink animations on first render', async () => {
18
+ const el = await setup();
19
+
20
+ expect(el.getAnimations().length).toBeGreaterThan(0);
21
+ });
22
+
23
+ it('cancels the blink animations on disconnect', async () => {
24
+ const el = await setup();
25
+
26
+ el.parentElement!.removeChild(el);
27
+
28
+ expect(el.getAnimations()).toHaveLength(0);
29
+ });
30
+
31
+ it('resumes blinking after disconnect and reconnect', async () => {
32
+ const el = await setup();
33
+ const parent = el.parentElement!;
34
+ const initial = el.getAnimations().length;
35
+
36
+ parent.removeChild(el);
37
+ expect(el.getAnimations()).toHaveLength(0);
38
+
39
+ // Reconnect without touching any property. firstUpdated() will not run
40
+ // again, so this only passes if blinking is reinstalled on update.
41
+ parent.appendChild(el);
42
+ await el.updateComplete;
43
+
44
+ expect(el.getAnimations()).toHaveLength(initial);
45
+ });
46
+
47
+ it('does not accumulate animations across repeated updates', async () => {
48
+ const el = await setup();
49
+ const initial = el.getAnimations().length;
50
+
51
+ el.nAlerts = 3;
52
+ await el.updateComplete;
53
+ el.nAlerts = 5;
54
+ await el.updateComplete;
55
+
56
+ expect(el.getAnimations()).toHaveLength(initial);
57
+ });
58
+ });
@@ -20,6 +20,7 @@ import {
20
20
  } from '../../alert-severity.js';
21
21
  import {classMap} from 'lit/directives/class-map.js';
22
22
  import {customElement} from '../../decorator.js';
23
+ import {blinkingAll} from '../../palettes/blinking.js';
23
24
 
24
25
  /**
25
26
  * `ObcAlertButtonType` – Enum for alert button visual and behavioral variants.
@@ -202,10 +203,14 @@ export class ObcAlertButton extends LitElement {
202
203
  override connectedCallback() {
203
204
  super.connectedCallback();
204
205
  window.addEventListener('resize', this.resizeListener);
206
+ if (this.hasUpdated) {
207
+ this.installBlinking();
208
+ }
205
209
  }
206
210
 
207
211
  override disconnectedCallback() {
208
212
  window.removeEventListener('resize', this.resizeListener);
213
+ this._blinkAnimationCancel?.();
209
214
  super.disconnectedCallback();
210
215
  }
211
216
 
@@ -280,6 +285,17 @@ export class ObcAlertButton extends LitElement {
280
285
  );
281
286
  }
282
287
 
288
+ private _blinkAnimationCancel?: () => void;
289
+
290
+ private installBlinking() {
291
+ this._blinkAnimationCancel?.();
292
+ this._blinkAnimationCancel = blinkingAll(this);
293
+ }
294
+
295
+ override updated() {
296
+ this.installBlinking();
297
+ }
298
+
283
299
  override render() {
284
300
  const hasAlerts = this.nAlerts > 0;
285
301
  const showCounter =
@@ -78,6 +78,7 @@
78
78
 
79
79
  &.level-critical {
80
80
  --bg-color: var(--critical-enabled-background-color);
81
+ --blink-on: var(--critical-blink-on);
81
82
  color: var(--on-critical-active-color);
82
83
  }
83
84
 
@@ -0,0 +1,122 @@
1
+ import {describe, expect, it} from 'vitest';
2
+ import './alert-frame.js';
3
+ import {ObcAlertFrame, ObcAlertFrameMode} from './alert-frame.js';
4
+ import {render} from 'vitest-browser-lit';
5
+ import {html} from 'lit';
6
+
7
+ describe('obc-alert-frame blinking lifecycle', () => {
8
+ async function setup(mode: ObcAlertFrameMode) {
9
+ const screen = render(
10
+ html`<obc-alert-frame .mode=${mode}></obc-alert-frame>`
11
+ );
12
+ const el = screen.baseElement.querySelector(
13
+ 'obc-alert-frame'
14
+ ) as ObcAlertFrame;
15
+ await el.updateComplete;
16
+ return el;
17
+ }
18
+
19
+ it('blinks while mode is unacked-active', async () => {
20
+ const el = await setup(ObcAlertFrameMode.unackedActive);
21
+
22
+ expect(el.getAnimations().length).toBeGreaterThan(0);
23
+ });
24
+
25
+ it('does not blink in other modes', async () => {
26
+ const el = await setup(ObcAlertFrameMode.ackedActive);
27
+
28
+ expect(el.getAnimations()).toHaveLength(0);
29
+ });
30
+
31
+ describe('mode transitions', () => {
32
+ it('starts blinking when mode becomes unacked-active', async () => {
33
+ const el = await setup(ObcAlertFrameMode.ackedActive);
34
+ expect(el.getAnimations()).toHaveLength(0);
35
+
36
+ el.mode = ObcAlertFrameMode.unackedActive;
37
+ await el.updateComplete;
38
+
39
+ expect(el.getAnimations().length).toBeGreaterThan(0);
40
+ });
41
+
42
+ it('stops blinking when mode leaves unacked-active', async () => {
43
+ const el = await setup(ObcAlertFrameMode.unackedActive);
44
+ expect(el.getAnimations().length).toBeGreaterThan(0);
45
+
46
+ el.mode = ObcAlertFrameMode.ackedActive;
47
+ await el.updateComplete;
48
+
49
+ expect(el.getAnimations()).toHaveLength(0);
50
+ });
51
+
52
+ it('stops blinking when mode becomes unacked-rectified', async () => {
53
+ const el = await setup(ObcAlertFrameMode.unackedActive);
54
+
55
+ el.mode = ObcAlertFrameMode.unackedRectified;
56
+ await el.updateComplete;
57
+
58
+ expect(el.getAnimations()).toHaveLength(0);
59
+ });
60
+
61
+ it('does not accumulate animations across repeated updates', async () => {
62
+ const el = await setup(ObcAlertFrameMode.unackedActive);
63
+ const initial = el.getAnimations().length;
64
+
65
+ el.fullWidth = true;
66
+ await el.updateComplete;
67
+ el.showIcon = true;
68
+ await el.updateComplete;
69
+
70
+ expect(el.getAnimations()).toHaveLength(initial);
71
+ });
72
+ });
73
+
74
+ describe('reconnection', () => {
75
+ it('resumes blinking after disconnect and reconnect', async () => {
76
+ const el = await setup(ObcAlertFrameMode.unackedActive);
77
+ const parent = el.parentElement!;
78
+ expect(el.getAnimations().length).toBeGreaterThan(0);
79
+
80
+ parent.removeChild(el);
81
+ expect(el.getAnimations()).toHaveLength(0);
82
+
83
+ // Reconnect without touching any property. firstUpdated() will not run
84
+ // again, so this only passes if blinking is reinstalled on update.
85
+ parent.appendChild(el);
86
+ await el.updateComplete;
87
+
88
+ expect(el.getAnimations().length).toBeGreaterThan(0);
89
+ });
90
+
91
+ it('does not resume blinking on reconnect when mode is not unacked-active', async () => {
92
+ const el = await setup(ObcAlertFrameMode.ackedActive);
93
+ const parent = el.parentElement!;
94
+
95
+ parent.removeChild(el);
96
+ parent.appendChild(el);
97
+ await el.updateComplete;
98
+
99
+ expect(el.getAnimations()).toHaveLength(0);
100
+ });
101
+
102
+ it('does not blink after disconnection when mode became unacked-active while detached', async () => {
103
+ const el = await setup(ObcAlertFrameMode.ackedActive);
104
+ const parent = el.parentElement!;
105
+
106
+ parent.removeChild(el);
107
+ await el.updateComplete;
108
+
109
+ expect(el.getAnimations().length).toBe(0);
110
+
111
+ el.mode = ObcAlertFrameMode.unackedActive;
112
+ await el.updateComplete;
113
+
114
+ expect(el.getAnimations().length).toBe(0);
115
+
116
+ parent.appendChild(el);
117
+ await el.updateComplete;
118
+
119
+ expect(el.getAnimations().length).toBeGreaterThan(0);
120
+ });
121
+ });
122
+ });
@@ -261,3 +261,15 @@ export const RectifiedUnactive: Story = {
261
261
  mode: ObcAlertFrameMode.unackedRectified,
262
262
  },
263
263
  };
264
+
265
+ export const CriticalUnacked: Story = {
266
+ args: {
267
+ type: ObcAlertFrameType.SmallSideFlip,
268
+ thickness: ObcAlertFrameThickness.Small,
269
+ status: ObcAlertFrameStatus.LevelCritical,
270
+ mode: ObcAlertFrameMode.unackedActive,
271
+ demoWidth: 200,
272
+ showIcon: true,
273
+ showAlertCategoryIcon: true,
274
+ },
275
+ };
@@ -20,6 +20,7 @@ import {
20
20
  getAlertBadgeComponent,
21
21
  AlertBadgeComponent,
22
22
  } from '../../alert-severity.js';
23
+ import {blinkingAll} from '../../palettes/blinking.js';
23
24
 
24
25
  export {AlertType as ObcAlertFrameStatus} from '../../types.js';
25
26
 
@@ -256,6 +257,42 @@ export class ObcAlertFrame extends LitElement {
256
257
  `;
257
258
  }
258
259
 
260
+ private _blinkAnimationCancel?: () => void;
261
+
262
+ private syncBlinking() {
263
+ if (
264
+ this.mode !== ObcAlertFrameMode.unackedActive &&
265
+ this._blinkAnimationCancel
266
+ ) {
267
+ this._blinkAnimationCancel();
268
+ this._blinkAnimationCancel = undefined;
269
+ }
270
+
271
+ if (
272
+ this.mode === ObcAlertFrameMode.unackedActive &&
273
+ !this._blinkAnimationCancel
274
+ ) {
275
+ this._blinkAnimationCancel = blinkingAll(this);
276
+ }
277
+ }
278
+
279
+ override connectedCallback() {
280
+ super.connectedCallback();
281
+ if (this.hasUpdated) {
282
+ this.syncBlinking();
283
+ }
284
+ }
285
+
286
+ override updated() {
287
+ this.syncBlinking();
288
+ }
289
+
290
+ override disconnectedCallback() {
291
+ super.disconnectedCallback();
292
+ this._blinkAnimationCancel?.();
293
+ this._blinkAnimationCancel = undefined;
294
+ }
295
+
259
296
  private flap() {
260
297
  if (this.type === ObcAlertFrameType.Regular) {
261
298
  return nothing;
@@ -0,0 +1,76 @@
1
+ import {describe, expect, it} from 'vitest';
2
+ import './alert-icon.js';
3
+ import {ObcAlertIcon} from './alert-icon.js';
4
+ import {render} from 'vitest-browser-lit';
5
+ import {html} from 'lit';
6
+ import {AlertType} from '../../types.js';
7
+
8
+ describe('obc-alert-icon blinking lifecycle', () => {
9
+ async function setup(acknowledged = false) {
10
+ const screen = render(
11
+ html`<obc-alert-icon
12
+ .alertType=${AlertType.Alarm}
13
+ .acknowledged=${acknowledged}
14
+ ></obc-alert-icon>`
15
+ );
16
+ const el = screen.baseElement.querySelector(
17
+ 'obc-alert-icon'
18
+ ) as ObcAlertIcon;
19
+ await el.updateComplete;
20
+ return el;
21
+ }
22
+
23
+ function wrapperAnimations(el: ObcAlertIcon) {
24
+ return el.getAnimations();
25
+ }
26
+
27
+ it('blinks for an unacknowledged blinking alert type', async () => {
28
+ const el = await setup();
29
+
30
+ expect(wrapperAnimations(el).length).toBeGreaterThan(0);
31
+ });
32
+
33
+ it('does not blink once acknowledged', async () => {
34
+ const el = await setup(true);
35
+
36
+ expect(wrapperAnimations(el)).toHaveLength(0);
37
+ });
38
+
39
+ it('stops blinking when the alert is acknowledged', async () => {
40
+ const el = await setup();
41
+ expect(wrapperAnimations(el).length).toBeGreaterThan(0);
42
+
43
+ el.acknowledged = true;
44
+ await el.updateComplete;
45
+
46
+ expect(wrapperAnimations(el)).toHaveLength(0);
47
+ });
48
+
49
+ it('resumes blinking after disconnect and reconnect', async () => {
50
+ const el = await setup();
51
+ const parent = el.parentElement!;
52
+ const initial = wrapperAnimations(el).length;
53
+ expect(initial).toBeGreaterThan(0);
54
+
55
+ parent.removeChild(el);
56
+ expect(wrapperAnimations(el)).toHaveLength(0);
57
+
58
+ // Reconnect without touching any property, so no Lit update is scheduled.
59
+ parent.appendChild(el);
60
+ await el.updateComplete;
61
+
62
+ expect(wrapperAnimations(el)).toHaveLength(initial);
63
+ });
64
+
65
+ it('does not accumulate animations across repeated updates', async () => {
66
+ const el = await setup();
67
+ const initial = wrapperAnimations(el).length;
68
+
69
+ el.active = true;
70
+ await el.updateComplete;
71
+ el.silenced = true;
72
+ await el.updateComplete;
73
+
74
+ expect(wrapperAnimations(el)).toHaveLength(initial);
75
+ });
76
+ });
@@ -50,6 +50,7 @@ import {
50
50
  import {lowSilencedA, lowSilencedB} from './icons/icon-low-silenced.js';
51
51
  import {lowRectifiedA, lowRectifiedB} from './icons/icon-low-rectified.js';
52
52
  import {lowAcknowledged} from './icons/icon-low-acknowledged.js';
53
+ import {blinkingAll} from '../../palettes/blinking.js';
53
54
 
54
55
  enum AlertIconState {
55
56
  Silenced = 'silenced',
@@ -343,6 +344,43 @@ export class ObcAlertIcon extends LitElement {
343
344
  return html`<div class="wrapper">${this.renderStaticIcon()}</div>`;
344
345
  }
345
346
 
347
+ private _blinkAnimationCancel?: () => void;
348
+
349
+ private syncBlinking(): void {
350
+ if (
351
+ this._blinkAnimationCancel &&
352
+ !supportsBlinking(this._effectiveType, this.acknowledged ?? false)
353
+ ) {
354
+ this._blinkAnimationCancel();
355
+ this._blinkAnimationCancel = undefined;
356
+ }
357
+ if (
358
+ !this._blinkAnimationCancel &&
359
+ supportsBlinking(this._effectiveType, this.acknowledged ?? false)
360
+ ) {
361
+ this._blinkAnimationCancel = blinkingAll(this);
362
+ }
363
+ }
364
+
365
+ override connectedCallback(): void {
366
+ super.connectedCallback();
367
+ if (this.hasUpdated) {
368
+ this.syncBlinking();
369
+ }
370
+ }
371
+
372
+ override updated(): void {
373
+ this.syncBlinking();
374
+ }
375
+
376
+ override disconnectedCallback(): void {
377
+ super.disconnectedCallback();
378
+ if (this._blinkAnimationCancel) {
379
+ this._blinkAnimationCancel();
380
+ this._blinkAnimationCancel = undefined;
381
+ }
382
+ }
383
+
346
384
  static override styles = css`
347
385
  .wrapper {
348
386
  height: 100%;
@@ -0,0 +1,46 @@
1
+ const CRITICAL_PERIOD = 1000; // ms
2
+ const ALARM_PERIOD = 2000; // ms
3
+ const WARNING_PERIOD = 4000; // ms
4
+ const LOW_PERIOD = 8000; // ms
5
+
6
+ function blinkingInstall(
7
+ el: HTMLElement,
8
+ period: number,
9
+ label: string
10
+ ): () => void {
11
+ const anim = el.animate(
12
+ [
13
+ {[label + '-on']: 1, [label + '-off']: 0},
14
+ {[label + '-on']: 0, [label + '-off']: 1},
15
+ ],
16
+ {duration: period, iterations: Infinity, easing: 'steps(2, jump-none)'}
17
+ );
18
+ anim.startTime = 0; // Common time origin for all animations, so they blink in sync.
19
+ return () => anim.cancel();
20
+ }
21
+
22
+ export function blinkingCritical(el: HTMLElement): () => void {
23
+ return blinkingInstall(el, CRITICAL_PERIOD, '--critical-blink');
24
+ }
25
+
26
+ export function blinkingAlarm(el: HTMLElement): () => void {
27
+ return blinkingInstall(el, ALARM_PERIOD, '--alarm-blink');
28
+ }
29
+
30
+ export function blinkingWarning(el: HTMLElement): () => void {
31
+ return blinkingInstall(el, WARNING_PERIOD, '--warning-blink');
32
+ }
33
+
34
+ export function blinkingLow(el: HTMLElement): () => void {
35
+ return blinkingInstall(el, LOW_PERIOD, '--low-blink');
36
+ }
37
+
38
+ export function blinkingAll(el: HTMLElement): () => void {
39
+ const animes = [
40
+ blinkingCritical(el),
41
+ blinkingAlarm(el),
42
+ blinkingWarning(el),
43
+ blinkingLow(el),
44
+ ];
45
+ return () => animes.forEach((anim) => anim());
46
+ }
@@ -121,45 +121,3 @@
121
121
  inherits: true;
122
122
  initial-value: 0;
123
123
  }
124
-
125
- @keyframes critical-blink {
126
- 0% {
127
- --critical-blink-on: 1;
128
- --critical-blink-off: 0;
129
- }
130
-
131
- 50% {
132
- --critical-blink-on: 0;
133
- --critical-blink-off: 1;
134
- }
135
-
136
- 100% {
137
- --critical-blink-on: 1;
138
- --critical-blink-off: 0;
139
- }
140
- }
141
-
142
- @keyframes low-blink {
143
- 0% {
144
- --low-blink-on: 1;
145
- --low-blink-off: 0;
146
- }
147
-
148
- 50% {
149
- --low-blink-on: 0;
150
- --low-blink-off: 1;
151
- }
152
-
153
- 100% {
154
- --low-blink-on: 1;
155
- --low-blink-off: 0;
156
- }
157
- }
158
-
159
- :root {
160
- animation:
161
- warning-blink 4s infinite,
162
- critical-blink 1s infinite,
163
- low-blink 8s infinite;
164
- animation-timing-function: steps(1);
165
- }