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.
Files changed (65) hide show
  1. package/package.json +1 -1
  2. package/src/components/button/api.ts +16 -0
  3. package/src/components/button/types.ts +9 -0
  4. package/src/components/menu/api.ts +144 -267
  5. package/src/components/menu/config.ts +84 -40
  6. package/src/components/menu/features/anchor.ts +243 -0
  7. package/src/components/menu/features/controller.ts +1167 -0
  8. package/src/components/menu/features/index.ts +5 -0
  9. package/src/components/menu/features/position.ts +353 -0
  10. package/src/components/menu/index.ts +31 -63
  11. package/src/components/menu/menu.ts +72 -104
  12. package/src/components/menu/types.ts +264 -447
  13. package/src/components/select/api.ts +78 -0
  14. package/src/components/select/config.ts +76 -0
  15. package/src/components/select/features.ts +317 -0
  16. package/src/components/select/index.ts +38 -0
  17. package/src/components/select/select.ts +73 -0
  18. package/src/components/select/types.ts +355 -0
  19. package/src/components/textfield/api.ts +78 -6
  20. package/src/components/textfield/features/index.ts +17 -0
  21. package/src/components/textfield/features/leading-icon.ts +127 -0
  22. package/src/components/textfield/features/placement.ts +149 -0
  23. package/src/components/textfield/features/prefix-text.ts +107 -0
  24. package/src/components/textfield/features/suffix-text.ts +100 -0
  25. package/src/components/textfield/features/supporting-text.ts +113 -0
  26. package/src/components/textfield/features/trailing-icon.ts +108 -0
  27. package/src/components/textfield/textfield.ts +51 -15
  28. package/src/components/textfield/types.ts +70 -0
  29. package/src/core/collection/adapters/base.ts +62 -0
  30. package/src/core/collection/collection.ts +300 -0
  31. package/src/core/collection/index.ts +57 -0
  32. package/src/core/collection/list-manager.ts +333 -0
  33. package/src/core/dom/classes.ts +81 -9
  34. package/src/core/dom/create.ts +30 -19
  35. package/src/core/layout/README.md +531 -166
  36. package/src/core/layout/array.ts +3 -4
  37. package/src/core/layout/config.ts +193 -0
  38. package/src/core/layout/create.ts +1 -2
  39. package/src/core/layout/index.ts +12 -2
  40. package/src/core/layout/object.ts +2 -3
  41. package/src/core/layout/processor.ts +60 -12
  42. package/src/core/layout/result.ts +1 -2
  43. package/src/core/layout/types.ts +105 -50
  44. package/src/core/layout/utils.ts +69 -61
  45. package/src/index.ts +6 -2
  46. package/src/styles/abstract/_variables.scss +18 -0
  47. package/src/styles/components/_button.scss +21 -5
  48. package/src/styles/components/{_chip.scss → _chips.scss} +118 -4
  49. package/src/styles/components/_menu.scss +109 -18
  50. package/src/styles/components/_select.scss +265 -0
  51. package/src/styles/components/_textfield.scss +233 -42
  52. package/src/styles/main.scss +24 -23
  53. package/src/styles/utilities/_layout.scss +665 -0
  54. package/src/components/menu/features/items-manager.ts +0 -457
  55. package/src/components/menu/features/keyboard-navigation.ts +0 -133
  56. package/src/components/menu/features/positioning.ts +0 -127
  57. package/src/components/menu/features/visibility.ts +0 -230
  58. package/src/components/menu/menu-item.ts +0 -86
  59. package/src/components/menu/utils.ts +0 -67
  60. package/src/components/textfield/features.ts +0 -322
  61. package/src/core/collection/adapters/base.js +0 -26
  62. package/src/core/collection/collection.js +0 -259
  63. package/src/core/collection/list-manager.js +0 -157
  64. /package/src/core/collection/adapters/{route.js → route.ts} +0 -0
  65. /package/src/{core/build → styles/utilities}/_ripple.scss +0 -0
@@ -1,150 +1,118 @@
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';
7
+ import { withPosition } from './features/position';
5
8
  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
9
  import { MenuConfig, MenuComponent } from './types';
11
- import {
12
- createBaseConfig,
13
- getElementConfig,
14
- getApiConfig
15
- } from './config';
10
+ import { createBaseConfig, getElementConfig, getApiConfig } from './config';
16
11
 
