mtrl 0.3.3 → 0.3.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.
- package/package.json +1 -1
- package/src/components/menu/api.ts +143 -268
- package/src/components/menu/config.ts +84 -40
- package/src/components/menu/features/anchor.ts +159 -0
- package/src/components/menu/features/controller.ts +970 -0
- package/src/components/menu/features/index.ts +4 -0
- package/src/components/menu/index.ts +31 -63
- package/src/components/menu/menu.ts +107 -97
- package/src/components/menu/types.ts +263 -447
- package/src/components/segmented-button/config.ts +59 -20
- package/src/components/segmented-button/index.ts +1 -1
- package/src/components/segmented-button/segment.ts +51 -97
- package/src/components/segmented-button/segmented-button.ts +114 -2
- package/src/components/segmented-button/types.ts +52 -0
- package/src/core/compose/features/icon.ts +15 -13
- 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 +2 -1
- package/src/styles/components/_button.scss +6 -0
- package/src/styles/components/_chip.scss +4 -5
- package/src/styles/components/_menu.scss +20 -8
- package/src/styles/components/_segmented-button.scss +173 -63
- package/src/styles/main.scss +23 -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/{core/build → styles/utilities}/_ripple.scss +0 -0
package/package.json
CHANGED
|
@@ -1,316 +1,191 @@
|
|
|
1
1
|
// src/components/menu/api.ts
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { MenuComponent, MenuContent, MenuPlacement, MenuEvent, MenuSelectEvent } from './types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
* API configuration options for menu component
|
|
7
|
+
* @category Components
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
interface ApiOptions {
|
|
11
|
+
menu: {
|
|
12
|
+
open: (event?: Event) => any;
|
|
13
|
+
close: (event?: Event) => any;
|
|
14
|
+
toggle: (event?: Event) => any;
|
|
15
|
+
isOpen: () => boolean;
|
|
16
|
+
setItems: (items: MenuContent[]) => any;
|
|
17
|
+
getItems: () => MenuContent[];
|
|
18
|
+
setPlacement: (placement: MenuPlacement) => any;
|
|
19
|
+
getPlacement: () => MenuPlacement;
|
|
20
|
+
};
|
|
21
|
+
anchor: {
|
|
22
|
+
setAnchor: (anchor: HTMLElement | string) => any;
|
|
23
|
+
getAnchor: () => HTMLElement;
|
|
24
|
+
};
|
|
25
|
+
events?: {
|
|
26
|
+
on: <T extends string>(event: T, handler: (event: any) => void) => any;
|
|
27
|
+
off: <T extends string>(event: T, handler: (event: any) => void) => any;
|
|
28
|
+
};
|
|
29
|
+
lifecycle: {
|
|
30
|
+
destroy: () => void;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Component with required elements and methods for API enhancement
|
|
36
|
+
* @category Components
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
interface ComponentWithElements {
|
|
40
|
+
element: HTMLElement;
|
|
41
|
+
on?: <T extends string>(event: T, handler: (event: any) => void) => any;
|
|
42
|
+
off?: <T extends string>(event: T, handler: (event: any) => void) => any;
|
|
43
|
+
emit?: (event: string, data: any) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Enhances a menu component with API methods.
|
|
48
|
+
* This follows the higher-order function pattern to add public API methods
|
|
49
|
+
* to the component, making them available to the end user.
|
|
11
50
|
*
|
|
12
|
-
* @param {ApiOptions} options - API configuration
|
|
51
|
+
* @param {ApiOptions} options - API configuration options
|
|
13
52
|
* @returns {Function} Higher-order function that adds API methods to component
|
|
14
|
-
* @internal
|
|
15
53
|
* @category Components
|
|
54
|
+
* @internal This is an internal utility for the Menu component
|
|
16
55
|
*/
|
|
17
|
-
export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
18
|
-
(component:
|
|
56
|
+
export const withAPI = ({ menu, anchor, events, lifecycle }: ApiOptions) =>
|
|
57
|
+
(component: ComponentWithElements): MenuComponent => ({
|
|
19
58
|
...component as any,
|
|
20
59
|
element: component.element,
|
|
21
|
-
|
|
60
|
+
|
|
22
61
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* If called when the menu is already visible, it has no effect.
|
|
27
|
-
*
|
|
28
|
-
* @returns {MenuComponent} Component instance for method chaining
|
|
29
|
-
* @example
|
|
30
|
-
* ```typescript
|
|
31
|
-
* // Show a menu
|
|
32
|
-
* menu.show();
|
|
33
|
-
*
|
|
34
|
-
* // Position and show a menu in one chain
|
|
35
|
-
* menu.position(buttonElement).show();
|
|
36
|
-
* ```
|
|
62
|
+
* Opens the menu
|
|
63
|
+
* @param event - Optional event that triggered the open
|
|
64
|
+
* @returns Menu component for chaining
|
|
37
65
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
66
|
+
open(event?: Event) {
|
|
67
|
+
menu.open(event);
|
|
40
68
|
return this;
|
|
41
69
|
},
|
|
42
|
-
|
|
70
|
+
|
|
43
71
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* If called when the menu is already hidden, it has no effect.
|
|
48
|
-
*
|
|
49
|
-
* @returns {MenuComponent} Component instance for method chaining
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* // Hide a menu
|
|
53
|
-
* menu.hide();
|
|
54
|
-
*
|
|
55
|
-
* // Listen for clicks outside the menu to hide it
|
|
56
|
-
* document.addEventListener('click', (event) => {
|
|
57
|
-
* if (menu.isVisible() && !menu.element.contains(event.target)) {
|
|
58
|
-
* menu.hide();
|
|
59
|
-
* }
|
|
60
|
-
* });
|
|
61
|
-
* ```
|
|
72
|
+
* Closes the menu
|
|
73
|
+
* @param event - Optional event that triggered the close
|
|
74
|
+
* @returns Menu component for chaining
|
|
62
75
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
close(event?: Event) {
|
|
77
|
+
menu.close(event);
|
|
65
78
|
return this;
|
|
66
79
|
},
|
|
67
|
-
|
|
80
|
+
|
|
68
81
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* This allows precise control over menu placement in the UI.
|
|
73
|
-
*
|
|
74
|
-
* @param {HTMLElement} target - Target element to position against
|
|
75
|
-
* @param {MenuPositionConfig} options - Position configuration
|
|
76
|
-
* @returns {MenuComponent} Component instance for method chaining
|
|
77
|
-
* @example
|
|
78
|
-
* ```typescript
|
|
79
|
-
* // Position relative to a button with default alignment (left, bottom)
|
|
80
|
-
* menu.position(document.getElementById('menuButton'));
|
|
81
|
-
*
|
|
82
|
-
* // Position with custom alignment
|
|
83
|
-
* menu.position(buttonElement, {
|
|
84
|
-
* align: 'right', // Align to the right edge of the target
|
|
85
|
-
* vAlign: 'top', // Position above the target
|
|
86
|
-
* offsetX: 5, // Add 5px horizontal offset
|
|
87
|
-
* offsetY: 10 // Add 10px vertical offset
|
|
88
|
-
* });
|
|
89
|
-
* ```
|
|
82
|
+
* Toggles the menu's open state
|
|
83
|
+
* @param event - Optional event that triggered the toggle
|
|
84
|
+
* @returns Menu component for chaining
|
|
90
85
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
toggle(event?: Event) {
|
|
87
|
+
menu.toggle(event);
|
|
93
88
|
return this;
|
|
94
89
|
},
|
|
95
|
-
|
|
90
|
+
|
|
96
91
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* Dynamically adds a new item to the menu. This can be called at any time,
|
|
100
|
-
* even after the menu has been rendered and shown.
|
|
101
|
-
*
|
|
102
|
-
* @param {MenuItemConfig} config - Item configuration
|
|
103
|
-
* @returns {MenuComponent} Component instance for method chaining
|
|
104
|
-
* @example
|
|
105
|
-
* ```typescript
|
|
106
|
-
* // Add a standard item
|
|
107
|
-
* menu.addItem({ name: 'edit', text: 'Edit' });
|
|
108
|
-
*
|
|
109
|
-
* // Add a divider
|
|
110
|
-
* menu.addItem({ type: 'divider' });
|
|
111
|
-
*
|
|
112
|
-
* // Add a disabled item
|
|
113
|
-
* menu.addItem({ name: 'print', text: 'Print', disabled: true });
|
|
114
|
-
*
|
|
115
|
-
* // Add an item with a submenu
|
|
116
|
-
* menu.addItem({
|
|
117
|
-
* name: 'share',
|
|
118
|
-
* text: 'Share',
|
|
119
|
-
* items: [
|
|
120
|
-
* { name: 'email', text: 'Email' },
|
|
121
|
-
* { name: 'link', text: 'Copy Link' }
|
|
122
|
-
* ]
|
|
123
|
-
* });
|
|
124
|
-
* ```
|
|
92
|
+
* Checks if the menu is currently open
|
|
93
|
+
* @returns True if the menu is open
|
|
125
94
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return this;
|
|
95
|
+
isOpen() {
|
|
96
|
+
return menu.isOpen();
|
|
129
97
|
},
|
|
130
|
-
|
|
98
|
+
|
|
131
99
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* If the item doesn't exist, no action is taken.
|
|
136
|
-
*
|
|
137
|
-
* @param {string} name - Name identifier of the item to remove
|
|
138
|
-
* @returns {MenuComponent} Component instance for method chaining
|
|
139
|
-
* @example
|
|
140
|
-
* ```typescript
|
|
141
|
-
* // Remove an item by name
|
|
142
|
-
* menu.removeItem('delete');
|
|
143
|
-
*
|
|
144
|
-
* // Check if a condition is met, then remove an item
|
|
145
|
-
* if (!userHasEditPermission) {
|
|
146
|
-
* menu.removeItem('edit');
|
|
147
|
-
* }
|
|
148
|
-
* ```
|
|
100
|
+
* Updates the menu items
|
|
101
|
+
* @param items - New array of menu items and dividers
|
|
102
|
+
* @returns Menu component for chaining
|
|
149
103
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
104
|
+
setItems(items: MenuContent[]) {
|
|
105
|
+
menu.setItems(items);
|
|
152
106
|
return this;
|
|
153
107
|
},
|
|
154
|
-
|
|
108
|
+
|
|
155
109
|
/**
|
|
156
|
-
* Gets
|
|
157
|
-
*
|
|
158
|
-
* Returns a Map containing all the current menu items, indexed by their name.
|
|
159
|
-
* Each entry contains the item's DOM element and configuration.
|
|
160
|
-
*
|
|
161
|
-
* @returns {Map<string, MenuItemData>} Map of item names to their data
|
|
162
|
-
* @example
|
|
163
|
-
* ```typescript
|
|
164
|
-
* // Get all items
|
|
165
|
-
* const items = menu.getItems();
|
|
166
|
-
*
|
|
167
|
-
* // Check if a specific item exists
|
|
168
|
-
* if (items.has('delete')) {
|
|
169
|
-
* console.log('Delete item exists');
|
|
170
|
-
* }
|
|
171
|
-
*
|
|
172
|
-
* // Get the element for a specific item
|
|
173
|
-
* const editItem = items.get('edit');
|
|
174
|
-
* if (editItem) {
|
|
175
|
-
* console.log('Edit item element:', editItem.element);
|
|
176
|
-
* console.log('Edit item config:', editItem.config);
|
|
177
|
-
* }
|
|
178
|
-
* ```
|
|
110
|
+
* Gets the current menu items
|
|
111
|
+
* @returns Array of current menu items and dividers
|
|
179
112
|
*/
|
|
180
113
|
getItems() {
|
|
181
|
-
return
|
|
114
|
+
return menu.getItems();
|
|
182
115
|
},
|
|
183
|
-
|
|
116
|
+
|
|
184
117
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* false otherwise.
|
|
189
|
-
*
|
|
190
|
-
* @returns {boolean} Whether the menu is visible
|
|
191
|
-
* @example
|
|
192
|
-
* ```typescript
|
|
193
|
-
* // Check if menu is visible before performing an action
|
|
194
|
-
* if (menu.isVisible()) {
|
|
195
|
-
* menu.hide();
|
|
196
|
-
* } else {
|
|
197
|
-
* menu.position(button).show();
|
|
198
|
-
* }
|
|
199
|
-
*
|
|
200
|
-
* // Toggle menu visibility
|
|
201
|
-
* toggleButton.addEventListener('click', () => {
|
|
202
|
-
* if (menu.isVisible()) {
|
|
203
|
-
* menu.hide();
|
|
204
|
-
* } else {
|
|
205
|
-
* menu.position(toggleButton).show();
|
|
206
|
-
* }
|
|
207
|
-
* });
|
|
208
|
-
* ```
|
|
118
|
+
* Updates the menu's anchor element
|
|
119
|
+
* @param anchorElement - New anchor element or selector
|
|
120
|
+
* @returns Menu component for chaining
|
|
209
121
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
122
|
+
setAnchor(anchorElement: HTMLElement | string) {
|
|
123
|
+
anchor.setAnchor(anchorElement);
|
|
124
|
+
return this;
|
|
212
125
|
},
|
|
213
|
-
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Gets the current anchor element
|
|
129
|
+
* @returns Current anchor element
|
|
130
|
+
*/
|
|
131
|
+
getAnchor() {
|
|
132
|
+
return anchor.getAnchor();
|
|
133
|
+
},
|
|
134
|
+
|
|
214
135
|
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* The handler will be called whenever the specified event occurs.
|
|
219
|
-
*
|
|
220
|
-
* @param {string} event - Event name to listen for
|
|
221
|
-
* @param {Function} handler - Callback function to execute when the event occurs
|
|
222
|
-
* @returns {MenuComponent} Component instance for method chaining
|
|
223
|
-
* @example
|
|
224
|
-
* ```typescript
|
|
225
|
-
* // Listen for item selection
|
|
226
|
-
* menu.on('select', (event) => {
|
|
227
|
-
* console.log(`Selected item: ${event.name}`);
|
|
228
|
-
*
|
|
229
|
-
* if (event.name === 'delete') {
|
|
230
|
-
* confirmDelete();
|
|
231
|
-
* }
|
|
232
|
-
* });
|
|
233
|
-
*
|
|
234
|
-
* // Listen for menu opening
|
|
235
|
-
* menu.on('open', () => {
|
|
236
|
-
* console.log('Menu opened');
|
|
237
|
-
* analytics.trackMenuOpen();
|
|
238
|
-
* });
|
|
239
|
-
*
|
|
240
|
-
* // Listen for submenu opening
|
|
241
|
-
* menu.on('submenuOpen', (data) => {
|
|
242
|
-
* console.log('Submenu opened:', data);
|
|
243
|
-
* });
|
|
244
|
-
* ```
|
|
136
|
+
* Updates the menu's placement
|
|
137
|
+
* @param placement - New placement value
|
|
138
|
+
* @returns Menu component for chaining
|
|
245
139
|
*/
|
|
246
|
-
|
|
247
|
-
|
|
140
|
+
setPlacement(placement: MenuPlacement) {
|
|
141
|
+
menu.setPlacement(placement);
|
|
248
142
|
return this;
|
|
249
143
|
},
|
|
250
|
-
|
|
144
|
+
|
|
251
145
|
/**
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* Removes a previously registered event handler.
|
|
255
|
-
* If the handler is not found, no action is taken.
|
|
256
|
-
*
|
|
257
|
-
* @param {string} event - Event name to stop listening for
|
|
258
|
-
* @param {Function} handler - The handler function to remove
|
|
259
|
-
* @returns {MenuComponent} Component instance for method chaining
|
|
260
|
-
* @example
|
|
261
|
-
* ```typescript
|
|
262
|
-
* // Define a handler function
|
|
263
|
-
* const handleSelection = (event) => {
|
|
264
|
-
* console.log(`Selected: ${event.name}`);
|
|
265
|
-
* };
|
|
266
|
-
*
|
|
267
|
-
* // Register the handler
|
|
268
|
-
* menu.on('select', handleSelection);
|
|
269
|
-
*
|
|
270
|
-
* // Later, unregister the handler
|
|
271
|
-
* menu.off('select', handleSelection);
|
|
272
|
-
* ```
|
|
146
|
+
* Gets the current menu placement
|
|
147
|
+
* @returns Current placement
|
|
273
148
|
*/
|
|
274
|
-
|
|
275
|
-
|
|
149
|
+
getPlacement() {
|
|
150
|
+
return menu.getPlacement();
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Adds an event listener to the menu
|
|
155
|
+
* @param event - Event name ('open', 'close', 'select')
|
|
156
|
+
* @param handler - Event handler function
|
|
157
|
+
* @returns Menu component for chaining
|
|
158
|
+
*/
|
|
159
|
+
on(event, handler) {
|
|
160
|
+
if (events?.on) {
|
|
161
|
+
events.on(event, handler);
|
|
162
|
+
} else if (component.on) {
|
|
163
|
+
component.on(event, handler);
|
|
164
|
+
}
|
|
276
165
|
return this;
|
|
277
166
|
},
|
|
278
|
-
|
|
167
|
+
|
|
279
168
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* the menu instance should not be used again.
|
|
285
|
-
*
|
|
286
|
-
* @returns {MenuComponent} Component instance
|
|
287
|
-
* @example
|
|
288
|
-
* ```typescript
|
|
289
|
-
* // When no longer needed, destroy the menu
|
|
290
|
-
* menu.destroy();
|
|
291
|
-
*
|
|
292
|
-
* // When navigating away or changing views
|
|
293
|
-
* function changeView() {
|
|
294
|
-
* // Clean up existing components
|
|
295
|
-
* contextMenu.destroy();
|
|
296
|
-
*
|
|
297
|
-
* // Set up new view
|
|
298
|
-
* setupNewView();
|
|
299
|
-
* }
|
|
300
|
-
* ```
|
|
169
|
+
* Removes an event listener from the menu
|
|
170
|
+
* @param event - Event name
|
|
171
|
+
* @param handler - Event handler function
|
|
172
|
+
* @returns Menu component for chaining
|
|
301
173
|
*/
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
lifecycle.destroy?.();
|
|
308
|
-
|
|
309
|
-
// Final cleanup - forcibly remove from DOM if still attached
|
|
310
|
-
if (component.element && component.element.parentNode) {
|
|
311
|
-
component.element.remove();
|
|
174
|
+
off(event, handler) {
|
|
175
|
+
if (events?.off) {
|
|
176
|
+
events.off(event, handler);
|
|
177
|
+
} else if (component.off) {
|
|
178
|
+
component.off(event, handler);
|
|
312
179
|
}
|
|
313
|
-
|
|
314
180
|
return this;
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Destroys the menu component and cleans up resources
|
|
185
|
+
*/
|
|
186
|
+
destroy() {
|
|
187
|
+
lifecycle.destroy();
|
|
315
188
|
}
|
|
316
|
-
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export default withAPI;
|
|
@@ -1,80 +1,124 @@
|
|
|
1
1
|
// src/components/menu/config.ts
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import {
|
|
4
4
|
createComponentConfig,
|
|
5
5
|
createElementConfig,
|
|
6
6
|
BaseComponentConfig
|
|
7
7
|
} from '../../core/config/component-config';
|
|
8
|
-
import { MenuConfig,
|
|
8
|
+
import { MenuConfig, MENU_PLACEMENT } from './types';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Default configuration for the Menu component
|
|
12
|
+
* These values will be used when not explicitly specified by the user.
|
|
12
13
|
*
|
|
13
|
-
* Defines the standard behavior and initial state for menus.
|
|
14
|
-
* These defaults are merged with user-provided configuration options.
|
|
15
|
-
*
|
|
16
|
-
* @internal
|
|
17
14
|
* @category Components
|
|
18
15
|
*/
|
|
19
16
|
export const defaultConfig: MenuConfig = {
|
|
20
|
-
/** Empty initial items array */
|
|
21
17
|
items: [],
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
placement: MENU_PLACEMENT.BOTTOM_START,
|
|
19
|
+
closeOnSelect: true,
|
|
20
|
+
closeOnClickOutside: true,
|
|
21
|
+
closeOnEscape: true,
|
|
22
|
+
openSubmenuOnHover: true,
|
|
23
|
+
offset: 8,
|
|
24
|
+
autoFlip: true,
|
|
25
|
+
visible: false
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* Creates the base configuration for Menu component
|
|
29
|
-
*
|
|
30
|
-
* Merges user-provided configuration with default values
|
|
31
|
-
* to ensure all required properties are available.
|
|
29
|
+
* Creates the base configuration for Menu component by merging user-provided
|
|
30
|
+
* config with default values.
|
|
32
31
|
*
|
|
33
32
|
* @param {MenuConfig} config - User provided configuration
|
|
34
33
|
* @returns {MenuConfig} Complete configuration with defaults applied
|
|
35
|
-
* @internal
|
|
36
34
|
* @category Components
|
|
35
|
+
* @internal
|
|
37
36
|
*/
|
|
38
|
-
export const createBaseConfig = (config: MenuConfig
|
|
39
|
-
|
|
37
|
+
export const createBaseConfig = (config: MenuConfig): MenuConfig => {
|
|
38
|
+
// First, ensure we have an anchor element
|
|
39
|
+
if (!config.anchor) {
|
|
40
|
+
throw new Error('Menu component requires an anchor element or selector');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Apply default configuration
|
|
44
|
+
return createComponentConfig(defaultConfig, config, 'menu') as MenuConfig;
|
|
45
|
+
};
|
|
40
46
|
|
|
41
47
|
/**
|
|
42
|
-
* Generates element configuration for the Menu component
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* Sets up proper accessibility attributes like ARIA roles and states.
|
|
48
|
+
* Generates element configuration for the Menu component.
|
|
49
|
+
* This function creates the necessary attributes and configuration
|
|
50
|
+
* for the DOM element creation process.
|
|
46
51
|
*
|
|
47
52
|
* @param {MenuConfig} config - Menu configuration
|
|
48
53
|
* @returns {Object} Element configuration object for withElement
|
|
49
|
-
* @internal
|
|
50
54
|
* @category Components
|
|
55
|
+
* @internal
|
|
51
56
|
*/
|
|
52
|
-
export const getElementConfig = (config: MenuConfig) =>
|
|
53
|
-
|
|
57
|
+
export const getElementConfig = (config: MenuConfig) => {
|
|
58
|
+
// Custom styles based on configuration
|
|
59
|
+
const styles: Record<string, string> = {};
|
|
60
|
+
|
|
61
|
+
if (config.width) {
|
|
62
|
+
styles.width = config.width;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (config.maxHeight) {
|
|
66
|
+
styles.maxHeight = config.maxHeight;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Element attributes
|
|
70
|
+
const attrs: Record<string, any> = {
|
|
71
|
+
role: 'menu',
|
|
72
|
+
tabindex: '-1',
|
|
73
|
+
'aria-hidden': (!config.visible).toString(),
|
|
74
|
+
style: Object.entries(styles)
|
|
75
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
76
|
+
.join(';')
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return createElementConfig(config, {
|
|
54
80
|
tag: 'div',
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
attrs,
|
|
82
|
+
className: [
|
|
83
|
+
config.visible ? 'menu--visible' : null,
|
|
84
|
+
config.class
|
|
85
|
+
].filter(Boolean),
|
|
86
|
+
forwardEvents: {
|
|
87
|
+
keydown: true
|
|
88
|
+
}
|
|
62
89
|
});
|
|
90
|
+
};
|
|
63
91
|
|
|
64
92
|
/**
|
|
65
|
-
* Creates API configuration for the Menu component
|
|
66
|
-
*
|
|
67
|
-
* Builds the configuration needed for the withAPI feature,
|
|
68
|
-
* including lifecycle methods for proper resource cleanup.
|
|
93
|
+
* Creates API configuration for the Menu component.
|
|
94
|
+
* This connects the core component features to the public API.
|
|
69
95
|
*
|
|
70
|
-
* @param {
|
|
71
|
-
* @returns {
|
|
72
|
-
* @internal
|
|
96
|
+
* @param {Object} component - Component with menu features
|
|
97
|
+
* @returns {Object} API configuration object
|
|
73
98
|
* @category Components
|
|
99
|
+
* @internal
|
|
74
100
|
*/
|
|
75
|
-
export const getApiConfig = (
|
|
101
|
+
export const getApiConfig = (component) => ({
|
|
102
|
+
menu: {
|
|
103
|
+
open: () => component.menu?.open(),
|
|
104
|
+
close: () => component.menu?.close(),
|
|
105
|
+
toggle: () => component.menu?.toggle(),
|
|
106
|
+
isOpen: () => component.menu?.isOpen() || false,
|
|
107
|
+
setItems: (items) => component.menu?.setItems(items),
|
|
108
|
+
getItems: () => component.menu?.getItems() || [],
|
|
109
|
+
setPlacement: (placement) => component.menu?.setPlacement(placement),
|
|
110
|
+
getPlacement: () => component.menu?.getPlacement()
|
|
111
|
+
},
|
|
112
|
+
anchor: {
|
|
113
|
+
setAnchor: (anchor) => component.anchor?.setAnchor(anchor),
|
|
114
|
+
getAnchor: () => component.anchor?.getAnchor()
|
|
115
|
+
},
|
|
116
|
+
events: {
|
|
117
|
+
on: (event, handler) => component.on?.(event, handler),
|
|
118
|
+
off: (event, handler) => component.off?.(event, handler)
|
|
119
|
+
},
|
|
76
120
|
lifecycle: {
|
|
77
|
-
destroy:
|
|
121
|
+
destroy: () => component.lifecycle?.destroy()
|
|
78
122
|
}
|
|
79
123
|
});
|
|
80
124
|
|