@dodlhuat/basix 1.2.0 → 1.2.2

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 (93) hide show
  1. package/README.md +266 -6
  2. package/css/accordion.scss +86 -87
  3. package/css/alert.scss +137 -137
  4. package/css/button.scss +48 -0
  5. package/css/calendar.scss +957 -0
  6. package/css/card.scss +65 -65
  7. package/css/chart.scss +270 -157
  8. package/css/chat-bubbles.scss +134 -68
  9. package/css/chips.scss +109 -19
  10. package/css/colors.scss +32 -32
  11. package/css/datepicker.scss +336 -336
  12. package/css/defaults.scss +90 -90
  13. package/css/docs.scss +529 -0
  14. package/css/editor.scss +36 -0
  15. package/css/file-uploader.scss +1 -1
  16. package/css/flyout-menu.scss +361 -361
  17. package/css/form.scss +0 -15
  18. package/css/gallery.scss +65 -6
  19. package/css/grid.scss +41 -40
  20. package/css/group-picker.scss +345 -0
  21. package/css/guitar-chords.css +250 -250
  22. package/css/icons.scss +330 -330
  23. package/css/parameters.scss +3 -3
  24. package/css/placeholder.scss +33 -33
  25. package/css/popover.scss +206 -0
  26. package/css/progress.scss +76 -32
  27. package/css/properties.scss +51 -36
  28. package/css/push-menu.scss +302 -174
  29. package/css/reset.scss +39 -39
  30. package/css/scrollbar.scss +62 -5
  31. package/css/sidebar-nav.scss +92 -0
  32. package/css/spinner.scss +65 -65
  33. package/css/stepper.scss +48 -12
  34. package/css/style.css +3155 -254
  35. package/css/style.css.map +1 -1
  36. package/css/style.min.css +1 -1
  37. package/css/style.scss +51 -45
  38. package/css/table.scss +199 -199
  39. package/css/tabs.scss +154 -123
  40. package/css/timeline.scss +83 -38
  41. package/css/timepicker.scss +100 -5
  42. package/css/toast.scss +81 -81
  43. package/css/virtual-dropdown.scss +35 -29
  44. package/js/calendar.js +532 -0
  45. package/js/calendar.ts +706 -0
  46. package/js/chart.js +573 -257
  47. package/js/chart.ts +692 -0
  48. package/js/code-viewer.js +10 -10
  49. package/js/code-viewer.ts +188 -188
  50. package/js/datepicker.ts +627 -627
  51. package/js/docs-nav.js +204 -0
  52. package/js/dropdown.ts +179 -179
  53. package/js/editor.js +50 -6
  54. package/js/editor.ts +483 -444
  55. package/js/file-uploader.js +1 -0
  56. package/js/file-uploader.ts +1 -0
  57. package/js/flyout-menu.js +14 -14
  58. package/js/flyout-menu.ts +249 -249
  59. package/js/form-builder.js +106 -106
  60. package/js/gallery.js +14 -8
  61. package/js/gallery.ts +245 -236
  62. package/js/group-picker.js +342 -0
  63. package/js/group-picker.ts +447 -0
  64. package/js/guitar-chords.js +268 -268
  65. package/js/lazy-loader.js +121 -121
  66. package/js/modal.ts +166 -166
  67. package/js/popover.js +163 -0
  68. package/js/popover.ts +219 -0
  69. package/js/position.js +108 -0
  70. package/js/position.ts +111 -0
  71. package/js/push-menu.js +113 -0
  72. package/js/push-menu.ts +284 -145
  73. package/js/request.js +50 -50
  74. package/js/scroll.ts +47 -47
  75. package/js/scrollbar.js +13 -0
  76. package/js/scrollbar.ts +324 -307
  77. package/js/select.ts +216 -216
  78. package/js/sidebar-nav.js +41 -0
  79. package/js/sidebar-nav.ts +66 -0
  80. package/js/table.ts +452 -452
  81. package/js/tabs.ts +279 -279
  82. package/js/theme.js +17 -6
  83. package/js/theme.ts +234 -224
  84. package/js/toast.ts +137 -137
  85. package/js/tooltip.js +6 -60
  86. package/js/tooltip.ts +184 -251
  87. package/js/tsconfig.json +18 -18
  88. package/js/utils.ts +83 -83
  89. package/js/virtual-dropdown.js +25 -25
  90. package/js/virtual-dropdown.ts +365 -365
  91. package/package.json +37 -39
  92. package/js/index.js +0 -816
  93. package/js/index.ts +0 -987