17
12
  /**
18
- * Creates a new Menu component
13
+ * Creates a new Menu component with the specified configuration.
14
+ *
15
+ * The Menu component implements the Material Design 3 menu specifications,
16
+ * providing a flexible dropdown menu system with support for nested menus,
17
+ * keyboard navigation, and ARIA accessibility.
18
+ *
19
+ * Menus are built using a functional composition pattern, applying various
20
+ * features through the pipe function for a modular architecture.
19
21
  *
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.
22
+ * The menu element is not added to the DOM until it's opened, and it's removed
23
+ * from the DOM when closed, following best practices for dropdown menus.
23
24
  *
24
- * The returned component provides these methods:
25
+ * @param {MenuConfig} config - Configuration options for the menu
26
+ * This must include an anchor element or selector, and an array of menu items.
27
+ * See {@link MenuConfig} for all available options.
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
+ * @returns {MenuComponent} A fully configured menu component instance with
30
+ * all requested features applied. The returned component has methods for
31
+ * menu manipulation, event handling, and lifecycle management.
36
32
  *
37
- * @param {MenuConfig} config - Menu configuration options
38
- * @returns {MenuComponent} Menu component instance
33
+ * @throws {Error} Throws an error if menu creation fails or if required
34
+ * configuration (like anchor) is missing.
35
+ *
36
+ * @category Components
39
37
  *
40
38
  * @example
41
- * ```typescript
42
- * // Create a basic menu with items
39
+ * // Create a simple menu anchored to a button
40
+ * const menuButton = document.getElementById('menu-button');
43
41
  * const menu = createMenu({
42
+ * anchor: menuButton,
44
43
  * items: [
45
- * { name: 'item1', text: 'Option 1' },
46
- * { name: 'item2', text: 'Option 2' },
44
+ * { id: 'item1', text: 'Option 1' },
45
+ * { id: 'item2', text: 'Option 2' },
47
46
  * { type: 'divider' },
48
- * { name: 'item3', text: 'Option 3' }
47
+ * { id: 'item3', text: 'Option 3' }
49
48
  * ]
50
49
  * });
51
50
  *
52
- * // Show the menu positioned relative to a button
53
- * const button = document.getElementById('menuButton');
54
- * menu.position(button).show();
55
- *
56
- * // Listen for item selection
51
+ * // Add event listener for item selection
57
52
  * menu.on('select', (event) => {
58
- * console.log(`Selected: ${event.name}`);
53
+ * console.log('Selected item:', event.itemId);
59
54
  * });
60
- * ```
55
+ *
56
+ * // Menu will be added to the DOM when opened and removed when closed
57
+ * menuButton.addEventListener('click', () => menu.toggle());
61
58
  *
62
59
  * @example
63
- * ```typescript
64
60
  * // Create a menu with nested submenus
65
61
  * const menu = createMenu({
62
+ * anchor: '#more-button',
66
63
  * items: [
67
- * { name: 'edit', text: 'Edit' },
64
+ * { id: 'edit', text: 'Edit', icon: '<svg>...</svg>' },
68
65
  * {
69
- * name: 'share',
66
+ * id: 'share',
70
67
  * text: 'Share',
71
- * items: [
72
- * { name: 'email', text: 'Email' },
73
- * { name: 'link', text: 'Copy Link' }
68
+ * hasSubmenu: true,
69
+ * submenu: [
70
+ * { id: 'email', text: 'Email' },
71
+ * { id: 'link', text: 'Copy link' }
74
72
  * ]
75
73
  * },
76
74
  * { type: 'divider' },
77
- * { name: 'delete', text: 'Delete' }
75
+ * { id: 'delete', text: 'Delete', icon: '<svg>...</svg>' }
78
76
  * ],
79
- * stayOpenOnSelect: true
77
+ * position: 'bottom-end'
80
78
  * });
81
79
  *
82
- * // Add items dynamically
83
- * menu.addItem({ name: 'newItem', text: 'New Item' });
84
- * ```
85
- *
86
80
  * @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();
81
+ * // Specify a custom position for the menu
82
+ * const filterMenu = createMenu({
83
+ * anchor: filterButton,
84
+ * items: filterOptions,
85
+ * position: MENU_POSITION.TOP_START,
86
+ * width: '240px',
87
+ * maxHeight: '400px'
106
88
  * });
107
89
  *
108
- * // Close menu when clicking outside
109
- * document.addEventListener('click', () => {
110
- * if (menu.isVisible()) {
111
- * menu.hide();
112
- * }
113
- * });
114
- * ```
90
+ * // Update the menu's position programmatically
91
+ * filterMenu.setPosition(MENU_POSITION.BOTTOM_END);
115
92
  */
116
- /**
117
- * @type {Function}
118
- * @name createMenu
119
- */
120
- const createMenu = (config: MenuConfig = {}): MenuComponent => {
121
- const baseConfig = createBaseConfig(config);
122
-
93
+ const createMenu = (config: MenuConfig): MenuComponent => {
123
94
  try {
124
- // Create menu component
95
+ // Validate and create the base configuration
96
+ const baseConfig = createBaseConfig(config);
97
+
98
+ // Create the component through functional composition
125
99
  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)
100
+ createBase, // Base component
101
+ withEvents(), // Event handling
102
+ withElement(getElementConfig(baseConfig)), // DOM element
103
+ withPosition(baseConfig), // Position management
104
+ withController(baseConfig), // Menu controller
105
+ withAnchor(baseConfig), // Anchor management
106
+ withLifecycle(), // Lifecycle management
107
+ comp => withAPI(getApiConfig(comp))(comp) // Public API
135
108
  )(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;
109
+
110
+ // The menu will be added to the DOM when opened and removed when closed
111
+
112
+ return menu;
145
113
  } 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)}`);
114
+ console.error('Menu creation error:', error);
115
+ throw new Error(`Failed to create menu: ${(error as Error).message}`);
148
116
  }
149
117
  };
150
118