@reliability-design/web 1.4.6 → 1.4.7

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 (146) hide show
  1. package/CHANGELOG.md +211 -49
  2. package/dist/components/rel-accordion/README.md +132 -132
  3. package/dist/components/rel-alert/README.md +145 -141
  4. package/dist/components/rel-autocomplete/README.md +156 -61
  5. package/dist/components/rel-avatar/README.md +116 -130
  6. package/dist/components/rel-badge/README.md +98 -93
  7. package/dist/components/rel-breadcrumb/README.md +96 -102
  8. package/dist/components/rel-button/README.md +160 -130
  9. package/dist/components/rel-calendar/README.md +109 -109
  10. package/dist/components/rel-card/README.md +75 -94
  11. package/dist/components/rel-cardmedia/README.md +100 -127
  12. package/dist/components/rel-carousel/README.md +184 -166
  13. package/dist/components/rel-checkbox/README.md +93 -75
  14. package/dist/components/rel-chip/README.md +132 -121
  15. package/dist/components/rel-container/README.md +107 -99
  16. package/dist/components/rel-date-picker/README.md +200 -289
  17. package/dist/components/rel-divider/README.md +78 -100
  18. package/dist/components/rel-drawer/README.md +187 -200
  19. package/dist/components/rel-kbd/README.md +141 -151
  20. package/dist/components/rel-menu/README.md +178 -151
  21. package/dist/components/rel-menubar/README.md +218 -329
  22. package/dist/components/rel-otp/README.md +170 -440
  23. package/dist/components/rel-pagination/README.md +100 -424
  24. package/dist/components/rel-popover/README.md +203 -568
  25. package/dist/components/rel-progress/README.md +88 -110
  26. package/dist/components/rel-radio/README.md +131 -138
  27. package/dist/components/rel-range-picker/README.md +162 -0
  28. package/dist/components/rel-resizable/README.md +199 -140
  29. package/dist/components/rel-segmented-button/README.md +155 -141
  30. package/dist/components/rel-select/README.md +154 -93
  31. package/dist/components/rel-sheet/README.md +168 -224
  32. package/dist/components/rel-skeleton/README.md +103 -0
  33. package/dist/components/rel-slider/README.md +130 -135
  34. package/dist/components/rel-snackbar/README.md +162 -134
  35. package/dist/components/rel-stepper/README.md +158 -160
  36. package/dist/components/rel-switch/README.md +108 -102
  37. package/dist/components/rel-tab/README.md +171 -163
  38. package/dist/components/rel-textfield/README.md +159 -69
  39. package/dist/components/rel-time-picker/README.md +122 -157
  40. package/dist/components/rel-toast/README.md +157 -148
  41. package/dist/components/rel-tooltip/README.md +108 -111
  42. package/dist/components/rel-upload/README.md +220 -0
  43. package/dist/index.d.mts +1147 -340
  44. package/dist/index.d.ts +1147 -340
  45. package/dist/index.js +3451 -3163
  46. package/dist/index.js.map +1 -1
  47. package/dist/index.mjs +3416 -3118
  48. package/dist/index.mjs.map +1 -1
  49. package/dist/styles/vars.css +877 -866
  50. package/dist/{vars-XNMTSRVN.css → vars-JHTUGNRG.css} +877 -866
  51. package/package.json +3 -2
  52. package/dist/components/rel-accordion/rel-accordion.ts +0 -336
  53. package/dist/components/rel-alert/rel-alert.ts +0 -305
  54. package/dist/components/rel-autocomplete/rel-autocomplete.ts +0 -720
  55. package/dist/components/rel-avatar/rel-avatar.ts +0 -142
  56. package/dist/components/rel-badge/rel-badge.ts +0 -175
  57. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +0 -106
  58. package/dist/components/rel-button/rel-button.ts +0 -560
  59. package/dist/components/rel-calendar/index.ts +0 -1
  60. package/dist/components/rel-calendar/rel-calendar.ts +0 -565
  61. package/dist/components/rel-card/rel-card.ts +0 -88
  62. package/dist/components/rel-cardmedia/rel-cardmedia.ts +0 -88
  63. package/dist/components/rel-carousel/index.ts +0 -1
  64. package/dist/components/rel-carousel/rel-carousel.ts +0 -723
  65. package/dist/components/rel-chart/README.md +0 -286
  66. package/dist/components/rel-chart/index.ts +0 -1
  67. package/dist/components/rel-chart/rel-chart.ts +0 -754
  68. package/dist/components/rel-checkbox/rel-checkbox.ts +0 -382
  69. package/dist/components/rel-chip/rel-chip.ts +0 -483
  70. package/dist/components/rel-container/rel-container.ts +0 -95
  71. package/dist/components/rel-date-picker/date-format.test.ts +0 -43
  72. package/dist/components/rel-date-picker/date-format.ts +0 -47
  73. package/dist/components/rel-date-picker/date-parse.test.ts +0 -116
  74. package/dist/components/rel-date-picker/date-parse.ts +0 -178
  75. package/dist/components/rel-date-picker/index.ts +0 -1
  76. package/dist/components/rel-date-picker/rel-date-picker.ts +0 -1676
  77. package/dist/components/rel-divider/rel-divider.ts +0 -54
  78. package/dist/components/rel-drawer/index.ts +0 -1
  79. package/dist/components/rel-drawer/rel-drawer-footer.ts +0 -27
  80. package/dist/components/rel-drawer/rel-drawer.ts +0 -607
  81. package/dist/components/rel-kbd/rel-kbd.ts +0 -188
  82. package/dist/components/rel-layout/rel-layout.ts +0 -49
  83. package/dist/components/rel-menu/rel-menu.ts +0 -541
  84. package/dist/components/rel-menubar/index.ts +0 -2
  85. package/dist/components/rel-menubar/rel-menubar-menu.css +0 -3
  86. package/dist/components/rel-menubar/rel-menubar-menu.ts +0 -17
  87. package/dist/components/rel-menubar/rel-menubar.ts +0 -777
  88. package/dist/components/rel-nav-menu/README.md +0 -289
  89. package/dist/components/rel-nav-menu/index.ts +0 -3
  90. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +0 -243
  91. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +0 -72
  92. package/dist/components/rel-nav-menu/rel-nav-menu.ts +0 -270
  93. package/dist/components/rel-navbar/README.md +0 -126
  94. package/dist/components/rel-navbar/rel-navbar.ts +0 -105
  95. package/dist/components/rel-otp/index.ts +0 -1
  96. package/dist/components/rel-otp/rel-otp.ts +0 -527
  97. package/dist/components/rel-pagination/index.ts +0 -1
  98. package/dist/components/rel-pagination/rel-pagination.ts +0 -522
  99. package/dist/components/rel-popover/index.ts +0 -2
  100. package/dist/components/rel-popover/rel-popover-content.ts +0 -81
  101. package/dist/components/rel-popover/rel-popover.ts +0 -475
  102. package/dist/components/rel-progress/rel-progress.ts +0 -234
  103. package/dist/components/rel-radio/rel-radio.ts +0 -481
  104. package/dist/components/rel-range-picker/rel-range-picker.ts +0 -1688
  105. package/dist/components/rel-resizable/index.ts +0 -3
  106. package/dist/components/rel-resizable/rel-resizable-group.ts +0 -405
  107. package/dist/components/rel-resizable/rel-resizable-handle.ts +0 -150
  108. package/dist/components/rel-resizable/rel-resizable-panel.ts +0 -58
  109. package/dist/components/rel-segmented-button/rel-segmented-button.ts +0 -217
  110. package/dist/components/rel-select/rel-select.ts +0 -1416
  111. package/dist/components/rel-sheet/rel-sheet.ts +0 -610
  112. package/dist/components/rel-sidebar/README.md +0 -209
  113. package/dist/components/rel-sidebar/rel-sidebar.ts +0 -1191
  114. package/dist/components/rel-skeleton/rel-skeleton.ts +0 -143
  115. package/dist/components/rel-slider/rel-slider.ts +0 -673
  116. package/dist/components/rel-snackbar/rel-snackbar.ts +0 -329
  117. package/dist/components/rel-stepper/rel-stepper.ts +0 -567
  118. package/dist/components/rel-switch/rel-switch.ts +0 -199
  119. package/dist/components/rel-tab/rel-tab.ts +0 -264
  120. package/dist/components/rel-table/README.md +0 -178
  121. package/dist/components/rel-table/rel-table-body.ts +0 -22
  122. package/dist/components/rel-table/rel-table-caption.ts +0 -30
  123. package/dist/components/rel-table/rel-table-cell.ts +0 -43
  124. package/dist/components/rel-table/rel-table-footer.ts +0 -24
  125. package/dist/components/rel-table/rel-table-head.ts +0 -47
  126. package/dist/components/rel-table/rel-table-header.ts +0 -22
  127. package/dist/components/rel-table/rel-table-row.ts +0 -37
  128. package/dist/components/rel-table/rel-table.ts +0 -62
  129. package/dist/components/rel-textfield/rel-textfield.ts +0 -1054
  130. package/dist/components/rel-time-picker/rel-time-picker.ts +0 -1482
  131. package/dist/components/rel-time-picker/time-parse.test.ts +0 -67
  132. package/dist/components/rel-time-picker/time-parse.ts +0 -85
  133. package/dist/components/rel-toast/rel-toast.ts +0 -206
  134. package/dist/components/rel-tooltip/rel-tooltip.ts +0 -193
  135. package/dist/components/rel-topbar/rel-topbar-actions.ts +0 -27
  136. package/dist/components/rel-topbar/rel-topbar-brand.ts +0 -54
  137. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +0 -69
  138. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +0 -31
  139. package/dist/components/rel-topbar/rel-topbar-item.ts +0 -324
  140. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -27
  141. package/dist/components/rel-topbar/rel-topbar-profile.ts +0 -243
  142. package/dist/components/rel-topbar/rel-topbar-utilities.ts +0 -27
  143. package/dist/components/rel-topbar/rel-topbar.ts +0 -446
  144. package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +0 -134
  145. package/dist/components/rel-upload/rel-upload-actions.ts +0 -163
  146. package/dist/components/rel-upload/rel-upload.ts +0 -968
