@reliability-design/web 1.4.8 → 1.4.10
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/CHANGELOG.md +214 -0
- package/custom-elements.json +18600 -0
- package/dist/components/rel-accordion/README.md +9 -2
- package/dist/components/rel-alert/README.md +5 -8
- package/dist/components/rel-autocomplete/README.md +209 -208
- package/dist/components/rel-avatar/README.md +228 -118
- package/dist/components/rel-breadcrumb/README.md +1 -0
- package/dist/components/rel-button/README.md +9 -9
- package/dist/components/rel-calendar/README.md +111 -109
- package/dist/components/rel-cardmedia/README.md +1 -1
- package/dist/components/rel-carousel/README.md +187 -186
- package/dist/components/rel-checkbox/README.md +159 -146
- package/dist/components/rel-chip/README.md +141 -138
- package/dist/components/rel-container/README.md +1 -1
- package/dist/components/rel-date-picker/README.md +221 -200
- package/dist/components/rel-drawer/README.md +4 -4
- package/dist/components/rel-kbd/README.md +188 -141
- package/dist/components/rel-layout/README.md +34 -34
- package/dist/components/rel-menu/README.md +17 -10
- package/dist/components/rel-menubar/README.md +218 -218
- package/dist/components/rel-otp/README.md +4 -8
- package/dist/components/rel-pagination/README.md +141 -100
- package/dist/components/rel-popover/README.md +220 -203
- package/dist/components/rel-radio/README.md +142 -131
- package/dist/components/rel-range-picker/README.md +194 -165
- package/dist/components/rel-resizable/README.md +220 -199
- package/dist/components/rel-segmented-button/README.md +158 -156
- package/dist/components/rel-select/README.md +22 -16
- package/dist/components/rel-sheet/README.md +3 -2
- package/dist/components/rel-signature-pad/README.md +161 -0
- package/dist/components/rel-slider/README.md +140 -131
- package/dist/components/rel-snackbar/README.md +170 -168
- package/dist/components/rel-split-button/README.md +129 -0
- package/dist/components/rel-stepper/README.md +171 -162
- package/dist/components/rel-switch/README.md +122 -117
- package/dist/components/rel-tab/README.md +184 -173
- package/dist/components/rel-textfield/README.md +23 -30
- package/dist/components/rel-time-picker/README.md +188 -164
- package/dist/components/rel-toast/README.md +20 -11
- package/dist/components/rel-tooltip/README.md +5 -4
- package/dist/components/rel-tree/README.md +157 -0
- package/dist/components/rel-upload/README.md +8 -5
- package/dist/index.d.mts +1698 -607
- package/dist/index.d.ts +1698 -607
- package/dist/index.js +12276 -6347
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12218 -6296
- package/dist/index.mjs.map +1 -1
- package/dist/styles/motion.css +31 -0
- package/dist/styles/motion.tokens.json +38 -0
- package/dist/styles/vars.css +88 -64
- package/dist/{vars-DXX6QOAG.css → vars-E22LYFMV.css} +88 -64
- package/package.json +60 -37
- package/dist/styles/focus-ring.ts +0 -84
|
@@ -1,156 +1,158 @@
|
|
|
1
|
-
# rel-segmented-button
|
|
2
|
-
|
|
3
|
-
A single-select segmented button group: a row of buttons on a 2px-inset track where exactly one segment is active at a time. Each entry in `options` renders as a native `<button role="radio">` inside a `role="radiogroup"`, so the group behaves like a compact radio group (one Tab stop, arrow keys move the selection). Use it for mutually exclusive view or mode switches with two to five short options (list / grid, day / week / month, sort order); do not use it for navigation between pages (use `rel-tab`) or for actions (use `rel-button`).
|
|
4
|
-
|
|
5
|
-
The control comes in three sizes that match the text field ramp — `small` 32px, `medium` 40px, `large` 48px — and each option can carry a leading `icon`.
|
|
6
|
-
|
|
7
|
-
## Usage
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<!-- Basic: options are set as a property, `active` holds the selected value -->
|
|
11
|
-
<rel-segmented-button id="view-mode" active="grid" field-aria-label="View mode"></rel-segmented-button>
|
|
12
|
-
<script>
|
|
13
|
-
document.getElementById('view-mode').options = [
|
|
14
|
-
{ label: 'List', value: 'list' },
|
|
15
|
-
{ label: 'Grid', value: 'grid' },
|
|
16
|
-
{ label: 'Table', value: 'table' }
|
|
17
|
-
];
|
|
18
|
-
</script>
|
|
19
|
-
|
|
20
|
-
<!-- Options as a JSON attribute, small size -->
|
|
21
|
-
<rel-segmented-button
|
|
22
|
-
size="small"
|
|
23
|
-
active="week"
|
|
24
|
-
field-aria-label="Calendar range"
|
|
25
|
-
options='[{"label":"Day","value":"day"},{"label":"Week","value":"week"},{"label":"Month","value":"month"}]'>
|
|
26
|
-
</rel-segmented-button>
|
|
27
|
-
|
|
28
|
-
<!-- Large size (48px), matching a large text field -->
|
|
29
|
-
<rel-segmented-button
|
|
30
|
-
size="large"
|
|
31
|
-
active="grid"
|
|
32
|
-
field-aria-label="View mode"
|
|
33
|
-
options='[{"label":"List","value":"list"},{"label":"Grid","value":"grid"}]'>
|
|
34
|
-
</rel-segmented-button>
|
|
35
|
-
|
|
36
|
-
<!-- Leading icons: `icon` names a rel-icon, sized 16 / 18 / 20 with the control -->
|
|
37
|
-
<rel-segmented-button
|
|
38
|
-
active="list"
|
|
39
|
-
field-aria-label="View mode"
|
|
40
|
-
options='[{"label":"List","value":"list","icon":"bullet-list"},{"label":"Grid","value":"grid","icon":"dashboard"}]'>
|
|
41
|
-
</rel-segmented-button>
|
|
42
|
-
|
|
43
|
-
<!-- Disabled option: rendered but not selectable or focusable -->
|
|
44
|
-
<rel-segmented-button id="plan" active="free" field-aria-label="Plan"></rel-segmented-button>
|
|
45
|
-
<script>
|
|
46
|
-
document.getElementById('plan').options = [
|
|
47
|
-
{ label: 'Free', value: 'free' },
|
|
48
|
-
{ label: 'Pro', value: 'pro' },
|
|
49
|
-
{ label: 'Enterprise', value: 'enterprise', disabled: true }
|
|
50
|
-
];
|
|
51
|
-
</script>
|
|
52
|
-
|
|
53
|
-
<!-- Alignment inside a wider host (align only matters when the host is stretched) -->
|
|
54
|
-
<rel-segmented-button
|
|
55
|
-
style="display: block; width: 100%"
|
|
56
|
-
align="middle"
|
|
57
|
-
active="asc"
|
|
58
|
-
field-aria-label="Sort direction"
|
|
59
|
-
options='[{"label":"Ascending","value":"asc"},{"label":"Descending","value":"desc"}]'>
|
|
60
|
-
</rel-segmented-button>
|
|
61
|
-
|
|
62
|
-
<!-- Listen for changes -->
|
|
63
|
-
<rel-segmented-button id="sort-order" active="newest" field-aria-label="Sort order"></rel-segmented-button>
|
|
64
|
-
<script>
|
|
65
|
-
const sortOrder = document.getElementById('sort-order');
|
|
66
|
-
sortOrder.options = [
|
|
67
|
-
{ label: 'Newest', value: 'newest' },
|
|
68
|
-
{ label: 'Oldest', value: 'oldest' },
|
|
69
|
-
{ label: 'Popular', value: 'popular' }
|
|
70
|
-
];
|
|
71
|
-
sortOrder.addEventListener('
|
|
72
|
-
console.log('Selected:', e.detail.value);
|
|
73
|
-
});
|
|
74
|
-
</script>
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The `SegmentOption` interface and the `SegmentSize` / `SegmentAlign` unions are exported from the package:
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
import type { SegmentOption } from '@reliability-design/web';
|
|
81
|
-
|
|
82
|
-
const options: SegmentOption[] = [
|
|
83
|
-
{ label: 'List', value: 'list' }, // label = visible text, value = what `active` / the event carry
|
|
84
|
-
{ label: 'Grid', value: 'grid', icon: 'dashboard' }, // icon = a rel-icon name, drawn before the label
|
|
85
|
-
{ label: 'Archive', value: 'archive', disabled: true } // rendered at 50% opacity, skipped by the arrow keys
|
|
86
|
-
];
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
<!-- readme-gen:start -->
|
|
90
|
-
|
|
91
|
-
<!-- Generated from rel-segmented-button.ts — do not edit; run readme-generate.mjs -->
|
|
92
|
-
|
|
93
|
-
## API
|
|
94
|
-
|
|
95
|
-
### Attributes / Properties
|
|
96
|
-
|
|
97
|
-
| Property | Attribute | Type | Default | Description |
|
|
98
|
-
| --- | --- | --- | --- | --- |
|
|
99
|
-
| `options` | `options` | `SegmentOption[]` | `[]` | The segments in order, each `{ label: string; value: string; disabled?: boolean; icon?: string; badge?: number \| string \| boolean }`. Set as a property (or a JSON array attribute); a disabled entry is rendered but cannot be selected, and `icon` names a `rel-icon` drawn before the label. |
|
|
100
|
-
| `active` | `active` | `string` | `""` | `value` of the currently selected segment. Updated by the component on click / arrow keys; set it to pre-select a segment (an unknown value selects nothing). |
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
- `
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
1
|
+
# rel-segmented-button
|
|
2
|
+
|
|
3
|
+
A single-select segmented button group: a row of buttons on a 2px-inset track where exactly one segment is active at a time. Each entry in `options` renders as a native `<button role="radio">` inside a `role="radiogroup"`, so the group behaves like a compact radio group (one Tab stop, arrow keys move the selection). Use it for mutually exclusive view or mode switches with two to five short options (list / grid, day / week / month, sort order); do not use it for navigation between pages (use `rel-tab`) or for actions (use `rel-button`).
|
|
4
|
+
|
|
5
|
+
The control comes in three sizes that match the text field ramp — `small` 32px, `medium` 40px, `large` 48px — and each option can carry a leading `icon`.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<!-- Basic: options are set as a property, `active` holds the selected value -->
|
|
11
|
+
<rel-segmented-button id="view-mode" active="grid" field-aria-label="View mode"></rel-segmented-button>
|
|
12
|
+
<script>
|
|
13
|
+
document.getElementById('view-mode').options = [
|
|
14
|
+
{ label: 'List', value: 'list' },
|
|
15
|
+
{ label: 'Grid', value: 'grid' },
|
|
16
|
+
{ label: 'Table', value: 'table' }
|
|
17
|
+
];
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<!-- Options as a JSON attribute, small size -->
|
|
21
|
+
<rel-segmented-button
|
|
22
|
+
size="small"
|
|
23
|
+
active="week"
|
|
24
|
+
field-aria-label="Calendar range"
|
|
25
|
+
options='[{"label":"Day","value":"day"},{"label":"Week","value":"week"},{"label":"Month","value":"month"}]'>
|
|
26
|
+
</rel-segmented-button>
|
|
27
|
+
|
|
28
|
+
<!-- Large size (48px), matching a large text field -->
|
|
29
|
+
<rel-segmented-button
|
|
30
|
+
size="large"
|
|
31
|
+
active="grid"
|
|
32
|
+
field-aria-label="View mode"
|
|
33
|
+
options='[{"label":"List","value":"list"},{"label":"Grid","value":"grid"}]'>
|
|
34
|
+
</rel-segmented-button>
|
|
35
|
+
|
|
36
|
+
<!-- Leading icons: `icon` names a rel-icon, sized 16 / 18 / 20 with the control -->
|
|
37
|
+
<rel-segmented-button
|
|
38
|
+
active="list"
|
|
39
|
+
field-aria-label="View mode"
|
|
40
|
+
options='[{"label":"List","value":"list","icon":"bullet-list"},{"label":"Grid","value":"grid","icon":"dashboard"}]'>
|
|
41
|
+
</rel-segmented-button>
|
|
42
|
+
|
|
43
|
+
<!-- Disabled option: rendered but not selectable or focusable -->
|
|
44
|
+
<rel-segmented-button id="plan" active="free" field-aria-label="Plan"></rel-segmented-button>
|
|
45
|
+
<script>
|
|
46
|
+
document.getElementById('plan').options = [
|
|
47
|
+
{ label: 'Free', value: 'free' },
|
|
48
|
+
{ label: 'Pro', value: 'pro' },
|
|
49
|
+
{ label: 'Enterprise', value: 'enterprise', disabled: true }
|
|
50
|
+
];
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<!-- Alignment inside a wider host (align only matters when the host is stretched) -->
|
|
54
|
+
<rel-segmented-button
|
|
55
|
+
style="display: block; width: 100%"
|
|
56
|
+
align="middle"
|
|
57
|
+
active="asc"
|
|
58
|
+
field-aria-label="Sort direction"
|
|
59
|
+
options='[{"label":"Ascending","value":"asc"},{"label":"Descending","value":"desc"}]'>
|
|
60
|
+
</rel-segmented-button>
|
|
61
|
+
|
|
62
|
+
<!-- Listen for changes -->
|
|
63
|
+
<rel-segmented-button id="sort-order" active="newest" field-aria-label="Sort order"></rel-segmented-button>
|
|
64
|
+
<script>
|
|
65
|
+
const sortOrder = document.getElementById('sort-order');
|
|
66
|
+
sortOrder.options = [
|
|
67
|
+
{ label: 'Newest', value: 'newest' },
|
|
68
|
+
{ label: 'Oldest', value: 'oldest' },
|
|
69
|
+
{ label: 'Popular', value: 'popular' }
|
|
70
|
+
];
|
|
71
|
+
sortOrder.addEventListener('rel-change', (e) => {
|
|
72
|
+
console.log('Selected:', e.detail.value);
|
|
73
|
+
});
|
|
74
|
+
</script>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The `SegmentOption` interface and the `SegmentSize` / `SegmentAlign` unions are exported from the package:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import type { SegmentOption } from '@reliability-design/web';
|
|
81
|
+
|
|
82
|
+
const options: SegmentOption[] = [
|
|
83
|
+
{ label: 'List', value: 'list' }, // label = visible text, value = what `active` / the event carry
|
|
84
|
+
{ label: 'Grid', value: 'grid', icon: 'dashboard' }, // icon = a rel-icon name, drawn before the label
|
|
85
|
+
{ label: 'Archive', value: 'archive', disabled: true } // rendered at 50% opacity, skipped by the arrow keys
|
|
86
|
+
];
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
<!-- readme-gen:start -->
|
|
90
|
+
|
|
91
|
+
<!-- Generated from rel-segmented-button.ts — do not edit; run readme-generate.mjs -->
|
|
92
|
+
|
|
93
|
+
## API
|
|
94
|
+
|
|
95
|
+
### Attributes / Properties
|
|
96
|
+
|
|
97
|
+
| Property | Attribute | Type | Default | Description |
|
|
98
|
+
| --- | --- | --- | --- | --- |
|
|
99
|
+
| `options` | `options` | `SegmentOption[]` | `[]` | The segments in order, each `{ label: string; value: string; disabled?: boolean; icon?: string; badge?: number \| string \| boolean }`. Set as a property (or a JSON array attribute); a disabled entry is rendered but cannot be selected, and `icon` names a `rel-icon` drawn before the label. |
|
|
100
|
+
| `active` | `active` | `string` | `""` | `value` of the currently selected segment. Updated by the component on click / arrow keys; set it to pre-select a segment (an unknown value selects nothing). |
|
|
101
|
+
| `disabled` | `disabled` | `boolean` | `false` | Disables the whole group (click and keyboard) without changing individual `options[].disabled` settings. Angular `control.disable()` uses it. New in 1.5.0. Reflected. |
|
|
102
|
+
| `size` | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Control height, matching the TextField ramp: `small` (32px — 28px segments, `action-md` 12px text, 16px icons), `medium` (default, 40px — 36px segments, `action-lg` 14px text, 18px icons) or `large` (48px — 44px segments, `action-xl` 16px text, 20px icons). |
|
|
103
|
+
| `align` | `align` | `"left" \| "middle" \| "right"` | `"left"` | `justify-content` of the group's inline-flex container: `left` (default), `middle` or `right`. Only visible when the host is made wider than its content. |
|
|
104
|
+
| `fieldAriaLabel` | `field-aria-label` | `string` | `undefined` | Accessible name for the radiogroup (it renders no visible group label). |
|
|
105
|
+
|
|
106
|
+
## Events
|
|
107
|
+
|
|
108
|
+
| Event | Detail | Bubbles / composed | Description |
|
|
109
|
+
| --- | --- | --- | --- |
|
|
110
|
+
| `changed` | `{ value: option.value }` | bubbles / composed | Legacy alias of `rel-change`, fired with the same detail at the same time. Kept indefinitely so existing listeners keep working; new code should use `rel-change`. |
|
|
111
|
+
| `rel-change` | `{ value: option.value }` | bubbles / composed | Fired after a non-disabled segment is selected by click or arrow / Home / End key (also when it was already the active one). Detail: `{ value: string }`. The canonical name — prefer it over the legacy `changed`, which fires alongside it with the same detail. |
|
|
112
|
+
|
|
113
|
+
## Slots
|
|
114
|
+
|
|
115
|
+
| Slot | Description |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| — | This element has no slots. |
|
|
118
|
+
|
|
119
|
+
## CSS Parts
|
|
120
|
+
|
|
121
|
+
| Part | Description |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `option` | Each segment's native `<button role="radio">`; the active one carries the `active` class and `aria-checked="true"`. |
|
|
124
|
+
| `icon` | A segment's optional leading `rel-icon` element. |
|
|
125
|
+
| `label` | The wrapper around a segment's label text. |
|
|
126
|
+
| `badge` | A segment's optional trailing `rel-badge`; rendered only when the option sets `badge`. |
|
|
127
|
+
| `base` | The `role="radiogroup"` track that lays out the segments. |
|
|
128
|
+
|
|
129
|
+
<!-- readme-gen:end -->
|
|
130
|
+
|
|
131
|
+
## Accessibility
|
|
132
|
+
|
|
133
|
+
- The container is a `role="radiogroup"`; give it a name with `field-aria-label` — the component renders no visible group label, so without it screen readers announce an unnamed group.
|
|
134
|
+
- Every segment is a native `<button role="radio">` with `aria-checked="true"` on the active one and `aria-checked="false"` on the rest. Disabled options get the native `disabled` attribute, so they are not focusable and do not fire `click`.
|
|
135
|
+
- Roving tabindex (APG radio-group pattern): the whole group is a single Tab stop. The active segment carries `tabindex="0"`; if `active` matches no enabled option, the first enabled segment takes the Tab stop instead (without becoming selected). All other segments are `tabindex="-1"`.
|
|
136
|
+
- Arrow keys both move focus and select, so they emit the change event immediately. Focus is moved to the newly active segment after the re-render.
|
|
137
|
+
- Focus ring: a 2px `border-focus` outline drawn inside the segment (`outline-offset: -2px`) and raised with `z-index: 1` so it stays above the neighbouring segment.
|
|
138
|
+
- Nothing is announced beyond the native `aria-checked` change.
|
|
139
|
+
|
|
140
|
+
| Key | Action |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `Tab` / `Shift+Tab` | Move focus into / out of the group (one stop, on the active segment) |
|
|
143
|
+
| `ArrowRight` / `ArrowDown` | Select and focus the next enabled segment (wraps to the first) |
|
|
144
|
+
| `ArrowLeft` / `ArrowUp` | Select and focus the previous enabled segment (wraps to the last) |
|
|
145
|
+
| `Home` / `End` | Select and focus the first / last enabled segment |
|
|
146
|
+
| `Enter` / `Space` | Native button activation: selects the focused segment |
|
|
147
|
+
|
|
148
|
+
## Notes / Edge Cases
|
|
149
|
+
|
|
150
|
+
- **Two events fire for one selection — listen to ONE of them or your handler runs twice.** The change event is `rel-change`, matching the rest of the library. The legacy un-prefixed `changed` fires alongside it with the same detail and is kept so existing listeners keep working — listen to one, not both. Both bubble and are composed, so either can be caught on any ancestor, and both fire when the already-active segment is clicked again.
|
|
151
|
+
- `active` is a plain string compared against `option.value`; setting it to a value that is not in `options` (or to a disabled option's value) renders no active segment.
|
|
152
|
+
- `options` is compared by reference: mutate-in-place changes are not picked up — assign a new array.
|
|
153
|
+
- The host is `display: inline-block` and the group `inline-flex`, so it sizes to its content; `align` has no visible effect until the host is stretched (e.g. `style="display: block; width: 100%"`). Every segment has `min-width: 80px`, so a group is never narrower than 80px per segment.
|
|
154
|
+
- Sizing: the track is a `bg-neutral` surface with `spacing-3xs` (2px) padding and a `border-radius-xs` corner — there is no outer border and no dividers between segments. Each segment carries the full `border-radius-xs` on all four corners in every state. Segment heights are 28 / 36 / 44 for `small` / `medium` / `large`, which with the 2px inset give control heights of 32 / 40 / 48.
|
|
155
|
+
- Per size: padding `spacing-3xs spacing-sm` / `spacing-2xs spacing-md` / `spacing-xs spacing-lg`, text `action-md` (12px) / `action-lg` (14px) / `action-xl` (16px), and leading icons at 16 / 18 / 20px with an `spacing-xs` (8px) gap.
|
|
156
|
+
- Colours: the active segment uses `action-neutral-fill` with inverse text (and `action-neutral-fill-hover` on hover); a non-active segment is transparent so the `action-overlay-hover` alpha composites over the track; disabled segments are drawn at 50% opacity with `bg-neutral-elevated-1` and a `not-allowed` cursor. Background and colour transitions are 0.2s.
|
|
157
|
+
- A per-option `icon` renders a `rel-icon` before the label. Its size rides `--rel-icon-size` set directly on the `rel-icon` element — not on a wrapper, since `rel-icon` reflects `size="md"` and its own `:host([size])` rule would otherwise win.
|
|
158
|
+
- No design-token overrides or `--rel-segmented-button-*` hooks are exposed; style through the `base` / `option` / `icon` / `label` parts.
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
<script>
|
|
11
11
|
const country = document.getElementById("country");
|
|
12
12
|
country.options = ["USA", "Canada", "Mexico"];
|
|
13
|
-
country.addEventListener("
|
|
13
|
+
country.addEventListener("rel-change", (e) => console.log(e.detail.value)); // "Canada"
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
|
-
<!-- Object options: value / label / icon / supportingText (
|
|
16
|
+
<!-- Object options: value / label / icon / supportingText (a second line under the label, in single and multi rows) -->
|
|
17
17
|
<script>
|
|
18
18
|
country.options = [
|
|
19
19
|
{ value: "air", label: "Air freight", icon: "air" },
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
24
|
<!-- Preselected value, helper text, icons -->
|
|
25
|
-
<rel-select label="Email preference" selected="weekly" supporting-text="How often we email you" left-icon="
|
|
26
|
-
<rel-select label="Custom chevron" right-icon="
|
|
25
|
+
<rel-select label="Email preference" selected="weekly" supporting-text="How often we email you" left-icon="email"></rel-select>
|
|
26
|
+
<rel-select label="Custom chevron" right-icon="chevron-down"></rel-select>
|
|
27
27
|
|
|
28
28
|
<!-- Sizes, corners, label styles -->
|
|
29
29
|
<rel-select label="Small" size="small"></rel-select>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
<script>
|
|
53
53
|
const tags = document.getElementById("tags");
|
|
54
54
|
tags.options = ["urgent", "fragile", "hazmat", "oversize", "refrigerated"];
|
|
55
|
-
tags.addEventListener("
|
|
55
|
+
tags.addEventListener("rel-change", (e) => console.log(e.detail.value)); // ["urgent", "fragile", ...]
|
|
56
56
|
</script>
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -85,13 +85,14 @@
|
|
|
85
85
|
| `searchPlaceholder` | `search-placeholder` | `string` | `"Search option"` | Placeholder of the in-panel search field (the field is hidden when `noSearch` is set). |
|
|
86
86
|
| `selectAllLabel` | `select-all-label` | `string` | `"Select all"` | Label of the Select-all row shown at the top of the panel in multi-select mode. |
|
|
87
87
|
| `maxVisibleChips` | `max-visible-chips` | `number` | `Infinity` | Optional hard cap on visible chips. By default (no cap) the visible count adapts to the trigger's width: chips collapse into the "+N" badge one by one instead of wrapping to a second line. |
|
|
88
|
-
| `noSearch` | `no-search` | `boolean` | `false` |
|
|
88
|
+
| `noSearch` | `no-search` | `boolean` | `false` | Hides the in-panel search field. The search row shows in single- and multi-select panels alike unless this is set; use it for short lists that don't need searching (Figma's "Single Option" dropdown has no search bar). |
|
|
89
89
|
|
|
90
90
|
## Events
|
|
91
91
|
|
|
92
92
|
| Event | Detail | Bubbles / composed | Description |
|
|
93
93
|
| --- | --- | --- | --- |
|
|
94
|
-
| `changed` | `{ value: this.multiple ? this.selectedValues : this.selected }` | bubbles / composed | Fired after any selection change (pick, checkbox toggle, Select-all, chip removal). Detail: `{ value }` — the selected value string in single mode, or the array of selected values when `multiple`. |
|
|
94
|
+
| `changed` | `{ value: this.multiple ? this.selectedValues : this.selected }` | bubbles / composed | Fired after any selection change (pick, checkbox toggle, Select-all, chip removal). Detail: `{ value }` — the selected value string in single mode, or the array of selected values when `multiple`. Legacy name, kept indefinitely: it fires together with `rel-change`, same detail, same moment. |
|
|
95
|
+
| `rel-change` | `{ value: this.multiple ? this.selectedValues : this.selected }` | bubbles / composed | Prefixed alias of `changed`, fired on the same action with the same detail (added in 1.4.9). The name the rest of the library uses — prefer it in new code. |
|
|
95
96
|
|
|
96
97
|
## Slots
|
|
97
98
|
|
|
@@ -105,9 +106,9 @@
|
|
|
105
106
|
| --- | --- |
|
|
106
107
|
| `label` | The visible label above the trigger (default label style) |
|
|
107
108
|
| `trigger` | The button that opens the panel; styled like a text field |
|
|
108
|
-
| `value` | The value area inside the trigger (
|
|
109
|
+
| `value` | The value area: the selected label or placeholder inside the trigger. In multi-select with chips (since 1.5.0) it is the chip overlay, a sibling laid over the trigger's value area rather than a child of it |
|
|
109
110
|
| `helper` | The supporting / error text below the trigger |
|
|
110
|
-
| `panel` | The dropdown listbox
|
|
111
|
+
| `panel` | The dropdown panel (a `role="group"` holding the search, Select-all and the options listbox; same node as `list`) |
|
|
111
112
|
| `list` | Alias of `panel` |
|
|
112
113
|
| `input` | The search input inside the panel |
|
|
113
114
|
| `option` | A single-select option row |
|
|
@@ -123,7 +124,8 @@ Set these on the host (or an ancestor) to customise the element without touching
|
|
|
123
124
|
| Custom property | Default | Description |
|
|
124
125
|
| --- | --- | --- |
|
|
125
126
|
| `--rel-field-label-reserve` | `calc(var(--rel-body-md-regular-line-height) + var(--rel-spacing-2xs))` | Height held for the label row whenever no label renders above the trigger — `labelStyle="none"`, and `labelStyle="floating"` where the label sits inside the trigger — so the trigger still lines up with labelled siblings in a form row. Defaults to that size's label line-height plus the wrapper gap (24px at `small`, 26px at `medium` and `large`). Set to `0` to collapse the row entirely (toolbars, table cells). |
|
|
126
|
-
| `--rel-
|
|
127
|
+
| `--rel-overlay-fallback-z` | `1001` | z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place. |
|
|
128
|
+
| `--rel-select-bg` | `var(--rel-bg-neutral)` | Background of the trigger, the panel and its sticky header. No longer paints behind the floating label — see `--rel-select-surface`. |
|
|
127
129
|
| `--rel-select-bg-disabled` | `var(--rel-bg-neutral-elevated-2)` | Trigger background when `state="disabled"` |
|
|
128
130
|
| `--rel-select-border-default` | `var(--rel-border-default)` | Trigger border colour at rest |
|
|
129
131
|
| `--rel-select-border-disabled` | `var(--rel-border-accent)` | Trigger border colour when `state="disabled"` |
|
|
@@ -132,7 +134,7 @@ Set these on the host (or an ancestor) to customise the element without touching
|
|
|
132
134
|
| `--rel-select-divider` | `var(--rel-border-subtle)` | Hairline under the search field, the Select-all row and every option row |
|
|
133
135
|
| `--rel-select-icon-color` | `var(--rel-text-default)` | Colour of the trigger icons, the search icon and option prefix icons |
|
|
134
136
|
| `--rel-select-label-color` | `var(--rel-text-label)` | Colour of the static label and of the floated label |
|
|
135
|
-
| `--rel-select-menu-border` | `var(--rel-border-
|
|
137
|
+
| `--rel-select-menu-border` | `var(--rel-border-subtle)` | Panel border colour. The panel is borderless by design and only draws this on dark surfaces, where its shadow alone can't separate it from the page. |
|
|
136
138
|
| `--rel-select-menu-radius` | `var(--rel-border-radius-sm)` | Panel corner radius |
|
|
137
139
|
| `--rel-select-menu-shadow` | `var(--rel-shadows-md)` | Panel box-shadow |
|
|
138
140
|
| `--rel-select-option-hover` | `var(--rel-action-overlay-hover)` | Background of the hovered / keyboard-highlighted / selected option and Select-all row |
|
|
@@ -142,6 +144,8 @@ Set these on the host (or an ancestor) to customise the element without touching
|
|
|
142
144
|
| `--rel-select-radius` | `var(--rel-border-radius-xs)` | Trigger corner radius when `border="rounded"` |
|
|
143
145
|
| `--rel-select-search-border` | `var(--rel-border-default)` | Border colour of the search field inside the panel |
|
|
144
146
|
| `--rel-select-supporting-color` | `var(--rel-text-body)` | Supporting text colour (non-error) |
|
|
147
|
+
| `--rel-select-surface` | `var(--rel-select-bg, var(--rel-bg-neutral))` | Colour of the strip the floating label lays over the trigger's top border and focus ring, so the label appears to sit in a notch. It overshoots the band by 2px on the page side, so it must match the surface the field sits on. Defaults to `--rel-select-bg`. Set it when the field sits on a surface that is not the trigger's own fill, otherwise the strip shows as a short dash of the wrong colour. Mirrors `--rel-textfield-surface`. |
|
|
148
|
+
| `--rel-select-surface-disabled` | `var(--rel-select-bg-disabled, var(--rel-bg-neutral-elevated-2))` | The same strip while `state="disabled"`, where the trigger fill changes. Defaults to `--rel-select-bg-disabled`. |
|
|
145
149
|
| `--rel-select-value-color` | `var(--rel-text-default)` | Colour of the selected value, the search text, option labels and the check mark |
|
|
146
150
|
|
|
147
151
|
<!-- readme-gen:end -->
|
|
@@ -150,10 +154,11 @@ Set these on the host (or an ancestor) to customise the element without touching
|
|
|
150
154
|
|
|
151
155
|
- The trigger is a native `<button role="combobox">` with `aria-haspopup="listbox"`, `aria-expanded`, `aria-controls` (the listbox id) and `aria-invalid` (true in the error state). Its accessible name is composed as `"{label}, {selected label}"` in single mode and `"{label}, a, b, c, plus N more"` in multi mode (first three labels), so the current selection is always announced with the name. `field-aria-labelledby` replaces that computed name entirely.
|
|
152
156
|
- The static label (`label-style="default"`) is a `<label for>` linked to the trigger. The floating label is a visual `<span>`; the trigger's `aria-label` still carries the name. With `label-style="none"` the name comes from `field-aria-label` and a console warning is logged once if neither `field-aria-label` nor `field-aria-labelledby` is set.
|
|
153
|
-
- The panel is `role="listbox"`
|
|
157
|
+
- The panel (part `panel`) is a `role="group"` named after the field (or "Options"): it holds the search combobox and the Select-all row, which a listbox may not contain. The `role="listbox"` (same name, `aria-multiselectable` in multi mode, and the id the trigger and search `aria-controls` point at) is the options container inside it; the Select-all row joins it through `aria-owns`. With no matches the listbox holds one disabled option with the "No matches found" / "No options available" text. Every row is `role="option"` with `aria-selected`; multi-select rows and the Select-all row carry an explicit `aria-label` and contain an `inert`, `aria-hidden` `rel-checkbox` used purely as the visual.
|
|
154
158
|
- The in-panel search input is itself a `role="combobox"` with `aria-expanded`, `aria-controls` and `aria-activedescendant` tracking the highlighted row. With `no-search` the trigger keeps focus while open and carries `aria-activedescendant` instead.
|
|
155
159
|
- Screen-reader announcements via the shared `announce()` helper: `"{label} selected"` / `"{label} deselected"` on a checkbox toggle, `"All options selected"` / `"All options deselected"`, `"{label} removed"` when a chip is removed, and `"N options available"` / `"No matches found"` / `"No options available"` while typing in the search field.
|
|
156
|
-
- Focus: opening the panel moves focus to the search input (with `preventScroll`) unless `no-search`; closing always returns focus to the trigger.
|
|
160
|
+
- Focus: opening the panel moves focus to the search input (with `preventScroll`) unless `no-search`; closing always returns focus to the trigger.
|
|
161
|
+
- Multi-select chips (changed in 1.5.0): the chips are no longer inside the trigger `<button>` (interactive content in a button is invalid HTML), and their bodies and the "+N" chip are no longer Tab stops that did nothing. They sit in an overlay over the trigger's value area: a click on a chip label or on "+N" opens the panel like any click on the field. The Tab order is the trigger, then each visible chip's remove button ("Remove {label}", at least 24 × 24px). Removing a chip from the keyboard moves focus to the next chip's remove button, else the previous one, else the trigger. Backspace / Delete on the trigger (or Backspace in an empty search field) still removes the most recent selection. The "+N" chip is hidden from assistive technology, because the trigger's name already ends "plus N more". Before 1.5.0 this README said the remove buttons were not Tab stops; they were, alongside the dead stops.
|
|
157
162
|
|
|
158
163
|
| Key | Action |
|
|
159
164
|
|-----|--------|
|
|
@@ -171,15 +176,16 @@ Set these on the host (or an ancestor) to customise the element without touching
|
|
|
171
176
|
|
|
172
177
|
## Notes / Edge Cases
|
|
173
178
|
|
|
174
|
-
-
|
|
179
|
+
- **Two events fire for one selection — listen to ONE of them or your handler runs twice.** `rel-change` (added in 1.4.9, matching the rest of the library) and the legacy `changed` fire together with the same detail; `detail.value` is a string in single mode and an array of strings when `multiple`. Existing `changed` listeners keep working.
|
|
175
180
|
- Opening the panel does not pre-highlight any row; the highlight appears on the first hover or arrow key so nothing reads as an unintended default.
|
|
181
|
+
- Since 1.5.0 the multi-select chip overlay is positioned from the field's own size (border, inline padding, icon size), not from the trigger's computed padding. Custom CSS that changes the inline padding through `::part(trigger)` therefore no longer moves the chips with it. Style `::part(value)` to adjust them as well. `::part(trigger)` is still the same button with the same box, and the chips are still `rel-chip` elements.
|
|
176
182
|
- `state="error"` clears itself to `"default"` on the next selection, toggle, Select-all or chip removal. `state="disabled"` dims the whole field to 50% opacity, makes chips non-removable and blocks pointer events on them.
|
|
177
183
|
- Turning `multiple` off resets `selectedValues` to `[]`. `selected` is ignored in multi mode and `selectedValues` in single mode.
|
|
178
184
|
- Chips never wrap: the number shown adapts to the trigger width (measured through a hidden copy of every chip and a `ResizeObserver`), with the remainder collapsed into a trailing "+N" chip. At least one chip always stays visible; `max-visible-chips` is an extra hard cap. A selected value with no matching option is shown as its raw value.
|
|
179
|
-
- Option `icon`
|
|
185
|
+
- Option `icon` and `supportingText` render in both single and multi rows. Both lines truncate to one line with an ellipsis in either mode (nothing wraps or hyphenates), and a row that carries a second line gets a native `title` with its full text so hovering reveals what was cut. An unknown icon name logs a `rel-icon` warning and renders nothing (no reserved space).
|
|
180
186
|
- A custom `right-icon` replaces the chevron and does not rotate when the panel opens; the default chevron rotates 180°.
|
|
181
187
|
- The search field uses the small field sizing for both `small` and `medium` selects; only `large` bumps it to medium. Empty results show "No matches found" (or "No options available" when `options` is empty).
|
|
182
188
|
- The `helper` part exists only while `supporting-text` is non-empty.
|
|
183
|
-
- The open listbox is promoted to the browser top layer (native Popover API, `popover="manual"`) so it renders correctly inside transformed or `overflow: hidden` containers such as a centered `rel-modal` or a `rel-drawer`; engines without the Popover API fall back to `position: fixed` with a measure-and-cancel correction. The panel matches the trigger width, flips above the trigger when there is not enough room below, is capped at 360px with internal scrolling, and closes on any outside pointerdown
|
|
189
|
+
- The open listbox is promoted to the browser top layer (native Popover API, `popover="manual"`) so it renders correctly inside transformed or `overflow: hidden` containers such as a centered `rel-modal` or a `rel-drawer`; engines without the Popover API fall back to `position: fixed` with a measure-and-cancel correction. The panel matches the trigger width, flips above the trigger when there is not enough room below, is capped at 360px with internal scrolling, and closes on any outside pointerdown. On an outside scroll (since 1.5.0) it **stays open and moves with the field while the field is still visible**, and closes once the field scrolls out of the viewport or its scroll container, or under a sticky header — so the top-layer panel never paints over page furniture (the reason 1.4.9 closed it on every scroll). Only the window and the field's own scroll containers count, including ones inside shadow roots such as `rel-modal`'s body; scrolls of unrelated elements and of the panel itself are ignored, and a wheel or swipe over the panel never scrolls the page (`overscroll-behavior: contain`).
|
|
184
190
|
- `--rel-field-label-reserve` (see Component hooks) keeps a `label-style="none"` select bottom-aligned with labelled siblings; set it to `0` in toolbars or table cells.
|
|
185
191
|
- The trigger heights (32 / 40 / 48px) match `rel-textfield` and the date / time / range picker triggers so mixed form rows line up.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<!-- Centred dialog, small width -->
|
|
27
27
|
<rel-modal side="center" size="small" title="Session expired" description="Sign in again to continue.">
|
|
28
|
-
<rel-button slot="footer" variant="primary">Sign in</rel-button>
|
|
28
|
+
<rel-button slot="footer" variant="primary" tone="brand">Sign in</rel-button>
|
|
29
29
|
</rel-modal>
|
|
30
30
|
|
|
31
31
|
<!-- Top-anchored sheet with extra header content -->
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<!-- Non-dismissible: no close button, backdrop and Escape ignored -->
|
|
61
61
|
<rel-modal id="locked" title="Accept terms">
|
|
62
62
|
<p>You must accept to continue.</p>
|
|
63
|
-
<rel-button slot="footer" variant="primary">Accept</rel-button>
|
|
63
|
+
<rel-button slot="footer" variant="primary" tone="brand">Accept</rel-button>
|
|
64
64
|
</rel-modal>
|
|
65
65
|
<script>
|
|
66
66
|
// Boolean props that default to true can only be switched off from JS —
|
|
@@ -159,6 +159,7 @@ Set these on the host (or an ancestor) to customise the element without touching
|
|
|
159
159
|
- `title` is also HTML's global tooltip attribute. The component reads the attribute into the property and then removes it from the host, so no native tooltip appears over the modal or its scrim.
|
|
160
160
|
- `subtitle` is an alias for `description` and is only rendered when `description` is empty.
|
|
161
161
|
- The footer is only rendered when the `footer` slot has content; the host gains a `has-footer` attribute while it does.
|
|
162
|
+
- The header always has a bottom border. The footer's top divider shows only while the body scrolls (the host gains `body-scrolls`); short content has no footer divider. Scrolling is re-measured when the body or any slotted child resizes, so content added after opening, children growing in place (including in a `fullscreen` modal, whose body never changes size itself), images loading and window resizes all update it.
|
|
162
163
|
- `dismissible` is the coarse switch and is ANDed with `close-on-backdrop-click`, `close-on-escape` and `show-close-button`; turning it off locks all three regardless of their own values.
|
|
163
164
|
- `loading` blocks backdrop clicks and the close button but does **not** block `Escape`; set `disabled` (or `close-on-escape` = false) if Escape must be blocked as well.
|
|
164
165
|
- Boolean props default to `true` (`dismissible`, `modal`, `close-on-*`, `show-close-button`, `auto-focus`, `restore-focus`, `scrollable`); an absent attribute keeps the default, so switch them off from JavaScript (`modal.closeOnEscape = false`).
|