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.
- 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/components/segmented-button/config.ts +59 -20
- package/src/components/segmented-button/index.ts +1 -1
- package/src/components/segmented-button/segment.ts +51 -97
- package/src/components/segmented-button/segmented-button.ts +114 -2
- package/src/components/segmented-button/types.ts +52 -0
- package/src/core/compose/features/icon.ts +15 -13
- 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/_button.scss +6 -0
- package/src/styles/components/_chip.scss +4 -5
- package/src/styles/components/_menu.scss +20 -8
- package/src/styles/components/_segmented-button.scss +173 -63
- 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
|
@@ -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;
|