@@ -50,6 +50,7 @@ class FileUploader {
50
50
  throw new Error(`FileUploader: Element not found for selector "${elementOrSelector}"`);
51
51
  }
52
52
  this.container = container;
53
+ this.container.classList.add('file-uploader');
53
54
  const dropZone = container.querySelector('#drop-zone');
54
55
  const fileInput = container.querySelector('#file-input');
55
56
  const fileList = container.querySelector('#file-list');
@@ -42,6 +42,7 @@ class FileUploader {
42
42
  }
43
43
 
44
44
  this.container = container;
45
+ this.container.classList.add('file-uploader');
45
46
 
46
47
  const dropZone = container.querySelector<HTMLElement>('#drop-zone');
47
48
  const fileInput = container.querySelector<HTMLInputElement>('#file-input');
package/js/flyout-menu.js CHANGED
@@ -66,11 +66,11 @@ class FlyoutMenu {
66
66
  const button = document.createElement('button');
67
67
  button.className = 'submenu-toggle';
68
68
  button.style.setProperty('--delay', `${(index + 1) * 0.1}s`);
69
- button.innerHTML = `
70
- ${text}
71
- <svg class="chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
72
- <polyline points="6 9 12 15 18 9"></polyline>
73
- </svg>
69
+ button.innerHTML = `
70
+ ${text}
71
+ <svg class="chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
72
+ <polyline points="6 9 12 15 18 9"></polyline>
73
+ </svg>
74
74
  `;
75
75
  li.insertBefore(button, nestedUl);
76
76
  // Recursively process nested UL
@@ -90,15 +90,15 @@ class FlyoutMenu {
90
90
  return;
91
91
  const header = document.createElement('div');
92
92
  header.className = 'flyout-header';
93
- header.innerHTML = `
94
- <span class="flyout-title">${this.options.title}</span>
95
- <button class="close-menu" aria-label="Close Menu">
96
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
97
- stroke-linecap="round" stroke-linejoin="round">
98
- <line x1="18" y1="6" x2="6" y2="18"></line>
99
- <line x1="6" y1="6" x2="18" y2="18"></line>
100
- </svg>
101
- </button>
93
+ header.innerHTML = `
94
+ <span class="flyout-title">${this.options.title}</span>
95
+ <button class="close-menu" aria-label="Close Menu">
96
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
97
+ stroke-linecap="round" stroke-linejoin="round">
98
+ <line x1="18" y1="6" x2="6" y2="18"></line>
99
+ <line x1="6" y1="6" x2="18" y2="18"></line>
100
+ </svg>
101
+ </button>
102
102
  `;
103
103
  this.flyoutMenu.prepend(header);
104
104
  }
