@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.
Files changed (58) hide show
  1. package/dist/components/rel-accordion/rel-accordion.ts +13 -1
  2. package/dist/components/rel-alert/rel-alert.ts +1 -1
  3. package/dist/components/rel-autocomplete/rel-autocomplete.ts +33 -2
  4. package/dist/components/rel-badge/rel-badge.ts +6 -1
  5. package/dist/components/rel-button/rel-button.ts +18 -2
  6. package/dist/components/rel-calendar/rel-calendar.ts +86 -18
  7. package/dist/components/rel-carousel/rel-carousel.ts +45 -2
  8. package/dist/components/rel-checkbox/rel-checkbox.ts +4 -1
  9. package/dist/components/rel-chip/rel-chip.ts +59 -19
  10. package/dist/components/rel-date-picker/date-parse.test.ts +116 -0
  11. package/dist/components/rel-date-picker/date-parse.ts +178 -0
  12. package/dist/components/rel-date-picker/rel-date-picker.ts +675 -93
  13. package/dist/components/rel-drawer/rel-drawer.ts +28 -61
  14. package/dist/components/rel-menu/rel-menu.ts +130 -15
  15. package/dist/components/rel-menubar/rel-menubar.ts +221 -63
  16. package/dist/components/rel-nav-menu/rel-nav-menu.ts +39 -3
  17. package/dist/components/rel-otp/rel-otp.ts +14 -4
  18. package/dist/components/rel-pagination/rel-pagination.ts +13 -0
  19. package/dist/components/rel-popover/rel-popover.ts +64 -5
  20. package/dist/components/rel-progress/rel-progress.ts +13 -4
  21. package/dist/components/rel-radio/README.md +6 -3
  22. package/dist/components/rel-radio/rel-radio.ts +236 -60
  23. package/dist/components/rel-range-picker/rel-range-picker.ts +581 -91
  24. package/dist/components/rel-resizable/rel-resizable-group.ts +20 -0
  25. package/dist/components/rel-segmented-button/rel-segmented-button.ts +55 -13
  26. package/dist/components/rel-select/rel-select.ts +203 -24
  27. package/dist/components/rel-sheet/rel-sheet.ts +184 -18
  28. package/dist/components/rel-sidebar/rel-sidebar.ts +50 -40
  29. package/dist/components/rel-skeleton/rel-skeleton.ts +9 -0
  30. package/dist/components/rel-slider/rel-slider.ts +6 -3
  31. package/dist/components/rel-snackbar/rel-snackbar.ts +20 -16
  32. package/dist/components/rel-stepper/rel-stepper.ts +47 -2
  33. package/dist/components/rel-switch/rel-switch.ts +3 -1
  34. package/dist/components/rel-tab/rel-tab.ts +32 -7
  35. package/dist/components/rel-textfield/rel-textfield.ts +263 -75
  36. package/dist/components/rel-time-picker/rel-time-picker.ts +749 -261
  37. package/dist/components/rel-time-picker/time-parse.test.ts +67 -0
  38. package/dist/components/rel-time-picker/time-parse.ts +85 -0
  39. package/dist/components/rel-toast/rel-toast.ts +4 -2
  40. package/dist/components/rel-tooltip/README.md +13 -5
  41. package/dist/components/rel-tooltip/rel-tooltip.ts +33 -0
  42. package/dist/components/rel-topbar/rel-topbar-actions.ts +1 -1
  43. package/dist/components/rel-topbar/rel-topbar-brand.ts +1 -1
  44. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +1 -1
  45. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +1 -1
  46. package/dist/components/rel-topbar/rel-topbar-item.ts +318 -319
  47. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +1 -1
  48. package/dist/components/rel-topbar/rel-topbar-profile.ts +224 -224
  49. package/dist/components/rel-topbar/rel-topbar-utilities.ts +1 -1
  50. package/dist/components/rel-topbar/rel-topbar.ts +440 -440
  51. package/dist/components/rel-upload/rel-upload.ts +25 -0
  52. package/dist/index.d.mts +461 -23
  53. package/dist/index.d.ts +461 -23
  54. package/dist/index.js +3906 -807
  55. package/dist/index.js.map +1 -1
  56. package/dist/index.mjs +4143 -1055
  57. package/dist/index.mjs.map +1 -1
  58. package/package.json +2 -2
