@reliability-design/web 1.0.6 → 1.2.0

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 (78) hide show
  1. package/dist/components/rel-accordion/rel-accordion.ts +20 -31
  2. package/dist/components/rel-alert/rel-alert.ts +81 -11
  3. package/dist/components/rel-avatar/rel-avatar.ts +6 -3
  4. package/dist/components/rel-badge/rel-badge.ts +8 -3
  5. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +6 -6
  6. package/dist/components/rel-button/rel-button.ts +118 -16
  7. package/dist/components/rel-calendar/rel-calendar.ts +16 -9
  8. package/dist/components/rel-card/rel-card.ts +9 -17
  9. package/dist/components/rel-cardmedia/rel-cardmedia.ts +20 -23
  10. package/dist/components/rel-carousel/rel-carousel.ts +596 -469
  11. package/dist/components/rel-checkbox/rel-checkbox.ts +98 -15
  12. package/dist/components/rel-chip/rel-chip.ts +291 -186
  13. package/dist/components/rel-container/rel-container.ts +2 -2
  14. package/dist/components/rel-date-picker/rel-date-picker.ts +23 -19
  15. package/dist/components/rel-divider/rel-divider.ts +12 -3
  16. package/dist/components/rel-drawer/rel-drawer-footer.ts +27 -0
  17. package/dist/components/rel-drawer/rel-drawer.ts +57 -113
  18. package/dist/components/rel-kbd/rel-kbd.ts +94 -118
  19. package/dist/components/rel-layout/rel-layout.ts +49 -58
  20. package/dist/components/rel-menu/rel-menu.ts +359 -327
  21. package/dist/components/rel-menubar/rel-menubar-menu.css +3 -0
  22. package/dist/components/rel-menubar/rel-menubar-menu.ts +2 -1
  23. package/dist/components/rel-menubar/rel-menubar.ts +602 -576
  24. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +240 -143
  25. package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -331
  26. package/dist/components/rel-navbar/rel-navbar.ts +13 -0
  27. package/dist/components/rel-otp/rel-otp.ts +22 -25
  28. package/dist/components/rel-pagination/rel-pagination.ts +112 -63
  29. package/dist/components/rel-popover/rel-popover-content.ts +6 -5
  30. package/dist/components/rel-popover/rel-popover.ts +42 -20
  31. package/dist/components/rel-progress/rel-progress.ts +10 -9
  32. package/dist/components/rel-radio/rel-radio.ts +228 -155
  33. package/dist/components/rel-range-picker/rel-range-picker.css +349 -0
  34. package/dist/components/rel-range-picker/rel-range-picker.ts +1079 -0
  35. package/dist/components/rel-resizable/rel-resizable-handle.ts +12 -15
  36. package/dist/components/rel-segmented-button/rel-segmented-button.ts +41 -20
  37. package/dist/components/rel-select/rel-select.ts +112 -27
  38. package/dist/components/rel-sheet/rel-sheet.ts +362 -683
  39. package/dist/components/rel-sidebar/rel-sidebar.ts +13 -5
  40. package/dist/components/rel-slider/rel-slider.ts +74 -29
  41. package/dist/components/rel-snackbar/rel-snackbar.ts +67 -37
  42. package/dist/components/rel-stepper/rel-stepper.ts +43 -31
  43. package/dist/components/rel-switch/rel-switch.ts +26 -12
  44. package/dist/components/rel-tab/rel-tab.ts +57 -37
  45. package/dist/components/rel-table/rel-table-body.ts +5 -0
  46. package/dist/components/rel-table/rel-table-caption.ts +6 -1
  47. package/dist/components/rel-table/rel-table-cell.ts +42 -49
  48. package/dist/components/rel-table/rel-table-footer.ts +5 -0
  49. package/dist/components/rel-table/rel-table-head.ts +13 -1
  50. package/dist/components/rel-table/rel-table-header.ts +5 -0
  51. package/dist/components/rel-table/rel-table-row.ts +7 -3
  52. package/dist/components/rel-table/rel-table.ts +8 -2
  53. package/dist/components/rel-textfield/rel-textfield.ts +153 -44
  54. package/dist/components/rel-toast/rel-toast.ts +56 -22
  55. package/dist/components/rel-tooltip/rel-tooltip.ts +52 -9
  56. package/dist/components/rel-topbar/rel-topbar-actions.ts +24 -0
  57. package/dist/components/rel-topbar/rel-topbar-brand.ts +47 -0
  58. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +63 -0
  59. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
  60. package/dist/components/rel-topbar/rel-topbar-item.ts +320 -0
  61. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +24 -0
  62. package/dist/components/rel-topbar/rel-topbar-profile.ts +222 -0
  63. package/dist/components/rel-topbar/rel-topbar-utilities.ts +24 -0
  64. package/dist/components/rel-topbar/rel-topbar.ts +438 -0
  65. package/dist/index.d.mts +375 -411
  66. package/dist/index.d.ts +375 -411
  67. package/dist/index.js +7587 -7980
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.mjs +7647 -8023
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/rel-menubar-menu-BEEQDAXW.css +3 -0
  72. package/dist/styles/vars-v2.css +525 -0
  73. package/dist/styles/vars.css +79 -27
  74. package/dist/{vars-ITL2K3KO.css → vars-I2PC74XD.css} +79 -27
  75. package/dist/vars-v2-VMGWTFJZ.css +525 -0
  76. package/package.json +3 -1
  77. package/dist/components/rel-quote-page/README.md +0 -166
  78. package/dist/components/rel-quote-page/rel-quote-page.ts +0 -816
