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
package/package.json
CHANGED
|
@@ -123,6 +123,22 @@ export const withAPI = ({ disabled, lifecycle }: ApiOptions) =>
|
|
|
123
123
|
return component.icon.getIcon();
|
|
124
124
|
},
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Sets the active state of the button
|
|
128
|
+
* Used to visually indicate the button's active state, such as when it has a menu open
|
|
129
|
+
*
|
|
130
|
+
* @param active - Whether the button should appear active
|
|
131
|
+
* @returns Button component for method chaining
|
|
132
|
+
*/
|
|
133
|
+
setActive(active: boolean) {
|
|
134
|
+
if (active) {
|
|
135
|
+
component.element.classList.add(`${component.getClass('button')}--active`);
|
|
136
|
+
} else {
|
|
137
|
+
component.element.classList.remove(`${component.getClass('button')}--active`);
|
|
138
|
+
}
|
|
139
|
+
return this;
|
|
140
|
+
},
|
|
141
|
+
|
|
126
142
|
/**
|
|
127
143
|
* Sets the button's aria-label attribute for accessibility
|
|
128
144
|
* @param label - Accessible label text
|
|
@@ -249,6 +249,15 @@ export interface ButtonComponent {
|
|
|
249
249
|
*/
|
|
250
250
|
updateCircularStyle: () => void;
|
|
251
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Sets the active state of the button
|
|
254
|
+
* Used to visually indicate the button's active state, such as when it has a menu open
|
|
255
|
+
*
|
|
256
|
+
* @param active - Whether the button should appear active
|
|
257
|
+
* @returns The button component for chaining
|
|
258
|
+
*/
|
|
259
|
+
setActive: (active: boolean) => ButtonComponent;
|
|
260
|
+
|
|
252
261
|
/**
|
|
253
262
|
* Adds an event listener to the button
|
|
254
263
|
* @param event - Event name ('click', 'focus', etc.)
|
|
@@ -1,316 +1,193 @@
|
|
|
1
1
|
// src/components/menu/api.ts
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { MenuComponent, MenuContent, MenuPosition, 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
|
+
setPosition: (position: MenuPosition) => any;
|
|
19
|
+
getPosition: () => MenuPosition;
|
|
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,
|
|
60
|
+
|
|
21
61
|
|
|
22
62
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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
|
-
* ```
|
|
63
|
+
* Opens the menu
|
|
64
|
+
* @param event - Optional event that triggered the open
|
|
65
|
+
* @param interactionType - The type of interaction that triggered the open ('mouse' or 'keyboard')
|
|
66
|
+
* @returns Menu component for chaining
|
|
37
67
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
68
|
+
open(event?: Event, interactionType: 'mouse' | 'keyboard' = 'mouse') {
|
|
69
|
+
menu.open(event, interactionType);
|
|
40
70
|
return this;
|
|
41
71
|
},
|
|
42
|
-
|
|
72
|
+
|
|
43
73
|
/**
|
|
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
|
-
* ```
|
|
74
|
+
* Closes the menu
|
|
75
|
+
* @param event - Optional event that triggered the close
|
|
76
|
+
* @returns Menu component for chaining
|
|
62
77
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
close(event?: Event) {
|
|
79
|
+
menu.close(event);
|
|
65
80
|
return this;
|
|
66
81
|
},
|
|
67
|
-
|
|
82
|
+
|
|
68
83
|
/**
|
|
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
|
-
* ```
|
|
84
|
+
* Toggles the menu's open state
|
|
85
|
+
* @param event - Optional event that triggered the toggle
|
|
86
|
+
* @returns Menu component for chaining
|
|
90
87
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
toggle(event?: Event) {
|
|
89
|
+
menu.toggle(event);
|
|
93
90
|
return this;
|
|
94
91
|
},
|
|
95
|
-
|
|
92
|
+
|
|
96
93
|
/**
|
|
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
|
-
* ```
|
|
94
|
+
* Checks if the menu is currently open
|
|
95
|
+
* @returns True if the menu is open
|
|
125
96
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return this;
|
|
97
|
+
isOpen() {
|
|
98
|
+
return menu.isOpen();
|
|
129
99
|
},
|
|
130
|
-
|
|
100
|
+
|
|
131
101
|
/**
|
|
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
|
-
* ```
|
|
102
|
+
* Updates the menu items
|
|
103
|
+
* @param items - New array of menu items and dividers
|
|
104
|
+
* @returns Menu component for chaining
|
|
149
105
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
106
|
+
setItems(items: MenuContent[]) {
|
|
107
|
+
menu.setItems(items);
|
|
152
108
|
return this;
|
|
153
109
|
},
|
|
154
|
-
|
|
110
|
+
|
|
155
111
|
/**
|
|
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
|
-
* ```
|
|
112
|
+
* Gets the current menu items
|
|
113
|
+
* @returns Array of current menu items and dividers
|
|
179
114
|
*/
|
|
180
115
|
getItems() {
|
|
181
|
-
return
|
|
116
|
+
return menu.getItems();
|
|
182
117
|
},
|
|
183
|
-
|
|
118
|
+
|
|
184
119
|
/**
|
|
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
|
-
* ```
|
|
120
|
+
* Updates the menu's anchor element
|
|
121
|
+
* @param anchorElement - New anchor element or selector
|
|
122
|
+
* @returns Menu component for chaining
|
|
209
123
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
124
|
+
setAnchor(anchorElement: HTMLElement | string) {
|
|
125
|
+
anchor.setAnchor(anchorElement);
|
|
126
|
+
return this;
|
|
212
127
|
},
|
|
213
|
-
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Gets the current anchor element
|
|
131
|
+
* @returns Current anchor element
|
|
132
|
+
*/
|
|
133
|
+
getAnchor() {
|
|
134
|
+
return anchor.getAnchor();
|
|
135
|
+
},
|
|
136
|
+
|
|
214
137
|
/**
|
|
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
|
-
* ```
|
|
138
|
+
* Updates the menu's position
|
|
139
|
+
* @param position - New position value
|
|
140
|
+
* @returns Menu component for chaining
|
|
245
141
|
*/
|
|
246
|
-
|
|
247
|
-
|
|
142
|
+
setPosition(position: MenuPosition) {
|
|
143
|
+
menu.setPosition(position);
|
|
248
144
|
return this;
|
|
249
145
|
},
|
|
250
|
-
|
|
146
|
+
|
|
251
147
|
/**
|
|
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
|
-
* ```
|
|
148
|
+
* Gets the current menu position
|
|
149
|
+
* @returns Current position
|
|
273
150
|
*/
|
|
274
|
-
|
|
275
|
-
|
|
151
|
+
getPosition() {
|
|
152
|
+
return menu.getPosition();
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Adds an event listener to the menu
|
|
157
|
+
* @param event - Event name ('open', 'close', 'select')
|
|
158
|
+
* @param handler - Event handler function
|
|
159
|
+
* @returns Menu component for chaining
|
|
160
|
+
*/
|
|
161
|
+
on(event, handler) {
|
|
162
|
+
if (events?.on) {
|
|
163
|
+
events.on(event, handler);
|
|
164
|
+
} else if (component.on) {
|
|
165
|
+
component.on(event, handler);
|
|
166
|
+
}
|
|
276
167
|
return this;
|
|
277
168
|
},
|
|
278
|
-
|
|
169
|
+
|
|
279
170
|
/**
|
|
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
|
-
* ```
|
|
171
|
+
* Removes an event listener from the menu
|
|
172
|
+
* @param event - Event name
|
|
173
|
+
* @param handler - Event handler function
|
|
174
|
+
* @returns Menu component for chaining
|
|
301
175
|
*/
|
|
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();
|
|
176
|
+
off(event, handler) {
|
|
177
|
+
if (events?.off) {
|
|
178
|
+
events.off(event, handler);
|
|
179
|
+
} else if (component.off) {
|
|
180
|
+
component.off(event, handler);
|
|
312
181
|
}
|
|
313
|
-
|
|
314
182
|
return this;
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Destroys the menu component and cleans up resources
|
|
187
|
+
*/
|
|
188
|
+
destroy() {
|
|
189
|
+
lifecycle.destroy();
|
|
315
190
|
}
|
|
316
|
-
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
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_POSITION } 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
|
+
position: MENU_POSITION.BOTTOM_START,
|
|
19
|
+
closeOnSelect: true,
|
|
20
|
+
closeOnClickOutside: true,
|
|
21
|
+
closeOnEscape: true,
|
|
22
|
+
openSubmenuOnHover: true,
|
|
23
|
+
offset: 0,
|
|
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
|
+
setPosition: (position) => component.menu?.setPosition(position),
|
|
110
|
+
getPosition: () => component.menu?.getPosition()
|
|
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
|
|