@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.143 → 2.0.0-next.145

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 (59) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +4865 -4498
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +259 -40
  4. package/dist/components/app-button/app-button.css.js +56 -0
  5. package/dist/components/app-button/app-button.css.js.map +1 -1
  6. package/dist/components/checkbox-item/checkbox-item.css.js +137 -12
  7. package/dist/components/checkbox-item/checkbox-item.css.js.map +1 -1
  8. package/dist/components/checkbox-item/checkbox-item.d.ts +68 -39
  9. package/dist/components/checkbox-item/checkbox-item.d.ts.map +1 -1
  10. package/dist/components/checkbox-item/checkbox-item.js +66 -25
  11. package/dist/components/checkbox-item/checkbox-item.js.map +1 -1
  12. package/dist/components/checkbox-list/checkbox-list-visibility.d.ts +16 -0
  13. package/dist/components/checkbox-list/checkbox-list-visibility.d.ts.map +1 -0
  14. package/dist/components/checkbox-list/checkbox-list-visibility.js +12 -0
  15. package/dist/components/checkbox-list/checkbox-list-visibility.js.map +1 -0
  16. package/dist/components/checkbox-list/checkbox-list.css.js +23 -0
  17. package/dist/components/checkbox-list/checkbox-list.css.js.map +1 -0
  18. package/dist/components/checkbox-list/checkbox-list.d.ts +66 -0
  19. package/dist/components/checkbox-list/checkbox-list.d.ts.map +1 -0
  20. package/dist/components/checkbox-list/checkbox-list.js +81 -0
  21. package/dist/components/checkbox-list/checkbox-list.js.map +1 -0
  22. package/dist/integration-systems/integration-app-bar/integration-app-bar.css.js +12 -1
  23. package/dist/integration-systems/integration-app-bar/integration-app-bar.css.js.map +1 -1
  24. package/dist/integration-systems/integration-app-bar/integration-app-bar.d.ts +2 -2
  25. package/dist/integration-systems/integration-app-bar/integration-app-bar.d.ts.map +1 -1
  26. package/dist/integration-systems/integration-app-bar/integration-app-bar.js +1 -21
  27. package/dist/integration-systems/integration-app-bar/integration-app-bar.js.map +1 -1
  28. package/dist/navigation-instruments/readout/readout.d.ts +11 -2
  29. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  30. package/dist/navigation-instruments/readout/readout.js +18 -0
  31. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  32. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +25 -0
  33. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  34. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +20 -4
  35. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  36. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +18 -6
  37. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/components/app-button/app-button.css +2 -0
  40. package/src/components/app-button/app-button.stories.ts +26 -0
  41. package/src/components/checkbox-item/checkbox-item.css +63 -17
  42. package/src/components/checkbox-item/checkbox-item.spec.ts +115 -0
  43. package/src/components/checkbox-item/checkbox-item.stories.ts +82 -97
  44. package/src/components/checkbox-item/checkbox-item.ts +118 -64
  45. package/src/components/checkbox-list/checkbox-list-visibility.spec.ts +68 -0
  46. package/src/components/checkbox-list/checkbox-list-visibility.ts +23 -0
  47. package/src/components/checkbox-list/checkbox-list.css +13 -0
  48. package/src/components/checkbox-list/checkbox-list.spec.ts +93 -0
  49. package/src/components/checkbox-list/checkbox-list.stories.ts +161 -0
  50. package/src/components/checkbox-list/checkbox-list.ts +130 -0
  51. package/src/integration-systems/integration-app-bar/integration-app-bar-sizing.stories.ts +130 -0
  52. package/src/integration-systems/integration-app-bar/integration-app-bar.css +11 -1
  53. package/src/integration-systems/integration-app-bar/integration-app-bar.ts +3 -27
  54. package/src/navigation-instruments/readout/readout.stories.ts +57 -4
  55. package/src/navigation-instruments/readout/readout.ts +28 -1
  56. package/src/navigation-instruments/readout-list/readout-list.stories.ts +62 -2
  57. package/src/navigation-instruments/readout-list-item/readout-list-item.css +21 -0
  58. package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +56 -3
  59. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +37 -19