@@ -1,327 +1,359 @@
1
- import { LitElement, html, css } from "lit";
2
- import { customElement, property, state } from "lit/decorators.js";
3
-
4
- export type MenuType = "default" | "checkbox" | "arrow";
5
-
6
- export interface MenuItem {
7
- label: string;
8
- active?: boolean;
9
- checked?: boolean;
10
- disabled?: boolean;
11
- submenu?: MenuItem[];
12
- open?: boolean;
13
- }
14
-
15
- @customElement("rel-menu")
16
- export class RelMenu extends LitElement {
17
- @property({ type: String }) type: MenuType = "default";
18
-
19
- @property({ type: Array }) items: MenuItem[] = [];
20
-
21
- @state() private focusedIndex: number = 0;
22
-
23
- static styles = css`
24
- :host {
25
- display: inline-block;
26
- }
27
-
28
- .rel-menu {
29
- position: relative;
30
- display: inline-block;
31
- background: var(--rel-colors-white, #fff);
32
- color: var(--rel-text-primary, #333);
33
- border-radius: var(--rel-borders-radius-border-radius-300);
34
- box-shadow: var(--rel-shadows-shadow-300);
35
- min-width: 220px;
36
- font-family: var(--rel-fontFamilies-primary, sans-serif);
37
- font-size: var(--rel-fontSizes-sm);
38
- padding: var(--rel-spacing-space-3xs) 0;
39
- outline: none;
40
- }
41
-
42
- .rel-menu:focus-visible {
43
- outline: 2px solid var(--rel-colors-blue-primary, #0091ea);
44
- outline-offset: 2px;
45
- }
46
-
47
- .rel-menu-item {
48
- position: relative;
49
- display: flex;
50
- align-items: center;
51
- gap: var(--rel-spacing-space-xs);
52
- padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-sm);
53
- line-height: var(--rel-lineHeights-sm);
54
- cursor: pointer;
55
- transition: background 0.15s ease, color 0.15s ease;
56
- user-select: none;
57
- }
58
-
59
- .rel-menu-item:hover {
60
- background: var(--rel-surface-secondary, #fafafa);
61
- }
62
-
63
- .rel-menu-item:active {
64
- background: var(--rel-colors-gray-divider, #bdbdbd);
65
- }
66
-
67
- .rel-menu-item.active {
68
- background: var(--rel-surface-secondary, #fafafa);
69
- font-weight: var(--rel-fontWeights-medium);
70
- }
71
-
72
- .rel-menu-item.disabled {
73
- background-color: transparent;
74
- color: var(--rel-text-disabled, #9e9e9e);
75
- cursor: not-allowed;
76
- }
77
-
78
- .rel-menu-item[data-focused="true"] {
79
- outline: 2px solid var(--rel-colors-blue-primary, #0091ea);
80
- outline-offset: -2px;
81
- }
82
-
83
- .menu-check-row {
84
- display: inline-flex;
85
- align-items: center;
86
- gap: var(--rel-spacing-space-2xs);
87
- width: 100%;
88
- }
89
-
90
- .menu-checkbox {
91
- width: 16px;
92
- height: 16px;
93
- cursor: pointer;
94
- }
95
-
96
- .menu-checkbox:disabled {
97
- cursor: not-allowed;
98
- }
99
-
100
- .menu-label {
101
- flex: 1 1 auto;
102
- }
103
-
104
- .chevron {
105
- font-size: var(--rel-fontSizes-lg);
106
- opacity: 0.75;
107
- transform: translateY(1px);
108
- }
109
-
110
- .rel-submenu {
111
- position: absolute;
112
- top: calc(-1 * var(--rel-spacing-space-3xs));
113
- left: calc(100% + var(--rel-spacing-space-2xs));
114
- min-width: 200px;
115
- background: var(--rel-colors-white, #fff);
116
- color: var(--rel-text-primary, #333);
117
- border-radius: var(--rel-borders-radius-border-radius-300);
118
- box-shadow: var(--rel-shadows-shadow-300);
119
- padding: var(--rel-spacing-space-3xs) 0;
120
- display: none;
121
- z-index: 20;
122
- }
123
-
124
- .rel-submenu.visible {
125
- display: block;
126
- }
127
-
128
- .rel-submenu .rel-menu-item {
129
- padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-sm);
130
- }
131
- `;
132
-
133
- connectedCallback() {
134
- super.connectedCallback();
135
- document.addEventListener("click", this.handleDocumentClick);
136
- }
137
-
138
- disconnectedCallback() {
139
- super.disconnectedCallback();
140
- document.removeEventListener("click", this.handleDocumentClick);
141
- }
142
-
143
- private handleDocumentClick = (e: MouseEvent) => {
144
- const path = e.composedPath() as HTMLElement[];
145
- const hit = path.some((n) => n?.classList?.contains("rel-menu"));
146
- if (!hit) {
147
- this.items.forEach((i) => (i.open = false));
148
- this.requestUpdate();
149
- }
150
- };
151
-
152
- private onItemEnter(item: MenuItem) {
153
- if (this.type === "arrow" && item.submenu?.length) {
154
- this.items.forEach((i) => {
155
- if (i !== item) i.open = false;
156
- });
157
- item.open = true;
158
- this.requestUpdate();
159
- }
160
- }
161
-
162
- private onItemLeave(_item: MenuItem) {
163
- // Keep open so user can move into submenu
164
- }
165
-
166
- private onItemClick(item: MenuItem, index: number, ev?: Event) {
167
- ev?.stopPropagation();
168
- if (item.disabled) return;
169
-
170
- this.focusedIndex = index;
171
-
172
- if (this.type === "checkbox") {
173
- item.checked = !item.checked;
174
- this.dispatchEvent(
175
- new CustomEvent("item-checked", {
176
- detail: { item, checked: item.checked },
177
- bubbles: true,
178
- composed: true,
179
- })
180
- );
181
- this.requestUpdate();
182
- return;
183
- }
184
-
185
- if (this.type === "arrow" && item.submenu?.length) {
186
- const next = !item.open;
187
- this.items.forEach((i) => (i.open = false));
188
- item.open = next;
189
- this.requestUpdate();
190
- return;
191
- }
192
-
193
- // Default: set active
194
- this.items.forEach((i) => (i.active = false));
195
- item.active = true;
196
- this.dispatchEvent(
197
- new CustomEvent("item-selected", {
198
- detail: { item },
199
- bubbles: true,
200
- composed: true,
201
- })
202
- );
203
- this.requestUpdate();
204
- }
205
-
206
- private onSubClick(parent: MenuItem, sub: MenuItem, ev?: Event) {
207
- ev?.stopPropagation();
208
- this.items.forEach((i) => {
209
- i.active = false;
210
- i.submenu?.forEach((s) => (s.active = false));
211
- });
212
- sub.active = true;
213
- parent.open = false;
214
- this.dispatchEvent(
215
- new CustomEvent("submenu-selected", {
216
- detail: { parent, sub },
217
- bubbles: true,
218
- composed: true,
219
- })
220
- );
221
- this.requestUpdate();
222
- }
223
-
224
- private toggleCheckbox(item: MenuItem, ev: Event) {
225
- ev.stopPropagation();
226
- if (!item.disabled) {
227
- item.checked = !item.checked;
228
- this.dispatchEvent(
229
- new CustomEvent("item-checked", {
230
- detail: { item, checked: item.checked },
231
- bubbles: true,
232
- composed: true,
233
- })
234
- );
235
- this.requestUpdate();
236
- }
237
- }
238
-
239
- private onKeydown(ev: KeyboardEvent) {
240
- const last = this.items.length - 1;
241
-
242
- if (ev.key === "ArrowDown") {
243
- this.focusedIndex = Math.min(last, this.focusedIndex + 1);
244
- ev.preventDefault();
245
- } else if (ev.key === "ArrowUp") {
246
- this.focusedIndex = Math.max(0, this.focusedIndex - 1);
247
- ev.preventDefault();
248
- } else if (ev.key === "ArrowRight") {
249
- const item = this.items[this.focusedIndex];
250
- if (this.type === "arrow" && item?.submenu?.length) {
251
- item.open = true;
252
- this.requestUpdate();
253
- }
254
- } else if (ev.key === "ArrowLeft" || ev.key === "Escape") {
255
- this.items.forEach((i) => (i.open = false));
256
- this.requestUpdate();
257
- } else if (ev.key === "Enter" || ev.key === " ") {
258
- const item = this.items[this.focusedIndex];
259
- this.onItemClick(item, this.focusedIndex);
260
- ev.preventDefault();
261
- }
262
- }
263
-
264
- private renderMenuItem(item: MenuItem, index: number) {
265
- const isFocused = this.focusedIndex === index;
266
-
267
- return html`
268
- <div
269
- class="rel-menu-item ${item.active ? "active" : ""} ${item.disabled ? "disabled" : ""}"
270
- role="menuitem"
271
- ?data-focused="${isFocused}"
272
- @mouseenter="${() => this.onItemEnter(item)}"
273
- @mouseleave="${() => this.onItemLeave(item)}"
274
- @click="${(e: Event) => this.onItemClick(item, index, e)}">
275
- ${this.type === "checkbox"
276
- ? html`
277
- <div class="menu-check-row">
278
- <input
279
- class="menu-checkbox"
280
- type="checkbox"
281
- .checked="${item.checked || false}"
282
- ?disabled="${item.disabled}"
283
- @click="${(e: Event) => this.toggleCheckbox(item, e)}" />
284
- <span class="menu-label">${item.label}</span>
285
- </div>
286
- `
287
- : html`
288
- <span class="menu-label">${item.label}</span>
289
- ${this.type === "arrow" && item.submenu?.length
290
- ? html`<span class="chevron">▸</span>`
291
- : ""}
292
- `}
293
- ${this.type === "arrow" && item.submenu?.length
294
- ? html`
295
- <div
296
- class="rel-submenu ${item.open ? "visible" : ""}"
297
- @click="${(e: Event) => e.stopPropagation()}">
298
- ${item.submenu.map(
299
- (sub) => html`
300
- <div
301
- class="rel-menu-item ${sub.active ? "active" : ""}"
302
- role="menuitem"
303
- @click="${(e: Event) => this.onSubClick(item, sub, e)}">
304
- <span class="menu-label">${sub.label}</span>
305
- </div>
306
- `
307
- )}
308
- </div>
309
- `
310
- : ""}
311
- </div>
312
- `;
313
- }
314
-
315
- render() {
316
- return html`
317
- <div
318
- class="rel-menu"
319
- role="menu"
320
- tabindex="0"
321
- @keydown="${this.onKeydown}"
322
- @click="${(e: Event) => e.stopPropagation()}">
323
- ${this.items.map((item, index) => this.renderMenuItem(item, index))}
324
- </div>
325
- `;
326
- }
327
- }
1
+ import { LitElement, html } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+ import "../../styles/vars-v2.css";
4
+
5
+ export type MenuType = "default" | "checkbox" | "arrow";
6
+
7
+ export interface MenuItem {
8
+ label: string;
9
+ active?: boolean;
10
+ checked?: boolean;
11
+ disabled?: boolean;
12
+ submenu?: MenuItem[];
13
+ open?: boolean;
14
+ }
15
+
16
+ @customElement("rel-menu-item")
17
+ export class RelMenuItem extends LitElement {
18
+ @property({ type: String }) label: string = "";
19
+ @property({ type: String }) value: string = "";
20
+ @property({ type: Boolean }) disabled: boolean = false;
21
+ @property({ type: String }) icon: string = "";
22
+
23
+ createRenderRoot() {
24
+ return this;
25
+ }
26
+
27
+ render() {
28
+ return html``;
29
+ }
30
+ }
31
+
32
+ @customElement("rel-menu")
33
+ export class RelMenu extends LitElement {
34
+ @property({ type: String }) type: MenuType = "default";
35
+
36
+ @property({ type: Array }) items: MenuItem[] = [];
37
+
38
+ @state() private focusedIndex: number = 0;
39
+ @state() private _items: MenuItem[] = [];
40
+
41
+ public static readonly _css = `
42
+ :host {
43
+ display: inline-block;
44
+ }
45
+
46
+ .rel-menu {
47
+ position: relative;
48
+ display: inline-block;
49
+ background: var(--rel-color-bg-primary);
50
+ color: var(--rel-color-text-primary);
51
+ border-radius: var(--rel-borders-radius-border-radius-300);
52
+ box-shadow: var(--rel-shadows-shadow-300);
53
+ min-width: 220px;
54
+ font-family: var(--rel-fontFamilies-primary, sans-serif);
55
+ font-size: var(--rel-fontSizes-sm);
56
+ padding: var(--rel-spacing-space-3xs) 0;
57
+ outline: none;
58
+ overflow: hidden;
59
+ }
60
+
61
+ .rel-menu:focus-visible,
62
+ :host(:focus-visible) .rel-menu {
63
+ outline: 2px solid var(--rel-color-border-focus);
64
+ outline-offset: 2px;
65
+ }
66
+
67
+ .rel-menu-item {
68
+ position: relative;
69
+ display: flex;
70
+ align-items: center;
71
+ gap: var(--rel-spacing-space-xs);
72
+ padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-sm);
73
+ line-height: var(--rel-lineHeights-sm);
74
+ cursor: pointer;
75
+ transition: background 0.15s ease, color 0.15s ease;
76
+ user-select: none;
77
+ }
78
+
79
+ .rel-menu-item:hover {
80
+ background: var(--rel-color-bg-hover);
81
+ }
82
+
83
+ .rel-menu-item:active {
84
+ background: var(--rel-color-bg-hover);
85
+ }
86
+
87
+ .rel-menu-item.active {
88
+ background: var(--rel-color-bg-hover);
89
+ font-weight: var(--rel-fontWeights-medium);
90
+ }
91
+
92
+ .rel-menu-item.disabled {
93
+ background-color: transparent;
94
+ color: var(--rel-color-text-disabled);
95
+ cursor: not-allowed;
96
+ }
97
+
98
+ .rel-menu-item[data-focused="true"] {
99
+ outline: 2px solid var(--rel-color-border-focus);
100
+ outline-offset: -2px;
101
+ }
102
+
103
+ .menu-check-row {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: var(--rel-spacing-space-2xs);
107
+ width: 100%;
108
+ }
109
+
110
+ .menu-checkbox {
111
+ width: 16px;
112
+ height: 16px;
113
+ cursor: pointer;
114
+ }
115
+
116
+ .menu-checkbox:disabled {
117
+ cursor: not-allowed;
118
+ }
119
+
120
+ .menu-label {
121
+ flex: 1 1 auto;
122
+ }
123
+
124
+ .chevron {
125
+ font-size: var(--rel-fontSizes-lg);
126
+ opacity: 0.75;
127
+ transform: translateY(1px);
128
+ }
129
+
130
+ .rel-submenu {
131
+ position: absolute;
132
+ top: calc(-1 * var(--rel-spacing-space-3xs));
133
+ left: calc(100% + var(--rel-spacing-space-2xs));
134
+ min-width: 200px;
135
+ background: var(--rel-color-bg-primary);
136
+ color: var(--rel-color-text-primary);
137
+ border-radius: var(--rel-borders-radius-border-radius-300);
138
+ box-shadow: var(--rel-shadows-shadow-300);
139
+ padding: var(--rel-spacing-space-3xs) 0;
140
+ visibility: hidden;
141
+ pointer-events: none;
142
+ z-index: 20;
143
+ }
144
+
145
+ .rel-submenu.visible {
146
+ visibility: visible;
147
+ pointer-events: auto;
148
+ }
149
+
150
+ .rel-submenu .rel-menu-item {
151
+ padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-sm);
152
+ }
153
+ `;
154
+
155
+ createRenderRoot() {
156
+ return this;
157
+ }
158
+
159
+ connectedCallback() {
160
+ super.connectedCallback();
161
+ document.addEventListener("click", this.handleDocumentClick);
162
+ }
163
+
164
+ disconnectedCallback() {
165
+ super.disconnectedCallback();
166
+ document.removeEventListener("click", this.handleDocumentClick);
167
+ }
168
+
169
+ updated(changedProperties: Map<string, unknown>) {
170
+ if (changedProperties.has('items')) {
171
+ this._items = this.items.map(item => ({ ...item }));
172
+ }
173
+ }
174
+
175
+ private handleDocumentClick = (e: MouseEvent) => {
176
+ if (!this.contains(e.target as Node)) {
177
+ this._items = this._items.map(i => ({ ...i, open: false }));
178
+ }
179
+ };
180
+
181
+ private onItemEnter(item: MenuItem, index: number) {
182
+ if (this.type === "arrow" && item.submenu?.length) {
183
+ this._items = this._items.map((i, idx) => ({ ...i, open: idx === index }));
184
+ }
185
+ }
186
+
187
+ private onItemLeave(_item: MenuItem) {
188
+ // Keep open so user can move into submenu
189
+ }
190
+
191
+ private onItemClick(item: MenuItem, index: number, ev?: Event) {
192
+ ev?.stopPropagation();
193
+ if (item.disabled) return;
194
+
195
+ this.focusedIndex = index;
196
+
197
+ if (this.type === "checkbox") {
198
+ const updated = { ...item, checked: !item.checked };
199
+ this._items = this._items.map((i, idx) => idx === index ? updated : i);
200
+ this.dispatchEvent(
201
+ new CustomEvent("rel-check", {
202
+ detail: { item: updated, checked: updated.checked },
203
+ bubbles: true,
204
+ composed: true,
205
+ })
206
+ );
207
+ return;
208
+ }
209
+
210
+ if (this.type === "arrow" && item.submenu?.length) {
211
+ const next = !item.open;
212
+ this._items = this._items.map((i, idx) => ({
213
+ ...i,
214
+ open: idx === index ? next : false,
215
+ }));
216
+ return;
217
+ }
218
+
219
+ // Default: set active
220
+ const updated = { ...item, active: true };
221
+ this._items = this._items.map((i, idx) => idx === index ? updated : { ...i, active: false });
222
+ this.dispatchEvent(
223
+ new CustomEvent("rel-select", {
224
+ detail: { item: updated },
225
+ bubbles: true,
226
+ composed: true,
227
+ })
228
+ );
229
+ }
230
+
231
+ private onSubClick(parentIndex: number, subIndex: number, ev?: Event) {
232
+ ev?.stopPropagation();
233
+ this._items = this._items.map((i, idx) => ({
234
+ ...i,
235
+ active: false,
236
+ open: false,
237
+ submenu: i.submenu?.map((s, sIdx) => ({
238
+ ...s,
239
+ active: idx === parentIndex && sIdx === subIndex,
240
+ })),
241
+ }));
242
+ this.dispatchEvent(
243
+ new CustomEvent("rel-submenu-select", {
244
+ detail: {
245
+ parent: this._items[parentIndex],
246
+ sub: this._items[parentIndex]?.submenu?.[subIndex],
247
+ },
248
+ bubbles: true,
249
+ composed: true,
250
+ })
251
+ );
252
+ }
253
+
254
+ private toggleCheckbox(item: MenuItem, ev: Event) {
255
+ ev.stopPropagation();
256
+ if (!item.disabled) {
257
+ item.checked = !item.checked;
258
+ this.dispatchEvent(
259
+ new CustomEvent("rel-check", {
260
+ detail: { item, checked: item.checked },
261
+ bubbles: true,
262
+ composed: true,
263
+ })
264
+ );
265
+ this.requestUpdate();
266
+ }
267
+ }
268
+
269
+ private onKeydown(ev: KeyboardEvent) {
270
+ const last = this._items.length - 1;
271
+
272
+ if (ev.key === "ArrowDown") {
273
+ this.focusedIndex = Math.min(last, this.focusedIndex + 1);
274
+ ev.preventDefault();
275
+ } else if (ev.key === "ArrowUp") {
276
+ this.focusedIndex = Math.max(0, this.focusedIndex - 1);
277
+ ev.preventDefault();
278
+ } else if (ev.key === "ArrowRight") {
279
+ const item = this._items[this.focusedIndex];
280
+ if (this.type === "arrow" && item?.submenu?.length) {
281
+ this._items = this._items.map((i, idx) => ({
282
+ ...i,
283
+ open: idx === this.focusedIndex ? true : i.open,
284
+ }));
285
+ }
286
+ } else if (ev.key === "ArrowLeft" || ev.key === "Escape") {
287
+ this._items = this._items.map(i => ({ ...i, open: false }));
288
+ } else if (ev.key === "Enter" || ev.key === " ") {
289
+ const item = this._items[this.focusedIndex];
290
+ this.onItemClick(item, this.focusedIndex);
291
+ ev.preventDefault();
292
+ }
293
+ }
294
+
295
+ private renderMenuItem(item: MenuItem, index: number) {
296
+ const isFocused = this.focusedIndex === index;
297
+
298
+ return html`
299
+ <div
300
+ class="rel-menu-item ${item.active ? "active" : ""} ${item.disabled ? "disabled" : ""}"
301
+ role="menuitem"
302
+ ?data-focused="${isFocused}"
303
+ @mouseenter="${() => this.onItemEnter(item, index)}"
304
+ @mouseleave="${() => this.onItemLeave(item)}"
305
+ @click="${(e: Event) => this.onItemClick(item, index, e)}">
306
+ ${this.type === "checkbox"
307
+ ? html`
308
+ <div class="menu-check-row">
309
+ <input
310
+ class="menu-checkbox"
311
+ type="checkbox"
312
+ .checked="${item.checked || false}"
313
+ ?disabled="${item.disabled}"
314
+ @click="${(e: Event) => this.toggleCheckbox(item, e)}" />
315
+ <span class="menu-label">${item.label}</span>
316
+ </div>
317
+ `
318
+ : html`
319
+ <span class="menu-label">${item.label}</span>
320
+ ${this.type === "arrow" && item.submenu?.length
321
+ ? html`<span class="chevron">▸</span>`
322
+ : ""}
323
+ `}
324
+ ${this.type === "arrow" && item.submenu?.length
325
+ ? html`
326
+ <div
327
+ class="rel-submenu ${item.open ? "visible" : ""}"
328
+ @click="${(e: Event) => e.stopPropagation()}">
329
+ ${item.submenu.map(
330
+ (sub, subIndex) => html`
331
+ <div
332
+ class="rel-menu-item ${sub.active ? "active" : ""}"
333
+ role="menuitem"
334
+ @click="${(e: Event) => this.onSubClick(index, subIndex, e)}">
335
+ <span class="menu-label">${sub.label}</span>
336
+ </div>
337
+ `
338
+ )}
339
+ </div>
340
+ `
341
+ : ""}
342
+ </div>
343
+ `;
344
+ }
345
+
346
+ render() {
347
+ return html`
348
+ <style>${RelMenu._css}</style>
349
+ <div
350
+ class="rel-menu"
351
+ role="menu"
352
+ tabindex="0"
353
+ @keydown="${this.onKeydown}"
354
+ @click="${(e: Event) => e.stopPropagation()}">
355
+ ${this._items.map((item, index) => this.renderMenuItem(item, index))}
356
+ </div>
357
+ `;
358
+ }
359
+ }