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

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 (51) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +118 -14
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +45 -1
  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/integration-systems/integration-button/integration-button.css.js +20 -13
  19. package/dist/integration-systems/integration-button/integration-button.css.js.map +1 -1
  20. package/dist/integration-systems/integration-button/integration-button.d.ts +1 -1
  21. package/dist/integration-systems/integration-button/integration-button.js +1 -1
  22. package/dist/integration-systems/integration-button/integration-button.js.map +1 -1
  23. package/dist/integration-systems/integration-dropdown-button/integration-dropdown-button.css.js +1 -0
  24. package/dist/integration-systems/integration-dropdown-button/integration-dropdown-button.css.js.map +1 -1
  25. package/dist/openbridge.css +0 -73
  26. package/dist/palettes/blinking.d.ts +6 -0
  27. package/dist/palettes/blinking.d.ts.map +1 -0
  28. package/dist/palettes/blinking.js +44 -0
  29. package/dist/palettes/blinking.js.map +1 -0
  30. package/package.json +4 -2
  31. package/script/agent-docs/emitters.ts +189 -0
  32. package/script/agent-docs/frontmatter.ts +118 -0
  33. package/script/check-slot-event-docs.ts +2 -2
  34. package/script/sync-agent-docs.test.ts +298 -0
  35. package/script/sync-agent-docs.ts +156 -0
  36. package/src/components/alert-button/alert-button.spec.ts +58 -0
  37. package/src/components/alert-button/alert-button.ts +16 -0
  38. package/src/components/alert-frame/alert-frame.css +1 -0
  39. package/src/components/alert-frame/alert-frame.spec.ts +122 -0
  40. package/src/components/alert-frame/alert-frame.stories.ts +12 -0
  41. package/src/components/alert-frame/alert-frame.ts +37 -0
  42. package/src/components/alert-icon/alert-icon.spec.ts +76 -0
  43. package/src/components/alert-icon/alert-icon.ts +38 -0
  44. package/src/icons/AGENTS.md +6 -0
  45. package/src/integration-systems/integration-button/integration-button.css +20 -13
  46. package/src/integration-systems/integration-button/integration-button.stories.ts +70 -44
  47. package/src/integration-systems/integration-button/integration-button.ts +2 -2
  48. package/src/integration-systems/integration-dropdown-button/integration-dropdown-button.css +1 -0
  49. package/src/palettes/blinking.ts +46 -0
  50. package/src/palettes/manual.css +0 -42
  51. package/src/palettes/variables.css +0 -36
