@reliability-design/web 1.4.5 → 1.4.6
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/dist/components/rel-alert/rel-alert.ts +1 -1
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +33 -2
- package/dist/components/rel-badge/rel-badge.ts +6 -1
- package/dist/components/rel-calendar/rel-calendar.ts +64 -18
- package/dist/components/rel-carousel/rel-carousel.ts +25 -2
- package/dist/components/rel-chip/rel-chip.ts +31 -20
- package/dist/components/rel-date-picker/date-parse.test.ts +116 -0
- package/dist/components/rel-date-picker/date-parse.ts +178 -0
- package/dist/components/rel-date-picker/rel-date-picker.ts +529 -90
- package/dist/components/rel-drawer/rel-drawer.ts +6 -3
- package/dist/components/rel-menu/rel-menu.ts +6 -0
- package/dist/components/rel-otp/rel-otp.ts +12 -2
- package/dist/components/rel-pagination/rel-pagination.ts +13 -0
- package/dist/components/rel-popover/rel-popover.ts +33 -2
- package/dist/components/rel-progress/rel-progress.ts +13 -4
- package/dist/components/rel-radio/README.md +6 -3
- package/dist/components/rel-radio/rel-radio.ts +236 -60
- package/dist/components/rel-range-picker/rel-range-picker.ts +452 -95
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +5 -1
- package/dist/components/rel-select/rel-select.ts +44 -13
- package/dist/components/rel-sheet/rel-sheet.ts +163 -18
- package/dist/components/rel-sidebar/rel-sidebar.ts +7 -1
- package/dist/components/rel-skeleton/rel-skeleton.ts +9 -0
- package/dist/components/rel-slider/rel-slider.ts +6 -3
- package/dist/components/rel-snackbar/rel-snackbar.ts +20 -16
- package/dist/components/rel-stepper/rel-stepper.ts +3 -1
- package/dist/components/rel-switch/rel-switch.ts +3 -1
- package/dist/components/rel-textfield/rel-textfield.ts +263 -75
- package/dist/components/rel-time-picker/rel-time-picker.ts +450 -94
- package/dist/components/rel-time-picker/time-parse.test.ts +67 -0
- package/dist/components/rel-time-picker/time-parse.ts +85 -0
- package/dist/components/rel-toast/rel-toast.ts +4 -2
- package/dist/components/rel-tooltip/README.md +13 -5
- package/dist/components/rel-tooltip/rel-tooltip.ts +3 -0
- package/dist/components/rel-upload/rel-upload.ts +25 -0
- package/dist/index.d.mts +241 -11
- package/dist/index.d.ts +241 -11
- package/dist/index.js +2573 -598
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2614 -640
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
1
|
+
import { LitElement, html, css, nothing } from "lit";
|
|
2
2
|
import { customElement, property, state, query } from "lit/decorators.js";
|
|
3
3
|
import "@reliability-design/icons/rel-icon";
|
|
4
4
|
import "./rel-drawer-footer";
|
|
@@ -550,6 +550,9 @@ export class RelDrawer extends LitElement {
|
|
|
550
550
|
style="${this.width ? `--drawer-width: ${this.width};` : ''}${this.maxHeight ? `--drawer-max-height: ${this.maxHeight};` : ''}${this.getPanelStyle()}"
|
|
551
551
|
role="${this.modal ? "dialog" : "region"}"
|
|
552
552
|
aria-modal="${this.modal}"
|
|
553
|
+
aria-labelledby="${hasTitle ? "drawer-title" : nothing}"
|
|
554
|
+
aria-describedby="${hasDescription ? "drawer-description" : nothing}"
|
|
555
|
+
aria-label="${hasTitle ? nothing : "Drawer"}"
|
|
553
556
|
tabindex="-1">
|
|
554
557
|
${showHandleBar
|
|
555
558
|
? html`<div
|
|
@@ -564,10 +567,10 @@ export class RelDrawer extends LitElement {
|
|
|
564
567
|
<div class="header-top">
|
|
565
568
|
<div>
|
|
566
569
|
${hasTitle
|
|
567
|
-
? html`<h2 class="title">${this.title}</h2>`
|
|
570
|
+
? html`<h2 class="title" id="drawer-title">${this.title}</h2>`
|
|
568
571
|
: ""}
|
|
569
572
|
${hasDescription
|
|
570
|
-
? html`<p class="description">${this.description}</p>`
|
|
573
|
+
? html`<p class="description" id="drawer-description">${this.description}</p>`
|
|
571
574
|
: ""}
|
|
572
575
|
</div>
|
|
573
576
|
${showCloseButton
|
|
@@ -450,6 +450,8 @@ export class RelMenu extends LitElement {
|
|
|
450
450
|
this.type === "checkbox"
|
|
451
451
|
? html`<rel-checkbox
|
|
452
452
|
class="menu-control"
|
|
453
|
+
inert
|
|
454
|
+
aria-hidden="true"
|
|
453
455
|
label="${item.label}"
|
|
454
456
|
status="${item.checked ? "checked" : "unchecked"}"
|
|
455
457
|
state="${item.disabled ? "disabled" : "default"}"
|
|
@@ -457,6 +459,8 @@ export class RelMenu extends LitElement {
|
|
|
457
459
|
: this.type === "select-option"
|
|
458
460
|
? html`<rel-radio
|
|
459
461
|
class="menu-control"
|
|
462
|
+
inert
|
|
463
|
+
aria-hidden="true"
|
|
460
464
|
.options="${[{ value: item.label, label: item.label, disabled: !!item.disabled }]}"
|
|
461
465
|
.selectedValue="${item.active ? item.label : null}"
|
|
462
466
|
></rel-radio>`
|
|
@@ -475,6 +479,7 @@ export class RelMenu extends LitElement {
|
|
|
475
479
|
class="rel-menu-item ${item.active ? "active" : ""} ${item.disabled ? "disabled" : ""}"
|
|
476
480
|
role="${role}"
|
|
477
481
|
aria-checked="${ariaChecked}"
|
|
482
|
+
aria-label="${this.type === "checkbox" || this.type === "select-option" ? item.label : nothing}"
|
|
478
483
|
aria-disabled="${item.disabled ? "true" : nothing}"
|
|
479
484
|
aria-haspopup="${hasSubmenu ? "menu" : nothing}"
|
|
480
485
|
aria-expanded="${hasSubmenu ? String(!!item.open) : nothing}"
|
|
@@ -524,6 +529,7 @@ export class RelMenu extends LitElement {
|
|
|
524
529
|
<div
|
|
525
530
|
part="list" class="rel-menu ${this.type === "fixed" || this.type === "fit" ? this.type : ""}"
|
|
526
531
|
role="menu"
|
|
532
|
+
aria-label="Menu"
|
|
527
533
|
tabindex="0"
|
|
528
534
|
aria-activedescendant="${activeDescendant}"
|
|
529
535
|
@keydown="${this.onKeydown}"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
1
|
+
import { LitElement, html, css, nothing } from "lit";
|
|
2
2
|
import { customElement, property, state, query } from "lit/decorators.js";
|
|
3
|
+
import { announce } from "../../utils/announce";
|
|
3
4
|
|
|
4
5
|
export type OtpType = "numeric" | "alphanumeric";
|
|
5
6
|
|
|
@@ -77,7 +78,7 @@ export class RelOtp extends LitElement {
|
|
|
77
78
|
height: 48px;
|
|
78
79
|
border: var(--rel-stroke-sm) solid var(--rel-border-default);
|
|
79
80
|
border-radius: var(--rel-border-radius-sm);
|
|
80
|
-
background: var(--rel-bg-neutral);
|
|
81
|
+
background: var(--rel-bg-neutral);
|
|
81
82
|
text-align: center;
|
|
82
83
|
color: var(--rel-text-default);
|
|
83
84
|
outline: none;
|
|
@@ -230,6 +231,7 @@ export class RelOtp extends LitElement {
|
|
|
230
231
|
composed: true,
|
|
231
232
|
})
|
|
232
233
|
);
|
|
234
|
+
announce("Code complete");
|
|
233
235
|
}
|
|
234
236
|
}
|
|
235
237
|
|
|
@@ -390,6 +392,13 @@ export class RelOtp extends LitElement {
|
|
|
390
392
|
|
|
391
393
|
this.updateValue();
|
|
392
394
|
|
|
395
|
+
const filled = this.cells.filter((c) => c).length;
|
|
396
|
+
if (filled < this.length) {
|
|
397
|
+
announce(
|
|
398
|
+
`${filled} ${this.type === "alphanumeric" ? "characters" : "digits"} entered`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
393
402
|
// Focus next empty cell or last cell
|
|
394
403
|
const nextEmptyIndex = this.cells.findIndex((c, i) => i >= startIndex && !c);
|
|
395
404
|
if (nextEmptyIndex >= 0) {
|
|
@@ -480,6 +489,7 @@ export class RelOtp extends LitElement {
|
|
|
480
489
|
?readonly="${this.readonly}"
|
|
481
490
|
?required="${this.required && index === 0}"
|
|
482
491
|
aria-label="${this.getAriaLabel(index)}"
|
|
492
|
+
aria-invalid="${this.invalid ? "true" : nothing}"
|
|
483
493
|
@pointerdown="${this.handlePointerDown}"
|
|
484
494
|
@input="${this.handleInput}"
|
|
485
495
|
@keydown="${this.handleKeyDown}"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LitElement, html, css, nothing } from "lit";
|
|
2
2
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
|
+
import { announce } from "../../utils/announce";
|
|
3
4
|
|
|
4
5
|
@customElement("rel-pagination")
|
|
5
6
|
export class RelPagination extends LitElement {
|
|
@@ -390,6 +391,18 @@ export class RelPagination extends LitElement {
|
|
|
390
391
|
const total = this.computedTotalPages;
|
|
391
392
|
if (this.disabled || newPage < 1 || newPage > total || newPage === this.activePage) return;
|
|
392
393
|
if (this.page === undefined) this.currentPage = newPage;
|
|
394
|
+
announce(`Page ${newPage} of ${total}`);
|
|
395
|
+
const focused = this.shadowRoot?.activeElement as HTMLButtonElement | null;
|
|
396
|
+
if (focused?.classList.contains("pag-nav-btn")) {
|
|
397
|
+
this.updateComplete.then(() => {
|
|
398
|
+
if (focused.isConnected && focused.disabled) {
|
|
399
|
+
const sibling = focused.parentElement?.querySelector<HTMLButtonElement>(
|
|
400
|
+
".pag-nav-btn:not(:disabled)"
|
|
401
|
+
);
|
|
402
|
+
sibling?.focus();
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
393
406
|
this.dispatchEvent(
|
|
394
407
|
new CustomEvent("rel-change", {
|
|
395
408
|
detail: { page: newPage },
|
|
@@ -5,6 +5,12 @@ import { getDeepActiveElement, getFocusableElements } from "../../utils/focus.js
|
|
|
5
5
|
type SideType = "top" | "right" | "bottom" | "left";
|
|
6
6
|
type AlignType = "start" | "center" | "end";
|
|
7
7
|
|
|
8
|
+
/** Every currently-open popover on the page (module-level, shared by all
|
|
9
|
+
* instances). Used so opening one popover light-dismisses any other open
|
|
10
|
+
* popover it is not nested inside — regardless of whether it was opened by
|
|
11
|
+
* pointer, keyboard, or programmatically. */
|
|
12
|
+
const openPopovers = new Set<RelPopover>();
|
|
13
|
+
|
|
8
14
|
@customElement("rel-popover")
|
|
9
15
|
export class RelPopover extends LitElement {
|
|
10
16
|
/* =======================
|
|
@@ -182,6 +188,9 @@ export class RelPopover extends LitElement {
|
|
|
182
188
|
private onOpen() {
|
|
183
189
|
this.returnFocusEl = getDeepActiveElement() ?? this.triggerEl;
|
|
184
190
|
|
|
191
|
+
this.closeOtherPopovers();
|
|
192
|
+
openPopovers.add(this);
|
|
193
|
+
|
|
185
194
|
this.addGlobalListeners();
|
|
186
195
|
this.showNativePopover();
|
|
187
196
|
|
|
@@ -192,11 +201,29 @@ export class RelPopover extends LitElement {
|
|
|
192
201
|
}
|
|
193
202
|
|
|
194
203
|
private onClose() {
|
|
204
|
+
openPopovers.delete(this);
|
|
195
205
|
this.removeGlobalListeners();
|
|
196
206
|
this.hideNativePopover();
|
|
197
207
|
this.restoreFocus();
|
|
198
208
|
}
|
|
199
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Light-dismiss every other open popover that isn't an ancestor of this
|
|
212
|
+
* one (nested popovers keep their parent open). This is what the outside-
|
|
213
|
+
* pointerdown listener would do for a click on our trigger — except the
|
|
214
|
+
* trigger handler stops that pointerdown in the capture phase, and a
|
|
215
|
+
* keyboard (Enter/Space) or programmatic open never produces one at all.
|
|
216
|
+
* Honors each popover's own `closeOnOutsideClick` setting.
|
|
217
|
+
*/
|
|
218
|
+
private closeOtherPopovers() {
|
|
219
|
+
for (const other of openPopovers) {
|
|
220
|
+
if (other === this) continue;
|
|
221
|
+
if (!other.closeOnOutsideClick) continue;
|
|
222
|
+
if (other.contains(this)) continue;
|
|
223
|
+
other.setOpen(false);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
200
227
|
/* =======================
|
|
201
228
|
* TOP LAYER (native Popover API)
|
|
202
229
|
*
|
|
@@ -234,7 +261,11 @@ export class RelPopover extends LitElement {
|
|
|
234
261
|
|
|
235
262
|
private addGlobalListeners() {
|
|
236
263
|
if (this.closeOnOutsideClick) {
|
|
237
|
-
|
|
264
|
+
// Capture phase: another popover's trigger handler (onHostPointerDown)
|
|
265
|
+
// calls stopPropagation() in its own capture listener, which would
|
|
266
|
+
// otherwise swallow the event before a bubble-phase document listener
|
|
267
|
+
// ever saw it — leaving this popover open alongside the new one.
|
|
268
|
+
document.addEventListener("pointerdown", this.onOutsidePointerDown, true);
|
|
238
269
|
}
|
|
239
270
|
|
|
240
271
|
if (this.closeOnEscape || this.trapFocus) {
|
|
@@ -246,7 +277,7 @@ export class RelPopover extends LitElement {
|
|
|
246
277
|
}
|
|
247
278
|
|
|
248
279
|
private removeGlobalListeners() {
|
|
249
|
-
document.removeEventListener("pointerdown", this.onOutsidePointerDown);
|
|
280
|
+
document.removeEventListener("pointerdown", this.onOutsidePointerDown, true);
|
|
250
281
|
document.removeEventListener("keydown", this.onKeyDown);
|
|
251
282
|
window.removeEventListener("resize", this.updatePosition);
|
|
252
283
|
window.removeEventListener("scroll", this.updatePosition, true);
|
|
@@ -15,6 +15,9 @@ export class RelProgress extends LitElement {
|
|
|
15
15
|
@property({ type: Number })
|
|
16
16
|
value = 0;
|
|
17
17
|
|
|
18
|
+
@property({ type: String })
|
|
19
|
+
label = "";
|
|
20
|
+
|
|
18
21
|
private radius = 15.9155;
|
|
19
22
|
private circumference = 2 * Math.PI * this.radius;
|
|
20
23
|
|
|
@@ -155,9 +158,14 @@ export class RelProgress extends LitElement {
|
|
|
155
158
|
return Math.min(100, Math.max(0, this.value));
|
|
156
159
|
}
|
|
157
160
|
|
|
161
|
+
private get accessibleName() {
|
|
162
|
+
if (this.label) return this.label;
|
|
163
|
+
return this.mode.endsWith("indeterminate") ? "Loading" : "Progress";
|
|
164
|
+
}
|
|
165
|
+
|
|
158
166
|
private renderLinearDeterminate() {
|
|
159
167
|
return html`
|
|
160
|
-
<div part="track" class="linear-track" role="progressbar"
|
|
168
|
+
<div part="track" class="linear-track" role="progressbar" aria-label="${this.accessibleName}"
|
|
161
169
|
aria-valuenow="${this.safeValue}" aria-valuemin="0" aria-valuemax="100">
|
|
162
170
|
<div part="indicator" class="linear-bar determinate" style="width: ${this.safeValue}%"></div>
|
|
163
171
|
</div>
|
|
@@ -166,7 +174,7 @@ export class RelProgress extends LitElement {
|
|
|
166
174
|
|
|
167
175
|
private renderLinearIndeterminate() {
|
|
168
176
|
return html`
|
|
169
|
-
<div part="track" class="linear-track" role="progressbar">
|
|
177
|
+
<div part="track" class="linear-track" role="progressbar" aria-label="${this.accessibleName}">
|
|
170
178
|
<div part="indicator" class="linear-bar indeterminate"></div>
|
|
171
179
|
</div>
|
|
172
180
|
`;
|
|
@@ -177,7 +185,8 @@ export class RelProgress extends LitElement {
|
|
|
177
185
|
this.circumference - (this.safeValue / 100) * this.circumference;
|
|
178
186
|
|
|
179
187
|
return html`
|
|
180
|
-
<div class="circular"
|
|
188
|
+
<div class="circular" role="progressbar" aria-label="${this.accessibleName}"
|
|
189
|
+
aria-valuenow="${this.safeValue}" aria-valuemin="0" aria-valuemax="100">
|
|
181
190
|
<svg viewBox="0 0 36 36" class="circular-svg">
|
|
182
191
|
<circle class="circular-bg" cx="18" cy="18" r="${this.radius}" />
|
|
183
192
|
<circle
|
|
@@ -195,7 +204,7 @@ export class RelProgress extends LitElement {
|
|
|
195
204
|
|
|
196
205
|
private renderCircularIndeterminate() {
|
|
197
206
|
return html`
|
|
198
|
-
<div class="circular">
|
|
207
|
+
<div class="circular" role="progressbar" aria-label="${this.accessibleName}">
|
|
199
208
|
<svg viewBox="0 0 50 50" class="spinner">
|
|
200
209
|
<circle
|
|
201
210
|
class="spinner-path"
|
|
@@ -61,9 +61,11 @@ A radio button group component that allows users to select a single option from
|
|
|
61
61
|
| `options` | `RadioOption[]` | `[]` | Array of radio options (see RadioOption interface below) |
|
|
62
62
|
| `name` | `string` | `"radio-group"` | Name attribute for the radio group (for form grouping) |
|
|
63
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
|
|
64
|
+
| `size` | `"small" \| "medium" \| "large"` | `"medium"` | Control scale (Figma Size L/M/S): `"medium"` = 20px control with body-lg labels; `"large"` = 24px control with body-xl labels; `"small"` = 20px control with body-md labels — S and M share the control size, only typography steps down (reflected attribute) |
|
|
65
|
+
| `density` | `"compact" \| "comfortable"` | `"compact"` | Supporting-text layout (Figma Density): `"compact"` puts the supporting text on its own line under the row, indented past the control; `"comfortable"` stacks label + supporting text beside the control (reflected attribute) |
|
|
66
|
+
| `alignment` | `"leading" \| "trailing"` | `"leading"` | Control position (Figma Layout): `"trailing"` pushes the control to the far edge of the row and always renders comfortable density — the spec has no compact trailing layout (reflected attribute) |
|
|
65
67
|
| `selectedValue` | `string \| null` | `null` | Currently selected option value (reflected attribute) |
|
|
66
|
-
| `disabled` | `boolean` | `false` | Disables the entire radio group |
|
|
68
|
+
| `disabled` | `boolean` | `false` | Disables the entire radio group (renders at 40% opacity) |
|
|
67
69
|
|
|
68
70
|
### RadioOption Interface
|
|
69
71
|
```typescript
|
|
@@ -72,7 +74,8 @@ interface RadioOption {
|
|
|
72
74
|
label: string; // Display label
|
|
73
75
|
supportingText?: string; // Optional helper text below the label
|
|
74
76
|
disabled?: boolean; // Disable individual option
|
|
75
|
-
|
|
77
|
+
icon?: string; // rel-icon name rendered before the label
|
|
78
|
+
avatar?: string; // 1–2 initials rendered as a circular chip before the label (ignored when icon is set)
|
|
76
79
|
}
|
|
77
80
|
```
|
|
78
81
|
|