@@ -0,0 +1,93 @@
1
+ import {afterEach, describe, expect, it} from 'vitest';
2
+ import './checkbox-list.js';
3
+ import '../checkbox-item/checkbox-item.js';
4
+ import type {ObcCheckboxItem} from '../checkbox-item/checkbox-item.js';
5
+ import {ObcCheckboxItemHoverStyle} from '../checkbox-item/checkbox-item.js';
6
+
7
+ const nextTick = () => new Promise((resolve) => setTimeout(resolve, 0));
8
+
9
+ function item(level: number, expandable = false, expanded = false) {
10
+ const el = document.createElement('obc-checkbox-item');
11
+ el.label = `Level ${level}`;
12
+ el.level = level;
13
+ el.expandable = expandable;
14
+ el.expanded = expanded;
15
+ return el;
16
+ }
17
+
18
+ async function mount(items: ObcCheckboxItem[]) {
19
+ const list = document.createElement('obc-checkbox-list');
20
+ items.forEach((i) => list.appendChild(i));
21
+ document.body.appendChild(list);
22
+ await list.updateComplete;
23
+ await nextTick();
24
+ return list;
25
+ }
26
+
27
+ afterEach(() => {
28
+ document.body.innerHTML = '';
29
+ });
30
+
31
+ describe('obc-checkbox-list', () => {
32
+ it('hides rows under a collapsed parent', async () => {
33
+ const rows = [item(1, true, false), item(2), item(1)];
34
+ await mount(rows);
35
+ expect(rows.map((r) => r.hidden)).toEqual([false, true, false]);
36
+ });
37
+
38
+ it('applies expand-toggle to the item and recomputes', async () => {
39
+ const rows = [item(1, true, false), item(2)];
40
+ await mount(rows);
41
+ rows[0]
42
+ .shadowRoot!.querySelector<HTMLButtonElement>('.chevron-button')!
43
+ .click();
44
+ await rows[0].updateComplete;
45
+ await nextTick();
46
+ expect(rows[0].expanded).toBe(true);
47
+ expect(rows[1].hidden).toBe(false);
48
+ });
49
+
50
+ it('recomputes when expanded is changed from outside', async () => {
51
+ const rows = [item(1, true, true), item(2)];
52
+ await mount(rows);
53
+ rows[0].expanded = false;
54
+ await rows[0].updateComplete;
55
+ await nextTick();
56
+ expect(rows[1].hidden).toBe(true);
57
+ });
58
+
59
+ it('forwards hoverStyle to every item, including late ones', async () => {
60
+ const rows = [item(0), item(0)];
61
+ const list = await mount(rows);
62
+ list.hoverStyle = ObcCheckboxItemHoverStyle.visualTarget;
63
+ await list.updateComplete;
64
+ expect(rows.map((r) => r.hoverStyle)).toEqual([
65
+ 'visual-target',
66
+ 'visual-target',
67
+ ]);
68
+ const late = item(0);
69
+ list.appendChild(late);
70
+ await nextTick();
71
+ expect(late.hoverStyle).toBe('visual-target');
72
+ });
73
+
74
+ it('recomputes when a collapsed row stops being expandable', async () => {
75
+ const rows = [item(1, true, false), item(2)];
76
+ await mount(rows);
77
+ expect(rows[1].hidden).toBe(true);
78
+ rows[0].expandable = false;
79
+ await rows[0].updateComplete;
80
+ await nextTick();
81
+ expect(rows[1].hidden).toBe(false);
82
+ });
83
+
84
+ it('is the group and keeps a role the consumer set', async () => {
85
+ const list = await mount([]);
86
+ expect(list.getAttribute('role')).toBe('group');
87
+ const custom = document.createElement('obc-checkbox-list');
88
+ custom.setAttribute('role', 'presentation');
89
+ document.body.appendChild(custom);
90
+ await custom.updateComplete;
91
+ expect(custom.getAttribute('role')).toBe('presentation');
92
+ });
93
+ });
@@ -0,0 +1,161 @@
1
+ import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {html} from 'lit';
3
+ import {ObcCheckboxList} from './checkbox-list.js';
4
+ import './checkbox-list.js';
5
+ import '../checkbox-item/checkbox-item.js';
6
+ import {ObcCheckboxItemHoverStyle} from '../checkbox-item/checkbox-item.js';
7
+ import {CheckboxStatus} from '../checkbox/checkbox.js';
8
+
9
+ const meta = {
10
+ title: 'UI Components/Selection Controls and Switches/Checkbox List',
11
+ tags: ['autodocs', '6.1', 'beta'],
12
+ component: 'obc-checkbox-list',
13
+ parameters: {
14
+ layout: 'centered',
15
+ actions: {
16
+ handles: ['change', 'expand-toggle'],
17
+ },
18
+ },
19
+ argTypes: {
20
+ hoverStyle: {
21
+ name: 'Hover style',
22
+ options: Object.values(ObcCheckboxItemHoverStyle),
23
+ control: {type: 'select'},
24
+ },
25
+ },
26
+ args: {
27
+ hoverStyle: ObcCheckboxItemHoverStyle.touchTarget,
28
+ },
29
+ } satisfies Meta<ObcCheckboxList>;
30
+
31
+ export default meta;
32
+ type Story = StoryObj<ObcCheckboxList>;
33
+
34
+ const regularRows = html`
35
+ <obc-checkbox-item label="Label"></obc-checkbox-item>
36
+ <obc-checkbox-item
37
+ label="Label"
38
+ status=${CheckboxStatus.checked}
39
+ ></obc-checkbox-item>
40
+ <obc-checkbox-item
41
+ label="Label"
42
+ status=${CheckboxStatus.checked}
43
+ ></obc-checkbox-item>
44
+ <obc-checkbox-item label="Label"></obc-checkbox-item>
45
+ `;
46
+
47
+ const nestedRows = html`
48
+ <obc-checkbox-item
49
+ level="1"
50
+ expandable
51
+ expanded
52
+ label="Label"
53
+ status=${CheckboxStatus.mixed}
54
+ ></obc-checkbox-item>
55
+ <obc-checkbox-item
56
+ level="2"
57
+ label="Label"
58
+ status=${CheckboxStatus.checked}
59
+ ></obc-checkbox-item>
60
+ <obc-checkbox-item level="2" label="Label"></obc-checkbox-item>
61
+ <obc-checkbox-item level="1" expandable label="Label"></obc-checkbox-item>
62
+ <obc-checkbox-item
63
+ level="2"
64
+ label="Hidden until expanded"
65
+ ></obc-checkbox-item>
66
+ `;
67
+
68
+ /** The Figma frame widths: 240px for a regular list, 320px for a nested one. */
69
+ const renderRegular: Story['render'] = (args) =>
70
+ html`<obc-checkbox-list style="width:240px" .hoverStyle=${args.hoverStyle}>
71
+ ${regularRows}
72
+ </obc-checkbox-list>`;
73
+
74
+ const renderNested: Story['render'] = (args) =>
75
+ html`<obc-checkbox-list style="width:320px" .hoverStyle=${args.hoverStyle}>
76
+ ${nestedRows}
77
+ </obc-checkbox-list>`;
78
+
79
+ export const Regular: Story = {
80
+ render: renderRegular,
81
+ };
82
+
83
+ export const Nested: Story = {
84
+ render: renderNested,
85
+ };
86
+
87
+ export const RegularVisualTarget: Story = {
88
+ args: {hoverStyle: ObcCheckboxItemHoverStyle.visualTarget},
89
+ render: renderRegular,
90
+ };
91
+
92
+ export const NestedVisualTarget: Story = {
93
+ args: {hoverStyle: ObcCheckboxItemHoverStyle.visualTarget},
94
+ render: renderNested,
95
+ };
96
+
97
+ export const WithDescriptions: Story = {
98
+ render: (args) =>
99
+ html`<obc-checkbox-list style="width:320px" .hoverStyle=${args.hoverStyle}>
100
+ <obc-checkbox-item
101
+ level="1"
102
+ expandable
103
+ expanded
104
+ label="Reports"
105
+ description="All report types"
106
+ status=${CheckboxStatus.mixed}
107
+ ></obc-checkbox-item>
108
+ <obc-checkbox-item
109
+ level="2"
110
+ label="Monthly"
111
+ description="Sent on the 1st"
112
+ status=${CheckboxStatus.checked}
113
+ ></obc-checkbox-item>
114
+ <obc-checkbox-item
115
+ level="2"
116
+ label="Quarterly"
117
+ description="Sent every third month"
118
+ ></obc-checkbox-item>
119
+ </obc-checkbox-list>`,
120
+ };
121
+
122
+ /** `level` is not capped at 2: each level above 1 adds one more spacer. */
123
+ export const DeepNesting: Story = {
124
+ render: (args) =>
125
+ html`<obc-checkbox-list style="width:320px" .hoverStyle=${args.hoverStyle}>
126
+ <obc-checkbox-item
127
+ level="1"
128
+ expandable
129
+ expanded
130
+ label="Documents"
131
+ status=${CheckboxStatus.mixed}
132
+ ></obc-checkbox-item>
133
+ <obc-checkbox-item
134
+ level="2"
135
+ expandable
136
+ expanded
137
+ label="Reports"
138
+ status=${CheckboxStatus.mixed}
139
+ ></obc-checkbox-item>
140
+ <obc-checkbox-item
141
+ level="3"
142
+ label="Monthly"
143
+ status=${CheckboxStatus.checked}
144
+ ></obc-checkbox-item>
145
+ <obc-checkbox-item level="3" label="Quarterly"></obc-checkbox-item>
146
+ <obc-checkbox-item
147
+ level="2"
148
+ expandable
149
+ label="Presentations"
150
+ ></obc-checkbox-item>
151
+ <obc-checkbox-item
152
+ level="3"
153
+ label="Hidden until expanded"
154
+ ></obc-checkbox-item>
155
+ <obc-checkbox-item
156
+ level="1"
157
+ expandable
158
+ label="Images"
159
+ ></obc-checkbox-item>
160
+ </obc-checkbox-list>`,
161
+ };
@@ -0,0 +1,130 @@
1
+ import {LitElement, html, unsafeCSS, type PropertyValues} from 'lit';
2
+ import {property} from 'lit/decorators.js';
3
+ import {customElement} from '../../decorator.js';
4
+ import componentStyle from './checkbox-list.css?inline';
5
+ import {
6
+ ObcCheckboxItem,
7
+ ObcCheckboxItemHoverStyle,
8
+ type ObcCheckboxItemExpandToggleEvent,
9
+ } from '../checkbox-item/checkbox-item.js';
10
+ import {computeHiddenRows} from './checkbox-list-visibility.js';
11
+
12
+ /**
13
+ * `<obc-checkbox-list>` – A vertical list of `<obc-checkbox-item>` rows that
14
+ * collapses the rows under a closed parent.
15
+ *
16
+ * ### Overview
17
+ * Rows are slotted flat, in document order; the tree is read from each row's
18
+ * `level`. When an expandable row is collapsed, the list sets `hidden` on
19
+ * every following row with a greater level until a row of equal or smaller
20
+ * level. The list also applies its `hoverStyle` to every row so a list reads
21
+ * as one surface.
22
+ *
23
+ * ### Features
24
+ * - Flat markup, level-driven hierarchy — no nesting of elements required.
25
+ * - Handles `expand-toggle`: sets the row's `expanded` and recomputes.
26
+ * - Reacts to `expandable`, `expanded` and `level` changes made from outside.
27
+ * - `hoverStyle` forwarded to slotted rows, including rows added later.
28
+ *
29
+ * ### Usage Guidelines
30
+ * - Start levels at 1 when any row is expandable so chevrons and checkboxes
31
+ * align; use level 0 for a plain list.
32
+ * - The list owns the `hidden` attribute of its rows; do not bind it yourself.
33
+ * - Selection stays per row: listen to each row's `change`.
34
+ *
35
+ * ### Accessibility
36
+ * The host is the `role="group"`; rows are ordinary checkboxes and buttons in
37
+ * the natural tab order and hidden rows leave it. This is a group of
38
+ * checkboxes with disclosure buttons, not a tree widget, so there is no
39
+ * roving tabindex. Name the group with `aria-label` or `aria-labelledby` on
40
+ * the host.
41
+ *
42
+ * ### Example
43
+ * ```html
44
+ * <obc-checkbox-list aria-label="Report types">
45
+ * <obc-checkbox-item level="1" expandable expanded status="mixed" label="Reports"></obc-checkbox-item>
46
+ * <obc-checkbox-item level="2" status="checked" label="Monthly"></obc-checkbox-item>
47
+ * <obc-checkbox-item level="2" label="Quarterly"></obc-checkbox-item>
48
+ * <obc-checkbox-item level="1" expandable label="Archive"></obc-checkbox-item>
49
+ * </obc-checkbox-list>
50
+ * ```
51
+ *
52
+ * @property hoverStyle - Hover treatment applied to every slotted row: `touch-target` or `visual-target`.
53
+ * @slot - `obc-checkbox-item` rows in document order; hierarchy comes from each row's `level`.
54
+ * @beta
55
+ */
56
+ @customElement('obc-checkbox-list')
57
+ export class ObcCheckboxList extends LitElement {
58
+ @property({type: String}) hoverStyle: ObcCheckboxItemHoverStyle =
59
+ ObcCheckboxItemHoverStyle.touchTarget;
60
+
61
+ private mutationObserver?: MutationObserver;
62
+
63
+ override connectedCallback(): void {
64
+ super.connectedCallback();
65
+ // The role sits on the host so a consumer's aria-label / aria-labelledby
66
+ // names the group.
67
+ if (!this.hasAttribute('role')) this.setAttribute('role', 'group');
68
+ // Rows are light-DOM children owned by the consumer, so `expandable`,
69
+ // `expanded` and `level` can change without any event reaching the list.
70
+ this.mutationObserver = new MutationObserver(() => this.sync());
71
+ this.mutationObserver.observe(this, {
72
+ childList: true,
73
+ attributes: true,
74
+ attributeFilter: ['expandable', 'expanded', 'level'],
75
+ subtree: true,
76
+ });
77
+ this.addEventListener('expand-toggle', this.onExpandToggle);
78
+ }
79
+
80
+ override disconnectedCallback(): void {
81
+ super.disconnectedCallback();
82
+ this.mutationObserver?.disconnect();
83
+ this.mutationObserver = undefined;
84
+ this.removeEventListener('expand-toggle', this.onExpandToggle);
85
+ }
86
+
87
+ override firstUpdated(): void {
88
+ this.sync();
89
+ }
90
+
91
+ override updated(changed: PropertyValues<this>): void {
92
+ if (changed.has('hoverStyle')) this.sync();
93
+ }
94
+
95
+ private onExpandToggle = (event: Event): void => {
96
+ const row = event.target;
97
+ if (!(row instanceof ObcCheckboxItem)) return;
98
+ row.expanded = (event as ObcCheckboxItemExpandToggleEvent).detail;
99
+ this.sync();
100
+ };
101
+
102
+ private rows(): ObcCheckboxItem[] {
103
+ return Array.from(this.children).filter(
104
+ (el): el is ObcCheckboxItem => el instanceof ObcCheckboxItem
105
+ );
106
+ }
107
+
108
+ private sync(): void {
109
+ const rows = this.rows();
110
+ const hidden = computeHiddenRows(rows);
111
+ rows.forEach((row, i) => {
112
+ row.hoverStyle = this.hoverStyle;
113
+ row.hidden = hidden[i];
114
+ });
115
+ }
116
+
117
+ override render() {
118
+ return html`<div class="list">
119
+ <slot @slotchange=${() => this.sync()}></slot>
120
+ </div>`;
121
+ }
122
+
123
+ static override styles = unsafeCSS(componentStyle);
124
+ }
125
+
126
+ declare global {
127
+ interface HTMLElementTagNameMap {
128
+ 'obc-checkbox-list': ObcCheckboxList;
129
+ }
130
+ }
@@ -0,0 +1,130 @@
1
+ import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {ObcIntegrationAppBar} from './integration-app-bar.js';
3
+ import './integration-app-bar.js';
4
+ import '../../components/app-button/app-button.js';
5
+ import '../../icons/icon-placeholder.js';
6
+ import {html} from 'lit';
7
+
8
+ const appLabels = [
9
+ 'Home',
10
+ 'Diagnostics',
11
+ 'Data Explorer',
12
+ 'Settings',
13
+ 'Alerts',
14
+ 'Reports',
15
+ 'System Overview',
16
+ 'Logs',
17
+ ];
18
+
19
+ const appBar = (labels: string[]) => html`
20
+ <obc-integration-app-bar>
21
+ ${labels.map(
22
+ (label, i) => html`
23
+ <obc-app-button
24
+ slot="apps"
25
+ size="small"
26
+ label=${label}
27
+ integration
28
+ ?checked=${i === 0}
29
+ >
30
+ <obi-placeholder slot="icon"></obi-placeholder>
31
+ </obc-app-button>
32
+ `
33
+ )}
34
+ </obc-integration-app-bar>
35
+ `;
36
+
37
+ const meta: Meta<typeof ObcIntegrationAppBar> = {
38
+ title: 'Integration Systems/App Bar/Sizing',
39
+ tags: ['experimental'],
40
+ component: 'obc-integration-app-bar',
41
+ parameters: {
42
+ layout: 'fullscreen',
43
+ docs: {
44
+ description: {
45
+ component:
46
+ 'How `obc-integration-app-bar` sizes labelled app buttons. The app row is a grid of auto-placed columns sized `minmax(var(--obc-integration-app-bar-app-width, max-content), 1fr)`; the cases below show how those equal-width tracks respond to mixed label lengths, narrow containers and wrapper elements.',
47
+ },
48
+ },
49
+ },
50
+ };
51
+
52
+ export default meta;
53
+ type Story = StoryObj<ObcIntegrationAppBar>;
54
+
55
+ export const EightLabelledApps: Story = {
56
+ render: () => appBar(appLabels),
57
+ parameters: {
58
+ docs: {
59
+ description: {
60
+ story:
61
+ 'In a wide bar, the grid gives every app the same column width, resolved from the widest label in the set. Short labels keep extra inline space, long labels stay on one line, and the row remains centred because every track uses the same size.',
62
+ },
63
+ },
64
+ },
65
+ };
66
+
67
+ export const NarrowBar: Story = {
68
+ render: () => html`
69
+ <div style="width: 560px; outline: 1px dashed magenta;">
70
+ ${appBar(appLabels)}
71
+ </div>
72
+ `,
73
+ parameters: {
74
+ docs: {
75
+ description: {
76
+ story:
77
+ 'The same eight buttons in a 560px wide bar. The grid keeps equal columns, but their combined minimum width can still exceed the available space, so the centred app row overflows the dashed frame instead of shrinking the buttons.',
78
+ },
79
+ },
80
+ },
81
+ };
82
+
83
+ export const LabelsSetAfterFirstRender: Story = {
84
+ render: () =>
85
+ appBar(['App', 'App', 'App', 'App', 'App', 'App', 'App', 'App']),
86
+ play: async ({canvasElement}) => {
87
+ await new Promise((resolve) => setTimeout(resolve, 1000));
88
+ const buttons = canvasElement.querySelectorAll('obc-app-button');
89
+ buttons.forEach((button, i) => {
90
+ button.label = appLabels[i];
91
+ });
92
+ },
93
+ parameters: {
94
+ docs: {
95
+ description: {
96
+ story:
97
+ 'The bar renders with placeholder labels and the real labels are assigned a second later, which matches data-bound or translated labels. CSS Grid recalculates the column sizing from the updated content, so the buttons expand from the placeholder width to the widest final label without a second measurement pass.',
98
+ },
99
+ },
100
+ },
101
+ };
102
+
103
+ export const ButtonsWrappedInAnElement: Story = {
104
+ render: () => html`
105
+ <obc-integration-app-bar>
106
+ ${appLabels.map(
107
+ (label, i) => html`
108
+ <div slot="apps">
109
+ <obc-app-button
110
+ size="small"
111
+ label=${label}
112
+ integration
113
+ ?checked=${i === 0}
114
+ >
115
+ <obi-placeholder slot="icon"></obi-placeholder>
116
+ </obc-app-button>
117
+ </div>
118
+ `
119
+ )}
120
+ </obc-integration-app-bar>
121
+ `,
122
+ parameters: {
123
+ docs: {
124
+ description: {
125
+ story:
126
+ 'Every app button sits inside a wrapper element, here a plain `div` standing in for the tooltip trigger a framework wrapper emits. The slotted wrapper is flattened with `display: contents`, so the button still becomes the grid item and keeps the same equal-width column sizing as the unwrapped case.',
127
+ },
128
+ },
129
+ },
130
+ };
@@ -24,8 +24,18 @@
24
24
  }
