@reliability-design/web 1.0.3 → 1.0.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 (114) hide show
  1. package/dist/components/rel-accordion/README.md +132 -0
  2. package/dist/components/rel-accordion/rel-accordion.ts +43 -16
  3. package/dist/components/rel-alert/README.md +141 -0
  4. package/dist/components/rel-alert/rel-alert.ts +18 -7
  5. package/dist/components/rel-avatar/README.md +119 -0
  6. package/dist/components/rel-badge/README.md +86 -0
  7. package/dist/components/rel-badge/rel-badge.ts +6 -6
  8. package/dist/components/rel-breadcrumb/README.md +102 -0
  9. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +12 -6
  10. package/dist/components/rel-button/README.md +130 -0
  11. package/dist/components/rel-calendar/README.md +109 -0
  12. package/dist/components/rel-calendar/index.ts +1 -0
  13. package/dist/components/rel-calendar/rel-calendar.ts +467 -0
  14. package/dist/components/rel-card/README.md +94 -0
  15. package/dist/components/rel-card/rel-card.ts +12 -3
  16. package/dist/components/rel-cardmedia/README.md +127 -0
  17. package/dist/components/rel-cardmedia/rel-cardmedia.ts +14 -4
  18. package/dist/components/rel-carousel/README.md +166 -0
  19. package/dist/components/rel-carousel/index.ts +1 -0
  20. package/dist/components/rel-carousel/rel-carousel.ts +469 -0
  21. package/dist/components/rel-chart/README.md +286 -0
  22. package/dist/components/rel-chart/index.ts +1 -0
  23. package/dist/components/rel-chart/rel-chart.ts +736 -0
  24. package/dist/components/rel-checkbox/README.md +77 -0
  25. package/dist/components/rel-checkbox/rel-checkbox.ts +18 -10
  26. package/dist/components/rel-chip/README.md +114 -0
  27. package/dist/components/rel-chip/rel-chip.ts +20 -4
  28. package/dist/components/rel-container/README.md +99 -0
  29. package/dist/components/rel-date-picker/README.md +253 -0
  30. package/dist/components/rel-date-picker/index.ts +1 -0
  31. package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
  32. package/dist/components/rel-divider/README.md +100 -0
  33. package/dist/components/rel-drawer/README.md +200 -0
  34. package/dist/components/rel-drawer/index.ts +1 -0
  35. package/dist/components/rel-drawer/rel-drawer.ts +621 -0
  36. package/dist/components/rel-kbd/README.md +151 -0
  37. package/dist/components/rel-kbd/rel-kbd.ts +118 -0
  38. package/dist/components/rel-layout/README.md +88 -0
  39. package/dist/components/rel-menu/README.md +151 -0
  40. package/dist/components/rel-menu/rel-menu.ts +18 -10
  41. package/dist/components/rel-menubar/README.md +329 -0
  42. package/dist/components/rel-menubar/index.ts +2 -0
  43. package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
  44. package/dist/components/rel-menubar/rel-menubar.ts +576 -0
  45. package/dist/components/rel-nav-menu/README.md +289 -0
  46. package/dist/components/rel-nav-menu/index.ts +3 -0
  47. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +143 -0
  48. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
  49. package/dist/components/rel-nav-menu/rel-nav-menu.ts +331 -0
  50. package/dist/components/rel-navbar/README.md +126 -0
  51. package/dist/components/rel-navbar/rel-navbar.ts +91 -0
  52. package/dist/components/rel-otp/README.md +440 -0
  53. package/dist/components/rel-otp/index.ts +1 -0
  54. package/dist/components/rel-otp/rel-otp.ts +504 -0
  55. package/dist/components/rel-pagination/README.md +410 -0
  56. package/dist/components/rel-pagination/index.ts +1 -0
  57. package/dist/components/rel-pagination/rel-pagination.ts +376 -0
  58. package/dist/components/rel-popover/README.md +568 -0
  59. package/dist/components/rel-popover/index.ts +2 -0
  60. package/dist/components/rel-popover/rel-popover-content.ts +55 -0
  61. package/dist/components/rel-popover/rel-popover.ts +327 -0
  62. package/dist/components/rel-progress/README.md +110 -0
  63. package/dist/components/rel-progress/rel-progress.ts +32 -5
  64. package/dist/components/rel-quote-page/README.md +166 -0
  65. package/dist/components/rel-quote-page/rel-quote-page.ts +816 -0
  66. package/dist/components/rel-radio/README.md +120 -0
  67. package/dist/components/rel-radio/rel-radio.ts +13 -5
  68. package/dist/components/rel-resizable/README.md +140 -0
  69. package/dist/components/rel-resizable/index.ts +3 -0
  70. package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
  71. package/dist/components/rel-resizable/rel-resizable-handle.ts +149 -0
  72. package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
  73. package/dist/components/rel-segmented-button/README.md +141 -0
  74. package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
  75. package/dist/components/rel-select/README.md +107 -0
  76. package/dist/components/rel-select/rel-select.ts +14 -14
  77. package/dist/components/rel-sheet/README.md +224 -0
  78. package/dist/components/rel-sheet/rel-sheet.ts +683 -0
  79. package/dist/components/rel-sidebar/README.md +209 -0
  80. package/dist/components/rel-sidebar/rel-sidebar.ts +1158 -0
  81. package/dist/components/rel-slider/README.md +135 -0
  82. package/dist/components/rel-slider/rel-slider.ts +19 -7
  83. package/dist/components/rel-snackbar/README.md +134 -0
  84. package/dist/components/rel-snackbar/rel-snackbar.ts +67 -2
  85. package/dist/components/rel-stepper/README.md +160 -0
  86. package/dist/components/rel-stepper/rel-stepper.ts +6 -6
  87. package/dist/components/rel-switch/README.md +102 -0
  88. package/dist/components/rel-switch/rel-switch.ts +15 -6
  89. package/dist/components/rel-tab/README.md +163 -0
  90. package/dist/components/rel-tab/rel-tab.ts +36 -14
  91. package/dist/components/rel-table/README.md +178 -0
  92. package/dist/components/rel-table/rel-table-body.ts +16 -0
  93. package/dist/components/rel-table/rel-table-caption.ts +22 -0
  94. package/dist/components/rel-table/rel-table-cell.ts +49 -0
  95. package/dist/components/rel-table/rel-table-footer.ts +18 -0
  96. package/dist/components/rel-table/rel-table-head.ts +33 -0
  97. package/dist/components/rel-table/rel-table-header.ts +16 -0
  98. package/dist/components/rel-table/rel-table-row.ts +32 -0
  99. package/dist/components/rel-table/rel-table.ts +47 -0
  100. package/dist/components/rel-textfield/README.md +90 -0
  101. package/dist/components/rel-textfield/rel-textfield.ts +35 -19
  102. package/dist/components/rel-toast/README.md +148 -0
  103. package/dist/components/rel-toast/rel-toast.ts +32 -14
  104. package/dist/components/rel-tooltip/README.md +103 -0
  105. package/dist/components/rel-tooltip/rel-tooltip.ts +9 -9
  106. package/dist/index.d.mts +836 -1
  107. package/dist/index.d.ts +836 -1
  108. package/dist/index.js +10658 -1493
  109. package/dist/index.js.map +1 -1
  110. package/dist/index.mjs +10616 -1499
  111. package/dist/index.mjs.map +1 -1
  112. package/dist/styles/vars.css +293 -183
  113. package/dist/{vars-6ZIUPIU4.css → vars-ITL2K3KO.css} +293 -183
  114. package/package.json +1 -1
