@reliability-design/web 1.4.3 → 1.4.5
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-accordion/rel-accordion.ts +13 -1
- package/dist/components/rel-autocomplete/README.md +1 -0
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +56 -1
- package/dist/components/rel-button/rel-button.ts +18 -2
- package/dist/components/rel-calendar/rel-calendar.ts +23 -1
- package/dist/components/rel-carousel/rel-carousel.ts +20 -0
- package/dist/components/rel-checkbox/README.md +69 -22
- package/dist/components/rel-checkbox/rel-checkbox.ts +119 -9
- package/dist/components/rel-chip/rel-chip.ts +29 -0
- package/dist/components/rel-date-picker/rel-date-picker.ts +156 -13
- package/dist/components/rel-drawer/rel-drawer.ts +22 -58
- package/dist/components/rel-menu/rel-menu.ts +124 -15
- package/dist/components/rel-menubar/rel-menubar.ts +221 -63
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +39 -3
- package/dist/components/rel-otp/rel-otp.ts +3 -3
- package/dist/components/rel-pagination/README.md +14 -0
- package/dist/components/rel-pagination/rel-pagination.ts +30 -14
- package/dist/components/rel-popover/rel-popover.ts +31 -3
- package/dist/components/rel-range-picker/rel-range-picker.ts +251 -3
- package/dist/components/rel-resizable/rel-resizable-group.ts +20 -0
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +50 -12
- package/dist/components/rel-select/README.md +1 -0
- package/dist/components/rel-select/rel-select.ts +1385 -1170
- package/dist/components/rel-sheet/rel-sheet.ts +22 -1
- package/dist/components/rel-sidebar/rel-sidebar.ts +43 -39
- package/dist/components/rel-stepper/rel-stepper.ts +44 -1
- package/dist/components/rel-tab/rel-tab.ts +32 -7
- package/dist/components/rel-time-picker/rel-time-picker.ts +338 -206
- package/dist/components/rel-tooltip/rel-tooltip.ts +30 -0
- package/dist/components/rel-topbar/rel-topbar-actions.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-brand.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-item.ts +318 -319
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-profile.ts +224 -224
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar.ts +440 -440
- package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +134 -0
- package/dist/components/rel-upload/rel-upload-actions.ts +163 -0
- package/dist/components/rel-upload/rel-upload.ts +943 -0
- package/dist/index.d.mts +491 -18
- package/dist/index.d.ts +491 -18
- package/dist/index.js +2748 -410
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2953 -627
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LitElement, html, css } from "lit";
|
|
2
2
|
import { customElement, property, query } from "lit/decorators.js";
|
|
3
3
|
import "@reliability-design/icons/rel-icon";
|
|
4
|
+
import { getDeepActiveElement, getFocusableElements, trapTabKey } from "../../utils/focus.js";
|
|
4
5
|
|
|
5
6
|
export type ModalSide = "top" | "bottom" | "center";
|
|
6
7
|
|
|
@@ -21,6 +22,7 @@ export class RelModal extends LitElement {
|
|
|
21
22
|
breakpoint: "responsive" | "web" = "responsive";
|
|
22
23
|
|
|
23
24
|
@query(".overlay") private overlayElement!: HTMLElement;
|
|
25
|
+
@query(".panel") private panelElement!: HTMLElement;
|
|
24
26
|
|
|
25
27
|
private previousFocus: HTMLElement | null = null;
|
|
26
28
|
private originalBodyOverflow = "";
|
|
@@ -322,9 +324,23 @@ export class RelModal extends LitElement {
|
|
|
322
324
|
}
|
|
323
325
|
|
|
324
326
|
private handleOpen() {
|
|
325
|
-
|
|
327
|
+
// Deep-resolved — document.activeElement retargets to shadow hosts whose
|
|
328
|
+
// .focus() may be a no-op, silently dropping the restore.
|
|
329
|
+
this.previousFocus = getDeepActiveElement();
|
|
326
330
|
this.lockScroll();
|
|
327
331
|
document.addEventListener("keydown", this.handleKeydown);
|
|
332
|
+
|
|
333
|
+
// Move focus into the dialog — before this, a keyboard user had to Tab
|
|
334
|
+
// through the entire page behind the overlay to reach the sheet.
|
|
335
|
+
if (this.modal) {
|
|
336
|
+
this.updateComplete.then(() => {
|
|
337
|
+
const panel = this.panelElement;
|
|
338
|
+
if (!panel) return;
|
|
339
|
+
const focusTarget = getFocusableElements(panel)[0];
|
|
340
|
+
if (focusTarget) focusTarget.focus();
|
|
341
|
+
else panel.focus();
|
|
342
|
+
});
|
|
343
|
+
}
|
|
328
344
|
this.dispatchEvent(
|
|
329
345
|
new CustomEvent("rel-open-change", {
|
|
330
346
|
detail: true,
|
|
@@ -374,6 +390,11 @@ export class RelModal extends LitElement {
|
|
|
374
390
|
if (e.key === "Escape" && this.dismissible) {
|
|
375
391
|
e.preventDefault();
|
|
376
392
|
this.open = false;
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
// aria-modal promised containment; actually deliver it.
|
|
396
|
+
if (e.key === "Tab" && this.modal && this.panelElement) {
|
|
397
|
+
trapTabKey(e, this.panelElement);
|
|
377
398
|
}
|
|
378
399
|
};
|
|
379
400
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
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
|
+
import { matchesShortcut, isEditableTarget } from "../../utils/shortcuts.js";
|
|
5
|
+
import { getDeepActiveElement } from "../../utils/focus.js";
|
|
4
6
|
|
|
5
7
|
export type SidebarVariant = "sidebar" | "floating" | "inset";
|
|
6
8
|
export type SidebarSide = "left" | "right";
|
|
@@ -11,12 +13,12 @@ export type SidebarChangeReason = "trigger" | "rail" | "overlay" | "escape" | "s
|
|
|
11
13
|
// ============================================================================
|
|
12
14
|
// rel-sidebar-provider (Root State Manager)
|
|
13
15
|
// ============================================================================
|
|
14
|
-
/**
|
|
15
|
-
* @deprecated The rel-sidebar family has no Figma design in RelDS 2.0 and is
|
|
16
|
-
* excluded from the parity contract. Compose sidebars from rel-drawer,
|
|
17
|
-
* rel-menu and rel-resizable instead. It remains exported for compatibility
|
|
18
|
-
* but will be removed in a future major version.
|
|
19
|
-
*/
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated The rel-sidebar family has no Figma design in RelDS 2.0 and is
|
|
18
|
+
* excluded from the parity contract. Compose sidebars from rel-drawer,
|
|
19
|
+
* rel-menu and rel-resizable instead. It remains exported for compatibility
|
|
20
|
+
* but will be removed in a future major version.
|
|
21
|
+
*/
|
|
20
22
|
@customElement("rel-sidebar-provider")
|
|
21
23
|
export class RelSidebarProvider extends LitElement {
|
|
22
24
|
@property({ type: Boolean, reflect: true }) open?: boolean;
|
|
@@ -206,17 +208,19 @@ export class RelSidebarProvider extends LitElement {
|
|
|
206
208
|
private handleKeyDown = (e: KeyboardEvent) => {
|
|
207
209
|
if (!this.keyboardShortcut) return;
|
|
208
210
|
|
|
209
|
-
// Ctrl
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
// Ctrl+B (Cmd+B on Apple) via the shared matcher — stricter than the old
|
|
212
|
+
// hand-rolled check, which also fired on Ctrl+Shift+B.
|
|
213
|
+
if (!matchesShortcut(e, "mod+b")) return;
|
|
214
|
+
// Leave the combo alone in text-editing contexts (bold in editors).
|
|
215
|
+
if (isEditableTarget(e)) return;
|
|
216
|
+
e.preventDefault();
|
|
212
217
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
218
|
+
if (this.isMobile || this.collapsible === "offcanvas") {
|
|
219
|
+
// Toggle open state
|
|
220
|
+
this.setOpen(!this.activeOpen, "shortcut");
|
|
221
|
+
} else if (this.collapsible === "icon") {
|
|
222
|
+
// Toggle collapsed state
|
|
223
|
+
this.setCollapsed(!this.activeCollapsed, "shortcut");
|
|
220
224
|
}
|
|
221
225
|
};
|
|
222
226
|
|
|
@@ -529,7 +533,7 @@ export class RelSidebar extends LitElement {
|
|
|
529
533
|
}
|
|
530
534
|
|
|
531
535
|
private setupFocusTrap() {
|
|
532
|
-
this.triggerElement =
|
|
536
|
+
this.triggerElement = getDeepActiveElement();
|
|
533
537
|
document.addEventListener("keydown", this.handleKeydown);
|
|
534
538
|
|
|
535
539
|
// Focus first focusable element or container
|
|
@@ -888,26 +892,26 @@ export class RelSidebarGroup extends LitElement {
|
|
|
888
892
|
export class RelSidebarGroupLabel extends LitElement {
|
|
889
893
|
static styles = css`
|
|
890
894
|
:host {
|
|
891
|
-
display: block;
|
|
895
|
+
display: block;
|
|
892
896
|
color: var(--rel-text-placeholder);
|
|
893
|
-
text-transform: uppercase;
|
|
897
|
+
text-transform: uppercase;
|
|
894
898
|
padding: var(--rel-spacing-xs, 8px) var(--rel-spacing-sm, 12px) var(--rel-spacing-2xs, 4px);
|
|
895
|
-
margin-bottom: var(--rel-spacing-2xs, 4px);
|
|
896
|
-
font-family: var(--rel-body-xs-semibold-font-family);
|
|
897
|
-
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
898
|
-
font-size: var(--rel-body-xs-semibold-font-size);
|
|
899
|
-
line-height: var(--rel-body-xs-semibold-line-height);
|
|
900
|
-
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
899
|
+
margin-bottom: var(--rel-spacing-2xs, 4px);
|
|
900
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
901
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
902
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
903
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
904
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
901
905
|
}
|
|
902
906
|
|
|
903
907
|
:host(.collapsed) {
|
|
904
|
-
text-align: center;
|
|
905
|
-
padding: var(--rel-spacing-xs, 8px) var(--rel-spacing-2xs, 4px) var(--rel-spacing-2xs, 4px);
|
|
906
|
-
font-family: var(--rel-body-xs-regular-font-family);
|
|
907
|
-
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
908
|
-
font-size: var(--rel-body-xs-regular-font-size);
|
|
909
|
-
line-height: var(--rel-body-xs-regular-line-height);
|
|
910
|
-
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
908
|
+
text-align: center;
|
|
909
|
+
padding: var(--rel-spacing-xs, 8px) var(--rel-spacing-2xs, 4px) var(--rel-spacing-2xs, 4px);
|
|
910
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
911
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
912
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
913
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
914
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
911
915
|
}
|
|
912
916
|
`;
|
|
913
917
|
|
|
@@ -1023,16 +1027,16 @@ export class RelSidebarMenuButton extends LitElement {
|
|
|
1023
1027
|
background: none;
|
|
1024
1028
|
border: none;
|
|
1025
1029
|
border-radius: var(--rel-border-radius-sm, 8px);
|
|
1026
|
-
cursor: pointer;
|
|
1030
|
+
cursor: pointer;
|
|
1027
1031
|
color: var(--rel-text-default);
|
|
1028
1032
|
transition: background 0.15s ease, color 0.15s ease;
|
|
1029
1033
|
text-align: left;
|
|
1030
|
-
position: relative;
|
|
1031
|
-
font-family: var(--rel-body-md-medium-font-family);
|
|
1032
|
-
font-weight: var(--rel-body-md-medium-font-weight);
|
|
1033
|
-
font-size: var(--rel-body-md-medium-font-size);
|
|
1034
|
-
line-height: var(--rel-body-md-medium-line-height);
|
|
1035
|
-
letter-spacing: var(--rel-body-md-medium-letter-spacing);
|
|
1034
|
+
position: relative;
|
|
1035
|
+
font-family: var(--rel-body-md-medium-font-family);
|
|
1036
|
+
font-weight: var(--rel-body-md-medium-font-weight);
|
|
1037
|
+
font-size: var(--rel-body-md-medium-font-size);
|
|
1038
|
+
line-height: var(--rel-body-md-medium-line-height);
|
|
1039
|
+
letter-spacing: var(--rel-body-md-medium-letter-spacing);
|
|
1036
1040
|
}
|
|
1037
1041
|
|
|
1038
1042
|
button:hover:not([disabled]) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
1
|
+
import { LitElement, html, css, nothing } from "lit";
|
|
2
2
|
import { customElement, property } from "lit/decorators.js";
|
|
3
3
|
|
|
4
4
|
export type StepStatus = "done" | "progress" | "disabled";
|
|
@@ -79,6 +79,17 @@ export class RelStepper extends LitElement {
|
|
|
79
79
|
.rel-step {
|
|
80
80
|
display: flex;
|
|
81
81
|
position: relative;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.rel-step.disabled {
|
|
86
|
+
cursor: not-allowed;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.rel-step:focus-visible {
|
|
90
|
+
outline: 2px solid var(--rel-border-focus);
|
|
91
|
+
outline-offset: 2px;
|
|
92
|
+
border-radius: var(--rel-border-radius-xs);
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
.rel-stepper.horizontal.text-below .rel-step {
|
|
@@ -402,6 +413,33 @@ export class RelStepper extends LitElement {
|
|
|
402
413
|
return classes.join(" ");
|
|
403
414
|
}
|
|
404
415
|
|
|
416
|
+
private onStepKeyDown(e: KeyboardEvent, step: Step, index: number) {
|
|
417
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
418
|
+
e.preventDefault();
|
|
419
|
+
this.onStepClick(step, index);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const prevKey = e.key === "ArrowLeft" || e.key === "ArrowUp";
|
|
424
|
+
const nextKey = e.key === "ArrowRight" || e.key === "ArrowDown";
|
|
425
|
+
if (!prevKey && !nextKey && e.key !== "Home" && e.key !== "End") return;
|
|
426
|
+
|
|
427
|
+
const steps = Array.from(
|
|
428
|
+
this.renderRoot.querySelectorAll<HTMLElement>(".rel-step:not(.disabled)")
|
|
429
|
+
);
|
|
430
|
+
if (steps.length === 0) return;
|
|
431
|
+
|
|
432
|
+
e.preventDefault();
|
|
433
|
+
const current = steps.indexOf(e.currentTarget as HTMLElement);
|
|
434
|
+
let target: number;
|
|
435
|
+
if (e.key === "Home") target = 0;
|
|
436
|
+
else if (e.key === "End") target = steps.length - 1;
|
|
437
|
+
else if (prevKey) target = (current - 1 + steps.length) % steps.length;
|
|
438
|
+
else target = (current + 1) % steps.length;
|
|
439
|
+
|
|
440
|
+
steps[target]?.focus();
|
|
441
|
+
}
|
|
442
|
+
|
|
405
443
|
private onStepClick(step: Step, index: number) {
|
|
406
444
|
if (step.status === "disabled") return;
|
|
407
445
|
|
|
@@ -465,7 +503,12 @@ export class RelStepper extends LitElement {
|
|
|
465
503
|
(step, idx) => html`
|
|
466
504
|
<div
|
|
467
505
|
class="${this.getStepClasses(step)}"
|
|
506
|
+
role="button"
|
|
507
|
+
tabindex="${this.isDisabled(step) ? -1 : 0}"
|
|
508
|
+
aria-disabled="${this.isDisabled(step) ? "true" : nothing}"
|
|
509
|
+
aria-current="${this.isProgress(step) ? "step" : nothing}"
|
|
468
510
|
@click="${() => this.onStepClick(step, idx)}"
|
|
511
|
+
@keydown="${(e: KeyboardEvent) => this.onStepKeyDown(e, step, idx)}"
|
|
469
512
|
>
|
|
470
513
|
${isVertical && isTextRight
|
|
471
514
|
? html`
|
|
@@ -33,6 +33,9 @@ export class RelTab extends LitElement {
|
|
|
33
33
|
size: "sm" | "md" | "lg" = "md";
|
|
34
34
|
@state() private _tabItems: RelTabItem[] = [];
|
|
35
35
|
|
|
36
|
+
/** Stable id prefix linking tabs to the shared tabpanel. */
|
|
37
|
+
private tabsId = `rel-tab-${Math.random().toString(36).slice(2)}`;
|
|
38
|
+
|
|
36
39
|
static styles = css`
|
|
37
40
|
:host {
|
|
38
41
|
color: var(--rel-text-default);
|
|
@@ -57,7 +60,7 @@ export class RelTab extends LitElement {
|
|
|
57
60
|
position: relative;
|
|
58
61
|
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
59
62
|
cursor: pointer;
|
|
60
|
-
color: var(--rel-text-body);
|
|
63
|
+
color: var(--rel-text-body);
|
|
61
64
|
transition: color 0.2s ease;
|
|
62
65
|
display: flex;
|
|
63
66
|
align-items: center;
|
|
@@ -194,14 +197,28 @@ export class RelTab extends LitElement {
|
|
|
194
197
|
next = (next + dir + tabs.length) % tabs.length;
|
|
195
198
|
if (!tabs[next]?.disabled) break;
|
|
196
199
|
}
|
|
197
|
-
this.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
this.selectAndFocusTab(next);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (event.key === "Home" || event.key === "End") {
|
|
205
|
+
event.preventDefault();
|
|
206
|
+
const tabs = this._effectiveTabs;
|
|
207
|
+
const order = [...tabs.keys()];
|
|
208
|
+
if (event.key === "End") order.reverse();
|
|
209
|
+
const next = order.find((i) => !tabs[i]?.disabled);
|
|
210
|
+
if (next !== undefined) this.selectAndFocusTab(next);
|
|
202
211
|
}
|
|
203
212
|
}
|
|
204
213
|
|
|
214
|
+
private selectAndFocusTab(index: number) {
|
|
215
|
+
this.selectTab(index);
|
|
216
|
+
this.updateComplete.then(() => {
|
|
217
|
+
const tabEls = this.shadowRoot?.querySelectorAll("[role=tab]");
|
|
218
|
+
(tabEls?.[index] as HTMLElement)?.focus();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
205
222
|
render() {
|
|
206
223
|
const tabs = this._effectiveTabs;
|
|
207
224
|
return html`
|
|
@@ -213,9 +230,11 @@ export class RelTab extends LitElement {
|
|
|
213
230
|
part="item" class="tab ${i === this.activeIndex
|
|
214
231
|
? "active"
|
|
215
232
|
: ""} ${tab.disabled ? "disabled" : ""}"
|
|
233
|
+
id="${this.tabsId}-tab-${i}"
|
|
216
234
|
role="tab"
|
|
217
235
|
aria-selected="${i === this.activeIndex}"
|
|
218
236
|
aria-disabled="${tab.disabled ?? false}"
|
|
237
|
+
aria-controls="${this.tabsId}-panel"
|
|
219
238
|
tabindex="${tab.disabled ? -1 : i === this.activeIndex ? 0 : -1}"
|
|
220
239
|
@click="${() => this.selectTab(i)}"
|
|
221
240
|
@keydown="${(e: KeyboardEvent) => this.onKeyDown(e, i)}"
|
|
@@ -231,7 +250,13 @@ export class RelTab extends LitElement {
|
|
|
231
250
|
`
|
|
232
251
|
)}
|
|
233
252
|
</div>
|
|
234
|
-
<div
|
|
253
|
+
<div
|
|
254
|
+
class="tab-content"
|
|
255
|
+
id="${this.tabsId}-panel"
|
|
256
|
+
role="tabpanel"
|
|
257
|
+
aria-labelledby="${this.tabsId}-tab-${this.activeIndex}"
|
|
258
|
+
tabindex="0"
|
|
259
|
+
>
|
|
235
260
|
<slot></slot>
|
|
236
261
|
</div>
|
|
237
262
|
`;
|