@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.88 → 2.0.0-next.90

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 (31) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +17477 -16780
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +742 -43
  4. package/dist/building-blocks/readout-block/readout-block.css.js +229 -0
  5. package/dist/building-blocks/readout-block/readout-block.css.js.map +1 -0
  6. package/dist/building-blocks/readout-block/readout-block.d.ts +146 -0
  7. package/dist/building-blocks/readout-block/readout-block.d.ts.map +1 -0
  8. package/dist/building-blocks/readout-block/readout-block.js +273 -0
  9. package/dist/building-blocks/readout-block/readout-block.js.map +1 -0
  10. package/dist/navigation-instruments/readout-list/readout-list.css.js +26 -0
  11. package/dist/navigation-instruments/readout-list/readout-list.css.js.map +1 -0
  12. package/dist/navigation-instruments/readout-list/readout-list.d.ts +70 -0
  13. package/dist/navigation-instruments/readout-list/readout-list.d.ts.map +1 -0
  14. package/dist/navigation-instruments/readout-list/readout-list.js +154 -0
  15. package/dist/navigation-instruments/readout-list/readout-list.js.map +1 -0
  16. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +139 -124
  17. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  18. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +57 -26
  19. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  20. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +139 -125
  21. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/building-blocks/readout-block/readout-block.css +192 -0
  24. package/src/building-blocks/readout-block/readout-block.stories.ts +347 -0
  25. package/src/building-blocks/readout-block/readout-block.ts +355 -0
  26. package/src/navigation-instruments/readout-list/readout-list.css +15 -0
  27. package/src/navigation-instruments/readout-list/readout-list.stories.ts +358 -0
  28. package/src/navigation-instruments/readout-list/readout-list.ts +216 -0
  29. package/src/navigation-instruments/readout-list-item/readout-list-item.css +141 -120
  30. package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +145 -29
  31. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +169 -146