@@ -0,0 +1,67 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { parseTimeInput } from "./time-parse";
4
+
5
+ const time = (hour: number, minute: number, second = 0) => ({
6
+ kind: "time" as const,
7
+ hour,
8
+ minute,
9
+ second,
10
+ });
11
+
12
+ test("now shortcut: n / now, any case, padded", () => {
13
+ assert.deepEqual(parseTimeInput("n"), { kind: "now" });
14
+ assert.deepEqual(parseTimeInput("N"), { kind: "now" });
15
+ assert.deepEqual(parseTimeInput(" now "), { kind: "now" });
16
+ });
17
+
18
+ test("bare hour", () => {
19
+ assert.deepEqual(parseTimeInput("9"), time(9, 0));
20
+ assert.deepEqual(parseTimeInput("09"), time(9, 0));
21
+ assert.deepEqual(parseTimeInput("17"), time(17, 0));
22
+ assert.deepEqual(parseTimeInput("0"), time(0, 0));
23
+ assert.deepEqual(parseTimeInput("23"), time(23, 0));
24
+ });
25
+
26
+ test("compact digits HMM / HHMM / HHMMSS", () => {
27
+ assert.deepEqual(parseTimeInput("930"), time(9, 30));
28
+ assert.deepEqual(parseTimeInput("0930"), time(9, 30));
29
+ assert.deepEqual(parseTimeInput("1705"), time(17, 5));
30
+ assert.deepEqual(parseTimeInput("093015"), time(9, 30, 15));
31
+ });
32
+
33
+ test("separated: colon, dot, space", () => {
34
+ assert.deepEqual(parseTimeInput("9:30"), time(9, 30));
35
+ assert.deepEqual(parseTimeInput("9.30"), time(9, 30));
36
+ assert.deepEqual(parseTimeInput("9 30"), time(9, 30));
37
+ assert.deepEqual(parseTimeInput("17:05:30"), time(17, 5, 30));
38
+ });
39
+
40
+ test("meridiem suffixes", () => {
41
+ assert.deepEqual(parseTimeInput("9 pm"), time(21, 0));
42
+ assert.deepEqual(parseTimeInput("9:30p"), time(21, 30));
43
+ assert.deepEqual(parseTimeInput("9:30 PM"), time(21, 30));
44
+ assert.deepEqual(parseTimeInput("9a"), time(9, 0));
45
+ assert.deepEqual(parseTimeInput("930pm"), time(21, 30));
46
+ });
47
+
48
+ test("12am → 0h, 12pm → 12h", () => {
49
+ assert.deepEqual(parseTimeInput("12am"), time(0, 0));
50
+ assert.deepEqual(parseTimeInput("12 pm"), time(12, 0));
51
+ assert.deepEqual(parseTimeInput("12:30 am"), time(0, 30));
52
+ });
53
+
54
+ test("invalid input rejected", () => {
55
+ assert.equal(parseTimeInput(""), null);
56
+ assert.equal(parseTimeInput(" "), null);
57
+ assert.equal(parseTimeInput("asdf"), null);
58
+ assert.equal(parseTimeInput("25:00"), null);
59
+ assert.equal(parseTimeInput("9:70"), null);
60
+ assert.equal(parseTimeInput("9:30:70"), null);
61
+ assert.equal(parseTimeInput("24"), null);
62
+ assert.equal(parseTimeInput("13pm"), null); // 12h clock with meridiem
63
+ assert.equal(parseTimeInput("0pm"), null);
64
+ assert.equal(parseTimeInput("12345"), null); // 5 digits is ambiguous
65
+ assert.equal(parseTimeInput("9:30:15:10"), null);
66
+ assert.equal(parseTimeInput("pm"), null);
67
+ });
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Free-form time parsing for rel-time-picker's typed keyboard entry.
3
+ *
4
+ * Pure (no DOM, no Date.now) so it is unit-testable in Node. The component
5
+ * owns range checks (minTime/maxTime, minuteStep snapping) and the "now"
6
+ * clock — this module only turns text into a time or a shortcut intent.
7
+ *
8
+ * Accepted input (case/whitespace-insensitive):
9
+ * "9" → 9:00 "930" → 9:30
10
+ * "0930" → 9:30 "093015" → 9:30:15
11
+ * "9:30" → 9:30 "9.30" → 9:30
12
+ * "9 30" → 9:30 "17:05:30" → 17:05:30
13
+ * "9 pm" → 21:00 "9:30p" → 21:30
14
+ * "12am" → 0:00 "12 pm" → 12:00
15
+ * "n", "now" → the Now shortcut
16
+ */
17
+
18
+ export type TimeInputResult =
19
+ | { kind: "time"; hour: number; minute: number; second: number }
20
+ | { kind: "now" }
21
+ | null;
22
+
23
+ export function parseTimeInput(text: string): TimeInputResult {
24
+ let input = text.trim().toLowerCase();
25
+ if (!input) return null;
26
+
27
+ if (input === "n" || input === "now") return { kind: "now" };
28
+
29
+ // Meridiem suffix: "am"/"pm"/"a"/"p", optionally space-separated.
30
+ let meridiem: "am" | "pm" | null = null;
31
+ const meridiemMatch = input.match(/\s*(am|pm|a|p)\.?$/);
32
+ if (meridiemMatch) {
33
+ meridiem = meridiemMatch[1].startsWith("a") ? "am" : "pm";
34
+ input = input.slice(0, meridiemMatch.index).trim();
35
+ }
36
+ if (!input) return null;
37
+
38
+ let hour: number;
39
+ let minute = 0;
40
+ let second = 0;
41
+
42
+ if (/^\d+$/.test(input)) {
43
+ // Bare digits: H, HH, HMM, HHMM, HHMMSS.
44
+ switch (input.length) {
45
+ case 1:
46
+ case 2:
47
+ hour = parseInt(input, 10);
48
+ break;
49
+ case 3:
50
+ hour = parseInt(input.slice(0, 1), 10);
51
+ minute = parseInt(input.slice(1), 10);
52
+ break;
53
+ case 4:
54
+ hour = parseInt(input.slice(0, 2), 10);
55
+ minute = parseInt(input.slice(2), 10);
56
+ break;
57
+ case 6:
58
+ hour = parseInt(input.slice(0, 2), 10);
59
+ minute = parseInt(input.slice(2, 4), 10);
60
+ second = parseInt(input.slice(4), 10);
61
+ break;
62
+ default:
63
+ return null;
64
+ }
65
+ } else {
66
+ // Separated parts: "H:MM", "H.MM", "H MM", "HH:MM:SS".
67
+ const parts = input.split(/[:.\s]+/);
68
+ if (parts.length < 2 || parts.length > 3) return null;
69
+ if (!parts.every((p) => /^\d{1,2}$/.test(p))) return null;
70
+ hour = parseInt(parts[0], 10);
71
+ minute = parseInt(parts[1], 10);
72
+ if (parts.length === 3) second = parseInt(parts[2], 10);
73
+ }
74
+
75
+ if (meridiem) {
76
+ // 12-hour clock: hour must be 1–12; 12am → 0, 12pm → 12.
77
+ if (hour < 1 || hour > 12) return null;
78
+ hour = meridiem === "am" ? hour % 12 : (hour % 12) + 12;
79
+ } else if (hour > 23) {
80
+ return null;
81
+ }
82
+ if (minute > 59 || second > 59) return null;
83
+
84
+ return { kind: "time", hour, minute, second };
85
+ }
@@ -1,5 +1,6 @@
1
1
  import { LitElement, html, css } from "lit";