@@ -0,0 +1,576 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, state, query } from "lit/decorators.js";
3
+ import { RelMenubarMenu } from "./rel-menubar-menu";
4
+
5
+ interface MenuState {
6
+ menu: RelMenubarMenu;
7
+ open: boolean;
8
+ panelElement?: HTMLElement;
9
+ }
10
+
11
+ @customElement("rel-menubar")
12
+ export class RelMenubar extends LitElement {
13
+ @state() private menus: MenuState[] = [];
14
+ @state() private openMenuIndex: number = -1;
15
+ @state() private focusedTriggerIndex: number = 0;
16
+ @state() private focusedItemIndex: number = 0;
17
+ @state() private openSubmenuElement: HTMLElement | null = null;
18
+
19
+ @query(".menubar-container") private container!: HTMLElement;
20
+
21
+ private hoverSwitchEnabled = false;
22
+
23
+ static styles = css`
24
+ :host {
25
+ display: block;
26
+ }
27
+
28
+ .menubar-container {
29
+ display: flex;
30
+ align-items: center;
31
+ background: var(--rel-colors-gray-light);
32
+ border: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
33
+ border-radius: var(--rel-borders-radius-border-radius-200);
34
+ padding: var(--rel-spacing-space-3xs);
35
+ gap: var(--rel-spacing-space-3xs);
36
+ font-family: var(--rel-fontFamilies-primary);
37
+ font-size: var(--rel-fontSizes-sm);
38
+ }
39
+
40
+ .menubar-trigger {
41
+ padding: var(--rel-spacing-space-2xs) var(--rel-spacing-space-sm);
42
+ border-radius: var(--rel-borders-radius-border-radius-100);
43
+ background: transparent;
44
+ border: none;
45
+ cursor: pointer;
46
+ font-family: inherit;
47
+ font-size: inherit;
48
+ color: var(--rel-colors-gray-heading);
49
+ transition: background 0.15s ease;
50
+ user-select: none;
51
+ outline: none;
52
+ }
53
+
54
+ .menubar-trigger:hover {
55
+ background: var(--rel-surface-secondary, #fafafa);
56
+ }
57
+
58
+ .menubar-trigger:focus-visible {
59
+ outline: 2px solid var(--rel-colors-blue-primary);
60
+ outline-offset: 2px;
61
+ }
62
+
63
+ .menubar-trigger.active {
64
+ background: var(--rel-surface-secondary, #fafafa);
65
+ }
66
+
67
+ .menubar-panel {
68
+ position: fixed;
69
+ min-width: 220px;
70
+ background: var(--rel-colors-white);
71
+ border: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
72
+ border-radius: var(--rel-borders-radius-border-radius-200);
73
+ box-shadow: var(--rel-shadows-shadow-300);
74
+ padding: var(--rel-spacing-space-2xs) 0;
75
+ z-index: 1000;
76
+ display: none;
77
+ }
78
+
79
+ .menubar-panel.open {
80
+ display: block;
81
+ }
82
+
83
+ .menu-item {
84
+ position: relative;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: space-between;
88
+ gap: var(--rel-spacing-space-sm);
89
+ padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-md);
90
+ cursor: pointer;
91
+ transition: background 0.15s ease;
92
+ user-select: none;
93
+ color: var(--rel-colors-gray-heading);
94
+ line-height: var(--rel-lineHeights-sm);
95
+ }
96
+
97
+ .menu-item:hover,
98
+ .menu-item.focused {
99
+ background: var(--rel-surface-secondary, #fafafa);
100
+ }
101
+
102
+ .menu-item[data-disabled="true"] {
103
+ opacity: 0.5;
104
+ cursor: not-allowed;
105
+ }
106
+
107
+ .menu-separator {
108
+ height: var(--rel-borders-width-stroke-100);
109
+ background: var(--rel-colors-gray-divider);
110
+ margin: var(--rel-spacing-space-2xs) var(--rel-spacing-space-xs);
111
+ }
112
+
113
+ .menu-checkbox,
114
+ .menu-radio-item {
115
+ display: flex;
116
+ align-items: center;
117
+ gap: var(--rel-spacing-space-xs);
118
+ padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-md);
119
+ cursor: pointer;
120
+ transition: background 0.15s ease;
121
+ user-select: none;
122
+ color: var(--rel-colors-gray-heading);
123
+ line-height: var(--rel-lineHeights-sm);
124
+ }
125
+
126
+ .menu-checkbox:hover,
127
+ .menu-checkbox.focused,
128
+ .menu-radio-item:hover,
129
+ .menu-radio-item.focused {
130
+ background: var(--rel-surface-secondary, #fafafa);
131
+ }
132
+
133
+ .menu-checkbox input[type="checkbox"],
134
+ .menu-radio-item::before {
135
+ margin: 0;
136
+ }
137
+
138
+ .menu-radio-item {
139
+ position: relative;
140
+ padding-left: calc(var(--rel-spacing-space-md) + var(--rel-spacing-space-md));
141
+ }
142
+
143
+ .menu-radio-item::before {
144
+ content: "";
145
+ position: absolute;
146
+ left: var(--rel-spacing-space-md);
147
+ width: 12px;
148
+ height: 12px;
149
+ border-radius: 50%;
150
+ border: var(--rel-borders-width-stroke-150) solid var(--rel-colors-gray-border);
151
+ background: var(--rel-colors-white);
152
+ }
153
+
154
+ .menu-radio-item.checked::after {
155
+ content: "";
156
+ position: absolute;
157
+ left: calc(var(--rel-spacing-space-md) + 3px);
158
+ width: 6px;
159
+ height: 6px;
160
+ border-radius: 50%;
161
+ background: var(--rel-colors-blue-primary);
162
+ }
163
+
164
+ .menu-submenu-trigger {
165
+ position: relative;
166
+ }
167
+
168
+ .menu-submenu-trigger::after {
169
+ content: "▸";
170
+ opacity: 0.6;
171
+ font-size: var(--rel-fontSizes-sm);
172
+ }
173
+
174
+ .submenu-panel {
175
+ position: fixed;
176
+ min-width: 200px;
177
+ background: var(--rel-colors-white);
178
+ border: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
179
+ border-radius: var(--rel-borders-radius-border-radius-200);
180
+ box-shadow: var(--rel-shadows-shadow-300);
181
+ padding: var(--rel-spacing-space-2xs) 0;
182
+ z-index: 1001;
183
+ display: none;
184
+ }
185
+
186
+ .submenu-panel.open {
187
+ display: block;
188
+ }
189
+
190
+ .menu-shortcut {
191
+ opacity: 0.6;
192
+ font-size: var(--rel-fontSizes-xs);
193
+ margin-left: auto;
194
+ display: flex;
195
+ align-items: center;
196
+ gap: var(--rel-spacing-space-3xs);
197
+ }
198
+ `;
199
+
200
+ connectedCallback() {
201
+ super.connectedCallback();
202
+ this.discoverMenus();
203
+ document.addEventListener("click", this.handleOutsideClick);
204
+ }
205
+
206
+ disconnectedCallback() {
207
+ super.disconnectedCallback();
208
+ document.removeEventListener("click", this.handleOutsideClick);
209
+ }
210
+
211
+ private discoverMenus() {
212
+ const menuElements = Array.from(
213
+ this.querySelectorAll("rel-menubar-menu")
214
+ ) as RelMenubarMenu[];
215
+ this.menus = menuElements.map((menu) => ({ menu, open: false }));
216
+ }
217
+
218
+ private handleOutsideClick = (e: MouseEvent) => {
219
+ const path = e.composedPath() as HTMLElement[];
220
+ const isInside = path.some(
221
+ (el) =>
222
+ el === this ||
223
+ el.classList?.contains("menubar-panel") ||
224
+ el.classList?.contains("submenu-panel")
225
+ );
226
+ if (!isInside) {
227
+ this.closeAllMenus();
228
+ }
229
+ };
230
+
231
+ private closeAllMenus() {
232
+ this.openMenuIndex = -1;
233
+ this.openSubmenuElement = null;
234
+ this.hoverSwitchEnabled = false;
235
+ this.requestUpdate();
236
+ }
237
+
238
+ private openMenu(index: number, triggerElement: HTMLElement) {
239
+ this.openMenuIndex = index;
240
+ this.focusedItemIndex = 0;
241
+ this.openSubmenuElement = null;
242
+ this.requestUpdate();
243
+
244
+ // Position the panel below the trigger
245
+ this.updateComplete.then(() => {
246
+ const panel = this.shadowRoot?.querySelector(
247
+ `.menubar-panel[data-menu-index="${index}"]`
248
+ ) as HTMLElement;
249
+ if (panel && triggerElement) {
250
+ const rect = triggerElement.getBoundingClientRect();
251
+ panel.style.top = `${rect.bottom + 4}px`;
252
+ panel.style.left = `${rect.left}px`;
253
+ }
254
+ });
255
+ }
256
+
257
+ private handleTriggerClick(index: number, event: Event) {
258
+ const target = event.currentTarget as HTMLElement;
259
+ if (this.openMenuIndex === index) {
260
+ this.closeAllMenus();
261
+ } else {
262
+ this.openMenu(index, target);
263
+ this.hoverSwitchEnabled = true;
264
+ }
265
+ }
266
+
267
+ private handleTriggerMouseEnter(index: number, event: Event) {
268
+ if (this.openMenuIndex !== -1 && this.hoverSwitchEnabled) {
269
+ const target = event.currentTarget as HTMLElement;
270
+ this.openMenu(index, target);
271
+ }
272
+ }
273
+
274
+ private handleTriggerKeyDown(event: KeyboardEvent) {
275
+ const { key } = event;
276
+
277
+ if (key === "ArrowRight") {
278
+ event.preventDefault();
279
+ this.focusedTriggerIndex = Math.min(
280
+ this.menus.length - 1,
281
+ this.focusedTriggerIndex + 1
282
+ );
283
+ this.focusTrigger(this.focusedTriggerIndex);
284
+ } else if (key === "ArrowLeft") {
285
+ event.preventDefault();
286
+ this.focusedTriggerIndex = Math.max(0, this.focusedTriggerIndex - 1);
287
+ this.focusTrigger(this.focusedTriggerIndex);
288
+ } else if (key === "Enter" || key === " ") {
289
+ event.preventDefault();
290
+ const trigger = this.shadowRoot?.querySelector(
291
+ `.menubar-trigger[data-index="${this.focusedTriggerIndex}"]`
292
+ ) as HTMLElement;
293
+ if (trigger) {
294
+ this.openMenu(this.focusedTriggerIndex, trigger);
295
+ // Focus first item
296
+ setTimeout(() => this.focusMenuItem(0), 50);
297
+ }
298
+ } else if (key === "ArrowDown" && this.openMenuIndex !== -1) {
299
+ event.preventDefault();
300
+ this.focusMenuItem(0);
301
+ }
302
+ }
303
+
304
+ private focusTrigger(index: number) {
305
+ const trigger = this.shadowRoot?.querySelector(
306
+ `.menubar-trigger[data-index="${index}"]`
307
+ ) as HTMLElement;
308
+ trigger?.focus();
309
+ }
310
+
311
+ private handlePanelKeyDown(event: KeyboardEvent) {
312
+ const { key } = event;
313
+ const menuContent = this.menus[this.openMenuIndex]?.menu;
314
+ if (!menuContent) return;
315
+
316
+ const items = this.getFocusableItems(menuContent);
317
+
318
+ if (key === "ArrowDown") {
319
+ event.preventDefault();
320
+ this.focusedItemIndex = Math.min(items.length - 1, this.focusedItemIndex + 1);
321
+ this.requestUpdate();
322
+ } else if (key === "ArrowUp") {
323
+ event.preventDefault();
324
+ this.focusedItemIndex = Math.max(0, this.focusedItemIndex - 1);
325
+ this.requestUpdate();
326
+ } else if (key === "ArrowRight") {
327
+ event.preventDefault();
328
+ const currentItem = items[this.focusedItemIndex];
329
+ if (currentItem?.hasAttribute("data-submenu")) {
330
+ this.openSubmenu(currentItem as HTMLElement);
331
+ }
332
+ } else if (key === "ArrowLeft") {
333
+ event.preventDefault();
334
+ if (this.openSubmenuElement) {
335
+ this.openSubmenuElement = null;
336
+ this.requestUpdate();
337
+ }
338
+ } else if (key === "Escape") {
339
+ event.preventDefault();
340
+ this.closeAllMenus();
341
+ this.focusTrigger(this.openMenuIndex);
342
+ } else if (key === "Enter" || key === " ") {
343
+ event.preventDefault();
344
+ const currentItem = items[this.focusedItemIndex];
345
+ if (currentItem) {
346
+ this.handleItemClick(currentItem as HTMLElement);
347
+ }
348
+ }
349
+ }
350
+
351
+ private getFocusableItems(menuContent: RelMenubarMenu): Element[] {
352
+ return Array.from(menuContent.children).filter(
353
+ (el) =>
354
+ el.hasAttribute("data-item") ||
355
+ el.hasAttribute("data-checkbox") ||
356
+ el.hasAttribute("data-radio") ||
357
+ el.hasAttribute("data-submenu")
358
+ );
359
+ }
360
+
361
+ private focusMenuItem(index: number) {
362
+ this.focusedItemIndex = index;
363
+ const panel = this.shadowRoot?.querySelector(
364
+ `.menubar-panel[data-menu-index="${this.openMenuIndex}"]`
365
+ ) as HTMLElement;
366
+ panel?.focus();
367
+ }
368
+
369
+ private handleItemClick(item: HTMLElement) {
370
+ if (item.hasAttribute("data-checkbox")) {
371
+ const checkbox = item.querySelector('input[type="checkbox"]') as HTMLInputElement;
372
+ if (checkbox) {
373
+ checkbox.checked = !checkbox.checked;
374
+ this.dispatchChangeEvent("checkbox", item.textContent?.trim() || "", checkbox.checked);
375
+ }
376
+ } else if (item.hasAttribute("data-radio")) {
377
+ const radioGroup = item.closest("[data-radio-group]") as HTMLElement;
378
+ const value = item.getAttribute("data-radio") || item.getAttribute("value") || "";
379
+ const name = radioGroup?.getAttribute("name") || "";
380
+
381
+ // Update visual state
382
+ const allRadios = radioGroup?.querySelectorAll("[data-radio]");
383
+ allRadios?.forEach((radio) => radio.removeAttribute("data-checked"));
384
+ item.setAttribute("data-checked", "true");
385
+
386
+ // Update internal value
387
+ if (radioGroup) {
388
+ radioGroup.setAttribute("value", value);
389
+ }
390
+
391
+ this.dispatchChangeEvent("radio", name, value);
392
+ this.requestUpdate();
393
+ } else if (item.hasAttribute("data-submenu")) {
394
+ this.openSubmenu(item);
395
+ } else {
396
+ // Regular item click
397
+ this.closeAllMenus();
398
+ }
399
+ }
400
+
401
+ private openSubmenu(submenuTrigger: HTMLElement) {
402
+ this.openSubmenuElement = submenuTrigger;
403
+ this.requestUpdate();
404
+
405
+ this.updateComplete.then(() => {
406
+ const submenuPanel = this.shadowRoot?.querySelector(
407
+ `.submenu-panel[data-submenu-id="${submenuTrigger.getAttribute("data-submenu") || ""}"]`
408
+ ) as HTMLElement;
409
+ if (submenuPanel) {
410
+ const rect = submenuTrigger.getBoundingClientRect();
411
+ submenuPanel.style.top = `${rect.top}px`;
412
+ submenuPanel.style.left = `${rect.right + 4}px`;
413
+ }
414
+ });
415
+ }
416
+
417
+ private handleSubmenuItemClick(item: HTMLElement) {
418
+ // Regular item - just close
419
+ this.closeAllMenus();
420
+ }
421
+
422
+ private dispatchChangeEvent(type: "checkbox" | "radio", labelOrName: string, value: string | boolean) {
423
+ this.dispatchEvent(
424
+ new CustomEvent("rel-menubar-change", {
425
+ detail: { type, [type === "checkbox" ? "label" : "name"]: labelOrName, [type === "checkbox" ? "checked" : "value"]: value },
426
+ bubbles: true,
427
+ composed: true,
428
+ })
429
+ );
430
+ }
431
+
432
+ private renderMenuItem(item: Element, index: number): any {
433
+ const isFocused = index === this.focusedItemIndex;
434
+
435
+ if (item.hasAttribute("data-item")) {
436
+ const button = item as HTMLButtonElement;
437
+ const disabled = button.disabled;
438
+ const shortcut = button.getAttribute("data-shortcut") || "";
439
+
440
+ return html`
441
+ <div
442
+ class="menu-item ${isFocused ? "focused" : ""}"
443
+ data-disabled="${disabled}"
444
+ @click="${() => !disabled && this.handleItemClick(item as HTMLElement)}"
445
+ >
446
+ <span>${button.textContent}</span>
447
+ ${shortcut ? html`<span class="menu-shortcut">${shortcut}</span>` : ""}
448
+ </div>
449
+ `;
450
+ }
451
+
452
+ if (item.hasAttribute("data-separator")) {
453
+ return html`<div class="menu-separator"></div>`;
454
+ }
455
+
456
+ if (item.hasAttribute("data-checkbox")) {
457
+ const label = item as HTMLLabelElement;
458
+ const checkbox = label.querySelector('input[type="checkbox"]') as HTMLInputElement;
459
+
460
+ return html`
461
+ <label
462
+ class="menu-checkbox ${isFocused ? "focused" : ""}"
463
+ @click="${(e: Event) => {
464
+ e.preventDefault();
465
+ this.handleItemClick(item as HTMLElement);
466
+ }}"
467
+ >
468
+ <input type="checkbox" .checked="${checkbox?.checked || false}" @click="${(e: Event) => e.stopPropagation()}" />
469
+ <span>${label.textContent?.replace(/\s+/g, " ").trim()}</span>
470
+ </label>
471
+ `;
472
+ }
473
+
474
+ if (item.hasAttribute("data-submenu")) {
475
+ const label = item.getAttribute("label") || item.textContent?.trim() || "";
476
+ const submenuId = item.getAttribute("data-submenu") || label;
477
+
478
+ return html`
479
+ <div
480
+ class="menu-item menu-submenu-trigger ${isFocused ? "focused" : ""}"
481
+ @click="${() => this.openSubmenu(item as HTMLElement)}"
482
+ @mouseenter="${() => this.openSubmenu(item as HTMLElement)}"
483
+ >
484
+ <span>${label}</span>
485
+ </div>
486
+ ${this.renderSubmenu(item as HTMLElement, submenuId)}
487
+ `;
488
+ }
489
+
490
+ if (item.hasAttribute("data-radio-group")) {
491
+ const radioButtons = Array.from(item.querySelectorAll("[data-radio]"));
492
+ const currentValue = item.getAttribute("value") || "";
493
+
494
+ return html`
495
+ ${radioButtons.map((radio) => {
496
+ const value = radio.getAttribute("data-radio") || radio.getAttribute("value") || "";
497
+ const isChecked = value === currentValue || radio.hasAttribute("data-checked");
498
+ const text = radio.textContent?.trim() || "";
499
+
500
+ return html`
501
+ <div
502
+ class="menu-radio-item ${isChecked ? "checked" : ""} ${isFocused ? "focused" : ""}"
503
+ @click="${() => this.handleItemClick(radio as HTMLElement)}"
504
+ >
505
+ <span>${text}</span>
506
+ </div>
507
+ `;
508
+ })}
509
+ `;
510
+ }
511
+
512
+ return null;
513
+ }
514
+
515
+ private renderSubmenu(submenuElement: HTMLElement, submenuId: string) {
516
+ const isOpen = this.openSubmenuElement === submenuElement;
517
+ const submenuItems = Array.from(submenuElement.children);
518
+
519
+ return html`
520
+ <div class="submenu-panel ${isOpen ? "open" : ""}" data-submenu-id="${submenuId}">
521
+ ${submenuItems.map((subItem) => {
522
+ if (subItem.hasAttribute("data-item")) {
523
+ const button = subItem as HTMLButtonElement;
524
+ return html`
525
+ <div
526
+ class="menu-item"
527
+ @click="${() => this.handleSubmenuItemClick(subItem as HTMLElement)}"
528
+ >
529
+ <span>${button.textContent}</span>
530
+ </div>
531
+ `;
532
+ }
533
+ return null;
534
+ })}
535
+ </div>
536
+ `;
537
+ }
538
+
539
+ private renderMenuPanel(menuState: MenuState, index: number) {
540
+ const isOpen = index === this.openMenuIndex;
541
+ const items = Array.from(menuState.menu.children);
542
+
543
+ return html`
544
+ <div
545
+ class="menubar-panel ${isOpen ? "open" : ""}"
546
+ data-menu-index="${index}"
547
+ tabindex="-1"
548
+ @keydown="${this.handlePanelKeyDown}"
549
+ >
550
+ ${items.map((item, idx) => this.renderMenuItem(item, idx))}
551
+ </div>
552
+ `;
553
+ }
554
+
555
+ render() {
556
+ return html`
557
+ <div class="menubar-container">
558
+ ${this.menus.map(
559
+ (menuState, index) => html`
560
+ <button
561
+ class="menubar-trigger ${index === this.openMenuIndex ? "active" : ""}"
562
+ data-index="${index}"
563
+ @click="${(e: Event) => this.handleTriggerClick(index, e)}"
564
+ @mouseenter="${(e: Event) => this.handleTriggerMouseEnter(index, e)}"
565
+ @keydown="${this.handleTriggerKeyDown}"
566
+ >
567
+ ${menuState.menu.label}
568
+ </button>
569
+ `
570
+ )}
571
+ </div>
572
+ ${this.menus.map((menuState, index) => this.renderMenuPanel(menuState, index))}
573
+ <slot style="display: none;"></slot>
574
+ `;
575
+ }
576
+ }