@@ -1,541 +0,0 @@
1
- import { LitElement, html, nothing } from "lit";
2
- import { customElement, property, state } from "lit/decorators.js";
3
- import "@reliability-design/icons/rel-icon";
4
- import "../rel-checkbox/rel-checkbox.js";
5
- import "../rel-radio/rel-radio.js";
6
-
7
- export type MenuType = "default" | "checkbox" | "arrow" | "select-option" | "fixed" | "fit";
8
-
9
- export interface MenuItem {
10
- label: string;
11
- active?: boolean;
12
- checked?: boolean;
13
- disabled?: boolean;
14
- submenu?: MenuItem[];
15
- open?: boolean;
16
- }
17
-
18
- @customElement("rel-menu-item")
19
- export class RelMenuItem extends LitElement {
20
- @property({ type: String }) label: string = "";
21
- @property({ type: String }) value: string = "";
22
- @property({ type: Boolean }) disabled: boolean = false;
23
- @property({ type: String }) icon: string = "";
24
-
25
- createRenderRoot() {
26
- return this;
27
- }
28
-
29
- render() {
30
- return html``;
31
- }
32
- }
33
-
34
- @customElement("rel-menu")
35
- export class RelMenu extends LitElement {
36
- @property({ type: String }) type: MenuType = "default";
37
-
38
- @property({ type: Array }) items: MenuItem[] = [];
39
-
40
- @state() private focusedIndex: number = 0;
41
- /** Index inside the open submenu that virtual focus sits on; -1 = main list. */
42
- @state() private subFocusedIndex: number = -1;
43
- @state() private _items: MenuItem[] = [];
44
-
45
- /** Stable id prefix for aria-activedescendant targets. */
46
- private menuId = `rel-menu-${Math.random().toString(36).slice(2)}`;
47
-
48
- public static readonly _css = `
49
- :host {
50
- color: var(--rel-text-default);
51
- display: inline-block;
52
- }
53
-
54
- .rel-menu {
55
- position: relative;
56
- display: inline-block;
57
- background: var(--rel-bg-neutral);
58
- color: var(--rel-text-default);
59
- border-radius: var(--rel-border-radius-md);
60
- box-shadow: var(--rel-shadows-shadow-300);
61
- min-width: 220px;
62
- /* No vertical padding: items fill edge-to-edge so a first/last-item hover
63
- reaches the menu's top/bottom. NOT overflow:hidden — that clips the
64
- type=arrow flyout (position:absolute at left:100%). Instead the first
65
- and last items carry the outer corner radius so hover backgrounds clip
66
- to the rounded shape without any overflow. */
67
- padding: 0;
68
- outline: none;
69
- overflow: visible;
70
- font-family: var(--rel-body-lg-regular-font-family);
71
- font-weight: var(--rel-body-lg-regular-font-weight);
72
- font-size: var(--rel-body-lg-regular-font-size);
73
- line-height: var(--rel-body-lg-regular-line-height);
74
- letter-spacing: var(--rel-body-lg-regular-letter-spacing);
75
- }
76
-
77
- .rel-menu:focus-visible,
78
- :host(:focus-visible) .rel-menu {
79
- outline: 2px solid var(--rel-border-focus);
80
- outline-offset: 2px;
81
- }
82
-
83
- .rel-menu-item {
84
- position: relative;
85
- display: flex;
86
- align-items: center;
87
- gap: var(--rel-spacing-xs);
88
- padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
89
- line-height: var(--rel-body-sm-regular-line-height);
90
- cursor: pointer;
91
- transition: background 0.15s ease, color 0.15s ease;
92
- user-select: none;
93
- }
94
-
95
- /* First/last items carry the menu's outer radius so their hover fill clips
96
- to the rounded corners (replaces the removed overflow:hidden). */
97
- .rel-menu > .rel-menu-item:first-child {
98
- border-top-left-radius: var(--rel-border-radius-md);
99
- border-top-right-radius: var(--rel-border-radius-md);
100
- }
101
-
102
- .rel-menu > .rel-menu-item:last-child {
103
- border-bottom-left-radius: var(--rel-border-radius-md);
104
- border-bottom-right-radius: var(--rel-border-radius-md);
105
- }
106
-
107
- .rel-menu-item:hover {
108
- background: var(--rel-bg-neutral-elevated-1);
109
- }
110
-
111
- .rel-menu-item:active {
112
- background: var(--rel-bg-neutral-elevated-1);
113
- }
114
-
115
- .rel-menu-item.active {
116
- background: var(--rel-bg-neutral-elevated-2);
117
- font-weight: var(--rel-body-lg-medium-font-weight);
118
- }
119
-
120
- .rel-menu-item.disabled {
121
- background-color: transparent;
122
- color: var(--rel-text-disabled);
123
- /* Keep the item pointer-interactive so cursor:not-allowed actually
124
- renders; clicks are already neutralised by the item.disabled guard in
125
- onItemClick/onItemEnter. pointer-events:none here would make the row
126
- transparent to the pointer and suppress the cursor. */
127
- cursor: not-allowed;
128
- }
129
-
130
- /* [data-focused] (presence, not ="true"): the ?data-focused boolean
131
- binding renders the attribute with an EMPTY value, so a ="true"
132
- selector never matches. */
133
- .rel-menu-item[data-focused] {
134
- outline: 2px solid var(--rel-border-focus);
135
- outline-offset: -2px;
136
- }
137
-
138
- /* Width modes (Figma Type=Fixed / Type=Fit) */
139
- .rel-menu.fixed {
140
- width: var(--rel-menu-width, 300px);
141
- min-width: 0;
142
- box-sizing: border-box;
143
- }
144
-
145
- .rel-menu.fit {
146
- min-width: 0;
147
- width: max-content;
148
- }
149
-
150
- /* Checkbox (Type=Checkbox) / radio (Type=Select Option) rows render the real
151
- rel-checkbox / rel-radio. pointer-events:none keeps the whole row the click
152
- target so onItemClick stays the single source of truth. */
153
- .menu-control {
154
- pointer-events: none;
155
- width: 100%;
156
- }
157
-
158
- .menu-label {
159
- flex: 1 1 auto;
160
- }
161
-
162
- /* Submenu affordance — same rel-icon chevron the menubar uses. */
163
- .submenu-chevron {
164
- margin-left: auto;
165
- display: inline-flex;
166
- align-items: center;
167
- opacity: 0.6;
168
- pointer-events: none;
169
- }
170
-
171
- /* Shortcut / hint text (Figma body-sm-regular, caption tone) */
172
- .menu-shortcut,
173
- ::slotted([slot="shortcut"]) {
174
- font-family: var(--rel-body-sm-regular-font-family);
175
- font-weight: var(--rel-body-sm-regular-font-weight);
176
- font-size: var(--rel-body-sm-regular-font-size);
177
- line-height: var(--rel-body-sm-regular-line-height);
178
- letter-spacing: var(--rel-body-sm-regular-letter-spacing);
179
- color: var(--rel-text-caption);
180
- margin-left: auto;
181
- padding-left: var(--rel-spacing-md);
182
- }
183
-
184
-
185
- .rel-submenu {
186
- border: 1px solid var(--rel-border-subtle);
187
- position: absolute;
188
- top: calc(-1 * var(--rel-spacing-3xs));
189
- /* flush against the trigger (no gap) so moving the pointer into the flyout
190
- never crosses a dead zone that would fire the trigger's mouseleave */
191
- left: 100%;
192
- min-width: 200px;
193
- background: var(--rel-bg-neutral);
194
- color: var(--rel-text-default);
195
- border-radius: var(--rel-border-radius-md);
196
- box-shadow: var(--rel-shadows-shadow-300);
197
- /* padding:0 + overflow:hidden — safe here (a submenu has no further nested
198
- flyout to clip) — so sub-item hovers fill fully and clip to the corners */
199
- padding: 0;
200
- overflow: hidden;
201
- visibility: hidden;
202
- pointer-events: none;
203
- z-index: 20;
204
- }
205
-
206
- .rel-submenu.visible {
207
- visibility: visible;
208
- pointer-events: auto;
209
- }
210
-
211
- .rel-submenu .rel-menu-item {
212
- padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
213
- }
214
- `;
215
-
216
- createRenderRoot() {
217
- return this;
218
- }
219
-
220
- connectedCallback() {
221
- super.connectedCallback();
222
- document.addEventListener("click", this.handleDocumentClick);
223
- }
224
-
225
- disconnectedCallback() {
226
- super.disconnectedCallback();
227
- document.removeEventListener("click", this.handleDocumentClick);
228
- }
229
-
230
- updated(changedProperties: Map<string, unknown>) {
231
- if (changedProperties.has('items')) {
232
- this._items = this.items.map(item => ({ ...item }));
233
- }
234
- }
235
-
236
- /** Index of a submenu locked open by a click (survives hover-out). -1 = none. */
237
- private lockedIndex = -1;
238
-
239
- private handleDocumentClick = (e: MouseEvent) => {
240
- // composedPath (NOT e.target): document-level e.target is retargeted to the
241
- // shadow host when this menu lives inside a shadow root, so this.contains()
242
- // wrongly reads an inside-click as outside and closes an open submenu on the
243
- // click that opened it. composedPath pierces shadow boundaries.
244
- if (!e.composedPath().includes(this)) {
245
- this.lockedIndex = -1;
246
- this.subFocusedIndex = -1;
247
- this._items = this._items.map(i => ({ ...i, open: false }));
248
- }
249
- };
250
-
251
- private onItemEnter(item: MenuItem, index: number) {
252
- if (item.disabled) return;
253
- if (this.type === "arrow" && item.submenu?.length) {
254
- // Hover opens this submenu; a click-locked one also stays open.
255
- this._items = this._items.map((i, idx) => ({ ...i, open: idx === index || idx === this.lockedIndex }));
256
- }
257
- }
258
-
259
- private onItemLeave(_item: MenuItem, index: number) {
260
- if (this.type !== "arrow") return;
261
- // Hover-out closes the submenu UNLESS it was locked open by a click. Moving
262
- // into the flyout doesn't fire this (the flyout is a DOM descendant of the
263
- // item and sits flush at left:100%, so there's no dead-zone gap).
264
- if (this.lockedIndex === index) return;
265
- this._items = this._items.map((i, idx) => ({ ...i, open: idx === this.lockedIndex }));
266
- }
267
-
268
- private onItemClick(item: MenuItem, index: number, ev?: Event) {
269
- ev?.stopPropagation();
270
- if (item.disabled) return;
271
-
272
- this.focusedIndex = index;
273
-
274
- if (this.type === "checkbox") {
275
- const updated = { ...item, checked: !item.checked };
276
- this._items = this._items.map((i, idx) => idx === index ? updated : i);
277
- this.dispatchEvent(
278
- new CustomEvent("rel-check", {
279
- detail: { item: updated, checked: updated.checked },
280
- bubbles: true,
281
- composed: true,
282
- })
283
- );
284
- return;
285
- }
286
-
287
- if (this.type === "arrow" && item.submenu?.length) {
288
- // Click LOCKS the submenu open (click again to unlock/close).
289
- const lock = this.lockedIndex !== index;
290
- this.lockedIndex = lock ? index : -1;
291
- this._items = this._items.map((i, idx) => ({ ...i, open: idx === index ? lock : false }));
292
- return;
293
- }
294
-
295
- // Default: set active
296
- const updated = { ...item, active: true };
297
- this._items = this._items.map((i, idx) => idx === index ? updated : { ...i, active: false });
298
- this.dispatchEvent(
299
- new CustomEvent("rel-select", {
300
- detail: { item: updated },
301
- bubbles: true,
302
- composed: true,
303
- })
304
- );
305
- }
306
-
307
- private onSubClick(parentIndex: number, subIndex: number, ev?: Event) {
308
- ev?.stopPropagation();
309
- this.lockedIndex = -1;
310
- this._items = this._items.map((i, idx) => ({
311
- ...i,
312
- active: false,
313
- open: false,
314
- submenu: i.submenu?.map((s, sIdx) => ({
315
- ...s,
316
- active: idx === parentIndex && sIdx === subIndex,
317
- })),
318
- }));
319
- this.dispatchEvent(
320
- new CustomEvent("rel-submenu-select", {
321
- detail: {
322
- parent: this._items[parentIndex],
323
- sub: this._items[parentIndex]?.submenu?.[subIndex],
324
- },
325
- bubbles: true,
326
- composed: true,
327
- })
328
- );
329
- }
330
-
331
- /** Keyboard-open locks the submenu (no hover-out to close it) and moves
332
- * virtual focus onto its first entry. */
333
- private openSubmenuAt(index: number) {
334
- this.lockedIndex = index;
335
- this.subFocusedIndex = 0;
336
- this._items = this._items.map((i, idx) => ({ ...i, open: idx === index }));
337
- }
338
-
339
- private closeSubmenus() {
340
- this.lockedIndex = -1;
341
- this.subFocusedIndex = -1;
342
- this._items = this._items.map(i => ({ ...i, open: false }));
343
- }
344
-
345
- private onKeydown(ev: KeyboardEvent) {
346
- const last = this._items.length - 1;
347
- const focusedItem = this._items[this.focusedIndex];
348
-
349
- // ── Virtual focus is inside an open submenu ──
350
- if (
351
- this.subFocusedIndex >= 0 &&
352
- this.type === "arrow" &&
353
- focusedItem?.open &&
354
- focusedItem.submenu?.length
355
- ) {
356
- const subLast = focusedItem.submenu.length - 1;
357
- switch (ev.key) {
358
- case "ArrowDown":
359
- this.subFocusedIndex = Math.min(subLast, this.subFocusedIndex + 1);
360
- ev.preventDefault();
361
- return;
362
- case "ArrowUp":
363
- this.subFocusedIndex = Math.max(0, this.subFocusedIndex - 1);
364
- ev.preventDefault();
365
- return;
366
- case "Home":
367
- this.subFocusedIndex = 0;
368
- ev.preventDefault();
369
- return;
370
- case "End":
371
- this.subFocusedIndex = subLast;
372
- ev.preventDefault();
373
- return;
374
- case "ArrowLeft":
375
- case "Escape":
376
- // Close only the submenu; don't let Escape also dismiss an
377
- // enclosing popover in the same keystroke.
378
- this.closeSubmenus();
379
- ev.preventDefault();
380
- ev.stopPropagation();
381
- return;
382
- case "Enter":
383
- case " ":
384
- this.onSubClick(this.focusedIndex, this.subFocusedIndex);
385
- ev.preventDefault();
386
- return;
387
- default:
388
- return;
389
- }
390
- }
391
-
392
- // ── Virtual focus is in the main list ──
393
- if (ev.key === "ArrowDown") {
394
- this.focusedIndex = Math.min(last, this.focusedIndex + 1);
395
- ev.preventDefault();
396
- } else if (ev.key === "ArrowUp") {
397
- this.focusedIndex = Math.max(0, this.focusedIndex - 1);
398
- ev.preventDefault();
399
- } else if (ev.key === "Home") {
400
- this.focusedIndex = 0;
401
- ev.preventDefault();
402
- } else if (ev.key === "End") {
403
- this.focusedIndex = last;
404
- ev.preventDefault();
405
- } else if (ev.key === "ArrowRight") {
406
- const item = this._items[this.focusedIndex];
407
- if (this.type === "arrow" && item?.submenu?.length && !item.disabled) {
408
- this.openSubmenuAt(this.focusedIndex);
409
- ev.preventDefault();
410
- }
411
- } else if (ev.key === "ArrowLeft" || ev.key === "Escape") {
412
- const anyOpen = this._items.some(i => i.open);
413
- if (anyOpen) {
414
- this.closeSubmenus();
415
- ev.preventDefault();
416
- if (ev.key === "Escape") ev.stopPropagation();
417
- }
418
- } else if (ev.key === "Enter" || ev.key === " ") {
419
- const item = this._items[this.focusedIndex];
420
- if (this.type === "arrow" && item?.submenu?.length && !item.disabled) {
421
- this.openSubmenuAt(this.focusedIndex);
422
- } else {
423
- this.onItemClick(item, this.focusedIndex);
424
- }
425
- ev.preventDefault();
426
- }
427
- }
428
-
429
- private renderMenuItem(item: MenuItem, index: number) {
430
- const isFocused = this.focusedIndex === index && this.subFocusedIndex < 0;
431
- const hasSubmenu = this.type === "arrow" && !!item.submenu?.length;
432
- const role =
433
- this.type === "checkbox"
434
- ? "menuitemcheckbox"
435
- : this.type === "select-option"
436
- ? "menuitemradio"
437
- : "menuitem";
438
- const ariaChecked =
439
- this.type === "checkbox"
440
- ? String(!!item.checked)
441
- : this.type === "select-option"
442
- ? String(!!item.active)
443
- : nothing;
444
-
445
- // Checkbox / select-option rows delegate their control to the real
446
- // rel-checkbox / rel-radio components (pointer-events:none so the whole ROW
447
- // stays the click target — onItemClick is the single source of truth and
448
- // the control just reflects item state).
449
- const control =
450
- this.type === "checkbox"
451
- ? html`<rel-checkbox
452
- class="menu-control"
453
- inert
454
- aria-hidden="true"
455
- label="${item.label}"
456
- status="${item.checked ? "checked" : "unchecked"}"
457
- state="${item.disabled ? "disabled" : "default"}"
458
- ></rel-checkbox>`
459
- : this.type === "select-option"
460
- ? html`<rel-radio
461
- class="menu-control"
462
- inert
463
- aria-hidden="true"
464
- .options="${[{ value: item.label, label: item.label, disabled: !!item.disabled }]}"
465
- .selectedValue="${item.active ? item.label : null}"
466
- ></rel-radio>`
467
- : html`
468
- <span class="menu-label">${item.label}</span>
469
- ${hasSubmenu
470
- ? html`<span class="submenu-chevron"
471
- ><rel-icon name="chevron-right" size="sm"></rel-icon
472
- ></span>`
473
- : ""}
474
- `;
475
-
476
- return html`
477
- <div
478
- id="${this.menuId}-item-${index}"
479
- class="rel-menu-item ${item.active ? "active" : ""} ${item.disabled ? "disabled" : ""}"
480
- role="${role}"
481
- aria-checked="${ariaChecked}"
482
- aria-label="${this.type === "checkbox" || this.type === "select-option" ? item.label : nothing}"
483
- aria-disabled="${item.disabled ? "true" : nothing}"
484
- aria-haspopup="${hasSubmenu ? "menu" : nothing}"
485
- aria-expanded="${hasSubmenu ? String(!!item.open) : nothing}"
486
- ?data-focused="${isFocused}"
487
- @mouseenter="${() => this.onItemEnter(item, index)}"
488
- @mouseleave="${() => this.onItemLeave(item, index)}"
489
- @click="${(e: Event) => this.onItemClick(item, index, e)}">
490
- ${control}
491
- ${hasSubmenu
492
- ? html`
493
- <div
494
- part="panel" class="rel-submenu ${item.open ? "visible" : ""}"
495
- role="menu"
496
- @click="${(e: Event) => e.stopPropagation()}">
497
- ${item.submenu!.map(
498
- (sub, subIndex) => html`
499
- <div
500
- id="${this.menuId}-item-${index}-sub-${subIndex}"
501
- class="rel-menu-item ${sub.active ? "active" : ""} ${sub.disabled ? "disabled" : ""}"
502
- role="menuitem"
503
- aria-disabled="${sub.disabled ? "true" : nothing}"
504
- ?data-focused="${this.focusedIndex === index && this.subFocusedIndex === subIndex}"
505
- @click="${(e: Event) => this.onSubClick(index, subIndex, e)}">
506
- <span class="menu-label">${sub.label}</span>
507
- </div>
508
- `
509
- )}
510
- </div>
511
- `
512
- : ""}
513
- </div>
514
- `;
515
- }
516
-
517
- render() {
518
- // DOM focus stays on the container; this tells AT which item virtual
519
- // focus is on as the user arrows around (main list or open submenu).
520
- const activeDescendant =
521
- this._items.length === 0
522
- ? nothing
523
- : this.subFocusedIndex >= 0
524
- ? `${this.menuId}-item-${this.focusedIndex}-sub-${this.subFocusedIndex}`
525
- : `${this.menuId}-item-${this.focusedIndex}`;
526
-
527
- return html`
528
- <style>${RelMenu._css}</style>
529
- <div
530
- part="list" class="rel-menu ${this.type === "fixed" || this.type === "fit" ? this.type : ""}"
531
- role="menu"
532
- aria-label="Menu"
533
- tabindex="0"
534
- aria-activedescendant="${activeDescendant}"
535
- @keydown="${this.onKeydown}"
536
- @click="${(e: Event) => e.stopPropagation()}">
537
- ${this._items.map((item, index) => this.renderMenuItem(item, index))}
538
- </div>
539
- `;
540
- }
541
- }
@@ -1,2 +0,0 @@
1
- export * from "./rel-menubar";
2
- export * from "./rel-menubar-menu";
@@ -1,3 +0,0 @@
1
- rel-menubar-menu {
2
- display: none;
3
- }
@@ -1,17 +0,0 @@
1
- import { LitElement, html } from "lit";
2
- import { customElement, property } from "lit/decorators.js";
3
- import "./rel-menubar-menu.css";
4
-
5
- @customElement("rel-menubar-menu")
6
- export class RelMenubarMenu extends LitElement {
7
- @property({ type: String }) label = "";
8
-
9
- // Disable shadow DOM to allow parent to access children
10
- createRenderRoot() {
11
- return this;
12
- }
13
-
14
- render() {
15
- return html``;
16
- }
17
- }