@@ -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,6 @@
1
+ # src/icons — generated, do not edit
2
+
3
+ Every file here is generated from Figma by `npm run download:icons`. Edits are
4
+ discarded on the next run. See [`docs/agents/generated-code.md`](../../../../docs/agents/generated-code.md).
5
+
6
+ Note `src/manual-icon/` is the opposite: hand-written, edit it normally.
@@ -27,9 +27,13 @@
27
27
  .touch-target {
28
28
  @mixin style style=integration-normal visibleWrapperClass=.content-container;
29
29
  @mixin font-body;
30
+ --leading-icon-size: var(
31
+ --app-components-integration-system-navigation-item-horizontal-icon-size
32
+ );
30
33
  anchor-name: --touch-target;
31
34
  display: flex;
32
35
  align-items: center;
36
+ text-align: left;
33
37
  gap: 4px;
34
38
  flex: 1;
35
39
  min-height: var(
@@ -169,20 +173,23 @@
169
173
  color: var(--integration-on-normal-neutral-color);
170
174
  }
171
175
 
172
- .icon.leading {
173
- width: var(
174
- --app-components-integration-system-navigation-item-horizontal-icon-size
175
- );
176
- height: var(
177
- --app-components-integration-system-navigation-item-horizontal-icon-size
176
+ &.has-status {
177
+ --leading-icon-size: var(
178
+ --app-components-integration-system-navigation-item-horizontal-icon-size-large
178
179
  );
179
- min-width: var(
180
- --app-components-integration-system-navigation-item-horizontal-icon-size
181
- );
182
- flex: 0 0
183
- var(
184
- --app-components-integration-system-navigation-item-horizontal-icon-size
185
- );
180
+ }
181
+
182
+ .icon.leading {
183
+ width: var(--leading-icon-size);
184
+ height: var(--leading-icon-size);
185
+ min-width: var(--leading-icon-size);
186
+ flex: 0 0 var(--leading-icon-size);
187
+
188
+ & ::slotted(*) {
189
+ display: block;
190
+ width: 100%;
191
+ height: 100%;
192
+ }
186
193
  }
187
194
 
188
195
  .icon.trailing {
@@ -8,6 +8,35 @@ import './integration-button.js';
8
8
  import {html} from 'lit';
9
9
  import '../../icons/icon-placeholder.js';
10
10
 
11
+ const LONG_STATUS =
12
+ 'Very long status for a vessel at sea doing something important';
13
+
14
+ const renderButton = (args: ObcIntegrationButton, statusText: string) => html`
15
+ <obc-integration-button
16
+ style="width: 320px; display: block;"
17
+ .hasLeadingIcon=${args.hasLeadingIcon}
18
+ .hasTrailingIcon=${args.hasTrailingIcon}
19
+ .hasTrailingIcon2=${args.hasTrailingIcon2}
20
+ .hasStatus=${args.hasStatus}
21
+ .readouts=${args.readouts}
22
+ .selected=${args.selected}
23
+ .activated=${args.activated}
24
+ .disabled=${args.disabled}
25
+ .dividerBottom=${args.dividerBottom}
26
+ .dividerRight=${args.dividerRight}
27
+ .variant=${args.variant}
28
+ .type=${args.type}
29
+ >
30
+ <obi-placeholder slot="leading-icon"></obi-placeholder>
31
+ <obi-placeholder slot="trailing-icon"></obi-placeholder>
32
+ <obi-placeholder slot="trailing-icon2"></obi-placeholder>
33
+ <div slot="label">Label</div>
34
+ <div slot="status">${statusText}</div>
35
+ <div slot="info-label">Info Label</div>
36
+ <div slot="info-status">Info Status</div>
37
+ </obc-integration-button>
38
+ `;
39
+
11
40
  const meta: Meta<ObcIntegrationButton> = {
12
41
  title: 'Integration Systems/Integration Button',
13
42
  tags: ['experimental'],
@@ -30,28 +59,7 @@ const meta: Meta<ObcIntegrationButton> = {
30
59
  value: 'integration-container-global-color',
31
60
  },
32
61
  },
33
- render: (args) => html`
34
- <obc-integration-button
35
- style="width: 320px; display: block;"
36
- .hasLeadingIcon=${args.hasLeadingIcon}
37
- .hasTrailingIcon=${args.hasTrailingIcon}
38
- .hasTrailingIcon2=${args.hasTrailingIcon2}
39
- .hasStatus=${args.hasStatus}
40
- .readouts=${args.readouts}
41
- .selected=${args.selected}
42
- .disabled=${args.disabled}
43
- .variant=${args.variant}
44
- .type=${args.type}
45
- >
46
- <obi-placeholder slot="leading-icon"></obi-placeholder>
47
- <obi-placeholder slot="trailing-icon"></obi-placeholder>
48
- <obi-placeholder slot="trailing-icon2"></obi-placeholder>
49
- <div slot="label">Label</div>
50
- <div slot="status">Status</div>
51
- <div slot="info-label">Info Label</div>
52
- <div slot="info-status">Info Status</div>
53
- </obc-integration-button>
54
- `,
62
+ render: (args) => renderButton(args, 'Status'),
55
63
  } satisfies Meta<ObcIntegrationButton>;
56
64
  export default meta;
57
65
 
@@ -61,28 +69,6 @@ export const WithStatus: StoryObj<ObcIntegrationButton> = {
61
69
  args: {
62
70
  hasStatus: true,
63
71
  },
64
- render: (args) => html`
65
- <obc-integration-button
66
- style="width: 320px; display: block;"
67
- .hasLeadingIcon=${args.hasLeadingIcon}
68
- .hasTrailingIcon=${args.hasTrailingIcon}
69
- .hasTrailingIcon2=${args.hasTrailingIcon2}
70
- .readouts=${args.readouts}
71
- .selected=${args.selected}
72
- .disabled=${args.disabled}
73
- .variant=${args.variant}
74
- .type=${args.type}
75
- .hasStatus=${args.hasStatus}
76
- >
77
- <obi-placeholder slot="leading-icon"></obi-placeholder>
78
- <obi-placeholder slot="trailing-icon"></obi-placeholder>
79
- <obi-placeholder slot="trailing-icon2"></obi-placeholder>
80
- <div slot="label">Label</div>
81
- <div slot="status">Status</div>
82
- <div slot="info-label">Info Label</div>
83
- <div slot="info-status">Info Status</div>
84
- </obc-integration-button>
85
- `,
86
72
  };
87
73
 
88
74
  export const Selected: StoryObj<ObcIntegrationButton> = {
@@ -111,9 +97,49 @@ export const Rich: StoryObj<ObcIntegrationButton> = {
111
97
  },
112
98
  };
113
99
 
100
+ export const RichWithStatus: StoryObj<ObcIntegrationButton> = {
101
+ args: {
102
+ type: IntegrationButtonType.rich,
103
+ hasStatus: true,
104
+ },
105
+ };
106
+
114
107
  export const Disabled: StoryObj<ObcIntegrationButton> = {
115
108
  args: {
116
109
  type: IntegrationButtonType.rich,
117
110
  disabled: true,
118
111
  },
119
112
  };
113
+
114
+ export const WithLongStatus: StoryObj<ObcIntegrationButton> = {
115
+ args: {
116
+ hasStatus: true,
117
+ },
118
+ render: (args) => renderButton(args, LONG_STATUS),
119
+ };
120
+
121
+ export const HugWithLongStatus: StoryObj<ObcIntegrationButton> = {
122
+ args: {
123
+ type: IntegrationButtonType.hug,
124
+ hasStatus: true,
125
+ hasTrailingIcon: false,
126
+ },
127
+ render: (args) => renderButton(args, LONG_STATUS),
128
+ };
129
+
130
+ export const RichWithLongStatus: StoryObj<ObcIntegrationButton> = {
131
+ args: {
132
+ type: IntegrationButtonType.rich,
133
+ hasStatus: true,
134
+ },
135
+ render: (args) => renderButton(args, LONG_STATUS),
136
+ };
137
+
138
+ export const RichWithLongReadoutLabel: StoryObj<ObcIntegrationButton> = {
139
+ args: {
140
+ type: IntegrationButtonType.rich,
141
+ hasStatus: true,
142
+ readouts: [{label: 'Estimated Time of Arrival', value: '12', unit: 'h'}],
143
+ },
144
+ render: (args) => renderButton(args, 'Status'),
145
+ };
@@ -24,7 +24,7 @@ export enum IntegrationButtonType {
24
24
  /**
25
25
  * `<obc-integration-button>` – A button component for integration systems.
26
26
  *
27
- * @slot leading-icon - Icon before label (shown when `hasLeadingIcon` is true)
27
+ * @slot leading-icon - Icon before label (shown when `hasLeadingIcon` is true); rendered at the large icon size when `hasStatus` is true
28
28
  * @slot trailing-icon - Icon after label (shown when `hasTrailingIcon` is true)
29
29
  * @slot trailing-icon2 - Icon after label (shown when `hasTrailingIcon2` is true)
30
30
  * @slot label - Label text
@@ -76,7 +76,7 @@ export class ObcIntegrationButton extends LitElement {
76
76
  selected: this.selected,
77
77
  activated: this.activated,
78
78
  disabled: this.disabled,
79
- 'has-description': this.hasStatus,
79
+ 'has-status': this.hasStatus,
80
80
  ['variant-' + this.variant]: true,
81
81
  ['type-' + this.type]: true,
82
82
  };
@@ -134,6 +134,7 @@
134
134
  flex-direction: column;
135
135
  align-items: flex-start;
136
136
  flex-grow: 1;
137
+ text-align: left;
137
138
  }
138
139
 
139
140
  & .text-container .label {
@@ -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
- }
@@ -14740,37 +14740,6 @@
14740
14740
  initial-value: 0;
14741
14741
  }
14742
14742
 
14743
- @keyframes warning-blink {
14744
- 0% {
14745
- --warning-blink-on: 1;
14746
- --warning-blink-off: 0;
14747
- --alarm-blink-on: 1;
14748
- --alarm-blink-off: 0;
14749
- }
14750
-
14751
- 25% {
14752
- --alarm-blink-on: 0;
14753
- --alarm-blink-off: 1;
14754
- }
14755
-
14756
- 50% {
14757
- --warning-blink-on: 0;
14758
- --warning-blink-off: 1;
14759
- --alarm-blink-on: 1;
14760
- --alarm-blink-off: 0;
14761
- }
14762
-
14763
- 75% {
14764
- --alarm-blink-on: 0;
14765
- --alarm-blink-off: 1;
14766
- }
14767
-
14768
- 100% {
14769
- --warning-blink-on: 1;
14770
- --warning-blink-off: 0;
14771
- }
14772
- }
14773
-
14774
14743
  @property --warning-blink-on {
14775
14744
  syntax: "<number>";
14776
14745
  inherits: true;
@@ -14782,8 +14751,3 @@
14782
14751
  inherits: true;
14783
14752
  initial-value: 0;
14784
14753
  }
14785
-
14786
- :root {
14787
- animation: warning-blink 4s infinite;
14788
- animation-timing-function: steps(1);
14789
- }