@@ -0,0 +1,358 @@
1
+ import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {html} from 'lit';
3
+ import {expect} from 'storybook/test';
4
+ import './readout-list.js';
5
+ import {
6
+ ReadoutListItemSize,
7
+ ReadoutListItemPriority,
8
+ ReadoutListItemDataQuality,
9
+ type ReadoutValueOptions,
10
+ } from '../readout-list-item/readout-list-item.js';
11
+ import '../readout-list-item/readout-list-item.js';
12
+ import type {AlertFrameConfig} from '../../components/alert-frame/alert-frame.js';
13
+ import {
14
+ ObcAlertFrameMode,
15
+ ObcAlertFrameType,
16
+ } from '../../components/alert-frame/alert-frame.js';
17
+ import {AlertType} from '../../types.js';
18
+
19
+ type ListArgs = {
20
+ showDebugOverlay: boolean;
21
+ };
22
+
23
+ type Row = {
24
+ label: string;
25
+ value: number | null;
26
+ unit: string;
27
+ size?: ReadoutListItemSize;
28
+ hasDegree?: boolean;
29
+ fractionDigits?: number;
30
+ priority?: ReadoutListItemPriority;
31
+ hasSetpoint?: boolean;
32
+ setpoint?: number;
33
+ hasAdvice?: boolean;
34
+ advice?: number;
35
+ off?: boolean;
36
+ // Row-level and per-block state, so the list stories can smoke-test data
37
+ // quality + alert frames alongside the auto-alignment.
38
+ dataQuality?: ReadoutListItemDataQuality;
39
+ alert?: AlertFrameConfig;
40
+ valueDataQuality?: ReadoutListItemDataQuality;
41
+ valueAlert?: AlertFrameConfig;
42
+ setpointDataQuality?: ReadoutListItemDataQuality;
43
+ };
44
+
45
+ function renderRow(row: Row) {
46
+ const valueOptions =
47
+ row.valueDataQuality || row.valueAlert
48
+ ? {dataQuality: row.valueDataQuality, alert: row.valueAlert}
49
+ : undefined;
50
+ const setpointOptions = row.setpointDataQuality
51
+ ? {dataQuality: row.setpointDataQuality}
52
+ : undefined;
53
+ return html`
54
+ <obc-readout-list-item
55
+ .label=${row.label}
56
+ .unit=${row.unit}
57
+ .value=${row.value}
58
+ .size=${row.size ?? ReadoutListItemSize.small}
59
+ .hasDegree=${row.hasDegree ?? false}
60
+ .fractionDigits=${row.fractionDigits ?? 0}
61
+ .priority=${row.priority}
62
+ .off=${row.off ?? false}
63
+ .hasSetpoint=${row.hasSetpoint ?? false}
64
+ .setpoint=${row.setpoint}
65
+ .hasAdvice=${row.hasAdvice ?? false}
66
+ .advice=${row.advice}
67
+ .dataQuality=${row.dataQuality}
68
+ .alert=${row.alert ?? false}
69
+ .valueOptions=${valueOptions}
70
+ .setpointOptions=${setpointOptions}
71
+ ></obc-readout-list-item>
72
+ `;
73
+ }
74
+
75
+ function renderList(rows: Row[], showDebugOverlay: boolean) {
76
+ return html`
77
+ <div
78
+ data-obc-theme="day"
79
+ style="background: var(--container-background-color); padding: 16px; width: 360px; box-sizing: border-box;"
80
+ >
81
+ <obc-readout-list .showDebugOverlay=${showDebugOverlay}>
82
+ ${rows.map(renderRow)}
83
+ </obc-readout-list>
84
+ </div>
85
+ `;
86
+ }
87
+
88
+ const meta = {
89
+ title: 'Instruments/Readout List',
90
+ tags: ['autodocs', '6.0', 'wip'],
91
+ component: 'obc-readout-list',
92
+ args: {
93
+ showDebugOverlay: true,
94
+ },
95
+ argTypes: {
96
+ showDebugOverlay: {control: {type: 'boolean'}},
97
+ },
98
+ } satisfies Meta<ListArgs>;
99
+
100
+ export default meta;
101
+ type Story = StoryObj<ListArgs>;
102
+
103
+ const MIXED_ROWS: Row[] = [
104
+ {label: 'Temperature', value: 45, unit: 'C', hasDegree: true},
105
+ {label: 'Heading', value: 287, unit: 'T', hasDegree: true},
106
+ {label: 'Pressure', value: 1013, unit: 'Pa', fractionDigits: 1},
107
+ {label: 'Distance', value: 4.2, unit: 'miles', fractionDigits: 1},
108
+ {label: 'Speed', value: 18, unit: 'kn', fractionDigits: 1},
109
+ ];
110
+
111
+ export const Default: Story = {
112
+ render: (args) => renderList(MIXED_ROWS, args.showDebugOverlay),
113
+ };
114
+
115
+ const DEGREE_ROWS: Row[] = [
116
+ {label: 'Heading', value: 287, unit: 'T', hasDegree: true},
117
+ {label: 'COG', value: 92, unit: 'T', hasDegree: true},
118
+ {label: 'Speed', value: 18, unit: 'kn'},
119
+ {label: 'Depth', value: 124, unit: 'm'},
120
+ ];
121
+
122
+ export const Degrees: Story = {
123
+ render: (args) => renderList(DEGREE_ROWS, args.showDebugOverlay),
124
+ };
125
+
126
+ const WARNING_FRAME: AlertFrameConfig = {
127
+ status: AlertType.Warning,
128
+ mode: ObcAlertFrameMode.ackedActive,
129
+ type: ObcAlertFrameType.Regular,
130
+ };
131
+ const ALARM_FRAME_ICON: AlertFrameConfig = {
132
+ status: AlertType.Alarm,
133
+ mode: ObcAlertFrameMode.ackedActive,
134
+ type: ObcAlertFrameType.SmallSideFlip,
135
+ showAlertCategoryIcon: true,
136
+ };
137
+
138
+ export const WithSetpoints: Story = {
139
+ render: (args) =>
140
+ renderList(
141
+ [
142
+ {
143
+ label: 'Heading',
144
+ value: 287,
145
+ unit: 'T',
146
+ hasDegree: true,
147
+ hasSetpoint: true,
148
+ setpoint: 290,
149
+ },
150
+ // invalid setpoint chip — auto-aligned alongside the others
151
+ {
152
+ label: 'Speed',
153
+ value: 8,
154
+ unit: 'kn',
155
+ fractionDigits: 1,
156
+ hasSetpoint: true,
157
+ setpoint: 12,
158
+ setpointDataQuality: ReadoutListItemDataQuality.invalid,
159
+ },
160
+ // advice reference
161
+ {label: 'Depth', value: 1013, unit: 'm', hasAdvice: true, advice: 1010},
162
+ // per-value alert frame — wraps value + unit
163
+ {label: 'Fuel', value: 42, unit: '%', valueAlert: WARNING_FRAME},
164
+ // whole row invalid + null value (dash)
165
+ {
166
+ label: 'Wind',
167
+ value: null,
168
+ unit: 'kn',
169
+ dataQuality: ReadoutListItemDataQuality.invalid,
170
+ },
171
+ // whole row low-integrity + row-level alert frame with a badge-icon flap
172
+ {
173
+ label: 'Course',
174
+ value: 120,
175
+ unit: 'T',
176
+ hasDegree: true,
177
+ dataQuality: ReadoutListItemDataQuality.lowIntegrity,
178
+ alert: ALARM_FRAME_ICON,
179
+ },
180
+ ],
181
+ args.showDebugOverlay
182
+ ),
183
+ };
184
+
185
+ export const TestDynamicRow: Story = {
186
+ render: (args) => renderList(MIXED_ROWS, args.showDebugOverlay),
187
+ play: async ({canvasElement}) => {
188
+ const list = canvasElement.querySelector('obc-readout-list');
189
+ const row = document.createElement(
190
+ 'obc-readout-list-item'
191
+ ) as HTMLElement & {
192
+ label: string;
193
+ value: number;
194
+ unit: string;
195
+ valueOptions?: ReadoutValueOptions;
196
+ };
197
+ row.label = 'Added';
198
+ row.value = 9;
199
+ row.unit = 'kn';
200
+ list?.appendChild(row);
201
+ await new Promise((resolve) => requestAnimationFrame(resolve));
202
+ await new Promise((resolve) => requestAnimationFrame(resolve));
203
+ await expect(row.valueOptions?.spaceReserver).toBe('0000.0');
204
+ },
205
+ };
206
+
207
+ // Richer starting set for the interactive demo: a plain degree row, an invalid
208
+ // setpoint, an advice reference, a per-value alert frame, a null/invalid row and
209
+ // a low-integrity row with a row-level alert frame — so the manual add/remove /
210
+ // re-align flow is exercised against the full range of state.
211
+ const MANUAL_ROWS: Row[] = [
212
+ {label: 'Heading', value: 287, unit: 'T', hasDegree: true},
213
+ {
214
+ label: 'Speed',
215
+ value: 8,
216
+ unit: 'kn',
217
+ fractionDigits: 1,
218
+ hasSetpoint: true,
219
+ setpoint: 12,
220
+ setpointDataQuality: ReadoutListItemDataQuality.invalid,
221
+ },
222
+ {label: 'Depth', value: 1013, unit: 'm', hasAdvice: true, advice: 1010},
223
+ {label: 'Fuel', value: 42, unit: '%', valueAlert: WARNING_FRAME},
224
+ {
225
+ label: 'Wind',
226
+ value: null,
227
+ unit: 'kn',
228
+ dataQuality: ReadoutListItemDataQuality.invalid,
229
+ },
230
+ {
231
+ label: 'Course',
232
+ value: 120,
233
+ unit: 'T',
234
+ hasDegree: true,
235
+ dataQuality: ReadoutListItemDataQuality.lowIntegrity,
236
+ alert: ALARM_FRAME_ICON,
237
+ },
238
+ ];
239
+
240
+ /**
241
+ * **Manual (Interactive)** — the rows are slotted children, so they cannot be
242
+ * driven by Storybook controls. Use the buttons to add / remove rows (a
243
+ * structural change the list re-aligns automatically) and to change the first
244
+ * row's value / unit (a property-only change, so it calls the public
245
+ * {@link ObcReadoutList.align} method). The debug overlay shows the reserved
246
+ * column widths reacting to each change. The starting rows include data-quality
247
+ * and alert-frame variations for a quick visual smoke test.
248
+ */
249
+ export const Manual: Story = {
250
+ name: 'Manual (Interactive)',
251
+ tags: ['skip-test'],
252
+ render: (args) => html`
253
+ <div
254
+ data-obc-theme="day"
255
+ style="background: var(--container-background-color); padding: 16px; width: 380px; box-sizing: border-box; display: flex; flex-direction: column; gap: 16px;"
256
+ >
257
+ <div style="font-size: 14px; color: var(--element-neutral-color, #888);">
258
+ Rows are slotted children, so Storybook controls can't drive them. Use
259
+ the buttons to add / remove rows (the list re-aligns automatically) and
260
+ to change the first row's value / unit (a property-only change, so it
261
+ calls
262
+ <code>align()</code>). The debug overlay shows the reserved column
263
+ widths.
264
+ </div>
265
+ <obc-readout-list
266
+ id="manual-list"
267
+ .showDebugOverlay=${args.showDebugOverlay}
268
+ >
269
+ ${MANUAL_ROWS.map(renderRow)}
270
+ </obc-readout-list>
271
+ <div style="display: flex; gap: 8px; flex-wrap: wrap;">
272
+ <button id="m-add" style="padding: 6px 12px; cursor: pointer;">
273
+ Add row
274
+ </button>
275
+ <button id="m-remove" style="padding: 6px 12px; cursor: pointer;">
276
+ Remove row
277
+ </button>
278
+ <button id="m-bump" style="padding: 6px 12px; cursor: pointer;">
279
+ Bump first value ×10
280
+ </button>
281
+ <button id="m-unit" style="padding: 6px 12px; cursor: pointer;">
282
+ Toggle first unit
283
+ </button>
284
+ </div>
285
+ <div
286
+ id="m-status"
287
+ style="font: 12px/1.4 monospace; color: var(--element-neutral-color, #666);"
288
+ >
289
+ ${MANUAL_ROWS.length} rows
290
+ </div>
291
+ </div>
292
+ `,
293
+ play: async ({canvasElement}) => {
294
+ const list = canvasElement.querySelector('#manual-list') as
295
+ | (HTMLElement & {align: () => void})
296
+ | null;
297
+ const status = canvasElement.querySelector(
298
+ '#m-status'
299
+ ) as HTMLElement | null;
300
+ const addBtn = canvasElement.querySelector(
301
+ '#m-add'
302
+ ) as HTMLButtonElement | null;
303
+ const removeBtn = canvasElement.querySelector(
304
+ '#m-remove'
305
+ ) as HTMLButtonElement | null;
306
+ const bumpBtn = canvasElement.querySelector(
307
+ '#m-bump'
308
+ ) as HTMLButtonElement | null;
309
+ const unitBtn = canvasElement.querySelector(
310
+ '#m-unit'
311
+ ) as HTMLButtonElement | null;
312
+ if (!list || !status || !addBtn || !removeBtn || !bumpBtn || !unitBtn) {
313
+ return;
314
+ }
315
+
316
+ const UNITS = ['kn', 'm', 'C', 'miles'];
317
+ let added = 0;
318
+ const rows = () =>
319
+ Array.from(
320
+ list.querySelectorAll('obc-readout-list-item')
321
+ ) as (HTMLElement & {
322
+ value: number | null;
323
+ unit: string;
324
+ })[];
325
+ const refresh = () => {
326
+ status.textContent = `${rows().length} rows`;
327
+ };
328
+
329
+ addBtn.onclick = () => {
330
+ added += 1;
331
+ const row = document.createElement(
332
+ 'obc-readout-list-item'
333
+ ) as HTMLElement & {label: string; value: number; unit: string};
334
+ row.label = `Row ${added}`;
335
+ row.value = added * 7;
336
+ row.unit = UNITS[added % UNITS.length];
337
+ list.appendChild(row);
338
+ refresh();
339
+ };
340
+ removeBtn.onclick = () => {
341
+ const all = rows();
342
+ all[all.length - 1]?.remove();
343
+ refresh();
344
+ };
345
+ bumpBtn.onclick = () => {
346
+ const first = rows()[0];
347
+ if (!first) return;
348
+ first.value = Math.round((first.value ?? 1) * 10);
349
+ list.align();
350
+ };
351
+ unitBtn.onclick = () => {
352
+ const first = rows()[0];
353
+ if (!first) return;
354
+ first.unit = first.unit === 'miles' ? 'T' : 'miles';
355
+ list.align();
356
+ };
357
+ },
358
+ };
@@ -0,0 +1,216 @@
1
+ import {LitElement, html, unsafeCSS, type PropertyValues} from 'lit';
2
+ import {property} from 'lit/decorators.js';
3
+ import componentStyle from './readout-list.css?inline';
4
+ import {customElement} from '../../decorator.js';
5
+ import '../readout-list-item/readout-list-item.js';
6
+ import {ObcReadoutListItem} from '../readout-list-item/readout-list-item.js';
7
+
8
+ const ITEM_TAG = 'obc-readout-list-item';
9
+
10
+ /** Child attributes whose change should re-trigger alignment (HTML-attribute usage). */
11
+ const OBSERVED_ATTRIBUTES = [
12
+ 'unit',
13
+ 'src',
14
+ 'value',
15
+ 'setpoint',
16
+ 'advice',
17
+ 'max-digits',
18
+ 'fraction-digits',
19
+ 'has-degree',
20
+ 'has-setpoint',
21
+ 'has-advice',
22
+ ];
23
+
24
+ /** Integer-digit count of a numeric value (sign and fraction excluded). */
25
+ function integerDigitCount(value: number | null | undefined): number {
26
+ if (value === null || value === undefined || Number.isNaN(value)) {
27
+ return 0;
28
+ }
29
+ return String(Math.trunc(Math.abs(value))).length;
30
+ }
31
+
32
+ /**
33
+ * `<obc-readout-list>` – A container that groups `<obc-readout-list-item>` rows
34
+ * and **auto-aligns their columns**.
35
+ *
36
+ * Because each row is its own custom element, cross-row column alignment is not
37
+ * automatic. This container inspects its rows and pushes shared width reservers
38
+ * down so the unit column, the value / setpoint / advice columns and the source
39
+ * column all line up — the same effect the `Readout List Item → ColumnAlignment`
40
+ * story achieves by hand, done for you. Alignment is always on.
41
+ *
42
+ * What it equalizes — derived from each row's data and broadcast to every row, so
43
+ * the widest value / unit / source is never clipped:
44
+ * - **Unit:** the longest `unit` becomes every row's unit space-reserver.
45
+ * - **Value / setpoint / advice:** the widest numeric width (max integer digits +
46
+ * max fraction digits across rows, derived from each row's `maxDigits` /
47
+ * `fractionDigits` / current values) is reserved on every row's numeric blocks.
48
+ * Reserving off digit counts keeps it stable as live values update.
49
+ * - **Source:** the longest `src` becomes every row's source space-reserver.
50
+ * - **Degree:** if any row has a degree, non-degree rows reserve the degree column
51
+ * (`hasDegreeSpacer`) so their digits line up with the degree rows; the spacer is
52
+ * cleared once no degree rows remain.
53
+ *
54
+ * The list **owns** these reservers: it recomputes them from the rows' data on
55
+ * every pass (and clears stale reservers / spacers when rows change), so a
56
+ * `spaceReserver` set directly on a row inside the list is overwritten. Drive the
57
+ * data (`maxDigits` / `fractionDigits` / `unit` / `src`) rather than setting a
58
+ * manual reserver when a row lives in a list.
59
+ *
60
+ * Alignment runs on `slotchange` and on child mutations (added/removed rows and
61
+ * HTML-attribute changes). When rows are updated via JS **properties** only (no
62
+ * attribute/DOM mutation), call {@link align} to recompute.
63
+ *
64
+ * @experimental Pilot for the new primitives + per-block options Readout API; the
65
+ * API may change in a future release.
66
+ *
67
+ * @slot - The `<obc-readout-list-item>` rows.
68
+ *
69
+ * @csspart list - The vertical stack container.
70
+ */
71
+ @customElement('obc-readout-list')
72
+ export class ObcReadoutList extends LitElement {
73
+ /**
74
+ * Development aid: outline each row's readout building blocks (red), degree
75
+ * columns (blue) and degree spacer (green) so the reserved column widths are
76
+ * visible. Propagated to every row. Off by default.
77
+ */
78
+ @property({type: Boolean, reflect: true}) showDebugOverlay = false;
79
+
80
+ private mutationObserver?: MutationObserver;
81
+
82
+ protected override updated(changed: PropertyValues): void {
83
+ super.updated(changed);
84
+ // A host-property change is not seen by the child MutationObserver, so
85
+ // re-propagate `showDebugOverlay` (and re-align) when it toggles.
86
+ if (changed.has('showDebugOverlay')) {
87
+ this.align();
88
+ }
89
+ }
90
+
91
+ override disconnectedCallback(): void {
92
+ this.mutationObserver?.disconnect();
93
+ this.mutationObserver = undefined;
94
+ super.disconnectedCallback();
95
+ }
96
+
97
+ /** The `obc-readout-list-item` rows, including those nested in wrapper elements. */
98
+ private get items(): ObcReadoutListItem[] {
99
+ const slot = this.shadowRoot?.querySelector('slot');
100
+ const assigned = slot?.assignedElements({flatten: true}) ?? [];
101
+ return assigned.flatMap((el) =>
102
+ el.tagName.toLowerCase() === ITEM_TAG
103
+ ? [el as ObcReadoutListItem]
104
+ : Array.from(el.querySelectorAll<ObcReadoutListItem>(ITEM_TAG))
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Recompute and apply the shared column reservers across all rows. Call this
110
+ * after updating rows via JS properties only (attribute/DOM changes are picked
111
+ * up automatically).
112
+ */
113
+ align(): void {
114
+ const items = this.items;
115
+ if (items.length === 0) {
116
+ return;
117
+ }
118
+
119
+ let maxIntegerDigits = 0;
120
+ let maxFractionDigits = 0;
121
+ let longestUnit = '';
122
+ let longestSrc = '';
123
+ let anyDegree = false;
124
+
125
+ for (const item of items) {
126
+ maxFractionDigits = Math.max(maxFractionDigits, item.fractionDigits ?? 0);
127
+ maxIntegerDigits = Math.max(
128
+ maxIntegerDigits,
129
+ item.maxDigits ?? 0,
130
+ integerDigitCount(item.value),
131
+ item.hasSetpoint ? integerDigitCount(item.setpoint) : 0,
132
+ item.hasAdvice ? integerDigitCount(item.advice) : 0
133
+ );
134
+ if (item.unit && item.unit.length > longestUnit.length) {
135
+ longestUnit = item.unit;
136
+ }
137
+ if (item.src && item.src.length > longestSrc.length) {
138
+ longestSrc = item.src;
139
+ }
140
+ if (item.hasDegree) {
141
+ anyDegree = true;
142
+ }
143
+ }
144
+
145
+ const numericReserver =
146
+ maxIntegerDigits > 0
147
+ ? '0'.repeat(maxIntegerDigits) +
148
+ (maxFractionDigits > 0 ? `.${'0'.repeat(maxFractionDigits)}` : '')
149
+ : undefined;
150
+
151
+ // Our writes are properties (no reflected attributes), so they do not trigger
152
+ // the MutationObserver; disconnecting around them is belt-and-suspenders.
153
+ this.mutationObserver?.disconnect();
154
+ for (const item of items) {
155
+ // Recompute every reserver / spacer on every pass (do not gate on a value
156
+ // being present), so stale state clears when rows change — e.g. when the
157
+ // last degree row, the last unit, or the last source is removed.
158
+ item.valueOptions = {
159
+ ...item.valueOptions,
160
+ spaceReserver: numericReserver,
161
+ };
162
+ item.setpointOptions = {
163
+ ...item.setpointOptions,
164
+ spaceReserver: numericReserver,
165
+ };
166
+ item.adviceOptions = {
167
+ ...item.adviceOptions,
168
+ spaceReserver: numericReserver,
169
+ };
170
+ item.unitOptions = {
171
+ ...item.unitOptions,
172
+ spaceReserver: longestUnit || undefined,
173
+ };
174
+ item.srcOptions = {
175
+ ...item.srcOptions,
176
+ spaceReserver: longestSrc || undefined,
177
+ };
178
+ item.hasDegreeSpacer = anyDegree && !item.hasDegree;
179
+ item.showDebugOverlay = this.showDebugOverlay;
180
+ }
181
+ this.observeChildren();
182
+ }
183
+
184
+ private observeChildren(): void {
185
+ if (!this.mutationObserver) {
186
+ this.mutationObserver = new MutationObserver(() => this.align());
187
+ }
188
+ this.mutationObserver.observe(this, {
189
+ childList: true,
190
+ subtree: true,
191
+ attributes: true,
192
+ attributeFilter: OBSERVED_ATTRIBUTES,
193
+ });
194
+ }
195
+
196
+ private handleSlotChange = (): void => {
197
+ this.align();
198
+ this.observeChildren();
199
+ };
200
+
201
+ override render() {
202
+ return html`
203
+ <div class="list" part="list">
204
+ <slot @slotchange=${this.handleSlotChange}></slot>
205
+ </div>
206
+ `;
207
+ }
208
+
209
+ static override styles = unsafeCSS(componentStyle);
210
+ }
211
+
212
+ declare global {
213
+ interface HTMLElementTagNameMap {
214
+ 'obc-readout-list': ObcReadoutList;
215
+ }
216
+ }