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.
Files changed (32) 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/core/dom/classes.ts +81 -9
  11. package/src/core/dom/create.ts +30 -19
  12. package/src/core/layout/README.md +531 -166
  13. package/src/core/layout/array.ts +3 -4
  14. package/src/core/layout/config.ts +193 -0
  15. package/src/core/layout/create.ts +1 -2
  16. package/src/core/layout/index.ts +12 -2
  17. package/src/core/layout/object.ts +2 -3
  18. package/src/core/layout/processor.ts +60 -12
  19. package/src/core/layout/result.ts +1 -2
  20. package/src/core/layout/types.ts +105 -50
  21. package/src/core/layout/utils.ts +69 -61
  22. package/src/index.ts +2 -1
  23. package/src/styles/components/_menu.scss +20 -8
  24. package/src/styles/main.scss +23 -23
  25. package/src/styles/utilities/_layout.scss +665 -0
  26. package/src/components/menu/features/items-manager.ts +0 -457
  27. package/src/components/menu/features/keyboard-navigation.ts +0 -133
  28. package/src/components/menu/features/positioning.ts +0 -127
  29. package/src/components/menu/features/visibility.ts +0 -230
  30. package/src/components/menu/menu-item.ts +0 -86
  31. package/src/components/menu/utils.ts +0 -67
  32. /package/src/{core/build → styles/utilities}/_ripple.scss +0 -0
@@ -0,0 +1,159 @@
1
+ // src/components/menu/features/anchor.ts
2
+
3
+ import { MenuConfig } from '../types';
4
+
5
+ /**
6
+ * Adds anchor functionality to menu component
7
+ * Manages the relationship between menu and its anchor element
8
+ *
9
+ * @param config - Menu configuration
10
+ * @returns Component enhancer with anchor management functionality
11
+ */
12
+ export const withAnchor = (config: MenuConfig) => component => {
13
+ if (!component.element) {
14
+ console.warn('Cannot initialize menu anchor: missing element');
15
+ return component;
16
+ }
17
+
18
+ // Track anchor state
19
+ const state = {
20
+ anchorElement: null as HTMLElement
21
+ };
22
+
23
+ /**
24
+ * Resolves the anchor element from string or direct reference
25
+ */
26
+ const resolveAnchor = (anchor: HTMLElement | string): HTMLElement => {
27
+ if (!anchor) return null;
28
+
29
+ if (typeof anchor === 'string') {
30
+ const element = document.querySelector(anchor);
31
+ if (!element) {
32
+ console.warn(`Menu anchor not found: ${anchor}`);
33
+ return null;
34
+ }
35
+ return element as HTMLElement;
36
+ }
37
+
38
+ return anchor;
39
+ };
40
+
41
+ /**
42
+ * Sets up anchor click handler for toggling menu
43
+ */
44
+ const setupAnchorEvents = (anchorElement: HTMLElement): void => {
45
+ if (!anchorElement) return;
46
+
47
+ // Remove previously attached event if any
48
+ if (state.anchorElement && state.anchorElement !== anchorElement) {
49
+ cleanup();
50
+ }
51
+
52
+ // Store reference
53
+ state.anchorElement = anchorElement;
54
+
55
+ // Add click handler
56
+ anchorElement.addEventListener('click', handleAnchorClick);
57
+
58
+ // Add ARIA attributes
59
+ anchorElement.setAttribute('aria-haspopup', 'true');
60
+ anchorElement.setAttribute('aria-expanded', 'false');
61
+
62
+ // Get menu ID or generate one
63
+ let menuId = component.element.id;
64
+ if (!menuId) {
65
+ menuId = `menu-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
66
+ component.element.id = menuId;
67
+ }
68
+
69
+ // Connect menu and anchor with ARIA
70
+ anchorElement.setAttribute('aria-controls', menuId);
71
+ };
72
+
73
+ /**
74
+ * Handles anchor element click
75
+ */
76
+ const handleAnchorClick = (e: MouseEvent): void => {
77
+ e.preventDefault();
78
+
79
+ // Toggle menu visibility
80
+ if (component.menu) {
81
+ const isOpen = component.menu.isOpen();
82
+
83
+ if (isOpen) {
84
+ component.menu.close(e);
85
+ state.anchorElement.setAttribute('aria-expanded', 'false');
86
+ } else {
87
+ component.menu.open(e);
88
+ state.anchorElement.setAttribute('aria-expanded', 'true');
89
+ }
90
+ }
91
+ };
92
+
93
+ /**
94
+ * Removes event listeners from anchor
95
+ */
96
+ const cleanup = (): void => {
97
+ if (state.anchorElement) {
98
+ state.anchorElement.removeEventListener('click', handleAnchorClick);
99
+ state.anchorElement.removeAttribute('aria-haspopup');
100
+ state.anchorElement.removeAttribute('aria-expanded');
101
+ state.anchorElement.removeAttribute('aria-controls');
102
+ }
103
+ };
104
+
105
+ // Initialize with provided anchor
106
+ const initialAnchor = resolveAnchor(config.anchor);
107
+ setupAnchorEvents(initialAnchor);
108
+
109
+ // Register with lifecycle if available
110
+ if (component.lifecycle) {
111
+ const originalDestroy = component.lifecycle.destroy || (() => {});
112
+ component.lifecycle.destroy = () => {
113
+ cleanup();
114
+ originalDestroy();
115
+ };
116
+ }
117
+
118
+ // Listen for menu state changes to update anchor
119
+ component.on('open', () => {
120
+ if (state.anchorElement) {
121
+ state.anchorElement.setAttribute('aria-expanded', 'true');
122
+ }
123
+ });
124
+
125
+ component.on('close', () => {
126
+ if (state.anchorElement) {
127
+ state.anchorElement.setAttribute('aria-expanded', 'false');
128
+ }
129
+ });
130
+
131
+ // Return enhanced component
132
+ return {
133
+ ...component,
134
+ anchor: {
135
+ /**
136
+ * Sets a new anchor element
137
+ * @param anchor - New anchor element or selector
138
+ * @returns Component for chaining
139
+ */
140
+ setAnchor(anchor: HTMLElement | string) {
141
+ const newAnchor = resolveAnchor(anchor);
142
+ if (newAnchor) {
143
+ setupAnchorEvents(newAnchor);
144
+ }
145
+ return component;
146
+ },
147
+
148
+ /**
149
+ * Gets the current anchor element
150
+ * @returns Current anchor element
151
+ */
152
+ getAnchor() {
153
+ return state.anchorElement;
154
+ }
155
+ }
156
+ };
157
+ };
158
+
159
+ export default withAnchor;