25
25
 
26
26
  .center {
27
- display: flex;
27
+ display: grid;
28
+ grid-auto-flow: column;
29
+ grid-auto-columns: minmax(
30
+ var(--obc-integration-app-bar-app-width, max-content),
31
+ 1fr
32
+ );
28
33
  gap: var(--app-components-app-menu-app-button-spacing);
29
34
  justify-content: center;
30
35
  }
31
36
  }
37
+
38
+ /* Svelte wrappers add a slot container that must not become a grid item. */
39
+ slot[name="apps"]::slotted(:not(obc-app-button)) {
40
+ display: contents;
41
+ }
@@ -3,7 +3,8 @@ import {customElement} from '../../decorator.js';
3
3
  import compentStyle from './integration-app-bar.css?inline';
4
4
 
5
5
  /**
6
- * @slot apps - Application buttons; slotted elements are normalized to a uniform width
6
+ * @slot apps - Application buttons; every button is laid out at the width of the widest one
7
+ * @cssprop [--obc-integration-app-bar-app-width=max-content] - Minimum width of each app-button grid track. Tracks may grow equally to fit wider content.
7
8
  * @experimental
8
9
  */
9
10
  @customElement('obc-integration-app-bar')
@@ -13,38 +14,13 @@ export class ObcIntegrationAppBar extends LitElement {
13
14
  <div class="wrapper">
14
15
  <div class="left-side"></div>
15
16
  <div class="center">
16
- <slot name="apps" @slotchange=${this.handleAppsSlotChange}></slot>
17
+ <slot name="apps"></slot>
17
18
  </div>
18
19
  <div class="right-side"></div>
19
20
  </div>
20
21
  `;
21
22
  }
22
23
 
23
- private handleAppsSlotChange(event: Event) {
24
- const slot = event.target as HTMLSlotElement;
25
- const buttons = slot.assignedElements() as HTMLElement[];
26
-
27
- if (buttons.length === 0) {
28
- return;
29
- }
30
-
31
- for (const btn of buttons) {
32
- btn.style.width = '';
33
- }
34
-
35
- requestAnimationFrame(() => {
36
- const maxWidth = Math.max(
37
- ...buttons.map((btn) => btn.getBoundingClientRect().width)
38
- );
39
-
40
- if (maxWidth > 0) {
41
- for (const btn of buttons) {
42
- btn.style.width = `${maxWidth}px`;
43
- }
44
- }
45
- });
46
- }
47
-
48
24
  static override styles = unsafeCSS(compentStyle);
49
25
  }
50
26
 
@@ -1,6 +1,6 @@
1
1
  import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
2
  import {html, nothing} from 'lit';
3
- import {userEvent} from 'storybook/test';
3
+ import {expect, spyOn, userEvent} from 'storybook/test';
4
4
  import {gsap} from 'gsap';
5
5
  import './readout.js';
6
6
  import '../../components/navigation-item/navigation-item.js';
@@ -1587,20 +1587,17 @@ export const TestCases: Story = {
1587
1587
  </style>
1588
1588
  <div class="test-case-container">
1589
1589
  <obc-readout
1590
- .size=${ReadoutSize.small}
1591
1590
  .direction=${ReadoutDirection.horizontal}
1592
1591
  .value=${123}
1593
1592
  .unit=${'kn'}
1594
1593
  ></obc-readout>
1595
1594
  <obc-readout
1596
- .size=${ReadoutSize.small}
1597
1595
  .direction=${ReadoutDirection.horizontal}
1598
1596
  .value=${'Thermo On'}
1599
1597
  .valueType=${ReadoutValueType.text}
1600
1598
  .label=${'Operating mode'}
1601
1599
  ></obc-readout>
1602
1600
  <obc-readout
1603
- .size=${ReadoutSize.small}
1604
1601
  .direction=${ReadoutDirection.horizontal}
1605
1602
  .value=${'Normal'}
1606
1603
  .valueType=${ReadoutValueType.text}
@@ -1610,6 +1607,62 @@ export const TestCases: Story = {
1610
1607
  },
1611
1608
  };
1612
1609
 
1610
+ /**
1611
+ * A horizontal readout given a `size` other than `large` warns once per
1612
+ * element. The tier is discarded (the arrangement exists in the large tier
1613
+ * only), and a silent discard is indistinguishable from a bug to a consumer
1614
+ * (#1182). A removed `size` attribute arrives as `null` through Lit's String
1615
+ * converter and counts as unset. Driven through `willUpdate` on detached
1616
+ * elements, like `Readout Block → TestValidationSurvivesUnrelatedUpdate`.
1617
+ */
1618
+ export const TestHorizontalSizeWarning: Story = {
1619
+ render: () => html`<span>Regression test — see the play function.</span>`,
1620
+ play: async () => {
1621
+ type Probe = HTMLElement & {
1622
+ size?: ReadoutSize;
1623
+ direction?: ReadoutDirection;
1624
+ willUpdate: (changed: Map<string, unknown>) => void;
1625
+ };
1626
+ const probe = (
1627
+ size: ReadoutSize | undefined,
1628
+ direction: ReadoutDirection
1629
+ ) => {
1630
+ const el = document.createElement('obc-readout') as Probe;
1631
+ el.size = size;
1632
+ el.direction = direction;
1633
+ return el;
1634
+ };
1635
+ const update = (el: Probe) => el.willUpdate(new Map());
1636
+ const warn = spyOn(console, 'warn').mockImplementation(() => undefined);
1637
+ try {
1638
+ update(probe(ReadoutSize.large, ReadoutDirection.horizontal));
1639
+ update(probe(ReadoutSize.small, ReadoutDirection.vertical));
1640
+ update(probe(undefined, ReadoutDirection.horizontal));
1641
+ const removed = probe(ReadoutSize.small, ReadoutDirection.horizontal);
1642
+ removed.setAttribute('size', ReadoutSize.small);
1643
+ removed.removeAttribute('size');
1644
+ update(removed);
1645
+ await expect(warn).not.toHaveBeenCalled();
1646
+
1647
+ const small = probe(ReadoutSize.small, ReadoutDirection.horizontal);
1648
+ update(small);
1649
+ await expect(warn).toHaveBeenCalledTimes(1);
1650
+ await expect(warn.mock.calls[0][0]).toMatch(
1651
+ /\[obc-readout\].*size.*horizontal/
1652
+ );
1653
+ await expect(warn.mock.calls[0][1]).toBe(small);
1654
+
1655
+ update(small);
1656
+ await expect(warn).toHaveBeenCalledTimes(1);
1657
+
1658
+ update(probe(ReadoutSize.medium, ReadoutDirection.horizontal));
1659
+ await expect(warn).toHaveBeenCalledTimes(2);
1660
+ } finally {
1661
+ warn.mockRestore();
1662
+ }
1663
+ },
1664
+ };
1665
+
1613
1666
  /**
1614
1667
  * Figma 6.1 "Equal size": value and setpoint always share the primary size —
1615
1668
  * the missing fourth mode beside primary-secondary (`always-visible`),