mtrl 0.3.5 → 0.3.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.
- package/package.json +1 -1
- package/src/components/button/api.ts +16 -0
- package/src/components/button/types.ts +9 -0
- package/src/components/menu/api.ts +144 -267
- package/src/components/menu/config.ts +84 -40
- package/src/components/menu/features/anchor.ts +243 -0
- package/src/components/menu/features/controller.ts +1167 -0
- package/src/components/menu/features/index.ts +5 -0
- package/src/components/menu/features/position.ts +353 -0
- package/src/components/menu/index.ts +31 -63
- package/src/components/menu/menu.ts +72 -104
- package/src/components/menu/types.ts +264 -447
- package/src/components/select/api.ts +78 -0
- package/src/components/select/config.ts +76 -0
- package/src/components/select/features.ts +317 -0
- package/src/components/select/index.ts +38 -0
- package/src/components/select/select.ts +73 -0
- package/src/components/select/types.ts +355 -0
- package/src/components/textfield/api.ts +78 -6
- package/src/components/textfield/features/index.ts +17 -0
- package/src/components/textfield/features/leading-icon.ts +127 -0
- package/src/components/textfield/features/placement.ts +149 -0
- package/src/components/textfield/features/prefix-text.ts +107 -0
- package/src/components/textfield/features/suffix-text.ts +100 -0
- package/src/components/textfield/features/supporting-text.ts +113 -0
- package/src/components/textfield/features/trailing-icon.ts +108 -0
- package/src/components/textfield/textfield.ts +51 -15
- package/src/components/textfield/types.ts +70 -0
- package/src/core/collection/adapters/base.ts +62 -0
- package/src/core/collection/collection.ts +300 -0
- package/src/core/collection/index.ts +57 -0
- package/src/core/collection/list-manager.ts +333 -0
- package/src/core/dom/classes.ts +81 -9
- package/src/core/dom/create.ts +30 -19
- package/src/core/layout/README.md +531 -166
- package/src/core/layout/array.ts +3 -4
- package/src/core/layout/config.ts +193 -0
- package/src/core/layout/create.ts +1 -2
- package/src/core/layout/index.ts +12 -2
- package/src/core/layout/object.ts +2 -3
- package/src/core/layout/processor.ts +60 -12
- package/src/core/layout/result.ts +1 -2
- package/src/core/layout/types.ts +105 -50
- package/src/core/layout/utils.ts +69 -61
- package/src/index.ts +6 -2
- package/src/styles/abstract/_variables.scss +18 -0
- package/src/styles/components/_button.scss +21 -5
- package/src/styles/components/{_chip.scss → _chips.scss} +118 -4
- package/src/styles/components/_menu.scss +109 -18
- package/src/styles/components/_select.scss +265 -0
- package/src/styles/components/_textfield.scss +233 -42
- package/src/styles/main.scss +24 -23
- package/src/styles/utilities/_layout.scss +665 -0
- package/src/components/menu/features/items-manager.ts +0 -457
- package/src/components/menu/features/keyboard-navigation.ts +0 -133
- package/src/components/menu/features/positioning.ts +0 -127
- package/src/components/menu/features/visibility.ts +0 -230
- package/src/components/menu/menu-item.ts +0 -86
- package/src/components/menu/utils.ts +0 -67
- package/src/components/textfield/features.ts +0 -322
- package/src/core/collection/adapters/base.js +0 -26
- package/src/core/collection/collection.js +0 -259
- package/src/core/collection/list-manager.js +0 -157
- /package/src/core/collection/adapters/{route.js → route.ts} +0 -0
- /package/src/{core/build → styles/utilities}/_ripple.scss +0 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// src/components/menu/features/position.ts
|
|
2
|
+
|
|
3
|
+
import { MenuConfig } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Menu position helper
|
|
7
|
+
* Provides functions for positioning menus and submenus
|
|
8
|
+
*/
|
|
9
|
+
export const createPositioner = (component, config: MenuConfig) => {
|
|
10
|
+
/**
|
|
11
|
+
* Positions the menu relative to its anchor
|
|
12
|
+
* Ensures the menu maintains proper spacing from viewport edges
|
|
13
|
+
* Makes sure the menu stays attached to anchor during scrolling
|
|
14
|
+
*
|
|
15
|
+
* @param menuElement - The menu element to position
|
|
16
|
+
* @param anchorElement - The element to anchor against
|
|
17
|
+
* @param preferredPosition - The preferred position
|
|
18
|
+
* @param isSubmenu - Whether this is a submenu (affects positioning logic)
|
|
19
|
+
*/
|
|
20
|
+
const positionElement = (
|
|
21
|
+
menuElement: HTMLElement,
|
|
22
|
+
anchorElement: HTMLElement,
|
|
23
|
+
preferredPosition: string,
|
|
24
|
+
isSubmenu = false
|
|
25
|
+
): void => {
|
|
26
|
+
if (!menuElement || !anchorElement) return;
|
|
27
|
+
|
|
28
|
+
// Ensure menu is positioned absolutely for proper scroll behavior
|
|
29
|
+
menuElement.style.position = 'absolute';
|
|
30
|
+
|
|
31
|
+
// Get current scroll position - critical for absolute positioning that tracks anchor
|
|
32
|
+
const scrollX = window.pageXOffset || document.documentElement.scrollLeft;
|
|
33
|
+
const scrollY = window.pageYOffset || document.documentElement.scrollTop;
|
|
34
|
+
|
|
35
|
+
// Make a copy of the menu for measurement without affecting the real menu
|
|
36
|
+
const tempMenu = menuElement.cloneNode(true) as HTMLElement;
|
|
37
|
+
|
|
38
|
+
// Make the temp menu visible but not displayed for measurement
|
|
39
|
+
tempMenu.style.visibility = 'hidden';
|
|
40
|
+
tempMenu.style.display = 'block';
|
|
41
|
+
tempMenu.style.position = 'absolute';
|
|
42
|
+
tempMenu.style.top = '0';
|
|
43
|
+
tempMenu.style.left = '0';
|
|
44
|
+
tempMenu.style.transform = 'none';
|
|
45
|
+
tempMenu.style.opacity = '0';
|
|
46
|
+
tempMenu.style.pointerEvents = 'none';
|
|
47
|
+
tempMenu.classList.add(`${component.getClass('menu--visible')}`); // Add visible class for proper dimensions
|
|
48
|
+
|
|
49
|
+
// Add it to the DOM temporarily
|
|
50
|
+
document.body.appendChild(tempMenu);
|
|
51
|
+
|
|
52
|
+
// Get measurements
|
|
53
|
+
const anchorRect = anchorElement.getBoundingClientRect();
|
|
54
|
+
const menuRect = tempMenu.getBoundingClientRect();
|
|
55
|
+
const viewportWidth = window.innerWidth;
|
|
56
|
+
const viewportHeight = window.innerHeight;
|
|
57
|
+
|
|
58
|
+
// Remove the temp element after measurements
|
|
59
|
+
document.body.removeChild(tempMenu);
|
|
60
|
+
|
|
61
|
+
// Get values needed for calculations
|
|
62
|
+
const offset = config.offset !== undefined ? config.offset : 8;
|
|
63
|
+
|
|
64
|
+
// Calculate position based on position
|
|
65
|
+
let top = 0;
|
|
66
|
+
let left = 0;
|
|
67
|
+
let calculatedPosition = preferredPosition;
|
|
68
|
+
|
|
69
|
+
// Different positioning logic for main menu vs submenu
|
|
70
|
+
if (isSubmenu) {
|
|
71
|
+
// Default position is to the right of parent
|
|
72
|
+
calculatedPosition = preferredPosition || 'right-start';
|
|
73
|
+
|
|
74
|
+
// Check if this would push the submenu out of the viewport
|
|
75
|
+
if (calculatedPosition.startsWith('right') && anchorRect.right + menuRect.width + offset > viewportWidth - 16) {
|
|
76
|
+
// Flip to the left side if it doesn't fit on the right
|
|
77
|
+
calculatedPosition = calculatedPosition.replace('right', 'left');
|
|
78
|
+
} else if (calculatedPosition.startsWith('left') && anchorRect.left - menuRect.width - offset < 16) {
|
|
79
|
+
// Flip to the right side if it doesn't fit on the left
|
|
80
|
+
calculatedPosition = calculatedPosition.replace('left', 'right');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check vertical positioning as well for submenus
|
|
84
|
+
// If submenu would extend beyond the bottom of the viewport, adjust positioning
|
|
85
|
+
if (anchorRect.top + menuRect.height > viewportHeight - 16) {
|
|
86
|
+
if (calculatedPosition === 'right-start') {
|
|
87
|
+
calculatedPosition = 'right-end';
|
|
88
|
+
} else if (calculatedPosition === 'left-start') {
|
|
89
|
+
calculatedPosition = 'left-end';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// For main menu, follow the standard position calculation
|
|
94
|
+
// First determine correct position based on original position
|
|
95
|
+
switch (preferredPosition) {
|
|
96
|
+
case 'top-start':
|
|
97
|
+
case 'top':
|
|
98
|
+
case 'top-end':
|
|
99
|
+
// Check if enough space above
|
|
100
|
+
if (anchorRect.top < menuRect.height + offset + 16) {
|
|
101
|
+
// Not enough space above, flip to bottom
|
|
102
|
+
calculatedPosition = preferredPosition.replace('top', 'bottom');
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case 'bottom-start':
|
|
107
|
+
case 'bottom':
|
|
108
|
+
case 'bottom-end':
|
|
109
|
+
// Check if enough space below
|
|
110
|
+
if (anchorRect.bottom + menuRect.height + offset + 16 > viewportHeight) {
|
|
111
|
+
// Not enough space below, check if more space above
|
|
112
|
+
if (anchorRect.top > (viewportHeight - anchorRect.bottom)) {
|
|
113
|
+
// More space above, flip to top
|
|
114
|
+
calculatedPosition = preferredPosition.replace('bottom', 'top');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
// Specifically handle right-start, right, left-start, and left positions
|
|
120
|
+
case 'right-start':
|
|
121
|
+
case 'right':
|
|
122
|
+
case 'left-start':
|
|
123
|
+
case 'left':
|
|
124
|
+
// Check if enough space below for these side positions
|
|
125
|
+
if (anchorRect.bottom + menuRect.height > viewportHeight - 16) {
|
|
126
|
+
// Not enough space below, shift the menu upward
|
|
127
|
+
if (preferredPosition === 'right-start') {
|
|
128
|
+
calculatedPosition = 'right-end';
|
|
129
|
+
} else if (preferredPosition === 'left-start') {
|
|
130
|
+
calculatedPosition = 'left-end';
|
|
131
|
+
} else if (preferredPosition === 'right') {
|
|
132
|
+
// For center aligned, shift up by half menu height plus some spacing
|
|
133
|
+
top = anchorRect.top - (menuRect.height - anchorRect.height) - offset;
|
|
134
|
+
} else if (preferredPosition === 'left') {
|
|
135
|
+
// For center aligned, shift up by half menu height plus some spacing
|
|
136
|
+
top = anchorRect.top - (menuRect.height - anchorRect.height) - offset;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Reset any existing position classes
|
|
144
|
+
const positionClasses = [
|
|
145
|
+
'position-top', 'position-bottom', 'position-right', 'position-left'
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
positionClasses.forEach(posClass => {
|
|
149
|
+
menuElement.classList.remove(`${component.getClass('menu')}--${posClass}`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Determine transform origin based on vertical position
|
|
153
|
+
// Start by checking the calculated position to determine transform origin
|
|
154
|
+
const menuAppearsAboveAnchor =
|
|
155
|
+
calculatedPosition.startsWith('top') ||
|
|
156
|
+
calculatedPosition === 'right-end' ||
|
|
157
|
+
calculatedPosition === 'left-end' ||
|
|
158
|
+
(calculatedPosition === 'right' && top < anchorRect.top) ||
|
|
159
|
+
(calculatedPosition === 'left' && top < anchorRect.top);
|
|
160
|
+
|
|
161
|
+
if (menuAppearsAboveAnchor) {
|
|
162
|
+
menuElement.classList.add(`${component.getClass('menu')}--position-top`);
|
|
163
|
+
} else if (calculatedPosition.startsWith('left')) {
|
|
164
|
+
menuElement.classList.add(`${component.getClass('menu')}--position-left`);
|
|
165
|
+
} else if (calculatedPosition.startsWith('right')) {
|
|
166
|
+
menuElement.classList.add(`${component.getClass('menu')}--position-right`);
|
|
167
|
+
} else {
|
|
168
|
+
menuElement.classList.add(`${component.getClass('menu')}--position-bottom`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Position calculation - important: getBoundingClientRect() returns values relative to viewport
|
|
172
|
+
// We need to add scroll position to get absolute position
|
|
173
|
+
switch (calculatedPosition) {
|
|
174
|
+
case 'top-start':
|
|
175
|
+
top = anchorRect.top + scrollY - menuRect.height - offset;
|
|
176
|
+
left = anchorRect.left + scrollX;
|
|
177
|
+
break;
|
|
178
|
+
case 'top':
|
|
179
|
+
top = anchorRect.top + scrollY - menuRect.height - offset;
|
|
180
|
+
left = anchorRect.left + scrollX + (anchorRect.width / 2) - (menuRect.width / 2);
|
|
181
|
+
break;
|
|
182
|
+
case 'top-end':
|
|
183
|
+
top = anchorRect.top + scrollY - menuRect.height - offset;
|
|
184
|
+
left = anchorRect.right + scrollX - menuRect.width;
|
|
185
|
+
break;
|
|
186
|
+
case 'right-start':
|
|
187
|
+
top = anchorRect.top + scrollY;
|
|
188
|
+
left = anchorRect.right + scrollX + offset;
|
|
189
|
+
break;
|
|
190
|
+
case 'right':
|
|
191
|
+
// Custom top position might be set above; only set if not already defined
|
|
192
|
+
if (top === 0) {
|
|
193
|
+
top = anchorRect.top + scrollY + (anchorRect.height / 2) - (menuRect.height / 2);
|
|
194
|
+
} else {
|
|
195
|
+
top += scrollY;
|
|
196
|
+
}
|
|
197
|
+
left = anchorRect.right + scrollX + offset;
|
|
198
|
+
break;
|
|
199
|
+
case 'right-end':
|
|
200
|
+
top = anchorRect.bottom + scrollY - menuRect.height;
|
|
201
|
+
left = anchorRect.right + scrollX + offset;
|
|
202
|
+
break;
|
|
203
|
+
case 'bottom-start':
|
|
204
|
+
top = anchorRect.bottom + scrollY + offset;
|
|
205
|
+
left = anchorRect.left + scrollX;
|
|
206
|
+
break;
|
|
207
|
+
case 'bottom':
|
|
208
|
+
top = anchorRect.bottom + scrollY + offset;
|
|
209
|
+
left = anchorRect.left + scrollX + (anchorRect.width / 2) - (menuRect.width / 2);
|
|
210
|
+
break;
|
|
211
|
+
case 'bottom-end':
|
|
212
|
+
top = anchorRect.bottom + scrollY + offset;
|
|
213
|
+
left = anchorRect.right + scrollX - menuRect.width;
|
|
214
|
+
break;
|
|
215
|
+
case 'left-start':
|
|
216
|
+
top = anchorRect.top + scrollY;
|
|
217
|
+
left = anchorRect.left + scrollX - menuRect.width - offset;
|
|
218
|
+
break;
|
|
219
|
+
case 'left':
|
|
220
|
+
// Custom top position might be set above; only set if not already defined
|
|
221
|
+
if (top === 0) {
|
|
222
|
+
top = anchorRect.top + scrollY + (anchorRect.height / 2) - (menuRect.height / 2);
|
|
223
|
+
} else {
|
|
224
|
+
top += scrollY;
|
|
225
|
+
}
|
|
226
|
+
left = anchorRect.left + scrollX - menuRect.width - offset;
|
|
227
|
+
break;
|
|
228
|
+
case 'left-end':
|
|
229
|
+
top = anchorRect.bottom + scrollY - menuRect.height;
|
|
230
|
+
left = anchorRect.left + scrollX - menuRect.width - offset;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Ensure the menu has proper spacing from viewport edges
|
|
235
|
+
|
|
236
|
+
// Top edge spacing - ensure the menu doesn't go above the viewport + padding
|
|
237
|
+
const minTopSpacing = 16; // Minimum distance from top of viewport
|
|
238
|
+
if (top - scrollY < minTopSpacing) {
|
|
239
|
+
top = minTopSpacing + scrollY;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Bottom edge spacing - ensure the menu doesn't go below the viewport - padding
|
|
243
|
+
const viewportBottomMargin = 16; // Minimum space from bottom of viewport
|
|
244
|
+
const bottomEdge = (top - scrollY) + menuRect.height;
|
|
245
|
+
|
|
246
|
+
if (bottomEdge > viewportHeight - viewportBottomMargin) {
|
|
247
|
+
// Option 1: We could adjust the top position
|
|
248
|
+
// top = scrollY + viewportHeight - viewportBottomMargin - menuRect.height;
|
|
249
|
+
|
|
250
|
+
// Option 2: Instead of moving the menu, adjust its height to fit (better UX)
|
|
251
|
+
const availableHeight = viewportHeight - (top - scrollY) - viewportBottomMargin;
|
|
252
|
+
|
|
253
|
+
// Set a minimum height to prevent tiny menus
|
|
254
|
+
const minMenuHeight = Math.min(menuRect.height, 100);
|
|
255
|
+
const newMaxHeight = Math.max(availableHeight, minMenuHeight);
|
|
256
|
+
|
|
257
|
+
// Update maxHeight to fit within viewport
|
|
258
|
+
menuElement.style.maxHeight = `${newMaxHeight}px`;
|
|
259
|
+
|
|
260
|
+
// If user has explicitly set a maxHeight, respect it if smaller
|
|
261
|
+
if (config.maxHeight) {
|
|
262
|
+
const configMaxHeight = parseInt(config.maxHeight, 10);
|
|
263
|
+
if (!isNaN(configMaxHeight) && configMaxHeight < parseInt(menuElement.style.maxHeight || '0', 10)) {
|
|
264
|
+
menuElement.style.maxHeight = config.maxHeight;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
// If there's plenty of space, use the config's maxHeight (if provided)
|
|
269
|
+
if (config.maxHeight) {
|
|
270
|
+
menuElement.style.maxHeight = config.maxHeight;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// For 'width: 100%' configuration, match the anchor width
|
|
275
|
+
if (config.width === '100%' && !isSubmenu) {
|
|
276
|
+
menuElement.style.width = `${anchorRect.width}px`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Apply final positions, ensuring menu stays within viewport
|
|
280
|
+
// The position is absolute, not fixed, so it must account for scroll
|
|
281
|
+
menuElement.style.top = `${Math.max(minTopSpacing + scrollY, top)}px`;
|
|
282
|
+
menuElement.style.left = `${Math.max(16 + scrollX, left)}px`;
|
|
283
|
+
|
|
284
|
+
// Make sure menu doesn't extend past right edge
|
|
285
|
+
if ((left - scrollX) + menuRect.width > viewportWidth - 16) {
|
|
286
|
+
// If we're going past the right edge, set right with fixed distance from edge
|
|
287
|
+
menuElement.style.left = 'auto';
|
|
288
|
+
menuElement.style.right = '16px';
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Positions the main menu relative to its anchor
|
|
294
|
+
*/
|
|
295
|
+
const positionMenu = (anchorElement: HTMLElement): void => {
|
|
296
|
+
if (!anchorElement || !component.element) return;
|
|
297
|
+
positionElement(component.element, anchorElement, config.position, false);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Positions a submenu relative to its parent menu item
|
|
302
|
+
* For deeply nested submenus, alternates side placement (right/left)
|
|
303
|
+
* @param submenuElement - The submenu element to position
|
|
304
|
+
* @param parentItemElement - The parent menu item element
|
|
305
|
+
* @param level - Nesting level for calculating position
|
|
306
|
+
*/
|
|
307
|
+
const positionSubmenu = (
|
|
308
|
+
submenuElement: HTMLElement,
|
|
309
|
+
parentItemElement: HTMLElement,
|
|
310
|
+
level: number = 1
|
|
311
|
+
): void => {
|
|
312
|
+
if (!submenuElement || !parentItemElement) return;
|
|
313
|
+
|
|
314
|
+
// Alternate between right and left positioning for deeper nesting levels
|
|
315
|
+
// This helps prevent menus from cascading off the screen
|
|
316
|
+
const prefPosition = level % 2 === 1 ? 'right-start' : 'left-start';
|
|
317
|
+
|
|
318
|
+
// Use higher z-index for deeper nested menus to ensure proper layering
|
|
319
|
+
submenuElement.style.zIndex = `${1000 + (level * 10)}`;
|
|
320
|
+
|
|
321
|
+
positionElement(submenuElement, parentItemElement, prefPosition, true);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
positionMenu,
|
|
326
|
+
positionSubmenu,
|
|
327
|
+
positionElement
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Adds positioning functionality to the menu component
|
|
333
|
+
*
|
|
334
|
+
* @param config - Menu configuration options
|
|
335
|
+
* @returns Component enhancer with positioning functionality
|
|
336
|
+
*/
|
|
337
|
+
export const withPosition = (config: MenuConfig) => component => {
|
|
338
|
+
// Do nothing if no element
|
|
339
|
+
if (!component.element) {
|
|
340
|
+
return component;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Create the positioner
|
|
344
|
+
const positioner = createPositioner(component, config);
|
|
345
|
+
|
|
346
|
+
// Return enhanced component
|
|
347
|
+
return {
|
|
348
|
+
...component,
|
|
349
|
+
position: positioner
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export default withPosition;
|
|
@@ -1,76 +1,44 @@
|
|
|
1
1
|
// src/components/menu/index.ts
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Menu component module
|
|
5
5
|
*
|
|
6
|
-
* Menu component
|
|
7
|
-
*
|
|
8
|
-
* interact with a button, action, or other control.
|
|
9
|
-
*
|
|
10
|
-
* The main export is the {@link default | createMenu} factory function that creates
|
|
11
|
-
* a {@link MenuComponent} instance with the provided configuration.
|
|
12
|
-
*
|
|
13
|
-
* Features:
|
|
14
|
-
* - Configurable positioning relative to other elements
|
|
15
|
-
* - Support for nested submenus
|
|
16
|
-
* - Keyboard navigation and accessibility
|
|
17
|
-
* - Item selection events
|
|
18
|
-
* - Automatic handling of outside clicks
|
|
19
|
-
* - Support for dividers and disabled items
|
|
20
|
-
* - Dynamic item management
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```typescript
|
|
24
|
-
* // Create a basic menu
|
|
25
|
-
* const menu = createMenu({
|
|
26
|
-
* items: [
|
|
27
|
-
* { name: 'edit', text: 'Edit' },
|
|
28
|
-
* { name: 'duplicate', text: 'Duplicate' },
|
|
29
|
-
* { type: 'divider' },
|
|
30
|
-
* { name: 'delete', text: 'Delete', class: 'danger-item' }
|
|
31
|
-
* ]
|
|
32
|
-
* });
|
|
33
|
-
*
|
|
34
|
-
* // Show the menu positioned relative to a button
|
|
35
|
-
* const button = document.getElementById('menuButton');
|
|
36
|
-
* button.addEventListener('click', () => {
|
|
37
|
-
* menu.position(button).show();
|
|
38
|
-
* });
|
|
39
|
-
*
|
|
40
|
-
* // Handle menu selection
|
|
41
|
-
* menu.on('select', (event) => {
|
|
42
|
-
* console.log(`Selected: ${event.name}`);
|
|
43
|
-
*
|
|
44
|
-
* if (event.name === 'delete') {
|
|
45
|
-
* // Confirm deletion
|
|
46
|
-
* if (confirm('Are you sure?')) {
|
|
47
|
-
* deleteItem();
|
|
48
|
-
* }
|
|
49
|
-
* }
|
|
50
|
-
* });
|
|
51
|
-
* ```
|
|
6
|
+
* The Menu component provides a Material Design 3 compliant dropdown menu
|
|
7
|
+
* system with support for nested menus, keyboard navigation, and accessibility.
|
|
52
8
|
*
|
|
9
|
+
* @module components/menu
|
|
53
10
|
* @category Components
|
|
54
11
|
*/
|
|
55
12
|
|
|
56
|
-
|
|
57
|
-
* Factory function to create a new Menu component.
|
|
58
|
-
* @see MenuComponent for the full API reference
|
|
59
|
-
*/
|
|
13
|
+
// Export main component factory
|
|
60
14
|
export { default } from './menu';
|
|
61
15
|
|
|
16
|
+
// Export types and interfaces
|
|
17
|
+
export type {
|
|
18
|
+
MenuConfig,
|
|
19
|
+
MenuComponent,
|
|
20
|
+
MenuItem,
|
|
21
|
+
MenuDivider,
|
|
22
|
+
MenuContent,
|
|
23
|
+
MenuEvent,
|
|
24
|
+
MenuSelectEvent,
|
|
25
|
+
MenuPosition
|
|
26
|
+
} from './types';
|
|
27
|
+
|
|
62
28
|
/**
|
|
63
|
-
*
|
|
29
|
+
* Constants for menu position values - use these instead of string literals
|
|
30
|
+
* for better code completion and type safety.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* import { createMenu, MENU_POSITION } from 'mtrl';
|
|
34
|
+
*
|
|
35
|
+
* // Create a menu positioned at the bottom-right of its anchor
|
|
36
|
+
* const menu = createMenu({
|
|
37
|
+
* anchor: '#dropdown-button',
|
|
38
|
+
* items: [...],
|
|
39
|
+
* position: MENU_POSITION.BOTTOM_END
|
|
40
|
+
* });
|
|
64
41
|
*
|
|
65
|
-
*
|
|
42
|
+
* @category Components
|
|
66
43
|
*/
|
|
67
|
-
export {
|
|
68
|
-
MenuConfig,
|
|
69
|
-
MenuComponent,
|
|
70
|
-
MenuItemConfig,
|
|
71
|
-
MenuPositionConfig,
|
|
72
|
-
MenuAlign,
|
|
73
|
-
MenuVerticalAlign,
|
|
74
|
-
MenuItemType,
|
|
75
|
-
MenuEvent
|
|
76
|
-
} from './types';
|
|
44
|
+
export { MENU_POSITION } from './types';
|