@reliability-design/web 1.4.1 → 1.4.3
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 +49 -22
- package/dist/components/rel-autocomplete/README.md +96 -0
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +634 -0
- package/dist/components/rel-button/rel-button.ts +17 -3
- package/dist/components/rel-checkbox/README.md +1 -0
- package/dist/components/rel-checkbox/rel-checkbox.ts +34 -0
- package/dist/components/rel-drawer/rel-drawer.ts +9 -0
- package/dist/components/rel-popover/rel-popover.ts +56 -0
- package/dist/components/rel-radio/README.md +16 -1
- package/dist/components/rel-radio/rel-radio.ts +64 -1
- package/dist/components/rel-select/README.md +10 -2
- package/dist/components/rel-select/rel-select.ts +238 -15
- package/dist/components/rel-sheet/rel-sheet.ts +20 -0
- package/dist/components/rel-textfield/README.md +16 -0
- package/dist/components/rel-textfield/rel-textfield.ts +109 -6
- package/dist/components/rel-time-picker/README.md +199 -0
- package/dist/components/rel-time-picker/rel-time-picker.ts +994 -0
- package/dist/index.d.mts +377 -77
- package/dist/index.d.ts +377 -77
- package/dist/index.js +4585 -2608
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3428 -1453
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +866 -866
- package/dist/{vars-BYCEEKWJ.css → vars-XNMTSRVN.css} +866 -866
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LitElement, html, css } from "lit";
|
|
2
2
|
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
import "@reliability-design/icons/rel-icon";
|
|
3
4
|
|
|
4
5
|
type CheckboxState = "default" | "error" | "disabled";
|
|
5
6
|
type CheckboxStatus = "unchecked" | "checked" | "indeterminate";
|
|
@@ -7,6 +8,8 @@ type CheckboxStatus = "unchecked" | "checked" | "indeterminate";
|
|
|
7
8
|
@customElement("rel-checkbox")
|
|
8
9
|
export class RelCheckbox extends LitElement {
|
|
9
10
|
@property({ type: String }) label = "Label";
|
|
11
|
+
/** rel-icon name rendered between the checkbox and the label (label prefix icon). */
|
|
12
|
+
@property({ type: String }) icon = "";
|
|
10
13
|
@property({ type: String }) supportingtext = "";
|
|
11
14
|
@property({ type: String }) state: CheckboxState = "default";
|
|
12
15
|
@property({ type: String }) status: CheckboxStatus = "unchecked";
|
|
@@ -36,6 +39,14 @@ export class RelCheckbox extends LitElement {
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
.row {
|
|
42
|
+
/* Containing block for the hidden input below — without this the
|
|
43
|
+
absolutely-positioned input resolves against the document (or any
|
|
44
|
+
positioned ancestor in the consumer page), where it neither moves
|
|
45
|
+
with intermediate scroll containers nor stays aligned with the
|
|
46
|
+
visible box. Tabbing to it then makes the browser scroll the WRONG
|
|
47
|
+
scroller (often the window past a 100vh overflow-hidden shell),
|
|
48
|
+
shoving the whole page up and leaving it in a broken state. */
|
|
49
|
+
position: relative;
|
|
39
50
|
display: flex;
|
|
40
51
|
align-items: center;
|
|
41
52
|
gap: var(--rel-spacing-xs);
|
|
@@ -44,6 +55,10 @@ export class RelCheckbox extends LitElement {
|
|
|
44
55
|
|
|
45
56
|
input {
|
|
46
57
|
position: absolute;
|
|
58
|
+
inset: 0;
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 100%;
|
|
61
|
+
margin: 0;
|
|
47
62
|
opacity: 0;
|
|
48
63
|
pointer-events: none;
|
|
49
64
|
}
|
|
@@ -147,6 +162,21 @@ export class RelCheckbox extends LitElement {
|
|
|
147
162
|
color: var(--rel-text-disabled);
|
|
148
163
|
}
|
|
149
164
|
|
|
165
|
+
/* Optional label prefix icon — tracks the label's color in every state
|
|
166
|
+
(rel-icon inherits currentColor). */
|
|
167
|
+
.icon-prefix {
|
|
168
|
+
flex-shrink: 0;
|
|
169
|
+
color: var(--rel-text-default);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.default .row:hover .icon-prefix {
|
|
173
|
+
color: var(--rel-text-body);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.disabled .icon-prefix {
|
|
177
|
+
color: var(--rel-text-disabled);
|
|
178
|
+
}
|
|
179
|
+
|
|
150
180
|
.label {
|
|
151
181
|
/* Figma: label rests on text-default; text-body is the hover color */
|
|
152
182
|
color: var(--rel-text-default);
|
|
@@ -226,6 +256,10 @@ export class RelCheckbox extends LitElement {
|
|
|
226
256
|
</svg>
|
|
227
257
|
</span>
|
|
228
258
|
|
|
259
|
+
${this.icon
|
|
260
|
+
? html`<rel-icon name="${this.icon}" size="md" class="icon-prefix"></rel-icon>`
|
|
261
|
+
: null}
|
|
262
|
+
|
|
229
263
|
<span part="label" class="label">${this.label}</span>
|
|
230
264
|
</label>
|
|
231
265
|
|
|
@@ -53,10 +53,19 @@ export class RelDrawer extends LitElement {
|
|
|
53
53
|
bottom: 0;
|
|
54
54
|
z-index: 1000;
|
|
55
55
|
pointer-events: none;
|
|
56
|
+
/* pointer-events:none only blocks the mouse — the closed drawer's
|
|
57
|
+
close button and slotted content would otherwise stay in the Tab
|
|
58
|
+
order and swallow keyboard focus into invisible off-screen UI.
|
|
59
|
+
visibility also removes them from the tab order; the transition
|
|
60
|
+
delays the flip to hidden until the close animation has played. */
|
|
61
|
+
visibility: hidden;
|
|
62
|
+
transition: visibility 0s linear var(--drawer-transition-duration);
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
.drawer-container.open {
|
|
59
66
|
pointer-events: auto;
|
|
67
|
+
visibility: visible;
|
|
68
|
+
transition-delay: 0s;
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
/* Overlay */
|
|
@@ -61,6 +61,16 @@ export class RelPopover extends LitElement {
|
|
|
61
61
|
position: fixed;
|
|
62
62
|
z-index: 1001;
|
|
63
63
|
display: none;
|
|
64
|
+
/* Reset native [popover] UA styles (the wrapper is promoted to the
|
|
65
|
+
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
66
|
+
panel — can't hijack its containing block or clip it). */
|
|
67
|
+
inset: auto;
|
|
68
|
+
margin: 0;
|
|
69
|
+
padding: 0;
|
|
70
|
+
border: none;
|
|
71
|
+
background: transparent;
|
|
72
|
+
overflow: visible;
|
|
73
|
+
color: inherit;
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
.content-wrapper[open] {
|
|
@@ -84,6 +94,7 @@ export class RelPopover extends LitElement {
|
|
|
84
94
|
super.disconnectedCallback();
|
|
85
95
|
this.removeEventListener("pointerdown", this.onHostPointerDown, true);
|
|
86
96
|
this.removeGlobalListeners();
|
|
97
|
+
this.hideNativePopover();
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
firstUpdated() {
|
|
@@ -157,6 +168,7 @@ export class RelPopover extends LitElement {
|
|
|
157
168
|
(document.activeElement as HTMLElement) ?? this.triggerEl;
|
|
158
169
|
|
|
159
170
|
this.addGlobalListeners();
|
|
171
|
+
this.showNativePopover();
|
|
160
172
|
|
|
161
173
|
requestAnimationFrame(() => {
|
|
162
174
|
this.updatePosition();
|
|
@@ -166,9 +178,41 @@ export class RelPopover extends LitElement {
|
|
|
166
178
|
|
|
167
179
|
private onClose() {
|
|
168
180
|
this.removeGlobalListeners();
|
|
181
|
+
this.hideNativePopover();
|
|
169
182
|
this.restoreFocus();
|
|
170
183
|
}
|
|
171
184
|
|
|
185
|
+
/* =======================
|
|
186
|
+
* TOP LAYER (native Popover API)
|
|
187
|
+
*
|
|
188
|
+
* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
189
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
190
|
+
* descendants, breaking viewport-based coordinates and letting the
|
|
191
|
+
* panel's overflow:hidden clip the content. Promoting the wrapper to
|
|
192
|
+
* the browser top layer sidesteps both. `manual` keeps light-dismiss
|
|
193
|
+
* and Escape handling under this component's own control.
|
|
194
|
+
* ======================= */
|
|
195
|
+
|
|
196
|
+
private showNativePopover() {
|
|
197
|
+
const w = this.contentWrapper;
|
|
198
|
+
if (!w || typeof w.showPopover !== "function") return;
|
|
199
|
+
try {
|
|
200
|
+
if (!w.matches(":popover-open")) w.showPopover();
|
|
201
|
+
} catch {
|
|
202
|
+
/* older engines / detached element — fixed-position fallback applies */
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private hideNativePopover() {
|
|
207
|
+
const w = this.contentWrapper;
|
|
208
|
+
if (!w || typeof w.hidePopover !== "function") return;
|
|
209
|
+
try {
|
|
210
|
+
if (w.matches(":popover-open")) w.hidePopover();
|
|
211
|
+
} catch {
|
|
212
|
+
/* already hidden or unsupported — nothing to do */
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
172
216
|
/* =======================
|
|
173
217
|
* GLOBAL LISTENERS
|
|
174
218
|
* ======================= */
|
|
@@ -312,6 +356,17 @@ export class RelPopover extends LitElement {
|
|
|
312
356
|
top = Math.max(8, Math.min(top, window.innerHeight - c.height - 8));
|
|
313
357
|
left = Math.max(8, Math.min(left, window.innerWidth - c.width - 8));
|
|
314
358
|
|
|
359
|
+
// Without top-layer support a transformed ancestor may be the containing
|
|
360
|
+
// block for position:fixed, shifting the wrapper away from the viewport
|
|
361
|
+
// coordinates computed above. Apply, re-measure, and cancel the offset.
|
|
362
|
+
this.contentWrapper.style.top = `${top}px`;
|
|
363
|
+
this.contentWrapper.style.left = `${left}px`;
|
|
364
|
+
const applied = this.contentWrapper.getBoundingClientRect();
|
|
365
|
+
top -= applied.top - top;
|
|
366
|
+
left -= applied.left - left;
|
|
367
|
+
this.contentWrapper.style.top = `${top}px`;
|
|
368
|
+
this.contentWrapper.style.left = `${left}px`;
|
|
369
|
+
|
|
315
370
|
this.contentStyle = `top:${top}px;left:${left}px;`;
|
|
316
371
|
};
|
|
317
372
|
|
|
@@ -344,6 +399,7 @@ export class RelPopover extends LitElement {
|
|
|
344
399
|
<div
|
|
345
400
|
id="${this.uniqueId}"
|
|
346
401
|
class="content-wrapper"
|
|
402
|
+
popover="manual"
|
|
347
403
|
style="${this.contentStyle}"
|
|
348
404
|
?open="${this.activeOpen}"
|
|
349
405
|
>
|
|
@@ -28,6 +28,17 @@ A radio button group component that allows users to select a single option from
|
|
|
28
28
|
planRadio.selectedValue = 'basic';
|
|
29
29
|
</script>
|
|
30
30
|
|
|
31
|
+
<!-- Horizontal row layout -->
|
|
32
|
+
<rel-radio id="sizeRadio" name="size-group" orientation="horizontal"></rel-radio>
|
|
33
|
+
<script>
|
|
34
|
+
const sizeRadio = document.getElementById('sizeRadio');
|
|
35
|
+
sizeRadio.options = [
|
|
36
|
+
{ value: 's', label: 'Small' },
|
|
37
|
+
{ value: 'm', label: 'Medium' },
|
|
38
|
+
{ value: 'l', label: 'Large' }
|
|
39
|
+
];
|
|
40
|
+
</script>
|
|
41
|
+
|
|
31
42
|
<!-- Listen for changes -->
|
|
32
43
|
<rel-radio id="preferenceRadio"></rel-radio>
|
|
33
44
|
<script>
|
|
@@ -49,6 +60,8 @@ A radio button group component that allows users to select a single option from
|
|
|
49
60
|
|------|------|---------|-------------|
|
|
50
61
|
| `options` | `RadioOption[]` | `[]` | Array of radio options (see RadioOption interface below) |
|
|
51
62
|
| `name` | `string` | `"radio-group"` | Name attribute for the radio group (for form grouping) |
|
|
63
|
+
| `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Layout axis: `"vertical"` stacks options in a column; `"horizontal"` lays them out in a wrapping row (reflected attribute) |
|
|
64
|
+
| `size` | `"small" \| "medium"` | `"medium"` | Control scale: `"medium"` = 20px circle with body-lg labels; `"small"` = 16px circle with body-md labels (reflected attribute) |
|
|
52
65
|
| `selectedValue` | `string \| null` | `null` | Currently selected option value (reflected attribute) |
|
|
53
66
|
| `disabled` | `boolean` | `false` | Disables the entire radio group |
|
|
54
67
|
|
|
@@ -82,7 +95,8 @@ None exposed.
|
|
|
82
95
|
- `--rel-fonts-weight-body-medium` - Font weight for labels
|
|
83
96
|
|
|
84
97
|
### Spacing Tokens
|
|
85
|
-
- `--rel-spacing-xs` - Gap between radio options
|
|
98
|
+
- `--rel-spacing-xs` - Gap between radio options (also the row gap when wrapping horizontally)
|
|
99
|
+
- `--rel-spacing-xl` - Column gap between options in `orientation="horizontal"`
|
|
86
100
|
- `--rel-spacing-3xs` - Gap within each option (label and supporting text)
|
|
87
101
|
- `--rel-spacing-2xs` - Gap between radio circle and label
|
|
88
102
|
|
|
@@ -94,6 +108,7 @@ None exposed.
|
|
|
94
108
|
- `--rel-text-danger` - Error state border and dot color
|
|
95
109
|
|
|
96
110
|
## Accessibility
|
|
111
|
+
- The option list renders with `role="radiogroup"` and `aria-orientation` reflecting the `orientation` prop
|
|
97
112
|
- Uses visually hidden native radio inputs for proper screen reader support
|
|
98
113
|
- Custom visual radio circle with proper focus indicators
|
|
99
114
|
- Focus visible state with outline offset for keyboard navigation
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LitElement, html, css } from "lit";
|
|
2
2
|
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
import type { Orientation } from "../rel-stepper/rel-stepper.js";
|
|
3
4
|
|
|
4
5
|
export interface RadioOption {
|
|
5
6
|
value: string;
|
|
@@ -14,6 +15,14 @@ export class RelRadio extends LitElement {
|
|
|
14
15
|
|
|
15
16
|
@property({ type: String }) name = "radio-group";
|
|
16
17
|
|
|
18
|
+
/** Layout axis of the option list. `"vertical"` (default) stacks options in
|
|
19
|
+
* a column; `"horizontal"` lays them out in a wrapping row. */
|
|
20
|
+
@property({ type: String, reflect: true }) orientation: Orientation = "vertical";
|
|
21
|
+
|
|
22
|
+
/** Control scale. `"medium"` (default) = 20px circle with body-lg labels;
|
|
23
|
+
* `"small"` = 16px circle with body-md labels. */
|
|
24
|
+
@property({ type: String, reflect: true }) size: "small" | "medium" = "medium";
|
|
25
|
+
|
|
17
26
|
@property({ type: String, reflect: true })
|
|
18
27
|
selectedValue: string | null = null;
|
|
19
28
|
|
|
@@ -45,6 +54,15 @@ export class RelRadio extends LitElement {
|
|
|
45
54
|
gap: var(--rel-spacing-xs);
|
|
46
55
|
}
|
|
47
56
|
|
|
57
|
+
/* Horizontal orientation — options flow in a wrapping row. Wider column
|
|
58
|
+
gap than the vertical rhythm so adjacent option labels read as separate
|
|
59
|
+
choices; row gap keeps the vertical value for wrapped lines. */
|
|
60
|
+
:host([orientation="horizontal"]) .rel-radio-group {
|
|
61
|
+
flex-direction: row;
|
|
62
|
+
flex-wrap: wrap;
|
|
63
|
+
gap: var(--rel-spacing-xs) var(--rel-spacing-xl);
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
.rel-radio-option {
|
|
49
67
|
display: inline-flex;
|
|
50
68
|
flex-direction: column;
|
|
@@ -76,6 +94,14 @@ export class RelRadio extends LitElement {
|
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
.radio-main {
|
|
97
|
+
/* Containing block for the hidden input below — without this the
|
|
98
|
+
absolutely-positioned input resolves against the document (or any
|
|
99
|
+
positioned ancestor in the consumer page), where it neither moves
|
|
100
|
+
with intermediate scroll containers nor stays aligned with the
|
|
101
|
+
visible radio. Tabbing to it then makes the browser scroll the
|
|
102
|
+
WRONG scroller, shoving the whole page up (same bug as
|
|
103
|
+
rel-checkbox — keep the two fixes in sync). */
|
|
104
|
+
position: relative;
|
|
79
105
|
display: flex;
|
|
80
106
|
align-items: center;
|
|
81
107
|
gap: var(--rel-spacing-2xs);
|
|
@@ -83,6 +109,10 @@ export class RelRadio extends LitElement {
|
|
|
83
109
|
|
|
84
110
|
input {
|
|
85
111
|
position: absolute;
|
|
112
|
+
inset: 0;
|
|
113
|
+
width: 100%;
|
|
114
|
+
height: 100%;
|
|
115
|
+
margin: 0;
|
|
86
116
|
opacity: 0;
|
|
87
117
|
pointer-events: none;
|
|
88
118
|
}
|
|
@@ -135,6 +165,34 @@ export class RelRadio extends LitElement {
|
|
|
135
165
|
|
|
136
166
|
}
|
|
137
167
|
|
|
168
|
+
/* Size variant (medium is the base scale above). Supporting-text indent =
|
|
169
|
+
circle width + the radio-main gap, so it stays aligned under the label. */
|
|
170
|
+
:host([size="small"]) .radio-custom {
|
|
171
|
+
width: 16px;
|
|
172
|
+
height: 16px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
:host([size="small"]) input:checked + .radio-custom::after {
|
|
176
|
+
inset: 3px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
:host([size="small"]) .radio-label {
|
|
180
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
181
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
182
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
183
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
184
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
:host([size="small"]) .radio-supporting {
|
|
188
|
+
margin-left: 24px;
|
|
189
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
190
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
191
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
192
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
193
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
194
|
+
}
|
|
195
|
+
|
|
138
196
|
/* Error state (Figma State=Error, both Is Checked values) */
|
|
139
197
|
.rel-radio-option.error-unchecked .radio-custom,
|
|
140
198
|
.rel-radio-option.error-checked .radio-custom {
|
|
@@ -231,7 +289,12 @@ export class RelRadio extends LitElement {
|
|
|
231
289
|
|
|
232
290
|
render() {
|
|
233
291
|
return html`
|
|
234
|
-
<div
|
|
292
|
+
<div
|
|
293
|
+
part="base"
|
|
294
|
+
class="rel-radio-group"
|
|
295
|
+
role="radiogroup"
|
|
296
|
+
aria-orientation="${this.orientation}"
|
|
297
|
+
>
|
|
235
298
|
${this.options.map((o) => this.renderOption(o))}
|
|
236
299
|
</div>
|
|
237
300
|
${this.error && this.errorMessage
|
|
@@ -34,7 +34,10 @@ A custom combobox-style select component built on the same textfield visual lang
|
|
|
34
34
|
| Name | Type | Default | Description |
|
|
35
35
|
|------|------|---------|-------------|
|
|
36
36
|
| `label` | `string` | `""` | Label text displayed above the select |
|
|
37
|
-
| `
|
|
37
|
+
| `label-style` | `"default" \| "floating" \| "none"` | `"default"` | Label presentation. `"default"` sits above the trigger, `"floating"` rests over the placeholder and animates onto the border line, `"none"` renders no label at all |
|
|
38
|
+
| `field-aria-label` | `string` | - | Accessible name for the trigger. Required with `label-style="none"` |
|
|
39
|
+
| `field-aria-labelledby` | `string` | - | IDREF list of external element(s) naming the trigger. Wins over `field-aria-label` |
|
|
40
|
+
| `options` | `(string \| SelectOptionObject)[]` | `[]` | Options to display in the dropdown. Each entry is either a plain string or an object: `{ value, label?, supportingText?, icon? }`. `icon` is a `rel-icon` name rendered as a prefix (leading icon) on the menu item — before the label in single-select rows, and between the checkbox and label in multi-select rows |
|
|
38
41
|
| `selected` | `string` | `""` | Currently selected option value |
|
|
39
42
|
| `state` | `"default" \| "error" \| "disabled"` | `"default"` | Visual and interaction state of the select |
|
|
40
43
|
| `supportingText` | `string` | `""` | Helper or error text displayed below the select |
|
|
@@ -80,6 +83,9 @@ None exposed.
|
|
|
80
83
|
### Border Tokens
|
|
81
84
|
- `--rel-border-radius-xs` - Rounded border radius (default `border="rounded"`)
|
|
82
85
|
|
|
86
|
+
### Layout Tokens
|
|
87
|
+
- `--rel-field-label-reserve` - Height held for the absent label row under `label-style="none"`, so the trigger bottom-aligns with labelled siblings. Defaults to the label line-height plus the wrapper gap; set to `0` to collapse the row entirely in compact contexts (toolbars, table cells)
|
|
88
|
+
|
|
83
89
|
## Accessibility
|
|
84
90
|
- Trigger input uses `role="combobox"` with `aria-haspopup="listbox"`, `aria-expanded`, and `aria-controls`
|
|
85
91
|
- Dropdown panel uses `role="listbox"`; each option uses `role="option"` with `aria-selected`
|
|
@@ -107,7 +113,9 @@ None exposed.
|
|
|
107
113
|
- Icons (left/right) adjust internal padding automatically to accommodate icon space
|
|
108
114
|
- The chevron is always visible on the far right and rotates 180° when the dropdown is open; a custom `rightIcon` replaces it entirely (no chevron) and does not rotate
|
|
109
115
|
- In error state, supporting text is styled with error color for visual consistency; the error state automatically clears once a valid option is selected
|
|
110
|
-
- Options
|
|
116
|
+
- Options accept plain strings or `{ value, label?, supportingText?, icon? }` objects; string entries use the same text for value and label
|
|
117
|
+
- In multi-select, selected chips stay on a single line: the number of visible chips adapts to the field's width and the rest collapse into a trailing "+N" chip (recomputed on selection changes and via `ResizeObserver` on resize). At least one chip is always shown. `max-visible-chips` is an optional additional hard cap (uncapped by default)
|
|
118
|
+
- An option's `icon` renders only when set — options without one simply omit the prefix icon (no reserved empty space), and an unknown icon name logs a `rel-icon` console warning and renders nothing
|
|
111
119
|
- Typed text that doesn't exactly match (case-insensitive) any option reverts to the last committed `selected` value on blur, Escape, or Tab
|
|
112
120
|
- The ghost-text suggestion shows the remainder of the first option whose name starts with the typed text (case-insensitive); it has no effect on filtering, which matches substrings anywhere in an option
|
|
113
121
|
- Dropdown panel is positioned via `position: fixed` and flips above the trigger if there isn't enough viewport space below
|