@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.
Files changed (48) hide show
  1. package/dist/components/rel-accordion/rel-accordion.ts +13 -1
  2. package/dist/components/rel-autocomplete/README.md +1 -0
  3. package/dist/components/rel-autocomplete/rel-autocomplete.ts +56 -1
  4. package/dist/components/rel-button/rel-button.ts +18 -2
  5. package/dist/components/rel-calendar/rel-calendar.ts +23 -1
  6. package/dist/components/rel-carousel/rel-carousel.ts +20 -0
  7. package/dist/components/rel-checkbox/README.md +69 -22
  8. package/dist/components/rel-checkbox/rel-checkbox.ts +119 -9
  9. package/dist/components/rel-chip/rel-chip.ts +29 -0
  10. package/dist/components/rel-date-picker/rel-date-picker.ts +156 -13
  11. package/dist/components/rel-drawer/rel-drawer.ts +22 -58
  12. package/dist/components/rel-menu/rel-menu.ts +124 -15
  13. package/dist/components/rel-menubar/rel-menubar.ts +221 -63
  14. package/dist/components/rel-nav-menu/rel-nav-menu.ts +39 -3
  15. package/dist/components/rel-otp/rel-otp.ts +3 -3
  16. package/dist/components/rel-pagination/README.md +14 -0
  17. package/dist/components/rel-pagination/rel-pagination.ts +30 -14
  18. package/dist/components/rel-popover/rel-popover.ts +31 -3
  19. package/dist/components/rel-range-picker/rel-range-picker.ts +251 -3
  20. package/dist/components/rel-resizable/rel-resizable-group.ts +20 -0
  21. package/dist/components/rel-segmented-button/rel-segmented-button.ts +50 -12
  22. package/dist/components/rel-select/README.md +1 -0
  23. package/dist/components/rel-select/rel-select.ts +1385 -1170
  24. package/dist/components/rel-sheet/rel-sheet.ts +22 -1
  25. package/dist/components/rel-sidebar/rel-sidebar.ts +43 -39
  26. package/dist/components/rel-stepper/rel-stepper.ts +44 -1
  27. package/dist/components/rel-tab/rel-tab.ts +32 -7
  28. package/dist/components/rel-time-picker/rel-time-picker.ts +338 -206
  29. package/dist/components/rel-tooltip/rel-tooltip.ts +30 -0
  30. package/dist/components/rel-topbar/rel-topbar-actions.ts +1 -1
  31. package/dist/components/rel-topbar/rel-topbar-brand.ts +1 -1
  32. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +1 -1
  33. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +1 -1
  34. package/dist/components/rel-topbar/rel-topbar-item.ts +318 -319
  35. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +1 -1
  36. package/dist/components/rel-topbar/rel-topbar-profile.ts +224 -224
  37. package/dist/components/rel-topbar/rel-topbar-utilities.ts +1 -1
  38. package/dist/components/rel-topbar/rel-topbar.ts +440 -440
  39. package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +134 -0
  40. package/dist/components/rel-upload/rel-upload-actions.ts +163 -0
  41. package/dist/components/rel-upload/rel-upload.ts +943 -0
  42. package/dist/index.d.mts +491 -18
  43. package/dist/index.d.ts +491 -18
  44. package/dist/index.js +2748 -410
  45. package/dist/index.js.map +1 -1
  46. package/dist/index.mjs +2953 -627
  47. package/dist/index.mjs.map +1 -1
  48. 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
- this.previousFocus = document.activeElement as HTMLElement;
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/Cmd + B
210
- if ((e.ctrlKey || e.metaKey) && e.key === "b") {
211
- e.preventDefault();
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
- if (this.isMobile || this.collapsible === "offcanvas") {
214
- // Toggle open state
215
- this.setOpen(!this.activeOpen, "shortcut");
216
- } else if (this.collapsible === "icon") {
217
- // Toggle collapsed state
218
- this.setCollapsed(!this.activeCollapsed, "shortcut");
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 = document.activeElement as HTMLElement;
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.selectTab(next);
198
- this.updateComplete.then(() => {
199
- const tabEls = this.shadowRoot?.querySelectorAll("[role=tab]");
200
- (tabEls?.[next] as HTMLElement)?.focus();
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 class="tab-content">
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
  `;