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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/components/menu/api.ts +143 -268
  3. package/src/components/menu/config.ts +84 -40
  4. package/src/components/menu/features/anchor.ts +159 -0
  5. package/src/components/menu/features/controller.ts +970 -0
  6. package/src/components/menu/features/index.ts +4 -0
  7. package/src/components/menu/index.ts +31 -63
  8. package/src/components/menu/menu.ts +107 -97
  9. package/src/components/menu/types.ts +263 -447
  10. package/src/components/segmented-button/config.ts +59 -20
  11. package/src/components/segmented-button/index.ts +1 -1
  12. package/src/components/segmented-button/segment.ts +51 -97
  13. package/src/components/segmented-button/segmented-button.ts +114 -2
  14. package/src/components/segmented-button/types.ts +52 -0
  15. package/src/core/compose/features/icon.ts +15 -13
  16. package/src/core/dom/classes.ts +81 -9
  17. package/src/core/dom/create.ts +30 -19
  18. package/src/core/layout/README.md +531 -166
  19. package/src/core/layout/array.ts +3 -4
  20. package/src/core/layout/config.ts +193 -0
  21. package/src/core/layout/create.ts +1 -2
  22. package/src/core/layout/index.ts +12 -2
  23. package/src/core/layout/object.ts +2 -3
  24. package/src/core/layout/processor.ts +60 -12
  25. package/src/core/layout/result.ts +1 -2
  26. package/src/core/layout/types.ts +105 -50
  27. package/src/core/layout/utils.ts +69 -61
  28. package/src/index.ts +2 -1
  29. package/src/styles/components/_button.scss +6 -0
  30. package/src/styles/components/_chip.scss +4 -5
  31. package/src/styles/components/_menu.scss +20 -8
  32. package/src/styles/components/_segmented-button.scss +173 -63
  33. package/src/styles/main.scss +23 -23
  34. package/src/styles/utilities/_layout.scss +665 -0
  35. package/src/components/menu/features/items-manager.ts +0 -457
  36. package/src/components/menu/features/keyboard-navigation.ts +0 -133
  37. package/src/components/menu/features/positioning.ts +0 -127
  38. package/src/components/menu/features/visibility.ts +0 -230
  39. package/src/components/menu/menu-item.ts +0 -86
  40. package/src/components/menu/utils.ts +0 -67
  41. /package/src/{core/build → styles/utilities}/_ripple.scss +0 -0
@@ -0,0 +1,4 @@
1
+ // src/components/menu/features/index.ts
2
+
3
+ export { default as withController } from './controller';
4
+ export { default as withAnchor } from './anchor';
@@ -1,76 +1,44 @@
1
1
  // src/components/menu/index.ts
2
2
 
3
3
  /**
4
- * @module Menu
4
+ * Menu component module
5
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
- * ```
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
- * Menu component types and interfaces
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
- * These types define the structure and behavior of the Menu component.
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
- * 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.
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
- * The returned component provides these methods:
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
- * `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
29
+ * @throws {Error} Throws an error if menu creation fails or if required
30
+ * configuration (like anchor) is missing.
36
31
  *
37
- * @param {MenuConfig} config - Menu configuration options
38
- * @returns {MenuComponent} Menu component instance
32
+ * @category Components
39
33
  *
40
34
  * @example
41
- * ```typescript
42
- * // Create a basic menu with items
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
- * { name: 'item1', text: 'Option 1' },
46
- * { name: 'item2', text: 'Option 2' },
40
+ * { id: 'item1', text: 'Option 1' },
41
+ * { id: 'item2', text: 'Option 2' },
47
42
  * { type: 'divider' },
48
- * { name: 'item3', text: 'Option 3' }
43
+ * { id: 'item3', text: 'Option 3' }
49
44
  * ]
50
45
  * });
51
46
  *
52
- * // Show the menu positioned relative to a button
53
- * const button = document.getElementById('menuButton');
54
- * menu.position(button).show();
47
+ * // Add the menu to the document
48
+ * document.body.appendChild(menu.element);
55
49
  *
56
- * // Listen for item selection
50
+ * // Add event listener for item selection
57
51
  * menu.on('select', (event) => {
58
- * console.log(`Selected: ${event.name}`);
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
- * { name: 'edit', text: 'Edit' },
60
+ * { id: 'edit', text: 'Edit', icon: '<svg>...</svg>' },
68
61
  * {
69
- * name: 'share',
62
+ * id: 'share',
70
63
  * text: 'Share',
71
- * items: [
72
- * { name: 'email', text: 'Email' },
73
- * { name: 'link', text: 'Copy Link' }
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
- * { name: 'delete', text: 'Delete' }
71
+ * { id: 'delete', text: 'Delete', icon: '<svg>...</svg>' }
78
72
  * ],
79
- * stayOpenOnSelect: true
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
- * // Add items dynamically
83
- * menu.addItem({ name: 'newItem', text: 'New Item' });
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
- * ```typescript
88
- * // Using menu with custom positioning
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
- * { name: 'cut', text: 'Cut' },
92
- * { name: 'copy', text: 'Copy' },
93
- * { name: 'paste', text: 'Paste' }
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
- * // 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();
128
+ * // Add event listener for item selection
129
+ * menu.on('select', (event) => {
130
+ * console.log('Selected item:', event.itemId);
106
131
  * });
107
132
  *
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
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 = {}): MenuComponent => {
121
- const baseConfig = createBaseConfig(config);
122
-
136
+ const createMenu = (config: MenuConfig): MenuComponent => {
123
137
  try {
124
- // Create menu component
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
- withLifecycle(),
130
- withItemsManager(baseConfig),
131
- withVisibility(baseConfig),
132
- withPositioning,
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
- // Handle circular dependency for submenus
138
- // This is needed because we need the complete menu factory function
139
- // to create submenus, but we can't import it directly in items-manager
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 instanceof Error ? error.message : String(error));
147
- throw new Error(`Failed to create menu: ${error instanceof Error ? error.message : String(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