2
2
  import { customElement, property, state } from "lit/decorators.js";
3
+ import { announce } from "../../utils/announce";
3
4
 
4
5
  export type ToastType = "default" | "error";
5
6
 
@@ -35,7 +36,7 @@ export class RelToast extends LitElement {
35
36
  align-items: center;
36
37
  gap: var(--rel-spacing-xs);
37
38
  background-color: var(--rel-bg-neutral-inverse-1);
38
- color: var(--rel-text-inverse);
39
+ color: var(--rel-text-inverse);
39
40
  border-radius: var(--rel-border-radius-sm);
40
41
  padding: var(--rel-spacing-sm) var(--rel-spacing-md);
41
42
  box-shadow: var(--rel-shadows-shadow-300);
@@ -135,6 +136,7 @@ export class RelToast extends LitElement {
135
136
  if (!changed.has('show')) return;
136
137
  if (this.show) {
137
138
  this.visible = true;
139
+ announce(this.message, this.type === "error" ? "assertive" : "polite");
138
140
  if (this._dismissTimer) clearTimeout(this._dismissTimer);
139
141
  if (this.duration > 0) {
140
142
  this._dismissTimer = setTimeout(() => this.close(), this.duration);
@@ -185,7 +187,7 @@ export class RelToast extends LitElement {
185
187
 
186
188
  render() {
187
189
  return html`
188
- <div part="base" class="toast ${this.type} ${this.visible ? "" : "hidden"}" role="status" aria-live="polite">
190
+ <div part="base" class="toast ${this.type} ${this.visible ? "" : "hidden"}" role="status" aria-live="polite" aria-atomic="true">
189
191
  <span part="label" class="toast-message">${this.message}</span>
190
192
 
191
193
  ${this.actionLabel
@@ -65,12 +65,19 @@ None exposed.
65
65
  - `--rel-spacing-sm` - Horizontal padding
66
66
 
67
67
  ### Color Tokens
68
- - `--rel-bg-neutral-inverse-1` - Background color (dark gray)
69
- - `--rel-bg-surface` - Text color
68
+ - `--rel-bg-neutral-inverse-2` - Background color (near-black in light theme, white in dark theme)
69
+ - `--rel-text-inverse` - Text color (white in light theme, near-black in dark theme)
70
+ - `--rel-colors-dark-alpha-3` - Elevation shadow color (Figma `shadow/sm`: 0 2px 4px at 10% black; applied via `drop-shadow` so the tail casts the same shadow as the bubble)
70
71
 
71
72
  ### Border Tokens
72
73
  - `--rel-border-radius-sm` - Border radius
73
74
 
75
+ ### Theming
76
+ Colors use inverse semantic tokens, so the tooltip adapts automatically when
77
+ `data-theme="dark"` (or `.dark`) is set on an ancestor — no page refresh needed.
78
+ Dark-theme rendering (white bubble, dark text) is unchanged by the elevation
79
+ shadow, which is effectively invisible on dark backgrounds.
80
+
74
81
  ## Accessibility
75
82
  - Uses `role="tooltip"` on the tooltip bubble
76
83
  - `aria-hidden="true"` on tooltip (content is supplemental, not critical)
@@ -78,11 +85,12 @@ None exposed.
78
85
  - For critical information, consider alternative patterns (e.g., visible labels or expandable sections)
79
86
 
80
87
  ### Keyboard Interactions
81
- - No keyboard interactions (hover-only)
82
- - For keyboard accessibility, ensure slotted content is focusable and consider showing tooltip on focus
88
+ - Tooltip shows on `:focus-within` as well as hover, so keyboard users see it when the slotted trigger receives focus (ensure the trigger is focusable)
89
+ - `Escape` dismisses the tooltip (APG); it re-arms once hover/focus leaves and returns
90
+ - Keyboard-triggered tooltips use the same theme-aware styling as hover-triggered ones
83
91
 
84
92
  ### Focus Behavior
85
- - Tooltip activation is hover-only by default
93
+ - Tooltip activates on hover or focus within the trigger
86
94
  - Does not interfere with focus on slotted content
87
95
 
88
96
  ## Notes / Edge Cases
@@ -43,6 +43,9 @@ export class RelTooltip extends LitElement {
43
43
  pointer-events: none;
44
44
  transition: opacity 0.2s ease;
45
45
  overflow: visible;
46
+ /* Figma shadow/sm; drop-shadow (not box-shadow) so the border-drawn
47
+ tail casts the same shadow as the bubble. */
48
+ filter: drop-shadow(0px 2px 4px var(--rel-colors-dark-alpha-3, rgba(11, 11, 11, 0.1)));
46
49
  }
47
50
 
48
51
  /* Figma Type=Multi Line: wrap inside a bounded bubble */
@@ -57,6 +60,12 @@ export class RelTooltip extends LitElement {
57
60
  opacity: 1;
58
61
  }
59
62
 
63
+ /* Escape-dismissed (APG): stays hidden until hover/focus leaves and
64
+ returns. Declared after the hover/focus rules so source order wins. */
65
+ :host([data-dismissed]) .tooltip-bubble {
66
+ opacity: 0;
67
+ }
68
+
60
69
  /* Positioning */
61
70
  .tooltip-bubble.above {
62
71
  bottom: calc(100% + 6px);
@@ -131,6 +140,30 @@ export class RelTooltip extends LitElement {
131
140
  }
132
141
  `;
133
142
 
143
+ connectedCallback() {
144
+ super.connectedCallback();
145
+ this.addEventListener("keydown", this.onKeydown);
146
+ this.addEventListener("mouseleave", this.clearDismissed);
147
+ this.addEventListener("focusout", this.clearDismissed);
148
+ }
149
+
150
+ disconnectedCallback() {
151
+ super.disconnectedCallback();
152
+ this.removeEventListener("keydown", this.onKeydown);
153
+ this.removeEventListener("mouseleave", this.clearDismissed);
154
+ this.removeEventListener("focusout", this.clearDismissed);
155
+ }
156
+
157
+ /** APG: Escape dismisses the tooltip. Not swallowed — an enclosing dialog
158
+ * may legitimately close on the same keystroke. */
159
+ private onKeydown = (e: KeyboardEvent) => {
160
+ if (e.key === "Escape") this.toggleAttribute("data-dismissed", true);
161
+ };
162
+
163
+ private clearDismissed = () => {
164
+ this.removeAttribute("data-dismissed");
165
+ };
166
+
134
167
  firstUpdated() {
135
168
  this.linkTrigger();
136
169
  }
@@ -3,7 +3,7 @@ import { customElement } from "lit/decorators.js";
3
3
 
4
4
  /**
5
5
  * @deprecated Part of the deprecated rel-topbar family. Use plain
6
- * `rel-button` elements inside the "App Topbar" recipe at /docs/recipes/app-topbar.
6
+ * `rel-button` elements inside the "Navbar" recipe at /docs/recipes/navbar.
7
7
  */
8
8
  @customElement("rel-topbar-actions")
9
9
  export class RelTopbarActions extends LitElement {
@@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators.js";
3
3
 
4
4
  /**
5
5
  * @deprecated Part of the deprecated rel-topbar family. Use a plain
6
- * `<a>`/logo markup inside the "App Topbar" recipe at /docs/recipes/app-topbar.
6
+ * `<a>`/logo markup inside the "Navbar" recipe at /docs/recipes/navbar.
7
7
  */
8
8
  @customElement("rel-topbar-brand")
9
9
  export class RelTopbarBrand extends LitElement {
@@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators.js";
3
3
 
4
4
  /**
5
5
  * @deprecated Part of the deprecated rel-topbar family. Use `rel-menu`'s
6
- * item slots or `rel-popover` content in the "App Topbar" recipe at /docs/recipes/app-topbar.
6
+ * item slots or `rel-popover` content in the "Navbar" recipe at /docs/recipes/navbar.
7
7
  */
8
8
  @customElement("rel-topbar-dropdown-item")
9
9
  export class RelTopbarDropdownItem extends LitElement {
@@ -3,7 +3,7 @@ import { customElement } from "lit/decorators.js";
3
3
 
4
4
  /**
5
5
  * @deprecated Part of the deprecated rel-topbar family. Use `rel-menu` or
6
- * `rel-popover` for nav dropdowns in the "App Topbar" recipe at /docs/recipes/app-topbar.
6
+ * `rel-popover` for nav dropdowns in the "Navbar" recipe at /docs/recipes/navbar.
7
7
  */
8
8
  @customElement("rel-topbar-dropdown")
9
9
  export class RelTopbarDropdown extends LitElement {