@reliability-design/web 1.4.4 → 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-accordion/rel-accordion.ts +13 -1
- 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-button/rel-button.ts +18 -2
- package/dist/components/rel-calendar/rel-calendar.ts +86 -18
- package/dist/components/rel-carousel/rel-carousel.ts +45 -2
- package/dist/components/rel-checkbox/rel-checkbox.ts +4 -1
- package/dist/components/rel-chip/rel-chip.ts +59 -19
- 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 +675 -93
- package/dist/components/rel-drawer/rel-drawer.ts +28 -61
- package/dist/components/rel-menu/rel-menu.ts +130 -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 +14 -4
- package/dist/components/rel-pagination/rel-pagination.ts +13 -0
- package/dist/components/rel-popover/rel-popover.ts +64 -5
- 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 +581 -91
- package/dist/components/rel-resizable/rel-resizable-group.ts +20 -0
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +55 -13
- package/dist/components/rel-select/rel-select.ts +203 -24
- package/dist/components/rel-sheet/rel-sheet.ts +184 -18
- package/dist/components/rel-sidebar/rel-sidebar.ts +50 -40
- 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 +47 -2
- package/dist/components/rel-switch/rel-switch.ts +3 -1
- package/dist/components/rel-tab/rel-tab.ts +32 -7
- package/dist/components/rel-textfield/rel-textfield.ts +263 -75
- package/dist/components/rel-time-picker/rel-time-picker.ts +749 -261
- 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 +33 -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/rel-upload.ts +25 -0
- package/dist/index.d.mts +461 -23
- package/dist/index.d.ts +461 -23
- package/dist/index.js +3906 -807
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4143 -1055
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
1
|
+
import { LitElement, html, css, nothing } from "lit";
|
|
2
2
|
import { customElement, property, query } from "lit/decorators.js";
|
|
3
3
|
import "@reliability-design/icons/rel-icon";
|
|
4
|
+
import "../rel-progress/rel-progress.js";
|
|
5
|
+
import { getDeepActiveElement, getFocusableElements, trapTabKey } from "../../utils/focus.js";
|
|
4
6
|
|
|
5
7
|
export type ModalSide = "top" | "bottom" | "center";
|
|
8
|
+
export type ModalSize = "sm" | "md" | "lg" | "xl" | "fullscreen";
|
|
6
9
|
|
|
7
10
|
@customElement("rel-modal")
|
|
8
11
|
export class RelModal extends LitElement {
|
|
@@ -10,9 +13,37 @@ export class RelModal extends LitElement {
|
|
|
10
13
|
@property({ type: String }) side: ModalSide = "bottom";
|
|
11
14
|
@property({ type: String }) title = "";
|
|
12
15
|
@property({ type: String }) description = "";
|
|
16
|
+
/** Alias for `description`; rendered only when `description` is empty. */
|
|
17
|
+
@property({ type: String }) subtitle = "";
|
|
13
18
|
@property({ type: Boolean }) dismissible = true;
|
|
14
19
|
@property({ type: Boolean }) modal = true;
|
|
15
20
|
|
|
21
|
+
/** Panel width scale. `md` matches the Figma Modal Sheet width; `fullscreen` covers the viewport. */
|
|
22
|
+
@property({ type: String, reflect: true }) size: ModalSize = "md";
|
|
23
|
+
|
|
24
|
+
/* Granular dismissal switches — each is ANDed with the legacy coarse
|
|
25
|
+
`dismissible` switch so existing `dismissible={false}` consumers keep
|
|
26
|
+
their fully-locked behavior. */
|
|
27
|
+
@property({ type: Boolean, attribute: "close-on-backdrop-click" }) closeOnBackdropClick = true;
|
|
28
|
+
@property({ type: Boolean, attribute: "close-on-escape" }) closeOnEscape = true;
|
|
29
|
+
@property({ type: Boolean, attribute: "show-close-button" }) showCloseButton = true;
|
|
30
|
+
|
|
31
|
+
/** Move focus into the panel when opened (modal mode only). */
|
|
32
|
+
@property({ type: Boolean, attribute: "auto-focus" }) autoFocus = true;
|
|
33
|
+
/** Return focus to the opener when closed. */
|
|
34
|
+
@property({ type: Boolean, attribute: "restore-focus" }) restoreFocus = true;
|
|
35
|
+
|
|
36
|
+
/** When false, the body no longer scrolls internally — tall content clips at the panel's max height. */
|
|
37
|
+
@property({ type: Boolean }) scrollable = true;
|
|
38
|
+
|
|
39
|
+
/** Shows a spinner overlay and blocks all interaction inside the panel. */
|
|
40
|
+
@property({ type: Boolean, reflect: true }) loading = false;
|
|
41
|
+
/** Blocks user-initiated closes (X / backdrop / Escape) and footer actions. */
|
|
42
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
43
|
+
|
|
44
|
+
/** Stacking order of the whole modal (backdrop + panel). */
|
|
45
|
+
@property({ type: Number, attribute: "z-index" }) zIndex = 1000;
|
|
46
|
+
|
|
16
47
|
/**
|
|
17
48
|
* Figma "Breakpoint" axis. responsive (default) collapses to a full-width
|
|
18
49
|
* bottom sheet below 480px; web keeps the fixed desktop panel at every size.
|
|
@@ -21,6 +52,7 @@ export class RelModal extends LitElement {
|
|
|
21
52
|
breakpoint: "responsive" | "web" = "responsive";
|
|
22
53
|
|
|
23
54
|
@query(".overlay") private overlayElement!: HTMLElement;
|
|
55
|
+
@query(".panel") private panelElement!: HTMLElement;
|
|
24
56
|
|
|
25
57
|
private previousFocus: HTMLElement | null = null;
|
|
26
58
|
private originalBodyOverflow = "";
|
|
@@ -84,16 +116,30 @@ export class RelModal extends LitElement {
|
|
|
84
116
|
overflow: hidden;
|
|
85
117
|
outline: none;
|
|
86
118
|
font-family: var(--rel-fonts-font-family);
|
|
87
|
-
width
|
|
119
|
+
/* md is the Figma Modal Sheet Breakpoint=Web width (708px cap). */
|
|
120
|
+
--modal-width: clamp(360px, 49vw, 708px);
|
|
121
|
+
width: var(--modal-width);
|
|
88
122
|
left: 50%;
|
|
89
123
|
transition: transform var(--modal-transition-duration) var(--modal-transition-timing),
|
|
90
124
|
opacity var(--modal-transition-duration) var(--modal-transition-timing);
|
|
91
125
|
}
|
|
92
126
|
|
|
127
|
+
.panel[data-size="sm"] {
|
|
128
|
+
--modal-width: min(400px, calc(100vw - 32px));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.panel[data-size="lg"] {
|
|
132
|
+
--modal-width: min(900px, calc(100vw - 48px));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.panel[data-size="xl"] {
|
|
136
|
+
--modal-width: min(1140px, calc(100vw - 48px));
|
|
137
|
+
}
|
|
138
|
+
|
|
93
139
|
.panel[data-side="bottom"] {
|
|
94
140
|
bottom: 48px;
|
|
95
141
|
left: 50%;
|
|
96
|
-
width:
|
|
142
|
+
width: var(--modal-width);
|
|
97
143
|
max-height: calc(100vh - 96px);
|
|
98
144
|
border-radius: var(--rel-border-radius-sm);
|
|
99
145
|
transform: translateX(-50%) translateY(calc(100% + 48px));
|
|
@@ -106,7 +152,7 @@ export class RelModal extends LitElement {
|
|
|
106
152
|
.panel[data-side="top"] {
|
|
107
153
|
top: 48px;
|
|
108
154
|
left: 50%;
|
|
109
|
-
width:
|
|
155
|
+
width: var(--modal-width);
|
|
110
156
|
max-height: calc(100vh - 96px);
|
|
111
157
|
border-radius: var(--rel-border-radius-sm);
|
|
112
158
|
transform: translateX(-50%) translateY(calc(-100% - 48px));
|
|
@@ -119,7 +165,7 @@ export class RelModal extends LitElement {
|
|
|
119
165
|
.panel[data-side="center"] {
|
|
120
166
|
top: 50%;
|
|
121
167
|
left: 50%;
|
|
122
|
-
width:
|
|
168
|
+
width: var(--modal-width);
|
|
123
169
|
max-height: calc(100vh - 96px);
|
|
124
170
|
border-radius: var(--rel-border-radius-sm);
|
|
125
171
|
transform: translate(-50%, -50%) scale(0.96);
|
|
@@ -157,6 +203,50 @@ export class RelModal extends LitElement {
|
|
|
157
203
|
}
|
|
158
204
|
}
|
|
159
205
|
|
|
206
|
+
/* size="fullscreen" — edge-to-edge at every side value; declared after the
|
|
207
|
+
side and responsive-collapse rules so it wins their specificity ties. */
|
|
208
|
+
.panel[data-size="fullscreen"],
|
|
209
|
+
.panel[data-size="fullscreen"][data-side="top"],
|
|
210
|
+
.panel[data-size="fullscreen"][data-side="center"],
|
|
211
|
+
.panel[data-size="fullscreen"][data-side="bottom"] {
|
|
212
|
+
top: 0;
|
|
213
|
+
bottom: 0;
|
|
214
|
+
left: 0;
|
|
215
|
+
right: 0;
|
|
216
|
+
width: 100%;
|
|
217
|
+
height: 100%;
|
|
218
|
+
max-height: none;
|
|
219
|
+
border-radius: 0;
|
|
220
|
+
transform: translateY(16px);
|
|
221
|
+
opacity: 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.modal-container.open .panel[data-size="fullscreen"],
|
|
225
|
+
.modal-container.open .panel[data-size="fullscreen"][data-side="top"],
|
|
226
|
+
.modal-container.open .panel[data-size="fullscreen"][data-side="center"],
|
|
227
|
+
.modal-container.open .panel[data-size="fullscreen"][data-side="bottom"] {
|
|
228
|
+
transform: translateY(0);
|
|
229
|
+
opacity: 1;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@media (max-width: 480px) {
|
|
233
|
+
:host([breakpoint="responsive"]) .panel[data-size="fullscreen"] {
|
|
234
|
+
top: 0;
|
|
235
|
+
bottom: 0;
|
|
236
|
+
left: 0;
|
|
237
|
+
right: 0;
|
|
238
|
+
width: 100%;
|
|
239
|
+
height: 100%;
|
|
240
|
+
max-height: none;
|
|
241
|
+
border-radius: 0;
|
|
242
|
+
transform: translateY(16px);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
:host([breakpoint="responsive"]) .modal-container.open .panel[data-size="fullscreen"] {
|
|
246
|
+
transform: translateY(0);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
160
250
|
.header {
|
|
161
251
|
display: flex;
|
|
162
252
|
align-items: flex-start;
|
|
@@ -239,12 +329,51 @@ export class RelModal extends LitElement {
|
|
|
239
329
|
outline-offset: 2px;
|
|
240
330
|
}
|
|
241
331
|
|
|
332
|
+
.close-button:disabled {
|
|
333
|
+
cursor: not-allowed;
|
|
334
|
+
opacity: 0.5;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.close-button:disabled:hover {
|
|
338
|
+
background: transparent;
|
|
339
|
+
}
|
|
340
|
+
|
|
242
341
|
.content {
|
|
243
342
|
flex: 1;
|
|
244
343
|
overflow-y: auto;
|
|
245
344
|
padding: var(--rel-spacing-xl);
|
|
246
345
|
}
|
|
247
346
|
|
|
347
|
+
.content.no-scroll {
|
|
348
|
+
overflow-y: visible;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
:host([disabled]) .footer {
|
|
352
|
+
opacity: 0.5;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.loading-overlay {
|
|
356
|
+
position: absolute;
|
|
357
|
+
inset: 0;
|
|
358
|
+
z-index: 10;
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
justify-content: center;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Scrim as a pseudo-element so its opacity doesn't dull the spinner. */
|
|
365
|
+
.loading-overlay::before {
|
|
366
|
+
content: "";
|
|
367
|
+
position: absolute;
|
|
368
|
+
inset: 0;
|
|
369
|
+
background: var(--rel-bg-surface);
|
|
370
|
+
opacity: 0.7;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.loading-overlay rel-progress {
|
|
374
|
+
position: relative;
|
|
375
|
+
}
|
|
376
|
+
|
|
248
377
|
.footer {
|
|
249
378
|
display: none;
|
|
250
379
|
}
|
|
@@ -322,9 +451,23 @@ export class RelModal extends LitElement {
|
|
|
322
451
|
}
|
|
323
452
|
|
|
324
453
|
private handleOpen() {
|
|
325
|
-
|
|
454
|
+
// Deep-resolved — document.activeElement retargets to shadow hosts whose
|
|
455
|
+
// .focus() may be a no-op, silently dropping the restore.
|
|
456
|
+
this.previousFocus = getDeepActiveElement();
|
|
326
457
|
this.lockScroll();
|
|
327
458
|
document.addEventListener("keydown", this.handleKeydown);
|
|
459
|
+
|
|
460
|
+
// Move focus into the dialog — before this, a keyboard user had to Tab
|
|
461
|
+
// through the entire page behind the overlay to reach the sheet.
|
|
462
|
+
if (this.modal && this.autoFocus) {
|
|
463
|
+
this.updateComplete.then(() => {
|
|
464
|
+
const panel = this.panelElement;
|
|
465
|
+
if (!panel) return;
|
|
466
|
+
const focusTarget = getFocusableElements(panel)[0];
|
|
467
|
+
if (focusTarget) focusTarget.focus();
|
|
468
|
+
else panel.focus();
|
|
469
|
+
});
|
|
470
|
+
}
|
|
328
471
|
this.dispatchEvent(
|
|
329
472
|
new CustomEvent("rel-open-change", {
|
|
330
473
|
detail: true,
|
|
@@ -337,11 +480,13 @@ export class RelModal extends LitElement {
|
|
|
337
480
|
private handleClose() {
|
|
338
481
|
this.unlockScroll();
|
|
339
482
|
document.removeEventListener("keydown", this.handleKeydown);
|
|
340
|
-
if (this.previousFocus?.focus) {
|
|
483
|
+
if (this.restoreFocus && this.previousFocus?.focus) {
|
|
341
484
|
this.updateComplete.then(() => {
|
|
342
485
|
this.previousFocus?.focus();
|
|
343
486
|
this.previousFocus = null;
|
|
344
487
|
});
|
|
488
|
+
} else {
|
|
489
|
+
this.previousFocus = null;
|
|
345
490
|
}
|
|
346
491
|
this.dispatchEvent(
|
|
347
492
|
new CustomEvent("rel-open-change", {
|
|
@@ -371,30 +516,38 @@ export class RelModal extends LitElement {
|
|
|
371
516
|
|
|
372
517
|
private handleKeydown = (e: KeyboardEvent) => {
|
|
373
518
|
if (!this.open) return;
|
|
374
|
-
if (e.key === "Escape" && this.dismissible) {
|
|
519
|
+
if (e.key === "Escape" && this.dismissible && this.closeOnEscape && !this.disabled) {
|
|
375
520
|
e.preventDefault();
|
|
376
521
|
this.open = false;
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
// aria-modal promised containment; actually deliver it.
|
|
525
|
+
if (e.key === "Tab" && this.modal && this.panelElement) {
|
|
526
|
+
trapTabKey(e, this.panelElement);
|
|
377
527
|
}
|
|
378
528
|
};
|
|
379
529
|
|
|
380
530
|
private handleOverlayClick = (e: MouseEvent) => {
|
|
381
|
-
if (!this.dismissible) return;
|
|
531
|
+
if (!this.dismissible || !this.closeOnBackdropClick || this.disabled || this.loading) return;
|
|
382
532
|
if (e.target === this.overlayElement) {
|
|
383
533
|
this.open = false;
|
|
384
534
|
}
|
|
385
535
|
};
|
|
386
536
|
|
|
387
537
|
private handleCloseButtonClick() {
|
|
538
|
+
if (this.disabled || this.loading) return;
|
|
388
539
|
this.open = false;
|
|
389
540
|
}
|
|
390
541
|
|
|
391
542
|
render() {
|
|
392
543
|
const hasTitle = !!this.title;
|
|
393
|
-
const
|
|
394
|
-
const
|
|
544
|
+
const effectiveDescription = this.description || this.subtitle;
|
|
545
|
+
const hasDescription = !!effectiveDescription;
|
|
546
|
+
const showCloseX = this.dismissible && this.showCloseButton;
|
|
547
|
+
const showHeader = hasTitle || hasDescription || showCloseX;
|
|
395
548
|
|
|
396
549
|
return html`
|
|
397
|
-
<div class="modal-container ${this.open ? "open" : ""}">
|
|
550
|
+
<div class="modal-container ${this.open ? "open" : ""}" style="z-index: ${this.zIndex}">
|
|
398
551
|
<div
|
|
399
552
|
part="overlay"
|
|
400
553
|
class="overlay ${!this.modal ? 'no-modal' : ''}"
|
|
@@ -404,19 +557,26 @@ export class RelModal extends LitElement {
|
|
|
404
557
|
part="panel"
|
|
405
558
|
class="panel"
|
|
406
559
|
data-side="${this.side}"
|
|
560
|
+
data-size="${this.size}"
|
|
407
561
|
role="${this.modal ? "dialog" : "region"}"
|
|
408
562
|
aria-modal="${this.modal}"
|
|
563
|
+
aria-busy="${this.loading ? "true" : nothing}"
|
|
564
|
+
aria-labelledby="${hasTitle ? "modal-title" : nothing}"
|
|
565
|
+
aria-describedby="${hasDescription ? "modal-description" : nothing}"
|
|
566
|
+
aria-label="${hasTitle ? nothing : "Dialog"}"
|
|
409
567
|
tabindex="-1">
|
|
410
568
|
|
|
411
|
-
<div part="header" class="header ${!showHeader ? "empty" : ""}">
|
|
569
|
+
<div part="header" class="header ${!showHeader ? "empty" : ""}" ?inert=${this.loading}>
|
|
412
570
|
<div class="header-text">
|
|
413
|
-
${hasTitle ? html`<h2 class="title">${this.title}</h2>` : ""}
|
|
414
|
-
${hasDescription ? html`<p class="description">${
|
|
571
|
+
${hasTitle ? html`<h2 class="title" id="modal-title">${this.title}</h2>` : ""}
|
|
572
|
+
${hasDescription ? html`<p class="description" id="modal-description">${effectiveDescription}</p>` : ""}
|
|
573
|
+
<slot name="header"></slot>
|
|
415
574
|
</div>
|
|
416
|
-
${
|
|
575
|
+
${showCloseX
|
|
417
576
|
? html`<button
|
|
418
577
|
part="close-button"
|
|
419
578
|
class="close-button"
|
|
579
|
+
?disabled=${this.disabled}
|
|
420
580
|
@click="${this.handleCloseButtonClick}"
|
|
421
581
|
aria-label="Close">
|
|
422
582
|
<rel-icon name="close" size="sm"></rel-icon>
|
|
@@ -424,13 +584,19 @@ export class RelModal extends LitElement {
|
|
|
424
584
|
: ""}
|
|
425
585
|
</div>
|
|
426
586
|
|
|
427
|
-
<div part="content" class="content">
|
|
587
|
+
<div part="content" class="content ${this.scrollable ? "" : "no-scroll"}" ?inert=${this.loading}>
|
|
428
588
|
<slot></slot>
|
|
429
589
|
</div>
|
|
430
590
|
|
|
431
|
-
<div part="footer" class="footer">
|
|
591
|
+
<div part="footer" class="footer" ?inert=${this.loading || this.disabled}>
|
|
432
592
|
<slot name="footer"></slot>
|
|
433
593
|
</div>
|
|
594
|
+
|
|
595
|
+
${this.loading
|
|
596
|
+
? html`<div part="loading" class="loading-overlay">
|
|
597
|
+
<rel-progress mode="circular-indeterminate" label="Loading"></rel-progress>
|
|
598
|
+
</div>`
|
|
599
|
+
: ""}
|
|
434
600
|
</div>
|
|
435
601
|
</div>
|
|
436
602
|
`;
|
|
@@ -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
|
|
@@ -666,6 +670,7 @@ export class RelSidebar extends LitElement {
|
|
|
666
670
|
data-side="${this.side}"
|
|
667
671
|
style="--sidebar-width: ${this.getWidthValue()}"
|
|
668
672
|
role="navigation"
|
|
673
|
+
aria-label="Sidebar"
|
|
669
674
|
aria-hidden="${isOffcanvas && !this.isOpen}"
|
|
670
675
|
tabindex="-1"
|
|
671
676
|
>
|
|
@@ -774,11 +779,16 @@ export class RelSidebarTrigger extends LitElement {
|
|
|
774
779
|
};
|
|
775
780
|
|
|
776
781
|
render() {
|
|
782
|
+
const p = this.provider;
|
|
783
|
+
const expanded =
|
|
784
|
+
p && !p.isMobile && p.collapsible === "icon"
|
|
785
|
+
? !this.isCollapsed
|
|
786
|
+
: this.isOpen;
|
|
777
787
|
return html`
|
|
778
788
|
<button
|
|
779
789
|
@click="${this.handleClick}"
|
|
780
790
|
type="button"
|
|
781
|
-
aria-expanded="${
|
|
791
|
+
aria-expanded="${expanded}"
|
|
782
792
|
aria-label="Toggle sidebar"
|
|
783
793
|
>
|
|
784
794
|
<slot>
|
|
@@ -888,26 +898,26 @@ export class RelSidebarGroup extends LitElement {
|
|
|
888
898
|
export class RelSidebarGroupLabel extends LitElement {
|
|
889
899
|
static styles = css`
|
|
890
900
|
:host {
|
|
891
|
-
display: block;
|
|
901
|
+
display: block;
|
|
892
902
|
color: var(--rel-text-placeholder);
|
|
893
|
-
text-transform: uppercase;
|
|
903
|
+
text-transform: uppercase;
|
|
894
904
|
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);
|
|
905
|
+
margin-bottom: var(--rel-spacing-2xs, 4px);
|
|
906
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
907
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
908
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
909
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
910
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
901
911
|
}
|
|
902
912
|
|
|
903
913
|
: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);
|
|
914
|
+
text-align: center;
|
|
915
|
+
padding: var(--rel-spacing-xs, 8px) var(--rel-spacing-2xs, 4px) var(--rel-spacing-2xs, 4px);
|
|
916
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
917
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
918
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
919
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
920
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
911
921
|
}
|
|
912
922
|
`;
|
|
913
923
|
|
|
@@ -1023,16 +1033,16 @@ export class RelSidebarMenuButton extends LitElement {
|
|
|
1023
1033
|
background: none;
|
|
1024
1034
|
border: none;
|
|
1025
1035
|
border-radius: var(--rel-border-radius-sm, 8px);
|
|
1026
|
-
cursor: pointer;
|
|
1036
|
+
cursor: pointer;
|
|
1027
1037
|
color: var(--rel-text-default);
|
|
1028
1038
|
transition: background 0.15s ease, color 0.15s ease;
|
|
1029
1039
|
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);
|
|
1040
|
+
position: relative;
|
|
1041
|
+
font-family: var(--rel-body-md-medium-font-family);
|
|
1042
|
+
font-weight: var(--rel-body-md-medium-font-weight);
|
|
1043
|
+
font-size: var(--rel-body-md-medium-font-size);
|
|
1044
|
+
line-height: var(--rel-body-md-medium-line-height);
|
|
1045
|
+
letter-spacing: var(--rel-body-md-medium-letter-spacing);
|
|
1036
1046
|
}
|
|
1037
1047
|
|
|
1038
1048
|
button:hover:not([disabled]) {
|
|
@@ -16,6 +16,15 @@ export class RelSkeleton extends LitElement {
|
|
|
16
16
|
protected override updated(changed: Map<string, unknown>) {
|
|
17
17
|
if (changed.has("width") && this.width) this.style.width = this.width;
|
|
18
18
|
if (changed.has("height") && this.height) this.style.height = this.height;
|
|
19
|
+
if (changed.has("loaded")) {
|
|
20
|
+
if (this.loaded) {
|
|
21
|
+
this.removeAttribute("role");
|
|
22
|
+
this.removeAttribute("aria-label");
|
|
23
|
+
} else {
|
|
24
|
+
this.setAttribute("role", "status");
|
|
25
|
+
this.setAttribute("aria-label", "Loading");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
static styles = css`
|
|
@@ -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 } from "lit/decorators.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -34,6 +34,8 @@ export class RelSlider extends LitElement {
|
|
|
34
34
|
/** Upper value when type="range" (attribute value-end). */
|
|
35
35
|
@property({ type: Number, attribute: "value-end" }) valueEnd: number = 100;
|
|
36
36
|
|
|
37
|
+
@property({ type: String, attribute: "field-aria-label" }) fieldAriaLabel?: string;
|
|
38
|
+
|
|
37
39
|
@state() private ticks: number[] = [];
|
|
38
40
|
|
|
39
41
|
private _onChange: (value: number) => void = () => {};
|
|
@@ -619,7 +621,7 @@ export class RelSlider extends LitElement {
|
|
|
619
621
|
<input
|
|
620
622
|
class="slider-input"
|
|
621
623
|
type="range"
|
|
622
|
-
aria-label="
|
|
624
|
+
aria-label="${this.fieldAriaLabel ? `${this.fieldAriaLabel} minimum` : "Minimum value"}"
|
|
623
625
|
.min="${String(this.min)}"
|
|
624
626
|
.max="${String(this.max)}"
|
|
625
627
|
.step="${String(this.step)}"
|
|
@@ -630,7 +632,7 @@ export class RelSlider extends LitElement {
|
|
|
630
632
|
<input
|
|
631
633
|
class="slider-input"
|
|
632
634
|
type="range"
|
|
633
|
-
aria-label="
|
|
635
|
+
aria-label="${this.fieldAriaLabel ? `${this.fieldAriaLabel} maximum` : "Maximum value"}"
|
|
634
636
|
.min="${String(this.min)}"
|
|
635
637
|
.max="${String(this.max)}"
|
|
636
638
|
.step="${String(this.step)}"
|
|
@@ -653,6 +655,7 @@ export class RelSlider extends LitElement {
|
|
|
653
655
|
<input
|
|
654
656
|
class="slider-input"
|
|
655
657
|
type="range"
|
|
658
|
+
aria-label="${this.fieldAriaLabel || nothing}"
|
|
656
659
|
.min="${String(this.min)}"
|
|
657
660
|
.max="${String(this.max)}"
|
|
658
661
|
.step="${this.type === 'discrete' ? String(this.step) : '1'}"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LitElement, html, css } from "lit";
|
|
2
2
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
3
|
import "@reliability-design/icons/rel-icon";
|
|
4
|
+
import { announce } from "../../utils/announce";
|
|
4
5
|
|
|
5
6
|
export type SnackbarLines = "single" | "double";
|
|
6
7
|
export type SnackbarType = "default" | "success" | "error" | "warning";
|
|
@@ -100,19 +101,19 @@ export class RelSnackbar extends LitElement {
|
|
|
100
101
|
min-width: 0;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
.snackbar-message {
|
|
104
|
+
.snackbar-message {
|
|
104
105
|
margin-right: var(--rel-spacing-sm);
|
|
105
|
-
flex: 1;
|
|
106
|
+
flex: 1;
|
|
106
107
|
|
|
107
|
-
font-family: var(--rel-body-md-regular-font-family);
|
|
108
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
108
109
|
|
|
109
|
-
font-weight: var(--rel-body-md-regular-font-weight);
|
|
110
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
110
111
|
|
|
111
|
-
font-size: var(--rel-body-md-regular-font-size);
|
|
112
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
112
113
|
|
|
113
|
-
line-height: var(--rel-body-md-regular-line-height);
|
|
114
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
114
115
|
|
|
115
|
-
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
116
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
116
117
|
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -137,17 +138,17 @@ export class RelSnackbar extends LitElement {
|
|
|
137
138
|
.snackbar-action {
|
|
138
139
|
background: none;
|
|
139
140
|
border: none;
|
|
140
|
-
color: var(--rel-text-static-light);
|
|
141
|
+
color: var(--rel-text-static-light);
|
|
141
142
|
cursor: pointer;
|
|
142
143
|
padding: 0;
|
|
143
144
|
display: flex;
|
|
144
145
|
align-items: center;
|
|
145
|
-
transition: opacity 0.2s ease;
|
|
146
|
-
font-family: var(--rel-action-md-font-family);
|
|
147
|
-
font-weight: var(--rel-action-md-font-weight);
|
|
148
|
-
font-size: var(--rel-action-md-font-size);
|
|
149
|
-
line-height: var(--rel-action-md-line-height);
|
|
150
|
-
letter-spacing: var(--rel-action-md-letter-spacing);
|
|
146
|
+
transition: opacity 0.2s ease;
|
|
147
|
+
font-family: var(--rel-action-md-font-family);
|
|
148
|
+
font-weight: var(--rel-action-md-font-weight);
|
|
149
|
+
font-size: var(--rel-action-md-font-size);
|
|
150
|
+
line-height: var(--rel-action-md-line-height);
|
|
151
|
+
letter-spacing: var(--rel-action-md-letter-spacing);
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
.snackbar-action:hover {
|
|
@@ -232,6 +233,9 @@ export class RelSnackbar extends LitElement {
|
|
|
232
233
|
if ((changed.has("duration") || changed.has("longerAction")) && this.show) {
|
|
233
234
|
this.setAutoDismiss();
|
|
234
235
|
}
|
|
236
|
+
if ((changed.has("show") || changed.has("message")) && this.show) {
|
|
237
|
+
announce(this.message, this.type === "error" ? "assertive" : "polite");
|
|
238
|
+
}
|
|
235
239
|
}
|
|
236
240
|
|
|
237
241
|
disconnectedCallback() {
|
|
@@ -297,7 +301,7 @@ export class RelSnackbar extends LitElement {
|
|
|
297
301
|
.join(" ");
|
|
298
302
|
|
|
299
303
|
return html`
|
|
300
|
-
<div class="${snackbarClasses}">
|
|
304
|
+
<div class="${snackbarClasses}" role="${this.type === "error" ? "alert" : "status"}">
|
|
301
305
|
<div class="snackbar-body">
|
|
302
306
|
${this.type !== "default" ? this.renderIcon() : ""}
|
|
303
307
|
<div part="label" class="snackbar-message">${this.message}</div>
|
|
@@ -313,7 +317,7 @@ export class RelSnackbar extends LitElement {
|
|
|
313
317
|
: ""}
|
|
314
318
|
${this.iconClose
|
|
315
319
|
? html`
|
|
316
|
-
<button part="close-button" class="snackbar-close" @click="${this.dismiss}">
|
|
320
|
+
<button part="close-button" class="snackbar-close" @click="${this.dismiss}" aria-label="Close">
|
|
317
321
|
<rel-icon name="close" size="md"></rel-icon>
|
|
318
322
|
</button>
|
|
319
323
|
`
|