package/js/flyout-menu.ts CHANGED
@@ -1,250 +1,250 @@
1
- interface FlyoutMenuOptions {
2
- triggerSelector?: string;
3
- menuSelector?: string;
4
- overlaySelector?: string;
5
- closeSelector?: string;
6
- submenuToggleSelector?: string;
7
- linkSelector?: string;
8
- direction?: 'right' | 'left';
9
- title?: string;
10
- footerText?: string;
11
- enableHeader?: boolean;
12
- enableFooter?: boolean;
13
- }
14
-
15
- class FlyoutMenu {
16
- private options: Required<FlyoutMenuOptions>;
17
- private menuTrigger: HTMLElement | null;
18
- private readonly flyoutMenu: HTMLElement | null;
19
- private flyoutOverlay: HTMLElement | null;
20
- private closeBtn: HTMLElement | null = null;
21
- private submenuToggles: NodeListOf<HTMLElement> | null = null;
22
- private menuLinks: NodeListOf<HTMLAnchorElement> | null = null;
23
-
24
- constructor(options: FlyoutMenuOptions = {}) {
25
- this.options = {
26
- triggerSelector: '.menu-trigger',
27
- menuSelector: '#flyoutMenu',
28
- overlaySelector: '#flyoutOverlay',
29
- closeSelector: '.close-menu',
30
- submenuToggleSelector: '.submenu-toggle',
31
- linkSelector: '.flyout-links > li > a',
32
- direction: 'right',
33
- title: 'Menu',
34
- footerText: '&copy; 2025 Brand Inc.',
35
- enableHeader: true,
36
- enableFooter: true,
37
- ...options
38
- };
39
-
40
- this.menuTrigger = document.querySelector(this.options.triggerSelector);
41
- this.flyoutMenu = document.querySelector(this.options.menuSelector);
42
- this.flyoutOverlay = document.querySelector(this.options.overlaySelector);
43
-
44
- this.open = this.open.bind(this);
45
- this.close = this.close.bind(this);
46
- this.handleSubmenu = this.handleSubmenu.bind(this);
47
- this.handleKeydown = this.handleKeydown.bind(this);
48
-
49
- this.init();
50
- }
51
-
52
- private init(): void {
53
- if (!this.flyoutMenu) {
54
- throw new Error(`FlyoutMenu: Menu element not found for selector "${this.options.menuSelector}"`);
55
- }
56
-
57
- this.hydrateMenu();
58
-
59
- if (this.options.enableHeader) this.renderHeader();
60
- if (this.options.enableFooter) this.renderFooter();
61
-
62
- this.closeBtn = document.querySelector(this.options.closeSelector);
63
- this.submenuToggles = this.flyoutMenu.querySelectorAll(this.options.submenuToggleSelector);
64
- this.menuLinks = this.flyoutMenu.querySelectorAll('a');
65
-
66
- this.setDirection(this.options.direction);
67
-
68
- this.bindEvents();
69
- }
70
-
71
- private hydrateMenu(): void {
72
- if (!this.flyoutMenu) return;
73
-
74
- const rootUl = this.flyoutMenu.querySelector('ul');
75
- if (rootUl) {
76
- rootUl.classList.add('flyout-links');
77
- this.processListItems(rootUl);
78
- }
79
- }
80
-
81
- private processListItems(ul: HTMLUListElement): void {
82
- const items = Array.from(ul.children) as HTMLLIElement[];
83
- items.forEach((li, index) => {
84
- // Check if it has a nested UL
85
- const nestedUl = li.querySelector('ul') as HTMLUListElement | null;
86
- if (nestedUl) {
87
- li.classList.add('has-submenu');
88
- nestedUl.classList.add('submenu');
89
-
90
- // Get text content (excluding nested UL text)
91
- const textNode = Array.from(li.childNodes).find(
92
- node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() !== ''
93
- ) as Text | undefined;
94
- const text = textNode?.textContent?.trim() || 'Menu Item';
95
- textNode?.remove();
96
-
97
- // Create Toggle Button
98
- const button = document.createElement('button');
99
- button.className = 'submenu-toggle';
100
- button.style.setProperty('--delay', `${(index + 1) * 0.1}s`);
101
- button.innerHTML = `
102
- ${text}
103
- <svg class="chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
104
- <polyline points="6 9 12 15 18 9"></polyline>
105
- </svg>
106
- `;
107
-
108
- li.insertBefore(button, nestedUl);
109
-
110
- // Recursively process nested UL
111
- this.processListItems(nestedUl);
112
- } else {
113
- // Leaf node - ensure it has a link
114
- const link = li.querySelector('a');
115
- if (link) {
116
- link.style.setProperty('--delay', `${(index + 1) * 0.1}s`);
117
- }
118
- }
119
- });
120
- }
121
-
122
- private renderHeader(): void {
123
- if (!this.flyoutMenu) return;
124
-
125
- const header = document.createElement('div');
126
- header.className = 'flyout-header';
127
- header.innerHTML = `
128
- <span class="flyout-title">${this.options.title}</span>
129
- <button class="close-menu" aria-label="Close Menu">
130
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
131
- stroke-linecap="round" stroke-linejoin="round">
132
- <line x1="18" y1="6" x2="6" y2="18"></line>
133
- <line x1="6" y1="6" x2="18" y2="18"></line>
134
- </svg>
135
- </button>
136
- `;
137
- this.flyoutMenu.prepend(header);
138
- }
139
-
140
- private renderFooter(): void {
141
- if (!this.flyoutMenu) return;
142
-
143
- const footer = document.createElement('div');
144
- footer.className = 'flyout-footer';
145
- footer.style.setProperty('--delay', '0.6s');
146
- footer.innerHTML = `<p>${this.options.footerText}</p>`;
147
- this.flyoutMenu.append(footer);
148
- }
149
-
150
- private bindEvents(): void {
151
- // Open
152
- this.menuTrigger?.addEventListener('click', this.open);
153
-
154
- // Close
155
- this.closeBtn?.addEventListener('click', this.close);
156
- this.flyoutOverlay?.addEventListener('click', this.close);
157
-
158
- // Submenus
159
- this.submenuToggles?.forEach(toggle => {
160
- toggle.addEventListener('click', (e) => this.handleSubmenu(e, toggle));
161
- });
162
-
163
- // Close on Link Click
164
- this.menuLinks?.forEach(link => {
165
- link.addEventListener('click', this.close);
166
- });
167
-
168
- // Keyboard navigation
169
- document.addEventListener('keydown', this.handleKeydown);
170
- }
171
-
172
- private open(): void {
173
- this.flyoutMenu?.classList.add('is-open');
174
- this.flyoutOverlay?.classList.add('is-visible');
175
- document.body.style.overflow = 'hidden';
176
- this.menuTrigger?.setAttribute('aria-expanded', 'true');
177
- }
178
-
179
- private close(): void {
180
- this.flyoutMenu?.classList.remove('is-open');
181
- this.flyoutOverlay?.classList.remove('is-visible');
182
- document.body.style.overflow = '';
183
- this.menuTrigger?.setAttribute('aria-expanded', 'false');
184
- }
185
-
186
- private handleSubmenu(e: Event, toggle: HTMLElement): void {
187
- e.preventDefault();
188
- e.stopPropagation();
189
-
190
- const submenu = toggle.nextElementSibling as HTMLElement | null;
191
- const parentLi = toggle.parentElement as HTMLLIElement | null;
192
- const parentUl = parentLi?.parentElement as HTMLUListElement | null;
193
-
194
- if (!parentUl || !parentLi) return;
195
-
196
- // Close other submenus at the same level
197
- const siblings = Array.from(parentUl.children) as HTMLLIElement[];
198
- siblings.forEach(sibling => {
199
- if (sibling !== parentLi) {
200
- const siblingSubmenu = sibling.querySelector('.submenu');
201
- const siblingToggle = sibling.querySelector('.submenu-toggle');
202
- if (siblingSubmenu?.classList.contains('is-open')) {
203
- siblingSubmenu.classList.remove('is-open');
204
- siblingToggle?.classList.remove('active');
205
- }
206
- }
207
- });
208
-
209
- toggle.classList.toggle('active');
210
- submenu?.classList.toggle('is-open');
211
- }
212
-
213
- private handleKeydown(e: KeyboardEvent): void {
214
- if (e.key === 'Escape' && this.flyoutMenu?.classList.contains('is-open')) {
215
- this.close();
216
- }
217
- }
218
-
219
- public setDirection(direction: 'left' | 'right'): void {
220
- if (!this.flyoutMenu) return;
221
-
222
- const validDirections: Array<'left' | 'right'> = ['left', 'right'];
223
- if (!validDirections.includes(direction)) return;
224
-
225
- this.flyoutMenu.classList.remove('flyout-from-right', 'flyout-from-left');
226
- this.flyoutMenu.classList.add(`flyout-from-${direction}`);
227
-
228
- this.options.direction = direction;
229
- }
230
-
231
- public destroy(): void {
232
- this.menuTrigger?.removeEventListener('click', this.open);
233
- this.closeBtn?.removeEventListener('click', this.close);
234
- this.flyoutOverlay?.removeEventListener('click', this.close);
235
-
236
- this.submenuToggles?.forEach(toggle => {
237
- toggle.removeEventListener('click', (e) => this.handleSubmenu(e, toggle));
238
- });
239
-
240
- this.menuLinks?.forEach(link => {
241
- link.removeEventListener('click', this.close);
242
- });
243
-
244
- document.removeEventListener('keydown', this.handleKeydown);
245
-
246
- document.body.style.overflow = '';
247
- }
248
- }
249
-
1
+ interface FlyoutMenuOptions {
2
+ triggerSelector?: string;
3
+ menuSelector?: string;
4
+ overlaySelector?: string;
5
+ closeSelector?: string;
6
+ submenuToggleSelector?: string;
7
+ linkSelector?: string;
8
+ direction?: 'right' | 'left';
9
+ title?: string;
10
+ footerText?: string;
11
+ enableHeader?: boolean;
12
+ enableFooter?: boolean;
13
+ }
14
+
15
+ class FlyoutMenu {
16
+ private options: Required<FlyoutMenuOptions>;
17
+ private menuTrigger: HTMLElement | null;
18
+ private readonly flyoutMenu: HTMLElement | null;
19
+ private flyoutOverlay: HTMLElement | null;
20
+ private closeBtn: HTMLElement | null = null;
21
+ private submenuToggles: NodeListOf<HTMLElement> | null = null;
22
+ private menuLinks: NodeListOf<HTMLAnchorElement> | null = null;
23
+
24
+ constructor(options: FlyoutMenuOptions = {}) {
25
+ this.options = {
26
+ triggerSelector: '.menu-trigger',
27
+ menuSelector: '#flyoutMenu',
28
+ overlaySelector: '#flyoutOverlay',
29
+ closeSelector: '.close-menu',
30
+ submenuToggleSelector: '.submenu-toggle',
31
+ linkSelector: '.flyout-links > li > a',
32
+ direction: 'right',
33
+ title: 'Menu',
34
+ footerText: '&copy; 2025 Brand Inc.',
35
+ enableHeader: true,
36
+ enableFooter: true,
37
+ ...options
38
+ };
39
+
40
+ this.menuTrigger = document.querySelector(this.options.triggerSelector);
41
+ this.flyoutMenu = document.querySelector(this.options.menuSelector);
42
+ this.flyoutOverlay = document.querySelector(this.options.overlaySelector);
43
+
44
+ this.open = this.open.bind(this);
45
+ this.close = this.close.bind(this);
46
+ this.handleSubmenu = this.handleSubmenu.bind(this);
47
+ this.handleKeydown = this.handleKeydown.bind(this);
48
+
49
+ this.init();
50
+ }
51
+
52
+ private init(): void {
53
+ if (!this.flyoutMenu) {
54
+ throw new Error(`FlyoutMenu: Menu element not found for selector "${this.options.menuSelector}"`);
55
+ }
56
+
57
+ this.hydrateMenu();
58
+
59
+ if (this.options.enableHeader) this.renderHeader();
60
+ if (this.options.enableFooter) this.renderFooter();
61
+
62
+ this.closeBtn = document.querySelector(this.options.closeSelector);
63
+ this.submenuToggles = this.flyoutMenu.querySelectorAll(this.options.submenuToggleSelector);
64
+ this.menuLinks = this.flyoutMenu.querySelectorAll('a');
65
+
66
+ this.setDirection(this.options.direction);
67
+
68
+ this.bindEvents();
69
+ }
70
+
71
+ private hydrateMenu(): void {
72
+ if (!this.flyoutMenu) return;
73
+
74
+ const rootUl = this.flyoutMenu.querySelector('ul');
75
+ if (rootUl) {
76
+ rootUl.classList.add('flyout-links');
77
+ this.processListItems(rootUl);
78
+ }
79
+ }
80
+
81
+ private processListItems(ul: HTMLUListElement): void {
82
+ const items = Array.from(ul.children) as HTMLLIElement[];
83
+ items.forEach((li, index) => {
84
+ // Check if it has a nested UL
85
+ const nestedUl = li.querySelector('ul') as HTMLUListElement | null;
86
+ if (nestedUl) {
87
+ li.classList.add('has-submenu');
88
+ nestedUl.classList.add('submenu');
89
+
90
+ // Get text content (excluding nested UL text)
91
+ const textNode = Array.from(li.childNodes).find(
92
+ node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() !== ''
93
+ ) as Text | undefined;
94
+ const text = textNode?.textContent?.trim() || 'Menu Item';
95
+ textNode?.remove();
96
+
97
+ // Create Toggle Button
98
+ const button = document.createElement('button');
99
+ button.className = 'submenu-toggle';
100
+ button.style.setProperty('--delay', `${(index + 1) * 0.1}s`);
101
+ button.innerHTML = `
102
+ ${text}
103
+ <svg class="chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
104
+ <polyline points="6 9 12 15 18 9"></polyline>
105
+ </svg>
106
+ `;
107
+
108
+ li.insertBefore(button, nestedUl);
109
+
110
+ // Recursively process nested UL
111
+ this.processListItems(nestedUl);
112
+ } else {
113
+ // Leaf node - ensure it has a link
114
+ const link = li.querySelector('a');
115
+ if (link) {
116
+ link.style.setProperty('--delay', `${(index + 1) * 0.1}s`);
117
+ }
118
+ }
119
+ });
120
+ }
121
+
122
+ private renderHeader(): void {
123
+ if (!this.flyoutMenu) return;
124
+
125
+ const header = document.createElement('div');
126
+ header.className = 'flyout-header';
127
+ header.innerHTML = `
128
+ <span class="flyout-title">${this.options.title}</span>
129
+ <button class="close-menu" aria-label="Close Menu">
130
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
131
+ stroke-linecap="round" stroke-linejoin="round">
132
+ <line x1="18" y1="6" x2="6" y2="18"></line>
133
+ <line x1="6" y1="6" x2="18" y2="18"></line>
134
+ </svg>
135
+ </button>
136
+ `;
137
+ this.flyoutMenu.prepend(header);
138
+ }
139
+
140
+ private renderFooter(): void {
141
+ if (!this.flyoutMenu) return;
142
+
143
+ const footer = document.createElement('div');
144
+ footer.className = 'flyout-footer';
145
+ footer.style.setProperty('--delay', '0.6s');
146
+ footer.innerHTML = `<p>${this.options.footerText}</p>`;
147
+ this.flyoutMenu.append(footer);
148
+ }
149
+
150
+ private bindEvents(): void {
151
+ // Open
152
+ this.menuTrigger?.addEventListener('click', this.open);
153
+
154
+ // Close
155
+ this.closeBtn?.addEventListener('click', this.close);
156
+ this.flyoutOverlay?.addEventListener('click', this.close);
157
+
158
+ // Submenus
159
+ this.submenuToggles?.forEach(toggle => {
160
+ toggle.addEventListener('click', (e) => this.handleSubmenu(e, toggle));
161
+ });
162
+
163
+ // Close on Link Click
164
+ this.menuLinks?.forEach(link => {
165
+ link.addEventListener('click', this.close);
166
+ });
167
+
168
+ // Keyboard navigation
169
+ document.addEventListener('keydown', this.handleKeydown);
170
+ }
171
+
172
+ private open(): void {
173
+ this.flyoutMenu?.classList.add('is-open');
174
+ this.flyoutOverlay?.classList.add('is-visible');
175
+ document.body.style.overflow = 'hidden';
176
+ this.menuTrigger?.setAttribute('aria-expanded', 'true');
177
+ }
178
+
179
+ private close(): void {
180
+ this.flyoutMenu?.classList.remove('is-open');
181
+ this.flyoutOverlay?.classList.remove('is-visible');
182
+ document.body.style.overflow = '';
183
+ this.menuTrigger?.setAttribute('aria-expanded', 'false');
184
+ }
185
+
186
+ private handleSubmenu(e: Event, toggle: HTMLElement): void {
187
+ e.preventDefault();
188
+ e.stopPropagation();
189
+
190
+ const submenu = toggle.nextElementSibling as HTMLElement | null;
191
+ const parentLi = toggle.parentElement as HTMLLIElement | null;
192
+ const parentUl = parentLi?.parentElement as HTMLUListElement | null;
193
+
194
+ if (!parentUl || !parentLi) return;
195
+
196
+ // Close other submenus at the same level
197
+ const siblings = Array.from(parentUl.children) as HTMLLIElement[];
198
+ siblings.forEach(sibling => {
199
+ if (sibling !== parentLi) {
200
+ const siblingSubmenu = sibling.querySelector('.submenu');
201
+ const siblingToggle = sibling.querySelector('.submenu-toggle');
202
+ if (siblingSubmenu?.classList.contains('is-open')) {
203
+ siblingSubmenu.classList.remove('is-open');
204
+ siblingToggle?.classList.remove('active');
205
+ }
206
+ }
207
+ });
208
+
209
+ toggle.classList.toggle('active');
210
+ submenu?.classList.toggle('is-open');
211
+ }
212
+
213
+ private handleKeydown(e: KeyboardEvent): void {
214
+ if (e.key === 'Escape' && this.flyoutMenu?.classList.contains('is-open')) {
215
+ this.close();
216
+ }
217
+ }
218
+
219
+ public setDirection(direction: 'left' | 'right'): void {
220
+ if (!this.flyoutMenu) return;
221
+
222
+ const validDirections: Array<'left' | 'right'> = ['left', 'right'];
223
+ if (!validDirections.includes(direction)) return;
224
+
225
+ this.flyoutMenu.classList.remove('flyout-from-right', 'flyout-from-left');
226
+ this.flyoutMenu.classList.add(`flyout-from-${direction}`);
227
+
228
+ this.options.direction = direction;
229
+ }
230
+
231
+ public destroy(): void {
232
+ this.menuTrigger?.removeEventListener('click', this.open);
233
+ this.closeBtn?.removeEventListener('click', this.close);
234
+ this.flyoutOverlay?.removeEventListener('click', this.close);
235
+
236
+ this.submenuToggles?.forEach(toggle => {
237
+ toggle.removeEventListener('click', (e) => this.handleSubmenu(e, toggle));
238
+ });
239
+
240
+ this.menuLinks?.forEach(link => {
241
+ link.removeEventListener('click', this.close);
242
+ });
243
+
244
+ document.removeEventListener('keydown', this.handleKeydown);
245
+
246
+ document.body.style.overflow = '';
247
+ }
248
+ }
249
+
250
250
  export { FlyoutMenu, type FlyoutMenuOptions };