mtrl 0.3.5 → 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/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/_menu.scss +20 -8
- 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
|
@@ -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
|
+
MenuPlacement
|
|
26
|
+
} from './types';
|
|
27
|
+
|
|
62
28
|
/**
|
|
63
|
-
*
|
|
29
|
+
* Constants for menu placement values - use these instead of string literals
|
|
30
|
+
* for better code completion and type safety.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* import { createMenu, MENU_PLACEMENT } 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
|
+
* placement: MENU_PLACEMENT.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_PLACEMENT } from './types';
|
|
@@ -1,150 +1,160 @@
|
|
|
1
1
|
// src/components/menu/menu.ts
|
|
2
|
+
|
|
2
3
|
import { pipe } from '../../core/compose';
|
|
3
4
|
import { createBase, withElement } from '../../core/compose/component';
|
|
4
5
|
import { withEvents, withLifecycle } from '../../core/compose/features';
|
|
6
|
+
import { withController, withAnchor } from './features';
|
|
5
7
|
import { withAPI } from './api';
|
|
6
|
-
import { withVisibility } from './features/visibility';
|
|
7
|
-
import { withItemsManager } from './features/items-manager';
|
|
8
|
-
import { withPositioning } from './features/positioning';
|
|
9
|
-
import { withKeyboardNavigation } from './features/keyboard-navigation';
|
|
10
8
|
import { MenuConfig, MenuComponent } from './types';
|
|
11
|
-
import {
|
|
12
|
-
createBaseConfig,
|
|
13
|
-
getElementConfig,
|
|
14
|
-
getApiConfig
|
|
15
|
-
} from './config';
|
|
9
|
+
import { createBaseConfig, getElementConfig, getApiConfig } from './config';
|
|
16
10
|
|
|
17
11
|
/**
|
|
18
|
-
* Creates a new Menu component
|
|
12
|
+
* Creates a new Menu component with the specified configuration.
|
|
13
|
+
*
|
|
14
|
+
* The Menu component implements the Material Design 3 menu specifications,
|
|
15
|
+
* providing a flexible dropdown menu system with support for nested menus,
|
|
16
|
+
* keyboard navigation, and ARIA accessibility.
|
|
17
|
+
*
|
|
18
|
+
* Menus are built using a functional composition pattern, applying various
|
|
19
|
+
* features through the pipe function for a modular architecture.
|
|
19
20
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* @param {MenuConfig} config - Configuration options for the menu
|
|
22
|
+
* This must include an anchor element or selector, and an array of menu items.
|
|
23
|
+
* See {@link MenuConfig} for all available options.
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
+
* @returns {MenuComponent} A fully configured menu component instance with
|
|
26
|
+
* all requested features applied. The returned component has methods for
|
|
27
|
+
* menu manipulation, event handling, and lifecycle management.
|
|
25
28
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
29
|
+
* @throws {Error} Throws an error if menu creation fails or if required
|
|
30
|
+
* configuration (like anchor) is missing.
|
|
36
31
|
*
|
|
37
|
-
* @
|
|
38
|
-
* @returns {MenuComponent} Menu component instance
|
|
32
|
+
* @category Components
|
|
39
33
|
*
|
|
40
34
|
* @example
|
|
41
|
-
*
|
|
42
|
-
*
|
|
35
|
+
* // Create a simple menu anchored to a button
|
|
36
|
+
* const menuButton = document.getElementById('menu-button');
|
|
43
37
|
* const menu = createMenu({
|
|
38
|
+
* anchor: menuButton,
|
|
44
39
|
* items: [
|
|
45
|
-
* {
|
|
46
|
-
* {
|
|
40
|
+
* { id: 'item1', text: 'Option 1' },
|
|
41
|
+
* { id: 'item2', text: 'Option 2' },
|
|
47
42
|
* { type: 'divider' },
|
|
48
|
-
* {
|
|
43
|
+
* { id: 'item3', text: 'Option 3' }
|
|
49
44
|
* ]
|
|
50
45
|
* });
|
|
51
46
|
*
|
|
52
|
-
* //
|
|
53
|
-
*
|
|
54
|
-
* menu.position(button).show();
|
|
47
|
+
* // Add the menu to the document
|
|
48
|
+
* document.body.appendChild(menu.element);
|
|
55
49
|
*
|
|
56
|
-
* //
|
|
50
|
+
* // Add event listener for item selection
|
|
57
51
|
* menu.on('select', (event) => {
|
|
58
|
-
* console.log(
|
|
52
|
+
* console.log('Selected item:', event.itemId);
|
|
59
53
|
* });
|
|
60
|
-
* ```
|
|
61
54
|
*
|
|
62
55
|
* @example
|
|
63
|
-
* ```typescript
|
|
64
56
|
* // Create a menu with nested submenus
|
|
65
57
|
* const menu = createMenu({
|
|
58
|
+
* anchor: '#more-button',
|
|
66
59
|
* items: [
|
|
67
|
-
* {
|
|
60
|
+
* { id: 'edit', text: 'Edit', icon: '<svg>...</svg>' },
|
|
68
61
|
* {
|
|
69
|
-
*
|
|
62
|
+
* id: 'share',
|
|
70
63
|
* text: 'Share',
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* {
|
|
64
|
+
* hasSubmenu: true,
|
|
65
|
+
* submenu: [
|
|
66
|
+
* { id: 'email', text: 'Email' },
|
|
67
|
+
* { id: 'link', text: 'Copy link' }
|
|
74
68
|
* ]
|
|
75
69
|
* },
|
|
76
70
|
* { type: 'divider' },
|
|
77
|
-
* {
|
|
71
|
+
* { id: 'delete', text: 'Delete', icon: '<svg>...</svg>' }
|
|
78
72
|
* ],
|
|
79
|
-
*
|
|
73
|
+
* placement: 'bottom-end'
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Programmatically control menu visibility
|
|
78
|
+
* const userMenu = createMenu({
|
|
79
|
+
* anchor: userAvatar,
|
|
80
|
+
* items: userMenuItems
|
|
80
81
|
* });
|
|
81
82
|
*
|
|
82
|
-
* //
|
|
83
|
-
*
|
|
84
|
-
*
|
|
83
|
+
* // Open the menu programmatically
|
|
84
|
+
* userMenu.open();
|
|
85
|
+
*
|
|
86
|
+
* // Later, close the menu
|
|
87
|
+
* userMenu.close();
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* Creates a new Menu component with the specified configuration.
|
|
91
|
+
*
|
|
92
|
+
* The Menu component implements the Material Design 3 menu specifications,
|
|
93
|
+
* providing a flexible dropdown menu system with support for nested menus,
|
|
94
|
+
* keyboard navigation, and ARIA accessibility.
|
|
95
|
+
*
|
|
96
|
+
* Menus are built using a functional composition pattern, applying various
|
|
97
|
+
* features through the pipe function for a modular architecture.
|
|
98
|
+
*
|
|
99
|
+
* The menu element is not added to the DOM until it's opened, and it's removed
|
|
100
|
+
* from the DOM when closed, following best practices for dropdown menus.
|
|
101
|
+
*
|
|
102
|
+
* @param {MenuConfig} config - Configuration options for the menu
|
|
103
|
+
* This must include an anchor element or selector, and an array of menu items.
|
|
104
|
+
* See {@link MenuConfig} for all available options.
|
|
105
|
+
*
|
|
106
|
+
* @returns {MenuComponent} A fully configured menu component instance with
|
|
107
|
+
* all requested features applied. The returned component has methods for
|
|
108
|
+
* menu manipulation, event handling, and lifecycle management.
|
|
109
|
+
*
|
|
110
|
+
* @throws {Error} Throws an error if menu creation fails or if required
|
|
111
|
+
* configuration (like anchor) is missing.
|
|
112
|
+
*
|
|
113
|
+
* @category Components
|
|
85
114
|
*
|
|
86
115
|
* @example
|
|
87
|
-
*
|
|
88
|
-
*
|
|
116
|
+
* // Create a simple menu anchored to a button
|
|
117
|
+
* const menuButton = document.getElementById('menu-button');
|
|
89
118
|
* const menu = createMenu({
|
|
119
|
+
* anchor: menuButton,
|
|
90
120
|
* items: [
|
|
91
|
-
* {
|
|
92
|
-
* {
|
|
93
|
-
* {
|
|
121
|
+
* { id: 'item1', text: 'Option 1' },
|
|
122
|
+
* { id: 'item2', text: 'Option 2' },
|
|
123
|
+
* { type: 'divider' },
|
|
124
|
+
* { id: 'item3', text: 'Option 3' }
|
|
94
125
|
* ]
|
|
95
126
|
* });
|
|
96
127
|
*
|
|
97
|
-
* //
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* align: 'right',
|
|
101
|
-
* vAlign: 'top',
|
|
102
|
-
* offsetY: 8
|
|
103
|
-
* }).show();
|
|
104
|
-
*
|
|
105
|
-
* event.stopPropagation();
|
|
128
|
+
* // Add event listener for item selection
|
|
129
|
+
* menu.on('select', (event) => {
|
|
130
|
+
* console.log('Selected item:', event.itemId);
|
|
106
131
|
* });
|
|
107
132
|
*
|
|
108
|
-
* //
|
|
109
|
-
*
|
|
110
|
-
* if (menu.isVisible()) {
|
|
111
|
-
* menu.hide();
|
|
112
|
-
* }
|
|
113
|
-
* });
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
/**
|
|
117
|
-
* @type {Function}
|
|
118
|
-
* @name createMenu
|
|
133
|
+
* // Menu will be added to the DOM when opened and removed when closed
|
|
134
|
+
* menuButton.addEventListener('click', () => menu.toggle());
|
|
119
135
|
*/
|
|
120
|
-
const createMenu = (config: MenuConfig
|
|
121
|
-
const baseConfig = createBaseConfig(config);
|
|
122
|
-
|
|
136
|
+
const createMenu = (config: MenuConfig): MenuComponent => {
|
|
123
137
|
try {
|
|
124
|
-
//
|
|
138
|
+
// Validate and create the base configuration
|
|
139
|
+
const baseConfig = createBaseConfig(config);
|
|
140
|
+
|
|
141
|
+
// Create the component through functional composition
|
|
125
142
|
const menu = pipe(
|
|
126
|
-
createBase,
|
|
127
|
-
withEvents(),
|
|
128
|
-
withElement(getElementConfig(baseConfig)),
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
withKeyboardNavigation(baseConfig),
|
|
134
|
-
comp => withAPI(getApiConfig(comp))(comp)
|
|
143
|
+
createBase, // Base component
|
|
144
|
+
withEvents(), // Event handling
|
|
145
|
+
withElement(getElementConfig(baseConfig)), // DOM element
|
|
146
|
+
withController(baseConfig), // Menu controller
|
|
147
|
+
withAnchor(baseConfig), // Anchor management
|
|
148
|
+
withLifecycle(), // Lifecycle management
|
|
149
|
+
comp => withAPI(getApiConfig(comp))(comp) // Public API
|
|
135
150
|
)(baseConfig);
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (menu.setCreateSubmenuFunction) {
|
|
141
|
-
menu.setCreateSubmenuFunction(createMenu);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return menu as MenuComponent;
|
|
151
|
+
|
|
152
|
+
// The menu will be added to the DOM when opened and removed when closed
|
|
153
|
+
|
|
154
|
+
return menu;
|
|
145
155
|
} catch (error) {
|
|
146
|
-
console.error('Menu creation error:', error
|
|
147
|
-
throw new Error(`Failed to create menu: ${error
|
|
156
|
+
console.error('Menu creation error:', error);
|
|
157
|
+
throw new Error(`Failed to create menu: ${(error as Error).message}`);
|
|
148
158
|
}
|
|
149
159
|
};
|
|
150
160
|
|