@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.66 → 2.0.0-next.68

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 (76) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +2738 -2408
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +347 -30
  4. package/dist/automation/automation-button/automation-button.css.js +9 -9
  5. package/dist/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.d.ts +1 -0
  6. package/dist/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.d.ts.map +1 -1
  7. package/dist/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.js +9 -28
  8. package/dist/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.js.map +1 -1
  9. package/dist/components/accordion-card/accordion-card.css.js +1 -1
  10. package/dist/components/dropdown-button/dropdown-button.d.ts +11 -0
  11. package/dist/components/dropdown-button/dropdown-button.d.ts.map +1 -1
  12. package/dist/components/dropdown-button/dropdown-button.js +24 -2
  13. package/dist/components/dropdown-button/dropdown-button.js.map +1 -1
  14. package/dist/components/keyboard-numeric/keyboard-numeric.d.ts +1 -2
  15. package/dist/components/keyboard-numeric/keyboard-numeric.d.ts.map +1 -1
  16. package/dist/components/keyboard-numeric/keyboard-numeric.js +10 -15
  17. package/dist/components/keyboard-numeric/keyboard-numeric.js.map +1 -1
  18. package/dist/components/number-input-field/number-input-field.d.ts +9 -0
  19. package/dist/components/number-input-field/number-input-field.d.ts.map +1 -1
  20. package/dist/components/number-input-field/number-input-field.js +30 -1
  21. package/dist/components/number-input-field/number-input-field.js.map +1 -1
  22. package/dist/components/number-input-field/number-input-format.d.ts +9 -0
  23. package/dist/components/number-input-field/number-input-format.d.ts.map +1 -1
  24. package/dist/components/number-input-field/number-input-format.js +33 -2
  25. package/dist/components/number-input-field/number-input-format.js.map +1 -1
  26. package/dist/components/stepper-box/stepper-box.css.js +0 -6
  27. package/dist/components/stepper-box/stepper-box.css.js.map +1 -1
  28. package/dist/components/stepper-box/stepper-box.d.ts +3 -13
  29. package/dist/components/stepper-box/stepper-box.d.ts.map +1 -1
  30. package/dist/components/stepper-box/stepper-box.js.map +1 -1
  31. package/dist/components/toggle-button-group/toggle-button-group.d.ts +14 -0
  32. package/dist/components/toggle-button-group/toggle-button-group.d.ts.map +1 -1
  33. package/dist/components/toggle-button-group/toggle-button-group.js +25 -5
  34. package/dist/components/toggle-button-group/toggle-button-group.js.map +1 -1
  35. package/dist/integration-systems/integration-fleet-button/integration-fleet-button.css.js +4 -0
  36. package/dist/integration-systems/integration-fleet-button/integration-fleet-button.css.js.map +1 -1
  37. package/dist/integration-systems/integration-fleet-button/integration-fleet-button.d.ts +1 -0
  38. package/dist/integration-systems/integration-fleet-button/integration-fleet-button.d.ts.map +1 -1
  39. package/dist/integration-systems/integration-fleet-button/integration-fleet-button.js +13 -1
  40. package/dist/integration-systems/integration-fleet-button/integration-fleet-button.js.map +1 -1
  41. package/dist/navigation-instruments/graph-mini/graph-mini.css.js +2 -1
  42. package/dist/navigation-instruments/graph-mini/graph-mini.css.js.map +1 -1
  43. package/dist/navigation-instruments/graph-mini/graph-mini.d.ts.map +1 -1
  44. package/dist/navigation-instruments/graph-mini/graph-mini.js +7 -5
  45. package/dist/navigation-instruments/graph-mini/graph-mini.js.map +1 -1
  46. package/dist/navigation-instruments/indicator-graph/indicator-graph.css.js +66 -0
  47. package/dist/navigation-instruments/indicator-graph/indicator-graph.css.js.map +1 -0
  48. package/dist/navigation-instruments/indicator-graph/indicator-graph.d.ts +54 -0
  49. package/dist/navigation-instruments/indicator-graph/indicator-graph.d.ts.map +1 -0
  50. package/dist/navigation-instruments/indicator-graph/indicator-graph.js +203 -0
  51. package/dist/navigation-instruments/indicator-graph/indicator-graph.js.map +1 -0
  52. package/docs/GettingStarted.mdx +28 -12
  53. package/package.json +1 -1
  54. package/src/automation/automation-button/automation-button.css +9 -9
  55. package/src/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.ts +9 -28
  56. package/src/components/accordion-card/accordion-card.css +1 -1
  57. package/src/components/dropdown-button/dropdown-button.stories.ts +22 -0
  58. package/src/components/dropdown-button/dropdown-button.ts +33 -4
  59. package/src/components/keyboard-numeric/keyboard-numeric.ts +12 -18
  60. package/src/components/number-input-field/number-input-field.spec.ts +134 -0
  61. package/src/components/number-input-field/number-input-field.ts +47 -0
  62. package/src/components/number-input-field/number-input-format.spec.ts +62 -0
  63. package/src/components/number-input-field/number-input-format.ts +59 -2
  64. package/src/components/stepper-box/stepper-box.css +0 -6
  65. package/src/components/stepper-box/stepper-box.ts +3 -13
  66. package/src/components/toggle-button-group/toggle-button-group.ts +36 -5
  67. package/src/components/toggle-button-group/toggle-button.stories.ts +31 -0
  68. package/src/integration-systems/integration-fleet-button/integration-fleet-button.css +4 -0
  69. package/src/integration-systems/integration-fleet-button/integration-fleet-button.stories.ts +9 -0
  70. package/src/integration-systems/integration-fleet-button/integration-fleet-button.ts +10 -1
  71. package/src/navigation-instruments/graph-mini/graph-mini.css +2 -1
  72. package/src/navigation-instruments/graph-mini/graph-mini.stories.ts +11 -3
  73. package/src/navigation-instruments/graph-mini/graph-mini.ts +7 -5
  74. package/src/navigation-instruments/indicator-graph/indicator-graph.css +55 -0
  75. package/src/navigation-instruments/indicator-graph/indicator-graph.stories.ts +116 -0
  76. package/src/navigation-instruments/indicator-graph/indicator-graph.ts +222 -0
