@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.
- package/bundle/openbridge-webcomponents.bundle.js +4865 -4498
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +259 -40
- package/dist/components/app-button/app-button.css.js +56 -0
- package/dist/components/app-button/app-button.css.js.map +1 -1
- package/dist/components/checkbox-item/checkbox-item.css.js +137 -12
- package/dist/components/checkbox-item/checkbox-item.css.js.map +1 -1
- package/dist/components/checkbox-item/checkbox-item.d.ts +68 -39
- package/dist/components/checkbox-item/checkbox-item.d.ts.map +1 -1
- package/dist/components/checkbox-item/checkbox-item.js +66 -25
- package/dist/components/checkbox-item/checkbox-item.js.map +1 -1
- package/dist/components/checkbox-list/checkbox-list-visibility.d.ts +16 -0
- package/dist/components/checkbox-list/checkbox-list-visibility.d.ts.map +1 -0
- package/dist/components/checkbox-list/checkbox-list-visibility.js +12 -0
- package/dist/components/checkbox-list/checkbox-list-visibility.js.map +1 -0
- package/dist/components/checkbox-list/checkbox-list.css.js +23 -0
- package/dist/components/checkbox-list/checkbox-list.css.js.map +1 -0
- package/dist/components/checkbox-list/checkbox-list.d.ts +66 -0
- package/dist/components/checkbox-list/checkbox-list.d.ts.map +1 -0
- package/dist/components/checkbox-list/checkbox-list.js +81 -0
- package/dist/components/checkbox-list/checkbox-list.js.map +1 -0
- package/dist/integration-systems/integration-app-bar/integration-app-bar.css.js +12 -1
- package/dist/integration-systems/integration-app-bar/integration-app-bar.css.js.map +1 -1
- package/dist/integration-systems/integration-app-bar/integration-app-bar.d.ts +2 -2
- package/dist/integration-systems/integration-app-bar/integration-app-bar.d.ts.map +1 -1
- package/dist/integration-systems/integration-app-bar/integration-app-bar.js +1 -21
- package/dist/integration-systems/integration-app-bar/integration-app-bar.js.map +1 -1
- package/dist/navigation-instruments/readout/readout.d.ts +11 -2
- package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
- package/dist/navigation-instruments/readout/readout.js +18 -0
- package/dist/navigation-instruments/readout/readout.js.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +25 -0
- package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +20 -4
- package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js +18 -6
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
- package/package.json +1 -1
- package/src/components/app-button/app-button.css +2 -0
- package/src/components/app-button/app-button.stories.ts +26 -0
- package/src/components/checkbox-item/checkbox-item.css +63 -17
- package/src/components/checkbox-item/checkbox-item.spec.ts +115 -0
- package/src/components/checkbox-item/checkbox-item.stories.ts +82 -97
- package/src/components/checkbox-item/checkbox-item.ts +118 -64
- package/src/components/checkbox-list/checkbox-list-visibility.spec.ts +68 -0
- package/src/components/checkbox-list/checkbox-list-visibility.ts +23 -0
- package/src/components/checkbox-list/checkbox-list.css +13 -0
- package/src/components/checkbox-list/checkbox-list.spec.ts +93 -0
- package/src/components/checkbox-list/checkbox-list.stories.ts +161 -0
- package/src/components/checkbox-list/checkbox-list.ts +130 -0
- package/src/integration-systems/integration-app-bar/integration-app-bar-sizing.stories.ts +130 -0
- package/src/integration-systems/integration-app-bar/integration-app-bar.css +11 -1
- package/src/integration-systems/integration-app-bar/integration-app-bar.ts +3 -27
- package/src/navigation-instruments/readout/readout.stories.ts +57 -4
- package/src/navigation-instruments/readout/readout.ts +28 -1
- package/src/navigation-instruments/readout-list/readout-list.stories.ts +62 -2
- package/src/navigation-instruments/readout-list-item/readout-list-item.css +21 -0
- package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +56 -3
- 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:
|
|
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;
|
|
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"
|
|
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`),
|