mtrl 0.3.1 → 0.3.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.
- package/.env +15 -0
- package/CONTRIBUTING.md +8 -8
- package/DOCS.md +3 -3
- package/README.md +43 -20
- package/TESTING.md +128 -18
- package/dist/index.js +14865 -0
- package/git-user-stats.js +545 -0
- package/index.ts +9 -67
- package/package.json +8 -3
- package/src/components/badge/api.ts +15 -1
- package/src/components/badge/badge.ts +43 -4
- package/src/components/badge/config.ts +40 -8
- package/src/components/badge/index.ts +64 -3
- package/src/components/badge/types.ts +175 -33
- package/src/components/button/api.ts +63 -1
- package/src/components/button/button.ts +39 -3
- package/src/components/button/config.ts +21 -4
- package/src/components/button/index.ts +26 -1
- package/src/components/button/types.ts +7 -1
- package/src/components/card/api.ts +78 -9
- package/src/components/card/card.ts +58 -3
- package/src/components/card/config.ts +41 -11
- package/src/components/card/features.ts +39 -12
- package/src/components/card/index.ts +84 -19
- package/src/components/card/types.ts +218 -29
- package/src/components/carousel/carousel.ts +92 -28
- package/src/components/carousel/constants.ts +107 -21
- package/src/components/carousel/index.ts +31 -13
- package/src/components/checkbox/checkbox.ts +83 -16
- package/src/components/checkbox/index.ts +43 -1
- package/src/components/checkbox/types.ts +219 -32
- package/src/components/chips/api.ts +194 -0
- package/src/components/{chip → chips/chip}/api.ts +42 -2
- package/src/components/chips/chip/chip.ts +131 -0
- package/src/components/{chip → chips/chip}/config.ts +3 -3
- package/src/components/chips/chip/index.ts +3 -0
- package/src/components/chips/chips.md +481 -0
- package/src/components/chips/chips.ts +75 -0
- package/src/components/chips/config.ts +109 -0
- package/src/components/chips/constants.ts +61 -0
- package/src/components/chips/features/chip-items.ts +33 -0
- package/src/components/chips/features/container.ts +77 -0
- package/src/components/chips/features/controller.ts +448 -0
- package/src/components/chips/features/index.ts +5 -0
- package/src/components/chips/features/label.ts +108 -0
- package/src/components/chips/index.ts +11 -0
- package/src/components/chips/schema.ts +61 -0
- package/src/components/{chip → chips}/types.ts +203 -92
- package/src/components/dialog/dialog.ts +99 -16
- package/src/components/dialog/index.ts +97 -1
- package/src/components/dialog/types.ts +375 -69
- package/src/components/divider/config.ts +90 -6
- package/src/components/divider/divider.ts +32 -2
- package/src/components/divider/features.ts +26 -0
- package/src/components/divider/index.ts +30 -0
- package/src/components/divider/types.ts +86 -9
- package/src/components/extended-fab/api.ts +53 -1
- package/src/components/extended-fab/config.ts +29 -1
- package/src/components/extended-fab/extended-fab.ts +28 -0
- package/src/components/extended-fab/index.ts +36 -0
- package/src/components/extended-fab/types.ts +458 -13
- package/src/components/fab/api.ts +42 -2
- package/src/components/fab/config.ts +29 -1
- package/src/components/fab/fab.ts +16 -2
- package/src/components/fab/index.ts +35 -0
- package/src/components/fab/types.ts +374 -10
- package/src/components/list/api.ts +12 -2
- package/src/components/list/config.ts +21 -0
- package/src/components/list/features.ts +6 -0
- package/src/components/list/index.ts +56 -1
- package/src/components/list/list-item.ts +46 -2
- package/src/components/list/list.ts +73 -2
- package/src/components/list/types.ts +172 -0
- package/src/components/list/utils.ts +26 -2
- package/src/components/menu/api.ts +217 -20
- package/src/components/menu/config.ts +27 -0
- package/src/components/menu/features/visibility.ts +55 -6
- package/src/components/menu/index.ts +64 -0
- package/src/components/menu/menu-item.ts +46 -3
- package/src/components/menu/menu.ts +77 -1
- package/src/components/menu/types.ts +404 -39
- package/src/components/sheet/config.ts +1 -2
- package/src/components/sheet/features/gestures.ts +1 -1
- package/src/components/sheet/features/position.ts +1 -2
- package/src/components/sheet/features/state.ts +1 -1
- package/src/components/sheet/index.ts +10 -2
- package/src/components/sheet/sheet.ts +1 -2
- package/src/components/sheet/types.ts +29 -1
- package/src/components/slider/api.ts +1 -1
- package/src/components/slider/config.ts +1 -1
- package/src/components/slider/features/controller.ts +1 -1
- package/src/components/slider/features/handlers.ts +1 -1
- package/src/components/slider/features/states.ts +1 -1
- package/src/components/slider/index.ts +12 -5
- package/src/components/slider/schema.ts +1 -1
- package/src/components/slider/types.ts +31 -0
- package/src/components/tabs/tab-api.ts +1 -1
- package/src/components/tabs/types.ts +1 -1
- package/src/components/tooltip/api.ts +6 -2
- package/src/components/tooltip/config.ts +9 -28
- package/src/components/tooltip/index.ts +10 -1
- package/src/components/tooltip/types.ts +38 -3
- package/src/index.ts +129 -31
- package/src/styles/abstract/_mixins.scss +23 -9
- package/src/styles/abstract/_variables.scss +14 -4
- package/src/styles/components/_card.scss +1 -1
- package/src/styles/components/_chip.scss +323 -113
- package/src/styles/components/_tabs.scss +1 -1
- package/CLAUDE.md +0 -33
- package/src/components/checkbox/constants.ts +0 -37
- package/src/components/chip/chip-set.ts +0 -225
- package/src/components/chip/chip.ts +0 -118
- package/src/components/chip/constants.ts +0 -28
- package/src/components/chip/index.ts +0 -12
- package/src/components/list/constants.ts +0 -116
- package/src/components/sheet/constants.ts +0 -20
- package/src/components/slider/constants.ts +0 -32
- package/src/components/tooltip/constants.ts +0 -27
- package/test/components/badge.test.ts +0 -545
- package/test/components/bottom-app-bar.test.ts +0 -303
- package/test/components/button.test.ts +0 -233
- package/test/components/card.test.ts +0 -560
- package/test/components/carousel.test.ts +0 -951
- package/test/components/checkbox.test.ts +0 -462
- package/test/components/chip.test.ts +0 -692
- package/test/components/datepicker.test.ts +0 -1124
- package/test/components/dialog.test.ts +0 -990
- package/test/components/divider.test.ts +0 -412
- package/test/components/extended-fab.test.ts +0 -672
- package/test/components/fab.test.ts +0 -561
- package/test/components/list.test.ts +0 -365
- package/test/components/menu.test.ts +0 -718
- package/test/components/navigation.test.ts +0 -186
- package/test/components/progress.test.ts +0 -567
- package/test/components/radios.test.ts +0 -699
- package/test/components/search.test.ts +0 -1135
- package/test/components/segmented-button.test.ts +0 -732
- package/test/components/sheet.test.ts +0 -641
- package/test/components/slider.test.ts +0 -1220
- package/test/components/snackbar.test.ts +0 -461
- package/test/components/switch.test.ts +0 -452
- package/test/components/tabs.test.ts +0 -1369
- package/test/components/textfield.test.ts +0 -400
- package/test/components/timepicker.test.ts +0 -592
- package/test/components/tooltip.test.ts +0 -630
- package/test/components/top-app-bar.test.ts +0 -566
- package/test/core/dom.attributes.test.ts +0 -148
- package/test/core/dom.classes.test.ts +0 -152
- package/test/core/dom.events.test.ts +0 -243
- package/test/core/emitter.test.ts +0 -141
- package/test/core/ripple.test.ts +0 -99
- package/test/core/state.store.test.ts +0 -189
- package/test/core/utils.normalize.test.ts +0 -61
- package/test/core/utils.object.test.ts +0 -120
- package/test/setup.js +0 -371
- package/test/setup.ts +0 -451
- package/tsconfig.json +0 -22
- package/typedoc.json +0 -28
- package/typedoc.simple.json +0 -14
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
import { ApiOptions, BaseComponent, MenuComponent, MenuItemConfig, MenuPositionConfig } from './types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Enhances menu component with API methods
|
|
6
|
-
*
|
|
5
|
+
* Enhances a menu component with public API methods
|
|
6
|
+
*
|
|
7
|
+
* This is a higher-order function that wraps a base component and exposes
|
|
8
|
+
* a standardized public API following the MenuComponent interface. It ensures
|
|
9
|
+
* all methods return the component instance for method chaining and handles
|
|
10
|
+
* proper resource cleanup during component destruction.
|
|
11
|
+
*
|
|
12
|
+
* @param {ApiOptions} options - API configuration including lifecycle methods
|
|
7
13
|
* @returns {Function} Higher-order function that adds API methods to component
|
|
14
|
+
* @internal
|
|
15
|
+
* @category Components
|
|
8
16
|
*/
|
|
9
17
|
export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
10
18
|
(component: BaseComponent): MenuComponent => ({
|
|
@@ -13,7 +21,19 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
23
|
* Shows the menu
|
|
16
|
-
*
|
|
24
|
+
*
|
|
25
|
+
* Makes the menu visible in the DOM. This method triggers the 'open' event.
|
|
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
|
+
* ```
|
|
17
37
|
*/
|
|
18
38
|
show(): MenuComponent {
|
|
19
39
|
component.show?.();
|
|
@@ -22,7 +42,23 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
22
42
|
|
|
23
43
|
/**
|
|
24
44
|
* Hides the menu
|
|
25
|
-
*
|
|
45
|
+
*
|
|
46
|
+
* Makes the menu invisible in the DOM. This method triggers the 'close' event.
|
|
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
|
+
* ```
|
|
26
62
|
*/
|
|
27
63
|
hide(): MenuComponent {
|
|
28
64
|
component.hide?.();
|
|
@@ -30,10 +66,27 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
30
66
|
},
|
|
31
67
|
|
|
32
68
|
/**
|
|
33
|
-
* Positions the menu relative to a target
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
69
|
+
* Positions the menu relative to a target element
|
|
70
|
+
*
|
|
71
|
+
* Calculates and sets the position of the menu relative to the specified target element.
|
|
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
|
+
* ```
|
|
37
90
|
*/
|
|
38
91
|
position(target: HTMLElement, options?: MenuPositionConfig): MenuComponent {
|
|
39
92
|
component.position?.(target, options);
|
|
@@ -42,8 +95,33 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
42
95
|
|
|
43
96
|
/**
|
|
44
97
|
* Adds an item to the menu
|
|
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
|
+
*
|
|
45
102
|
* @param {MenuItemConfig} config - Item configuration
|
|
46
|
-
* @returns {MenuComponent} Component instance
|
|
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
|
+
* ```
|
|
47
125
|
*/
|
|
48
126
|
addItem(config: MenuItemConfig): MenuComponent {
|
|
49
127
|
component.addItem?.(config);
|
|
@@ -51,9 +129,23 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
51
129
|
},
|
|
52
130
|
|
|
53
131
|
/**
|
|
54
|
-
* Removes an item
|
|
55
|
-
*
|
|
56
|
-
*
|
|
132
|
+
* Removes an item from the menu
|
|
133
|
+
*
|
|
134
|
+
* Dynamically removes an item from the menu by its name identifier.
|
|
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
|
+
* ```
|
|
57
149
|
*/
|
|
58
150
|
removeItem(name: string): MenuComponent {
|
|
59
151
|
component.removeItem?.(name);
|
|
@@ -61,8 +153,29 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
61
153
|
},
|
|
62
154
|
|
|
63
155
|
/**
|
|
64
|
-
* Gets all registered items
|
|
65
|
-
*
|
|
156
|
+
* Gets all registered menu items
|
|
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
|
+
* ```
|
|
66
179
|
*/
|
|
67
180
|
getItems() {
|
|
68
181
|
return component.getItems?.() || new Map();
|
|
@@ -70,7 +183,29 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
70
183
|
|
|
71
184
|
/**
|
|
72
185
|
* Checks if the menu is currently visible
|
|
186
|
+
*
|
|
187
|
+
* Returns true if the menu is currently visible in the DOM,
|
|
188
|
+
* false otherwise.
|
|
189
|
+
*
|
|
73
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
|
+
* ```
|
|
74
209
|
*/
|
|
75
210
|
isVisible(): boolean {
|
|
76
211
|
return component.isVisible?.() || false;
|
|
@@ -78,9 +213,35 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
78
213
|
|
|
79
214
|
/**
|
|
80
215
|
* Registers an event handler
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
216
|
+
*
|
|
217
|
+
* Subscribes to menu events like 'select', 'open', 'close', etc.
|
|
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
|
+
* ```
|
|
84
245
|
*/
|
|
85
246
|
on(event: string, handler: Function): MenuComponent {
|
|
86
247
|
component.on?.(event, handler);
|
|
@@ -89,9 +250,26 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
89
250
|
|
|
90
251
|
/**
|
|
91
252
|
* Unregisters an event handler
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
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
|
+
* ```
|
|
95
273
|
*/
|
|
96
274
|
off(event: string, handler: Function): MenuComponent {
|
|
97
275
|
component.off?.(event, handler);
|
|
@@ -100,7 +278,26 @@ export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
|
100
278
|
|
|
101
279
|
/**
|
|
102
280
|
* Destroys the menu component and cleans up resources
|
|
281
|
+
*
|
|
282
|
+
* Performs complete cleanup by removing event listeners, DOM elements,
|
|
283
|
+
* and references to prevent memory leaks. After calling this method,
|
|
284
|
+
* the menu instance should not be used again.
|
|
285
|
+
*
|
|
103
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
|
+
* ```
|
|
104
301
|
*/
|
|
105
302
|
destroy(): MenuComponent {
|
|
106
303
|
// First close any open submenus
|
|
@@ -9,24 +9,45 @@ import { MenuConfig, BaseComponent, ApiOptions } from './types';
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Default configuration for the Menu component
|
|
12
|
+
*
|
|
13
|
+
* Defines the standard behavior and initial state for menus.
|
|
14
|
+
* These defaults are merged with user-provided configuration options.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
* @category Components
|
|
12
18
|
*/
|
|
13
19
|
export const defaultConfig: MenuConfig = {
|
|
20
|
+
/** Empty initial items array */
|
|
14
21
|
items: [],
|
|
22
|
+
|
|
23
|
+
/** By default, menus close when an item is selected */
|
|
15
24
|
stayOpenOnSelect: false
|
|
16
25
|
};
|
|
17
26
|
|
|
18
27
|
/**
|
|
19
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.
|
|
32
|
+
*
|
|
20
33
|
* @param {MenuConfig} config - User provided configuration
|
|
21
34
|
* @returns {MenuConfig} Complete configuration with defaults applied
|
|
35
|
+
* @internal
|
|
36
|
+
* @category Components
|
|
22
37
|
*/
|
|
23
38
|
export const createBaseConfig = (config: MenuConfig = {}): MenuConfig =>
|
|
24
39
|
createComponentConfig(defaultConfig, config, 'menu') as MenuConfig;
|
|
25
40
|
|
|
26
41
|
/**
|
|
27
42
|
* Generates element configuration for the Menu component
|
|
43
|
+
*
|
|
44
|
+
* Creates the configuration needed for the DOM element creation.
|
|
45
|
+
* Sets up proper accessibility attributes like ARIA roles and states.
|
|
46
|
+
*
|
|
28
47
|
* @param {MenuConfig} config - Menu configuration
|
|
29
48
|
* @returns {Object} Element configuration object for withElement
|
|
49
|
+
* @internal
|
|
50
|
+
* @category Components
|
|
30
51
|
*/
|
|
31
52
|
export const getElementConfig = (config: MenuConfig) =>
|
|
32
53
|
createElementConfig(config, {
|
|
@@ -42,8 +63,14 @@ export const getElementConfig = (config: MenuConfig) =>
|
|
|
42
63
|
|
|
43
64
|
/**
|
|
44
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.
|
|
69
|
+
*
|
|
45
70
|
* @param {BaseComponent} comp - Component with lifecycle feature
|
|
46
71
|
* @returns {ApiOptions} API configuration object
|
|
72
|
+
* @internal
|
|
73
|
+
* @category Components
|
|
47
74
|
*/
|
|
48
75
|
export const getApiConfig = (comp: BaseComponent): ApiOptions => ({
|
|
49
76
|
lifecycle: {
|
|
@@ -4,8 +4,24 @@ import { MENU_EVENT, MENU_CLASSES } from '../utils';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Adds visibility management functionality to a menu component
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* This feature adds the ability to show and hide the menu with smooth transitions,
|
|
9
|
+
* along with proper event handling for clicks outside the menu and keyboard shortcuts.
|
|
10
|
+
* It implements the following functionality:
|
|
11
|
+
*
|
|
12
|
+
* - Tracking visibility state
|
|
13
|
+
* - Showing the menu with animation
|
|
14
|
+
* - Hiding the menu with animation
|
|
15
|
+
* - Automatic handling of clicks outside the menu
|
|
16
|
+
* - Keyboard shortcut (Escape) for dismissing the menu
|
|
17
|
+
* - Proper ARIA attributes for accessibility
|
|
18
|
+
* - Event emission for component state changes
|
|
19
|
+
*
|
|
20
|
+
* @param {MenuConfig} config - Menu configuration options
|
|
21
|
+
* @returns {Function} Component enhancer function that adds visibility features
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
* @category Components
|
|
9
25
|
*/
|
|
10
26
|
export const withVisibility = (config: MenuConfig) => (component: BaseComponent): BaseComponent => {
|
|
11
27
|
let isVisible = false;
|
|
@@ -19,6 +35,13 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
19
35
|
|
|
20
36
|
/**
|
|
21
37
|
* Shows the menu
|
|
38
|
+
*
|
|
39
|
+
* Makes the menu visible with a smooth transition animation.
|
|
40
|
+
* Handles DOM insertion, event binding, and ARIA attribute updates.
|
|
41
|
+
* Emits an 'open' event when the menu becomes visible.
|
|
42
|
+
*
|
|
43
|
+
* @returns {BaseComponent} The component instance for method chaining
|
|
44
|
+
* @internal
|
|
22
45
|
*/
|
|
23
46
|
show() {
|
|
24
47
|
if (isVisible) return this;
|
|
@@ -67,6 +90,14 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
67
90
|
|
|
68
91
|
/**
|
|
69
92
|
* Hides the menu
|
|
93
|
+
*
|
|
94
|
+
* Makes the menu invisible with a smooth transition animation.
|
|
95
|
+
* Handles event cleanup, ARIA attribute updates, and DOM removal.
|
|
96
|
+
* Emits a 'close' event when the menu becomes hidden.
|
|
97
|
+
* Also ensures any open submenus are closed first.
|
|
98
|
+
*
|
|
99
|
+
* @returns {BaseComponent} The component instance for method chaining
|
|
100
|
+
* @internal
|
|
70
101
|
*/
|
|
71
102
|
hide() {
|
|
72
103
|
// Return early if already hidden
|
|
@@ -130,7 +161,12 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
130
161
|
|
|
131
162
|
/**
|
|
132
163
|
* Returns whether the menu is currently visible
|
|
133
|
-
*
|
|
164
|
+
*
|
|
165
|
+
* Provides the current visibility state of the menu component.
|
|
166
|
+
* This method is used internally and exposed via the public API.
|
|
167
|
+
*
|
|
168
|
+
* @returns {boolean} True if the menu is visible, false otherwise
|
|
169
|
+
* @internal
|
|
134
170
|
*/
|
|
135
171
|
isVisible() {
|
|
136
172
|
return isVisible;
|
|
@@ -139,7 +175,14 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
139
175
|
|
|
140
176
|
/**
|
|
141
177
|
* Handles clicks outside the menu
|
|
142
|
-
*
|
|
178
|
+
*
|
|
179
|
+
* Event handler for detecting and responding to mouse clicks outside the menu.
|
|
180
|
+
* Checks if the click occurred outside both the menu and its origin element,
|
|
181
|
+
* and if so, hides the menu. This provides the auto-dismiss behavior expected
|
|
182
|
+
* of temporary surfaces like menus.
|
|
183
|
+
*
|
|
184
|
+
* @param {MouseEvent} event - Mouse event containing target information
|
|
185
|
+
* @internal
|
|
143
186
|
*/
|
|
144
187
|
const handleOutsideClick = (event: MouseEvent) => {
|
|
145
188
|
if (!isVisible) return;
|
|
@@ -165,8 +208,14 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
165
208
|
};
|
|
166
209
|
|
|
167
210
|
/**
|
|
168
|
-
* Handles keyboard events
|
|
169
|
-
*
|
|
211
|
+
* Handles keyboard events for the menu
|
|
212
|
+
*
|
|
213
|
+
* Event handler for keyboard interactions with the menu.
|
|
214
|
+
* Currently implements the Escape key to dismiss the menu,
|
|
215
|
+
* following standard interaction patterns for temporary UI surfaces.
|
|
216
|
+
*
|
|
217
|
+
* @param {KeyboardEvent} event - Keyboard event containing key information
|
|
218
|
+
* @internal
|
|
170
219
|
*/
|
|
171
220
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
172
221
|
if (!isVisible) return;
|
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
// src/components/menu/index.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module Menu
|
|
5
|
+
*
|
|
6
|
+
* Menu component following Material Design 3 guidelines.
|
|
7
|
+
* Menus display a list of choices on a temporary surface, appearing when users
|
|
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
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @category Components
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Factory function to create a new Menu component.
|
|
58
|
+
* @see MenuComponent for the full API reference
|
|
59
|
+
*/
|
|
2
60
|
export { default } from './menu';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Menu component types and interfaces
|
|
64
|
+
*
|
|
65
|
+
* These types define the structure and behavior of the Menu component.
|
|
66
|
+
*/
|
|
3
67
|
export {
|
|
4
68
|
MenuConfig,
|
|
5
69
|
MenuComponent,
|
|
@@ -3,10 +3,53 @@ import { MenuItemConfig } from './types';
|
|
|
3
3
|
import { MENU_ITEM_TYPE, getMenuClass } from './utils';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Creates a menu item
|
|
6
|
+
* Creates a DOM element for a menu item
|
|
7
|
+
*
|
|
8
|
+
* Generates an HTMLElement (li) based on the provided configuration.
|
|
9
|
+
* Handles different types of menu items (standard, divider, submenu),
|
|
10
|
+
* applies proper CSS classes, and sets appropriate ARIA attributes
|
|
11
|
+
* for accessibility.
|
|
12
|
+
*
|
|
7
13
|
* @param {MenuItemConfig} itemConfig - Item configuration
|
|
8
|
-
* @param {string} prefix - CSS class prefix
|
|
9
|
-
* @returns {HTMLElement} Menu item element
|
|
14
|
+
* @param {string} prefix - CSS class prefix (default: 'mtrl')
|
|
15
|
+
* @returns {HTMLElement} Menu item DOM element
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // Create a standard menu item
|
|
20
|
+
* const itemElement = createMenuItem(
|
|
21
|
+
* { name: 'edit', text: 'Edit' },
|
|
22
|
+
* 'mtrl'
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* // Create a disabled menu item
|
|
26
|
+
* const disabledItem = createMenuItem(
|
|
27
|
+
* { name: 'print', text: 'Print', disabled: true },
|
|
28
|
+
* 'mtrl'
|
|
29
|
+
* );
|
|
30
|
+
*
|
|
31
|
+
* // Create a divider
|
|
32
|
+
* const divider = createMenuItem(
|
|
33
|
+
* { type: 'divider' },
|
|
34
|
+
* 'mtrl'
|
|
35
|
+
* );
|
|
36
|
+
*
|
|
37
|
+
* // Create an item with submenu indicator
|
|
38
|
+
* const submenuItem = createMenuItem(
|
|
39
|
+
* {
|
|
40
|
+
* name: 'share',
|
|
41
|
+
* text: 'Share',
|
|
42
|
+
* items: [
|
|
43
|
+
* { name: 'email', text: 'Email' },
|
|
44
|
+
* { name: 'link', text: 'Copy Link' }
|
|
45
|
+
* ]
|
|
46
|
+
* },
|
|
47
|
+
* 'mtrl'
|
|
48
|
+
* );
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
* @category Components
|
|
10
53
|
*/
|
|
11
54
|
export const createMenuItem = (itemConfig: MenuItemConfig, prefix: string): HTMLElement => {
|
|
12
55
|
const item = document.createElement('li');
|
|
@@ -16,7 +16,25 @@ import {
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Creates a new Menu component
|
|
19
|
-
*
|
|
19
|
+
*
|
|
20
|
+
* Creates a Material Design 3 menu that displays a list of choices on a temporary surface.
|
|
21
|
+
* The menu can be positioned relative to other elements, trigger events on selection,
|
|
22
|
+
* and support keyboard navigation.
|
|
23
|
+
*
|
|
24
|
+
* The returned component provides these methods:
|
|
25
|
+
*
|
|
26
|
+
* `show()` - Shows the menu
|
|
27
|
+
* `hide()` - Hides the menu
|
|
28
|
+
* `position(target, options)` - Positions the menu relative to a target element
|
|
29
|
+
* `addItem(config)` - Adds a menu item
|
|
30
|
+
* `removeItem(name)` - Removes a menu item
|
|
31
|
+
* `getItems()` - Gets all menu items
|
|
32
|
+
* `isVisible()` - Checks if the menu is visible
|
|
33
|
+
* `on(event, handler)` - Adds event listener
|
|
34
|
+
* `off(event, handler)` - Removes event listener
|
|
35
|
+
* `destroy()` - Destroys the menu component
|
|
36
|
+
*
|
|
37
|
+
* @param {MenuConfig} config - Menu configuration options
|
|
20
38
|
* @returns {MenuComponent} Menu component instance
|
|
21
39
|
*
|
|
22
40
|
* @example
|
|
@@ -40,6 +58,64 @@ import {
|
|
|
40
58
|
* console.log(`Selected: ${event.name}`);
|
|
41
59
|
* });
|
|
42
60
|
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // Create a menu with nested submenus
|
|
65
|
+
* const menu = createMenu({
|
|
66
|
+
* items: [
|
|
67
|
+
* { name: 'edit', text: 'Edit' },
|
|
68
|
+
* {
|
|
69
|
+
* name: 'share',
|
|
70
|
+
* text: 'Share',
|
|
71
|
+
* items: [
|
|
72
|
+
* { name: 'email', text: 'Email' },
|
|
73
|
+
* { name: 'link', text: 'Copy Link' }
|
|
74
|
+
* ]
|
|
75
|
+
* },
|
|
76
|
+
* { type: 'divider' },
|
|
77
|
+
* { name: 'delete', text: 'Delete' }
|
|
78
|
+
* ],
|
|
79
|
+
* stayOpenOnSelect: true
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* // Add items dynamically
|
|
83
|
+
* menu.addItem({ name: 'newItem', text: 'New Item' });
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* // Using menu with custom positioning
|
|
89
|
+
* const menu = createMenu({
|
|
90
|
+
* items: [
|
|
91
|
+
* { name: 'cut', text: 'Cut' },
|
|
92
|
+
* { name: 'copy', text: 'Copy' },
|
|
93
|
+
* { name: 'paste', text: 'Paste' }
|
|
94
|
+
* ]
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // Position with custom alignment
|
|
98
|
+
* contextButton.addEventListener('click', (event) => {
|
|
99
|
+
* menu.position(contextButton, {
|
|
100
|
+
* align: 'right',
|
|
101
|
+
* vAlign: 'top',
|
|
102
|
+
* offsetY: 8
|
|
103
|
+
* }).show();
|
|
104
|
+
*
|
|
105
|
+
* event.stopPropagation();
|
|
106
|
+
* });
|
|
107
|
+
*
|
|
108
|
+
* // Close menu when clicking outside
|
|
109
|
+
* document.addEventListener('click', () => {
|
|
110
|
+
* if (menu.isVisible()) {
|
|
111
|
+
* menu.hide();
|
|
112
|
+
* }
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
/**
|
|
117
|
+
* @type {Function}
|
|
118
|
+
* @name createMenu
|
|
43
119
|
*/
|
|
44
120
|
const createMenu = (config: MenuConfig = {}): MenuComponent => {
|
|
45
121
|
const baseConfig = createBaseConfig(config);
|