@@ -2,6 +2,7 @@ import {describe, it, expect} from 'vitest';
2
2
  import {
3
3
  formatNumberForDisplay,
4
4
  getLocaleNumberSeparators,
5
+ isAllowedIntermediateInput,
5
6
  parseNumberInput,
6
7
  removeGroupingFromDisplay,
7
8
  valuesEqual,
@@ -22,6 +23,17 @@ describe('parseNumberInput', () => {
22
23
  expect(parseNumberInput('10,')).toBe(10);
23
24
  });
24
25
 
26
+ it('parses grouped numbers with mixed separators', () => {
27
+ expect(parseNumberInput('1,234.5')).toBe(1234.5);
28
+ expect(parseNumberInput('1.234,5')).toBe(1234.5);
29
+ expect(parseNumberInput('1 234,5')).toBe(1234.5);
30
+ expect(parseNumberInput("1'234.5")).toBe(1234.5);
31
+ });
32
+
33
+ it('parses grouped integers', () => {
34
+ expect(parseNumberInput('1,234,567')).toBe(1234567);
35
+ });
36
+
25
37
  it('returns NaN for sign-only input', () => {
26
38
  expect(Number.isNaN(parseNumberInput('-'))).toBe(true);
27
39
  expect(Number.isNaN(parseNumberInput('+'))).toBe(true);
@@ -153,3 +165,53 @@ describe('valuesEqual', () => {
153
165
  expect(valuesEqual(1, 2)).toBe(false);
154
166
  });
155
167
  });
168
+
169
+ describe('isAllowedIntermediateInput', () => {
170
+ const enUsOptions = {decimalSeparator: '.', groupSeparator: ','};
171
+
172
+ it('allows empty string', () => {
173
+ expect(isAllowedIntermediateInput('', enUsOptions)).toBe(true);
174
+ });
175
+
176
+ it('allows digits', () => {
177
+ expect(isAllowedIntermediateInput('123', enUsOptions)).toBe(true);
178
+ });
179
+
180
+ it('allows leading sign', () => {
181
+ expect(isAllowedIntermediateInput('-', enUsOptions)).toBe(true);
182
+ expect(isAllowedIntermediateInput('+12.5', enUsOptions)).toBe(true);
183
+ });
184
+
185
+ it('allows trailing decimal separator', () => {
186
+ expect(isAllowedIntermediateInput('10.', enUsOptions)).toBe(true);
187
+ expect(isAllowedIntermediateInput(',5', enUsOptions)).toBe(true);
188
+ });
189
+
190
+ it('allows grouped numbers with default separators', () => {
191
+ expect(isAllowedIntermediateInput('1,234.5', enUsOptions)).toBe(true);
192
+ });
193
+
194
+ it('rejects letters', () => {
195
+ expect(isAllowedIntermediateInput('12a')).toBe(false);
196
+ expect(isAllowedIntermediateInput('e')).toBe(false);
197
+ });
198
+
199
+ it('rejects symbols outside the allowed set', () => {
200
+ expect(isAllowedIntermediateInput('12!')).toBe(false);
201
+ expect(isAllowedIntermediateInput('12$')).toBe(false);
202
+ });
203
+
204
+ it('rejects sign in non-leading position', () => {
205
+ expect(isAllowedIntermediateInput('1-2')).toBe(false);
206
+ });
207
+
208
+ it('honours custom decimal separator', () => {
209
+ const opts = {decimalSeparator: ',', groupSeparator: ' '};
210
+ expect(isAllowedIntermediateInput('1 234,5', opts)).toBe(true);
211
+ expect(isAllowedIntermediateInput('1,234.5', opts)).toBe(false);
212
+ });
213
+
214
+ it('allows whitespace', () => {
215
+ expect(isAllowedIntermediateInput(' 12 ')).toBe(true);
216
+ });
217
+ });
@@ -26,12 +26,69 @@ export function parseNumberInput(display: string): number {
26
26
  return NaN;
27
27
  }
28
28
 
29
- const normalized = trimmed.replace(',', '.');
29
+ // Drop whitespace and apostrophe grouping that may appear in pasted or
30
+ // locale-formatted numbers (e.g. `1 234`, `1'234`).
31
+ let normalized = trimmed.replace(/[\s']/g, '');
32
+
33
+ const commaCount = (normalized.match(/,/g) ?? []).length;
34
+ const dotCount = (normalized.match(/\./g) ?? []).length;
35
+
36
+ if (commaCount > 0 && dotCount > 0) {
37
+ // Both separators present: the right-most one is the decimal separator,
38
+ // the other is a grouping separator.
39
+ const decimalSeparator =
40
+ normalized.lastIndexOf(',') > normalized.lastIndexOf('.') ? ',' : '.';
41
+ const groupSeparator = decimalSeparator === ',' ? '.' : ',';
42
+ normalized = normalized.split(groupSeparator).join('');
43
+ normalized = normalized.replace(decimalSeparator, '.');
44
+ } else if (commaCount > 1) {
45
+ // Multiple commas without a dot can only be grouping separators.
46
+ normalized = normalized.split(',').join('');
47
+ } else if (commaCount === 1) {
48
+ // A single comma is treated as the decimal separator.
49
+ normalized = normalized.replace(',', '.');
50
+ } else if (dotCount > 1) {
51
+ // Multiple dots without a comma can only be grouping separators.
52
+ normalized = normalized.split('.').join('');
53
+ }
30
54
 
31
55
  const n = Number(normalized);
32
56
  return Number.isFinite(n) ? n : NaN;
33
57
  }
34
58
 
59
+ function escapeRegex(value: string): string {
60
+ return value.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&');
61
+ }
62
+
63
+ /**
64
+ * Permissive per-keystroke validator used to reject non-numeric characters
65
+ * before they reach the input. Allows half-typed states like `10.`, `-`, `,5`,
66
+ * `1,234.`; final correctness is enforced by `parseNumberInput` at commit.
67
+ *
68
+ * Accepts: optional leading sign, digits, the active decimal separator,
69
+ * the active group separator, and whitespace.
70
+ */
71
+ export function isAllowedIntermediateInput(
72
+ value: string,
73
+ options: NumberInputFormatOptions = {}
74
+ ): boolean {
75
+ if (value === '') return true;
76
+
77
+ const localeSeparators = getLocaleNumberSeparators();
78
+ const decimalSeparator =
79
+ options.decimalSeparator ?? localeSeparators.decimalSeparator;
80
+ const groupSeparator =
81
+ options.groupSeparator ?? localeSeparators.groupSeparator;
82
+
83
+ const allowedChars = new Set<string>([decimalSeparator]);
84
+ if (groupSeparator) {
85
+ allowedChars.add(groupSeparator);
86
+ }
87
+ const allowedClass = [...allowedChars].map(escapeRegex).join('') + '0-9 \\t';
88
+ const pattern = new RegExp(`^[+\\-]?[${allowedClass}]*$`);
89
+ return pattern.test(value);
90
+ }
91
+
35
92
  function clampFractionDigits(
36
93
  minFractionDigits: number | undefined,
37
94
  maxFractionDigits: number | undefined
@@ -110,7 +167,7 @@ export function removeGroupingFromDisplay(
110
167
  return display;
111
168
  }
112
169
 
113
- const escaped = groupSeparator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
170
+ const escaped = escapeRegex(groupSeparator);
114
171
  return display.replace(new RegExp(escaped, 'g'), '');
115
172
  }
116
173
 
@@ -41,9 +41,3 @@
41
41
  :host([disabled]) .helper-text {
42
42
  color: var(--element-disabled-color);
43
43
  }
44
-
45
- :host([disabled]) .field-wrapper.has-unit-slot .unit-slot {
46
- color: var(--on-normal-disabled-color);
47
- border-color: var(--normal-disabled-border-color);
48
- background: var(--normal-disabled-background-color);
49
- }
@@ -36,22 +36,15 @@ export enum ObcStepperBoxType {
36
36
  * - `up-down`: Uses up and down chevron icons for vertical adjustment.
37
37
  * - `left-right`: Uses left and right chevron icons for horizontal adjustment.
38
38
  * - **Value Display:**
39
- * - Optional unit label via the `unit` slot.
39
+ * - Optional unit label via the `unit` property.
40
40
  * - **Helper Text:**
41
41
  * - When `helperText` is set, displays additional helper or status text below the control.
42
42
  * - **Icon Buttons:**
43
43
  * - Both increment and decrement actions are triggered by icon buttons, with icons adapting to the selected type.
44
- * - **Customizable Layout:**
45
- * - Supports flexible content via the `unit` slot.
46
44
  *
47
45
  * ### Usage Guidelines
48
46
  * Use `obc-stepper-box` for scenarios where users need to adjust a value in discrete steps, such as quantity pickers, setting numeric parameters, or cycling through options. It is ideal when you want to prevent invalid input and provide a clear, touch-friendly interface for value changes.
49
47
  *
50
- * ### Slots
51
- * | Slot Name | Renders When... | Purpose |
52
- * |--------------- |--------------------------|-----------------------------------------|
53
- * | unit | If provided | Unit label (e.g., "km", "%"). |
54
- *
55
48
  * ### Events
56
49
  * - `down` – Fired when the decrement (left or down) button is clicked.
57
50
  * - `up` – Fired when the increment (right or up) button is clicked.
@@ -63,12 +56,9 @@ export enum ObcStepperBoxType {
63
56
  *
64
57
  * **Example:**
65
58
  * ```
66
- * <obc-stepper-box type="up-down" .value=${5} helperText="Set weight">
67
- * <div slot="unit">kg</div>
68
- * </obc-stepper-box>
59
+ * <obc-stepper-box type="up-down" value="5" unit="kg" helperText="Set weight"></obc-stepper-box>
69
60
  * ```
70
61
  *
71
- * @slot unit - Unit label (e.g., "km", "%")
72
62
  * @fires down {CustomEvent<{value: number}>} Fired when the decrement (left or down) button is clicked
73
63
  * @fires up {CustomEvent<{value: number}>} Fired when the increment (right or up) button is clicked
74
64
  * @fires input {CustomEvent<{value: string}>} Fired when the user types in the number input field
@@ -119,7 +109,7 @@ export class ObcStepperBox extends LitElement {
119
109
  @property({type: Number}) stepDown = 1;
120
110
 
121
111
  /**
122
- * Unit text displayed inside the field. Overridden by the `unit` slot if assigned.
112
+ * Unit text displayed inside the field.
123
113
  */
124
114
  @property({type: String}) unit = '';
125
115
 
@@ -47,6 +47,10 @@ export type ObcToggleButtonGroupValueChangeEvent = CustomEvent<{
47
47
  * - **External control mode:** When `externalControl` is true, the group emits selection events but does not
48
48
  * update its own `value` property, allowing parent components to manage state (useful for form libraries or
49
49
  * custom state management).
50
+ * - **Empty selection mode:** When `allowEmptySelection` is true, a `value` that does not match any enabled
51
+ * option leaves the group with no option selected, instead of defaulting to the first enabled option. Use
52
+ * this when a selection may legitimately be absent (e.g. unset, loading, or error states) so the UI does not
53
+ * imply a choice the user has not made.
50
54
  * - **Disabled state:** Setting `disabled` on the group disables all contained options at once. Individual
51
55
  * options can also be disabled independently while the group remains enabled.
52
56
  * - **Divider management:** Automatically shows visual dividers between options and hides the divider after
@@ -177,6 +181,17 @@ export class ObcToggleButtonGroup extends LitElement {
177
181
  */
178
182
  @property({type: Boolean}) externalControl = false;
179
183
 
184
+ /**
185
+ * If true, a `value` that does not match any enabled option leaves the group with no option selected
186
+ * instead of defaulting to the first enabled option.
187
+ *
188
+ * This also applies when the currently selected option becomes disabled: the group clears its selection
189
+ * rather than falling back to another option.
190
+ *
191
+ * Defaults to false (the first enabled option is selected when the value does not match).
192
+ */
193
+ @property({type: Boolean}) allowEmptySelection = false;
194
+
180
195
  /**
181
196
  * Disables the entire toggle button group and all contained options when true.
182
197
  *
@@ -233,8 +248,12 @@ export class ObcToggleButtonGroup extends LitElement {
233
248
  this.setNoDivider();
234
249
  return;
235
250
  }
236
- const fallback = this.getFirstSelectableOption();
237
- newValue = fallback?.value || '';
251
+ if (this.allowEmptySelection) {
252
+ newValue = '';
253
+ } else {
254
+ const fallback = this.getFirstSelectableOption();
255
+ newValue = fallback?.value || '';
256
+ }
238
257
  }
239
258
 
240
259
  this.value = newValue;
@@ -333,9 +352,13 @@ export class ObcToggleButtonGroup extends LitElement {
333
352
  });
334
353
 
335
354
  if (!this.value || !this.getOptionByValue(this.value)) {
336
- const firstSelectable = this.getFirstSelectableOption();
337
- if (firstSelectable) {
338
- this.updateSelection(firstSelectable.value, false);
355
+ if (this.allowEmptySelection) {
356
+ this.updateSelection('', false);
357
+ } else {
358
+ const firstSelectable = this.getFirstSelectableOption();
359
+ if (firstSelectable) {
360
+ this.updateSelection(firstSelectable.value, false);
361
+ }
339
362
  }
340
363
  } else {
341
364
  this.updateSelection(this.value, false);
@@ -349,6 +372,10 @@ export class ObcToggleButtonGroup extends LitElement {
349
372
  private handleOptionDisabledChange() {
350
373
  const currentOption = this.getOptionByValue(this.value);
351
374
  if (currentOption?.disabled && this.hasAnyEnabledOption()) {
375
+ if (this.allowEmptySelection) {
376
+ this.updateSelection('');
377
+ return;
378
+ }
352
379
  const firstSelectable = this.getFirstSelectableOption();
353
380
  if (firstSelectable) {
354
381
  this.updateSelection(firstSelectable.value);
@@ -412,6 +439,10 @@ export class ObcToggleButtonGroup extends LitElement {
412
439
 
413
440
  const currentOption = this.getOptionByValue(this.value);
414
441
  if (currentOption?.disabled && this.hasAnyEnabledOption()) {
442
+ if (this.allowEmptySelection) {
443
+ this.updateSelection('');
444
+ return;
445
+ }
415
446
  const firstSelectable = this.getFirstSelectableOption();
416
447
  if (firstSelectable) {
417
448
  this.updateSelection(firstSelectable.value);
@@ -52,6 +52,9 @@ const meta: Meta<typeof ObcToggleButtonGroup> = {
52
52
  large: {
53
53
  control: {type: 'boolean'},
54
54
  },
55
+ allowEmptySelection: {
56
+ control: {type: 'boolean'},
57
+ },
55
58
  },
56
59
  render: (args) =>
57
60
  html` <div
@@ -483,3 +486,31 @@ export const ExternalControlActivatedDemo: Story = {
483
486
  </div>
484
487
  `,
485
488
  };
489
+
490
+ export const EmptySelectionWhenUnmatched: Story = {
491
+ args: {
492
+ type: ObcToggleButtonOptionType.text,
493
+ variant: ObcToggleButtonOptionVariant.regular,
494
+ value: 'none',
495
+ allowEmptySelection: true,
496
+ },
497
+ render: (args) => html`
498
+ <div style="display: flex; flex-direction: column; gap: 16px;">
499
+ <p>
500
+ With <code>allowEmptySelection</code> enabled, a value that does not
501
+ match any option (here <code>"none"</code>) leaves the group with no
502
+ option selected instead of defaulting to the first option.
503
+ </p>
504
+ <obc-toggle-button-group
505
+ value="${args.value}"
506
+ variant="${args.variant}"
507
+ type="${args.type}"
508
+ .allowEmptySelection="${args.allowEmptySelection}"
509
+ >
510
+ <obc-toggle-button-option value="1">Option 1</obc-toggle-button-option>
511
+ <obc-toggle-button-option value="2">Option 2</obc-toggle-button-option>
512
+ <obc-toggle-button-option value="3">Option 3</obc-toggle-button-option>
513
+ </obc-toggle-button-group>
514
+ </div>
515
+ `,
516
+ };
@@ -138,6 +138,10 @@
138
138
  gap: 8px;
139
139
  align-self: stretch;
140
140
 
141
+ &:not(.show-alerts) {
142
+ display: none;
143
+ }
144
+
141
145
  .alert-icon {
142
146
  width: 24px;
143
147
  height: 24px;
@@ -17,6 +17,7 @@ const meta: Meta<typeof ObcIntegrationFleetButton> = {
17
17
  ],
18
18
  alertTopic: 'Navigation alert',
19
19
  alerts: {alarm: 1, warning: 5, caution: 2},
20
+ showAlerts: true,
20
21
  },
21
22
  parameters: {
22
23
  backgrounds: {
@@ -32,6 +33,7 @@ const meta: Meta<typeof ObcIntegrationFleetButton> = {
32
33
  .alertTopic=${args.alertTopic}
33
34
  .alerts=${args.alerts}
34
35
  .selected=${args.selected}
36
+ .showAlerts=${args.showAlerts}
35
37
  >
36
38
  <obi-placeholder slot="alert-topic-icon"></obi-placeholder>
37
39
  </obc-integration-fleet-button>
@@ -66,3 +68,10 @@ export const SelectedNoAlerts: Story = {
66
68
  alerts: {alarm: 0, warning: 0, caution: 0},
67
69
  },
68
70
  };
71
+
72
+ export const HideAlerts: Story = {
73
+ args: {
74
+ selected: false,
75
+ showAlerts: false,
76
+ },
77
+ };
@@ -2,6 +2,7 @@ import {LitElement, html, unsafeCSS} from 'lit';
2
2
  import {customElement} from '../../decorator.js';
3
3
  import compentStyle from './integration-fleet-button.css?inline';
4
4
  import {property} from 'lit/decorators.js';
5
+ import {classMap} from 'lit/directives/class-map.js';
5
6
  import {msg} from '@lit/localize';
6
7
  import '../../components/badge/badge.js';
7
8
 
@@ -24,6 +25,7 @@ export class ObcIntegrationFleetButton extends LitElement {
24
25
  warning: number;
25
26
  caution: number;
26
27
  } = {alarm: 0, warning: 0, caution: 0};
28
+ @property({type: Boolean}) showAlerts: boolean = false;
27
29
 
28
30
  override render() {
29
31
  const hasAlert =
@@ -47,7 +49,14 @@ export class ObcIntegrationFleetButton extends LitElement {
47
49
  </div>`
48
50
  )}
49
51
  </div>
50
- <div class="alerts ${hasAlert ? 'has-alert' : 'no-alert'}">
52
+ <div
53
+ class=${classMap({
54
+ alerts: true,
55
+ 'has-alert': hasAlert,
56
+ 'no-alert': !hasAlert,
57
+ 'show-alerts': this.showAlerts,
58
+ })}
59
+ >
51
60
  <div class="alert-icon ">
52
61
  <slot name="alert-topic-icon"></slot>
53
62
  </div>
@@ -27,7 +27,8 @@
27
27
  position: absolute;
28
28
  width: 4px;
29
29
  height: 4px;
30
- right: -3px;
30
+ right: -3.5px;
31
+ bottom: 0;
31
32
  border-radius: 50%;
32
33
  background: var(--element-neutral-color);
33
34
  border: 1px solid var(--border-silhouette-color);
@@ -19,13 +19,22 @@ type Story = StoryObj<ObcGraphMini>;
19
19
 
20
20
  export const Primary: Story = {};
21
21
 
22
+ export const WithRange: Story = {
23
+ args: {
24
+ minY: -10,
25
+ maxY: 10,
26
+ },
27
+ };
28
+
22
29
  export const Realtime: Story = {
23
30
  tags: ['skip-test'],
24
31
  args: {
25
32
  data: [
26
33
  Array.from({length: 60}, (_, i) => i),
27
- Array.from({length: 60}, (_, i) => Math.sin((i / 60) * 2 * Math.PI)),
34
+ Array.from({length: 60}, (_, i) => Math.sin((i / 60 / 5) * 2 * Math.PI)),
28
35
  ],
36
+ minY: -1.1,
37
+ maxY: 1.1,
29
38
  },
30
39
  play: async ({canvasElement}) => {
31
40
  const graph = canvasElement.querySelector('obc-graph-mini') as ObcGraphMini;
@@ -35,10 +44,9 @@ export const Realtime: Story = {
35
44
 
36
45
  let i = graph.data[0].length;
37
46
  setInterval(() => {
38
- console.log('updating', i);
39
47
  const data = graph.data;
40
48
  const x = [...data[0], i];
41
- const y = [...data[1], Math.sin((i / 60) * 2 * Math.PI)];
49
+ const y = [...data[1], Math.sin((i / 60 / 5) * 2 * Math.PI)];
42
50
  x.shift();
43
51
  y.shift();
44
52
  const newData: [number[], number[]] = [x, y];
@@ -36,8 +36,8 @@ export class ObcGraphMini extends LitElement {
36
36
 
37
37
  override firstUpdated() {
38
38
  const opts = {
39
- width: 48,
40
- height: 48,
39
+ width: 32,
40
+ height: 32,
41
41
  scales: {
42
42
  x: {time: false, show: false},
43
43
  y: {
@@ -61,7 +61,7 @@ export class ObcGraphMini extends LitElement {
61
61
  {},
62
62
  {
63
63
  stroke: this.getCssColor('--element-neutral-color'),
64
- width: 2,
64
+ width: 1,
65
65
  points: {show: false},
66
66
  },
67
67
  ],
@@ -83,7 +83,7 @@ export class ObcGraphMini extends LitElement {
83
83
  }
84
84
  const stroke = this.getCssColor('--element-neutral-color');
85
85
  // @ts-expect-error - stroke is not a property of the Series interface
86
- this.uplot.setSeries(1, {stroke: stroke, width: 2, points: {show: false}});
86
+ this.uplot.setSeries(1, {stroke: stroke, width: 1, points: {show: false}});
87
87
  }
88
88
 
89
89
  private updateY() {
@@ -107,7 +107,9 @@ export class ObcGraphMini extends LitElement {
107
107
  override render() {
108
108
  return html`
109
109
  <div class="chart-container">
110
- <div id="chart"><div id="dot" style="bottom: ${this.y}px; "></div></div>
110
+ <div id="chart">
111
+ <div id="dot" style="transform: translateY(${-this.y}px);"></div>
112
+ </div>
111
113
  </div>
112
114
  `;
113
115
  }
@@ -0,0 +1,55 @@
1
+ .chart-container {
2
+ width: 100%;
3
+ height: 100%;
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ position: relative;
8
+ }
9
+
10
+ canvas {
11
+ position: absolute;
12
+ width: 100%;
13
+ height: 100%;
14
+ top: 0;
15
+ left: 0;
16
+ }
17
+
18
+ #dot {
19
+ position: absolute;
20
+ width: 4px;
21
+ height: 4px;
22
+ right: -3px;
23
+ bottom: -3px;
24
+ border-radius: 50%;
25
+ background: var(--element-neutral-color);
26
+ border: 1px solid var(--border-silhouette-color);
27
+ z-index: 1;
28
+ }
29
+
30
+ .enhanced #dot {
31
+ background: var(--element-neutral-enhanced-color);
32
+ }
33
+
34
+ .medium #dot {
35
+ width: 6px;
36
+ height: 6px;
37
+ right: -4px;
38
+ bottom: -4px;
39
+ }
40
+
41
+ .large #dot {
42
+ width: 8px;
43
+ height: 8px;
44
+ right: -5px;
45
+ bottom: -5px;
46
+ }
47
+
48
+ #zero-line {
49
+ position: absolute;
50
+ width: 100%;
51
+ height: 1px;
52
+ bottom: -0.5px;
53
+ background: var(--element-disabled-color);
54
+ border-radius: 2px;
55
+ }
@@ -0,0 +1,116 @@
1
+ import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {
3
+ ObcIndicatorGraph,
4
+ ObcIndicatorGraphPriority,
5
+ ObcIndicatorGraphSize,
6
+ } from './indicator-graph.js';
7
+ import './indicator-graph.js';
8
+ import {widthDecorator} from '../../storybook-util.js';
9
+ const meta: Meta<typeof ObcIndicatorGraph> = {
10
+ title: 'Bars and Graphs/Indicator Graph',
11
+ tags: ['6.0', 'autodocs'],
12
+ component: 'obc-indicator-graph',
13
+ decorators: [widthDecorator],
14
+ args: {
15
+ data: [
16
+ Array.from({length: 30}, (_, i) => i),
17
+ Array.from({length: 30}, (_, i) => 2 + Math.sin((i / 30) * 2 * Math.PI)),
18
+ ],
19
+ width: 400,
20
+ height: 400,
21
+ },
22
+ argTypes: {
23
+ width: {control: {type: 'range', min: 100, max: 1000, step: 1}},
24
+ height: {control: {type: 'range', min: 100, max: 1000, step: 1}},
25
+ },
26
+ } satisfies Meta<ObcIndicatorGraph>;
27
+
28
+ export default meta;
29
+ type Story = StoryObj<ObcIndicatorGraph>;
30
+
31
+ export const Primary: Story = {};
32
+
33
+ export const WithRange: Story = {
34
+ args: {
35
+ layout: {
36
+ size: ObcIndicatorGraphSize.medium,
37
+ priority: ObcIndicatorGraphPriority.regular,
38
+ y: {min: -10, max: 10},
39
+ },
40
+ },
41
+ };
42
+
43
+ export const Enhanced: Story = {
44
+ args: {
45
+ layout: {
46
+ size: ObcIndicatorGraphSize.medium,
47
+ priority: ObcIndicatorGraphPriority.enhanced,
48
+ },
49
+ },
50
+ };
51
+
52
+ export const Small: Story = {
53
+ args: {
54
+ layout: {
55
+ size: ObcIndicatorGraphSize.small,
56
+ },
57
+ },
58
+ };
59
+
60
+ export const Medium: Story = {
61
+ args: {
62
+ layout: {
63
+ size: ObcIndicatorGraphSize.medium,
64
+ },
65
+ },
66
+ };
67
+
68
+ export const Large: Story = {
69
+ args: {
70
+ layout: {
71
+ size: ObcIndicatorGraphSize.large,
72
+ },
73
+ },
74
+ };
75
+
76
+ export const Realtime: Story = {
77
+ tags: ['skip-test'],
78
+ args: {
79
+ data: [
80
+ Array.from({length: 60}, (_, i) => i),
81
+ Array.from(
82
+ {length: 60},
83
+ (_, i) => 2 + Math.sin((i / 60 / 5) * 2 * Math.PI)
84
+ ),
85
+ ],
86
+ layout: {
87
+ size: ObcIndicatorGraphSize.medium,
88
+ priority: ObcIndicatorGraphPriority.regular,
89
+ y: {max: 4},
90
+ },
91
+ },
92
+ play: async ({canvasElement}) => {
93
+ const graph = canvasElement.querySelector(
94
+ 'obc-indicator-graph'
95
+ ) as ObcIndicatorGraph;
96
+ if (!graph) {
97
+ throw new Error('Graph not found');
98
+ }
99
+
100
+ let i = graph.data[0].length;
101
+ const intervalId = setInterval(() => {
102
+ if (!graph.isConnected) {
103
+ clearInterval(intervalId);
104
+ return;
105
+ }
106
+ const data = graph.data;
107
+ const x = [...data[0], i];
108
+ const y = [...data[1], 2 + Math.sin((i / 60 / 5) * 2 * Math.PI)];
109
+ x.shift();
110
+ y.shift();
111
+ const newData: [number[], number[]] = [x, y];
112
+ graph.data = newData;
113
+ i++;
114
+ }, 1000 / 60);
115
+ },
116
+ };