mtrl 0.2.7 → 0.2.8
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/demo/build.ts +349 -0
- package/demo/index.html +110 -0
- package/demo/main.js +448 -0
- package/demo/styles.css +239 -0
- package/package.json +14 -3
- package/server.ts +86 -0
- package/src/components/badge/api.ts +23 -14
- package/src/components/badge/badge.ts +2 -2
- package/src/components/badge/config.ts +10 -11
- package/src/components/badge/features.ts +15 -10
- package/src/components/badge/index.ts +27 -2
- package/src/components/badge/types.ts +28 -8
- package/src/components/bottom-app-bar/bottom-app-bar.ts +2 -44
- package/src/components/bottom-app-bar/config.ts +1 -45
- package/src/components/bottom-app-bar/index.ts +7 -1
- package/src/components/bottom-app-bar/types.ts +7 -1
- package/src/components/button/button.ts +0 -1
- package/src/components/button/config.ts +1 -2
- package/src/components/button/index.ts +10 -2
- package/src/components/button/types.ts +14 -2
- package/src/components/card/config.ts +17 -9
- package/src/components/card/content.ts +8 -10
- package/src/components/card/features.ts +4 -6
- package/src/components/card/index.ts +29 -2
- package/src/components/card/types.ts +6 -23
- package/src/components/checkbox/config.ts +3 -4
- package/src/components/checkbox/index.ts +1 -2
- package/src/components/checkbox/types.ts +12 -3
- package/src/components/chip/api.ts +170 -221
- package/src/components/chip/chip.ts +34 -302
- package/src/components/chip/config.ts +1 -2
- package/src/components/chip/index.ts +10 -2
- package/src/components/chip/types.ts +224 -35
- package/src/components/datepicker/api.ts +18 -25
- package/src/components/datepicker/config.ts +9 -12
- package/src/components/datepicker/datepicker.ts +7 -12
- package/src/components/datepicker/index.ts +10 -7
- package/src/components/datepicker/render.ts +16 -18
- package/src/components/datepicker/types.ts +164 -35
- package/src/components/datepicker/utils.ts +1 -2
- package/src/components/dialog/api.ts +7 -8
- package/src/components/dialog/config.ts +3 -4
- package/src/components/dialog/features.ts +56 -22
- package/src/components/dialog/index.ts +38 -8
- package/src/components/dialog/types.ts +33 -10
- package/src/components/divider/index.ts +5 -1
- package/src/components/extended-fab/config.ts +6 -2
- package/src/components/extended-fab/index.ts +7 -2
- package/src/components/extended-fab/types.ts +21 -4
- package/src/components/fab/config.ts +3 -4
- package/src/components/fab/fab.ts +1 -1
- package/src/components/fab/index.ts +7 -2
- package/src/components/fab/types.ts +21 -4
- package/src/components/list/config.ts +4 -5
- package/src/components/list/features.ts +6 -7
- package/src/components/list/index.ts +7 -9
- package/src/components/list/list-item.ts +12 -13
- package/src/components/list/types.ts +50 -5
- package/src/components/list/utils.ts +30 -3
- package/src/components/menu/features/items-manager.ts +9 -9
- package/src/components/menu/features/positioning.ts +7 -7
- package/src/components/menu/features/visibility.ts +7 -7
- package/src/components/menu/index.ts +7 -9
- package/src/components/menu/menu-item.ts +6 -6
- package/src/components/menu/menu.ts +22 -0
- package/src/components/menu/types.ts +29 -10
- package/src/components/menu/utils.ts +67 -0
- package/src/components/navigation/config.ts +22 -10
- package/src/components/navigation/features/items.ts +31 -27
- package/src/components/navigation/index.ts +0 -6
- package/src/components/navigation/nav-item.ts +12 -24
- package/src/components/navigation/navigation.ts +4 -6
- package/src/components/navigation/types.ts +228 -203
- package/src/components/progress/api.ts +2 -3
- package/src/components/progress/config.ts +2 -3
- package/src/components/progress/index.ts +0 -1
- package/src/components/progress/progress.ts +1 -2
- package/src/components/progress/types.ts +186 -33
- package/src/components/radios/config.ts +1 -1
- package/src/components/radios/index.ts +0 -1
- package/src/components/radios/types.ts +0 -7
- package/src/components/search/config.ts +1 -2
- package/src/components/search/features/search.ts +14 -15
- package/src/components/search/features/states.ts +5 -1
- package/src/components/search/features/structure.ts +3 -4
- package/src/components/search/index.ts +0 -3
- package/src/components/search/types.ts +18 -6
- package/src/components/segmented-button/config.ts +20 -7
- package/src/components/segmented-button/segment.ts +6 -7
- package/src/components/segmented-button/segmented-button.ts +4 -5
- package/src/components/segmented-button/types.ts +37 -2
- package/src/components/slider/types.ts +34 -8
- package/src/components/snackbar/config.ts +2 -3
- package/src/components/snackbar/constants.ts +0 -32
- package/src/components/snackbar/index.ts +0 -1
- package/src/components/snackbar/position.ts +9 -1
- package/src/components/snackbar/types.ts +122 -46
- package/src/components/switch/config.ts +2 -3
- package/src/components/switch/index.ts +0 -1
- package/src/components/switch/types.ts +3 -2
- package/src/components/tabs/config.ts +3 -4
- package/src/components/tabs/index.ts +0 -15
- package/src/components/tabs/tab-api.ts +12 -4
- package/src/components/tabs/tab.ts +18 -6
- package/src/components/tabs/types.ts +13 -3
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/index.ts +0 -1
- package/src/components/textfield/types.ts +17 -3
- package/src/components/timepicker/api.ts +1 -1
- package/src/components/timepicker/clockdial.ts +1 -1
- package/src/components/timepicker/config.ts +102 -4
- package/src/components/timepicker/index.ts +1 -6
- package/src/components/timepicker/render.ts +1 -1
- package/src/components/timepicker/timepicker.ts +1 -1
- package/src/components/tooltip/api.ts +1 -1
- package/src/components/tooltip/config.ts +27 -6
- package/src/components/tooltip/index.ts +0 -1
- package/src/components/tooltip/types.ts +13 -3
- package/src/core/compose/features/textlabel.ts +0 -3
- package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +1 -1
- package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +70 -67
- package/src/styles/main.scss +98 -49
- package/src/components/badge/constants.ts +0 -40
- package/src/components/button/constants.ts +0 -11
- package/src/components/card/constants.ts +0 -84
- package/src/components/datepicker/constants.ts +0 -98
- package/src/components/dialog/constants.ts +0 -32
- package/src/components/extended-fab/constants.ts +0 -36
- package/src/components/fab/constants.ts +0 -41
- package/src/components/menu/constants.ts +0 -154
- package/src/components/navigation/constants.ts +0 -200
- package/src/components/progress/constants.ts +0 -29
- package/src/components/search/constants.ts +0 -21
- package/src/components/segmented-button/constants.ts +0 -42
- package/src/components/switch/constants.ts +0 -80
- package/src/components/tabs/constants.ts +0 -89
- package/src/components/timepicker/constants.ts +0 -138
- /package/src/{components/badge/_styles.scss → styles/components/_badge.scss} +0 -0
- /package/src/{components/bottom-app-bar/_styles.scss → styles/components/_bottom-app-bar.scss} +0 -0
- /package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -0
- /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
- /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
- /package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -0
- /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
- /package/src/{components/datepicker/_styles.scss → styles/components/_datepicker.scss} +0 -0
- /package/src/{components/dialog/_styles.scss → styles/components/_dialog.scss} +0 -0
- /package/src/{components/divider/_styles.scss → styles/components/_divider.scss} +0 -0
- /package/src/{components/extended-fab/_styles.scss → styles/components/_extended-fab.scss} +0 -0
- /package/src/{components/fab/_styles.scss → styles/components/_fab.scss} +0 -0
- /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
- /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
- /package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +0 -0
- /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
- /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
- /package/src/{components/search/_styles.scss → styles/components/_search.scss} +0 -0
- /package/src/{components/segmented-button/_styles.scss → styles/components/_segmented-button.scss} +0 -0
- /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
- /package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +0 -0
- /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
- /package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -0
- /package/src/{components/timepicker/_styles.scss → styles/components/_timepicker.scss} +0 -0
- /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
- /package/src/{components/top-app-bar/_styles.scss → styles/components/_top-app-bar.scss} +0 -0
- /package/src/styles/utilities/{_color.scss → _colors.scss} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/components/menu/features/items-manager.ts
|
|
2
2
|
import { createMenuItem } from '../menu-item';
|
|
3
|
-
import {
|
|
3
|
+
import { MENU_EVENT } from '../utils';
|
|
4
4
|
import { BaseComponent, MenuConfig, MenuItemConfig, MenuItemData } from '../types';
|
|
5
5
|
|
|
6
6
|
interface SubmenuMap {
|
|
@@ -63,8 +63,8 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
// Handle submenu selection
|
|
66
|
-
submenu.on?.(
|
|
67
|
-
component.emit?.(
|
|
66
|
+
submenu.on?.(MENU_EVENT.SELECT, (detail: any) => {
|
|
67
|
+
component.emit?.(MENU_EVENT.SELECT, {
|
|
68
68
|
name: `${name}:${detail.name}`,
|
|
69
69
|
text: detail.text,
|
|
70
70
|
path: [name, detail.name]
|
|
@@ -116,7 +116,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
|
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
// Emit submenu open event
|
|
119
|
-
component.emit?.(
|
|
119
|
+
component.emit?.(MENU_EVENT.SUBMENU_OPEN, { name });
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
122
|
|
|
@@ -143,7 +143,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
|
|
|
143
143
|
activeSubmenu = null;
|
|
144
144
|
|
|
145
145
|
// Emit submenu close event
|
|
146
|
-
component.emit?.(
|
|
146
|
+
component.emit?.(MENU_EVENT.SUBMENU_CLOSE, { name });
|
|
147
147
|
};
|
|
148
148
|
|
|
149
149
|
/**
|
|
@@ -160,7 +160,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
|
|
|
160
160
|
|
|
161
161
|
// Cancel any pending close timer for this item
|
|
162
162
|
if (closeTimers.has(name)) {
|
|
163
|
-
window.clearTimeout(closeTimers.get(name));
|
|
163
|
+
window.clearTimeout(closeTimers.get(name)!);
|
|
164
164
|
closeTimers.delete(name);
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -194,7 +194,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
|
|
|
194
194
|
if (submenu && submenu.element) {
|
|
195
195
|
// Cancel any existing close timer for this item
|
|
196
196
|
if (closeTimers.has(name)) {
|
|
197
|
-
window.clearTimeout(closeTimers.get(name));
|
|
197
|
+
window.clearTimeout(closeTimers.get(name)!);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
// Set a new close timer
|
|
@@ -254,7 +254,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
|
|
|
254
254
|
// For regular items, emit select event
|
|
255
255
|
const name = item.getAttribute('data-name');
|
|
256
256
|
if (name) {
|
|
257
|
-
component.emit?.(
|
|
257
|
+
component.emit?.(MENU_EVENT.SELECT, { name, text: item.textContent });
|
|
258
258
|
// Hide menu after selection unless configured otherwise
|
|
259
259
|
if (!config.stayOpenOnSelect) {
|
|
260
260
|
component.hide?.();
|
|
@@ -430,7 +430,7 @@ export const withItemsManager = (config: MenuConfig) => (component: BaseComponen
|
|
|
430
430
|
|
|
431
431
|
/**
|
|
432
432
|
* Gets all registered items
|
|
433
|
-
* @returns {Map<string,
|
|
433
|
+
* @returns {Map<string, MenuItemData>} Map of item names to data
|
|
434
434
|
*/
|
|
435
435
|
getItems(): Map<string, MenuItemData> {
|
|
436
436
|
const result = new Map<string, MenuItemData>();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/components/menu/features/positioning.ts
|
|
2
2
|
import { BaseComponent, MenuPositionConfig, MenuPosition } from '../types';
|
|
3
|
-
import {
|
|
3
|
+
import { MENU_ALIGNMENT, MENU_VERTICAL_ALIGNMENT } from '../utils';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Positions a menu element relative to a target element
|
|
@@ -34,8 +34,8 @@ export const positionMenu = (
|
|
|
34
34
|
menuElement.style.opacity = originalOpacity;
|
|
35
35
|
|
|
36
36
|
const {
|
|
37
|
-
align =
|
|
38
|
-
vAlign =
|
|
37
|
+
align = MENU_ALIGNMENT.LEFT,
|
|
38
|
+
vAlign = MENU_VERTICAL_ALIGNMENT.BOTTOM,
|
|
39
39
|
offsetX = 0,
|
|
40
40
|
offsetY = 0
|
|
41
41
|
} = options;
|
|
@@ -44,16 +44,16 @@ export const positionMenu = (
|
|
|
44
44
|
let top = targetRect.bottom + offsetY;
|
|
45
45
|
|
|
46
46
|
// Handle horizontal alignment
|
|
47
|
-
if (align ===
|
|
47
|
+
if (align === MENU_ALIGNMENT.RIGHT) {
|
|
48
48
|
left = targetRect.right - menuRect.width + offsetX;
|
|
49
|
-
} else if (align ===
|
|
49
|
+
} else if (align === MENU_ALIGNMENT.CENTER) {
|
|
50
50
|
left = targetRect.left + (targetRect.width - menuRect.width) / 2 + offsetX;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// Handle vertical alignment
|
|
54
|
-
if (vAlign ===
|
|
54
|
+
if (vAlign === MENU_VERTICAL_ALIGNMENT.TOP) {
|
|
55
55
|
top = targetRect.top - menuRect.height + offsetY;
|
|
56
|
-
} else if (vAlign ===
|
|
56
|
+
} else if (vAlign === MENU_VERTICAL_ALIGNMENT.MIDDLE) {
|
|
57
57
|
top = targetRect.top + (targetRect.height - menuRect.height) / 2 + offsetY;
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/components/menu/features/visibility.ts
|
|
2
2
|
import { BaseComponent, MenuConfig } from '../types';
|
|
3
|
-
import {
|
|
3
|
+
import { MENU_EVENT, MENU_CLASSES } from '../utils';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Adds visibility management functionality to a menu component
|
|
@@ -56,11 +56,11 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
56
56
|
// Force a reflow before adding the visible class for animation
|
|
57
57
|
// eslint-disable-next-line no-void
|
|
58
58
|
void component.element.offsetHeight;
|
|
59
|
-
component.element.classList.add(`${prefix}
|
|
59
|
+
component.element.classList.add(`${prefix}-${MENU_CLASSES.VISIBLE}`);
|
|
60
60
|
component.element.setAttribute('aria-hidden', 'false');
|
|
61
61
|
|
|
62
62
|
// Emit open event
|
|
63
|
-
component.emit?.(
|
|
63
|
+
component.emit?.(MENU_EVENT.OPEN, {});
|
|
64
64
|
|
|
65
65
|
return this;
|
|
66
66
|
},
|
|
@@ -92,7 +92,7 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// Hide the menu with visual indication first
|
|
95
|
-
component.element.classList.remove(`${prefix}
|
|
95
|
+
component.element.classList.remove(`${prefix}-${MENU_CLASSES.VISIBLE}`);
|
|
96
96
|
component.element.setAttribute('aria-hidden', 'true');
|
|
97
97
|
|
|
98
98
|
// Define a reliable cleanup function
|
|
@@ -123,7 +123,7 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
123
123
|
setTimeout(cleanupElement, 300);
|
|
124
124
|
|
|
125
125
|
// Emit close event
|
|
126
|
-
component.emit?.(
|
|
126
|
+
component.emit?.(MENU_EVENT.CLOSE, {});
|
|
127
127
|
|
|
128
128
|
return this;
|
|
129
129
|
},
|
|
@@ -145,7 +145,7 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
145
145
|
if (!isVisible) return;
|
|
146
146
|
|
|
147
147
|
// Store the opening button if available
|
|
148
|
-
const
|
|
148
|
+
const origin = config.origin?.element;
|
|
149
149
|
|
|
150
150
|
// Check if click is outside the menu but not on the opening button
|
|
151
151
|
const clickedElement = event.target as Node;
|
|
@@ -156,7 +156,7 @@ export const withVisibility = (config: MenuConfig) => (component: BaseComponent)
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// Don't close if the click is on the opening button (it will handle opening/closing)
|
|
159
|
-
if (
|
|
159
|
+
if (origin && (origin === clickedElement || origin.contains(clickedElement))) {
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
// src/components/menu/index.ts
|
|
2
|
-
export { default } from './menu'
|
|
3
|
-
export {
|
|
4
|
-
MENU_ALIGN,
|
|
5
|
-
MENU_VERTICAL_ALIGN,
|
|
6
|
-
MENU_ITEM_TYPES,
|
|
7
|
-
MENU_EVENTS
|
|
8
|
-
} from './constants'
|
|
2
|
+
export { default } from './menu';
|
|
9
3
|
export {
|
|
10
4
|
MenuConfig,
|
|
11
5
|
MenuComponent,
|
|
12
6
|
MenuItemConfig,
|
|
13
|
-
MenuPositionConfig
|
|
14
|
-
|
|
7
|
+
MenuPositionConfig,
|
|
8
|
+
MenuAlign,
|
|
9
|
+
MenuVerticalAlign,
|
|
10
|
+
MenuItemType,
|
|
11
|
+
MenuEvent
|
|
12
|
+
} from './types';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/components/menu/menu-item.ts
|
|
2
2
|
import { MenuItemConfig } from './types';
|
|
3
|
-
import {
|
|
3
|
+
import { MENU_ITEM_TYPE, getMenuClass } from './utils';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a menu item element
|
|
@@ -10,10 +10,10 @@ import { MENU_ITEM_TYPES } from './constants';
|
|
|
10
10
|
*/
|
|
11
11
|
export const createMenuItem = (itemConfig: MenuItemConfig, prefix: string): HTMLElement => {
|
|
12
12
|
const item = document.createElement('li');
|
|
13
|
-
item.className = `${prefix}
|
|
13
|
+
item.className = `${prefix}-${getMenuClass('ITEM')}`;
|
|
14
14
|
|
|
15
|
-
if (itemConfig.type ===
|
|
16
|
-
item.className = `${prefix}
|
|
15
|
+
if (itemConfig.type === MENU_ITEM_TYPE.DIVIDER) {
|
|
16
|
+
item.className = `${prefix}-${getMenuClass('DIVIDER')}`;
|
|
17
17
|
return item;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ export const createMenuItem = (itemConfig: MenuItemConfig, prefix: string): HTML
|
|
|
23
23
|
|
|
24
24
|
if (itemConfig.disabled) {
|
|
25
25
|
item.setAttribute('aria-disabled', 'true');
|
|
26
|
-
item.className += ` ${prefix}
|
|
26
|
+
item.className += ` ${prefix}-${getMenuClass('ITEM')}--disabled`;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (itemConfig.name) {
|
|
@@ -33,7 +33,7 @@ export const createMenuItem = (itemConfig: MenuItemConfig, prefix: string): HTML
|
|
|
33
33
|
item.textContent = itemConfig.text || '';
|
|
34
34
|
|
|
35
35
|
if (itemConfig.items?.length) {
|
|
36
|
-
item.className += ` ${prefix}
|
|
36
|
+
item.className += ` ${prefix}-${getMenuClass('ITEM')}--submenu`;
|
|
37
37
|
item.setAttribute('aria-haspopup', 'true');
|
|
38
38
|
item.setAttribute('aria-expanded', 'false');
|
|
39
39
|
// We don't need to add a submenu indicator as it's handled by CSS ::after
|
|
@@ -18,6 +18,28 @@ import {
|
|
|
18
18
|
* Creates a new Menu component
|
|
19
19
|
* @param {MenuConfig} config - Menu configuration
|
|
20
20
|
* @returns {MenuComponent} Menu component instance
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Create a basic menu with items
|
|
25
|
+
* const menu = createMenu({
|
|
26
|
+
* items: [
|
|
27
|
+
* { name: 'item1', text: 'Option 1' },
|
|
28
|
+
* { name: 'item2', text: 'Option 2' },
|
|
29
|
+
* { type: 'divider' },
|
|
30
|
+
* { name: 'item3', text: 'Option 3' }
|
|
31
|
+
* ]
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Show the menu positioned relative to a button
|
|
35
|
+
* const button = document.getElementById('menuButton');
|
|
36
|
+
* menu.position(button).show();
|
|
37
|
+
*
|
|
38
|
+
* // Listen for item selection
|
|
39
|
+
* menu.on('select', (event) => {
|
|
40
|
+
* console.log(`Selected: ${event.name}`);
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
21
43
|
*/
|
|
22
44
|
const createMenu = (config: MenuConfig = {}): MenuComponent => {
|
|
23
45
|
const baseConfig = createBaseConfig(config);
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
// src/components/menu/types.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Menu alignment options
|
|
5
|
+
* @category Components
|
|
6
|
+
*/
|
|
7
|
+
export type MenuAlign = 'left' | 'right' | 'center';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Menu vertical alignment options
|
|
11
|
+
* @category Components
|
|
12
|
+
*/
|
|
13
|
+
export type MenuVerticalAlign = 'top' | 'bottom' | 'middle';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Menu item types
|
|
17
|
+
* @category Components
|
|
18
|
+
*/
|
|
19
|
+
export type MenuItemType = 'item' | 'divider';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Menu events
|
|
23
|
+
* @category Components
|
|
24
|
+
*/
|
|
25
|
+
export type MenuEvent = 'select' | 'open' | 'close' | 'submenuOpen' | 'submenuClose';
|
|
7
26
|
|
|
8
27
|
/**
|
|
9
28
|
* Menu item configuration
|
|
@@ -16,7 +35,7 @@ export interface MenuItemConfig {
|
|
|
16
35
|
text: string;
|
|
17
36
|
|
|
18
37
|
/** Type of menu item */
|
|
19
|
-
type?:
|
|
38
|
+
type?: MenuItemType | string;
|
|
20
39
|
|
|
21
40
|
/** Whether the item is disabled */
|
|
22
41
|
disabled?: boolean;
|
|
@@ -33,10 +52,10 @@ export interface MenuItemConfig {
|
|
|
33
52
|
*/
|
|
34
53
|
export interface MenuPositionConfig {
|
|
35
54
|
/** Horizontal alignment */
|
|
36
|
-
align?:
|
|
55
|
+
align?: MenuAlign | string;
|
|
37
56
|
|
|
38
57
|
/** Vertical alignment */
|
|
39
|
-
vAlign?:
|
|
58
|
+
vAlign?: MenuVerticalAlign | string;
|
|
40
59
|
|
|
41
60
|
/** Horizontal offset in pixels */
|
|
42
61
|
offsetX?: number;
|
|
@@ -94,8 +113,8 @@ export interface MenuConfig {
|
|
|
94
113
|
/** Whether to keep menu open after selection */
|
|
95
114
|
stayOpenOnSelect?: boolean;
|
|
96
115
|
|
|
97
|
-
/**
|
|
98
|
-
|
|
116
|
+
/** Orgin element that opens the menu */
|
|
117
|
+
origin?: HTMLElement | { element: HTMLElement };
|
|
99
118
|
|
|
100
119
|
/** Parent item element (for submenus) */
|
|
101
120
|
parentItem?: HTMLElement;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/components/menu/utils.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Menu alignment constants for internal use
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export const MENU_ALIGNMENT = {
|
|
8
|
+
LEFT: 'left',
|
|
9
|
+
RIGHT: 'right',
|
|
10
|
+
CENTER: 'center'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Menu vertical alignment constants for internal use
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export const MENU_VERTICAL_ALIGNMENT = {
|
|
18
|
+
TOP: 'top',
|
|
19
|
+
BOTTOM: 'bottom',
|
|
20
|
+
MIDDLE: 'middle'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Menu item types for internal use
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export const MENU_ITEM_TYPE = {
|
|
28
|
+
ITEM: 'item',
|
|
29
|
+
DIVIDER: 'divider'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Menu events for internal use
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export const MENU_EVENT = {
|
|
37
|
+
SELECT: 'select',
|
|
38
|
+
OPEN: 'open',
|
|
39
|
+
CLOSE: 'close',
|
|
40
|
+
SUBMENU_OPEN: 'submenuOpen',
|
|
41
|
+
SUBMENU_CLOSE: 'submenuClose'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Menu CSS classes for internal use
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export const MENU_CLASSES = {
|
|
49
|
+
ROOT: 'menu',
|
|
50
|
+
ITEM: 'menu-item',
|
|
51
|
+
ITEM_CONTAINER: 'menu-item-container',
|
|
52
|
+
LIST: 'menu-list',
|
|
53
|
+
DIVIDER: 'menu-divider',
|
|
54
|
+
SUBMENU: 'menu--submenu',
|
|
55
|
+
VISIBLE: 'menu--visible',
|
|
56
|
+
DISABLED: 'menu--disabled'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Gets a class name for menu elements
|
|
61
|
+
* @param {string} element - Element name from MENU_CLASSES
|
|
62
|
+
* @returns {string} The class name
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
export const getMenuClass = (element: keyof typeof MENU_CLASSES): string => {
|
|
66
|
+
return MENU_CLASSES[element];
|
|
67
|
+
};
|
|
@@ -5,15 +5,14 @@ import {
|
|
|
5
5
|
BaseComponentConfig
|
|
6
6
|
} from '../../core/config/component-config';
|
|
7
7
|
import { NavigationConfig, BaseComponent, ApiOptions } from './types';
|
|
8
|
-
import { NAV_VARIANTS, NAV_POSITIONS, NAV_BEHAVIORS } from './constants';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Default configuration for the Navigation component
|
|
12
11
|
*/
|
|
13
12
|
export const defaultConfig: NavigationConfig = {
|
|
14
|
-
variant:
|
|
15
|
-
position:
|
|
16
|
-
behavior:
|
|
13
|
+
variant: 'rail',
|
|
14
|
+
position: 'left',
|
|
15
|
+
behavior: 'fixed',
|
|
17
16
|
items: [],
|
|
18
17
|
showLabels: true,
|
|
19
18
|
scrimEnabled: false
|
|
@@ -32,16 +31,29 @@ export const createBaseConfig = (config: NavigationConfig = {}): NavigationConfi
|
|
|
32
31
|
* @param {NavigationConfig} config - Navigation configuration
|
|
33
32
|
* @returns {Object} Element configuration object for withElement
|
|
34
33
|
*/
|
|
35
|
-
export const getElementConfig = (config: NavigationConfig) =>
|
|
36
|
-
|
|
34
|
+
export const getElementConfig = (config: NavigationConfig) => {
|
|
35
|
+
// Build class list - start with the variant class
|
|
36
|
+
const variantClass = config.variant ? `${config.prefix}-nav--${config.variant}` : '';
|
|
37
|
+
|
|
38
|
+
// Add position class if specified
|
|
39
|
+
const positionClass = config.position ? `${config.prefix}-nav--${config.position}` : '';
|
|
40
|
+
|
|
41
|
+
// Add user-provided classes
|
|
42
|
+
const userClass = config.class || '';
|
|
43
|
+
|
|
44
|
+
// Combine all classes
|
|
45
|
+
const classNames = [variantClass, positionClass, userClass].filter(Boolean);
|
|
46
|
+
|
|
47
|
+
return createElementConfig(config, {
|
|
37
48
|
tag: 'nav',
|
|
38
49
|
componentName: 'nav',
|
|
39
50
|
attrs: {
|
|
40
51
|
role: 'navigation',
|
|
41
52
|
'aria-label': config.ariaLabel || 'Main Navigation'
|
|
42
53
|
},
|
|
43
|
-
className:
|
|
54
|
+
className: classNames
|
|
44
55
|
});
|
|
56
|
+
};
|
|
45
57
|
|
|
46
58
|
/**
|
|
47
59
|
* Creates API configuration for the Navigation component
|
|
@@ -50,11 +62,11 @@ export const getElementConfig = (config: NavigationConfig) =>
|
|
|
50
62
|
*/
|
|
51
63
|
export const getApiConfig = (comp: BaseComponent): ApiOptions => ({
|
|
52
64
|
disabled: {
|
|
53
|
-
enable: comp.disabled?.enable,
|
|
54
|
-
disable: comp.disabled?.disable
|
|
65
|
+
enable: comp.disabled?.enable || (() => {}),
|
|
66
|
+
disable: comp.disabled?.disable || (() => {})
|
|
55
67
|
},
|
|
56
68
|
lifecycle: {
|
|
57
|
-
destroy: comp.lifecycle?.destroy
|
|
69
|
+
destroy: comp.lifecycle?.destroy || (() => {})
|
|
58
70
|
}
|
|
59
71
|
});
|
|
60
72
|
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
// src/components/navigation/features/items.ts
|
|
2
2
|
import { createNavItem, getAllNestedItems } from '../nav-item';
|
|
3
|
-
import { NavItemConfig, NavItemData } from '../types';
|
|
4
|
-
|
|
5
|
-
// Type definitions to help with TypeScript conversion
|
|
6
|
-
interface Component {
|
|
7
|
-
element: HTMLElement;
|
|
8
|
-
emit?: (event: string, data: any) => void;
|
|
9
|
-
lifecycle?: {
|
|
10
|
-
destroy: () => void;
|
|
11
|
-
};
|
|
12
|
-
[key: string]: any;
|
|
13
|
-
}
|
|
3
|
+
import { NavItemConfig, NavItemData, BaseComponent, NavClass } from '../types';
|
|
14
4
|
|
|
15
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Interface for a component with items management capabilities
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
interface ItemsComponent extends BaseComponent {
|
|
16
10
|
items: Map<string, NavItemData>;
|
|
17
11
|
addItem: (config: NavItemConfig) => ItemsComponent;
|
|
18
12
|
removeItem: (id: string) => ItemsComponent;
|
|
@@ -23,15 +17,25 @@ interface ItemsComponent extends Component {
|
|
|
23
17
|
setActive: (id: string) => ItemsComponent;
|
|
24
18
|
}
|
|
25
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Interface for navigation configuration
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
26
24
|
interface NavigationConfig {
|
|
27
25
|
prefix?: string;
|
|
28
26
|
items?: NavItemConfig[];
|
|
29
27
|
[key: string]: any;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Adds navigation items management to a component
|
|
32
|
+
* @param {NavigationConfig} config - Navigation configuration
|
|
33
|
+
* @returns {Function} Component enhancer function
|
|
34
|
+
*/
|
|
35
|
+
export const withNavItems = (config: NavigationConfig) => (component: BaseComponent): ItemsComponent => {
|
|
33
36
|
const items = new Map<string, NavItemData>();
|
|
34
37
|
let activeItem: NavItemData | null = null;
|
|
38
|
+
const prefix = config.prefix || 'mtrl';
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
41
|
* Recursively stores items in the items Map
|
|
@@ -43,9 +47,9 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
43
47
|
|
|
44
48
|
if (itemConfig.items?.length) {
|
|
45
49
|
itemConfig.items.forEach(nestedConfig => {
|
|
46
|
-
const container = item.closest(`.${
|
|
50
|
+
const container = item.closest(`.${prefix}-${NavClass.ITEM_CONTAINER}`);
|
|
47
51
|
if (container) {
|
|
48
|
-
const nestedContainer = container.querySelector(`.${
|
|
52
|
+
const nestedContainer = container.querySelector(`.${prefix}-${NavClass.NESTED_CONTAINER}`);
|
|
49
53
|
if (nestedContainer) {
|
|
50
54
|
const nestedItem = nestedContainer.querySelector(`[data-id="${nestedConfig.id}"]`) as HTMLElement;
|
|
51
55
|
if (nestedItem) {
|
|
@@ -68,7 +72,7 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
68
72
|
const role = item.getAttribute('role');
|
|
69
73
|
|
|
70
74
|
if (active) {
|
|
71
|
-
item.classList.add(`${
|
|
75
|
+
item.classList.add(`${prefix}-${NavClass.ITEM}--active`);
|
|
72
76
|
|
|
73
77
|
// Set appropriate attribute based on role
|
|
74
78
|
if (role === 'tab') {
|
|
@@ -79,7 +83,7 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
79
83
|
item.setAttribute('aria-current', 'page');
|
|
80
84
|
}
|
|
81
85
|
} else {
|
|
82
|
-
item.classList.remove(`${
|
|
86
|
+
item.classList.remove(`${prefix}-${NavClass.ITEM}--active`);
|
|
83
87
|
|
|
84
88
|
// Remove appropriate attribute based on role
|
|
85
89
|
if (role === 'tab') {
|
|
@@ -94,7 +98,7 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
94
98
|
// Create initial items
|
|
95
99
|
if (config.items) {
|
|
96
100
|
config.items.forEach(itemConfig => {
|
|
97
|
-
const item = createNavItem(itemConfig, component.element,
|
|
101
|
+
const item = createNavItem(itemConfig, component.element, prefix);
|
|
98
102
|
storeItem(itemConfig, item);
|
|
99
103
|
|
|
100
104
|
if (itemConfig.active) {
|
|
@@ -106,7 +110,7 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
106
110
|
|
|
107
111
|
// Handle item clicks
|
|
108
112
|
component.element.addEventListener('click', (event: Event) => {
|
|
109
|
-
const item = (event.target as HTMLElement).closest(`.${
|
|
113
|
+
const item = (event.target as HTMLElement).closest(`.${prefix}-${NavClass.ITEM}`) as HTMLElement;
|
|
110
114
|
if (!item || (item as any).disabled || item.getAttribute('aria-haspopup') === 'menu') return;
|
|
111
115
|
|
|
112
116
|
const id = item.dataset.id;
|
|
@@ -151,12 +155,12 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
151
155
|
|
|
152
156
|
if (!currentItem) return path;
|
|
153
157
|
|
|
154
|
-
let parentContainer = currentItem.element.closest(`.${
|
|
158
|
+
let parentContainer = currentItem.element.closest(`.${prefix}-${NavClass.NESTED_CONTAINER}`);
|
|
155
159
|
while (parentContainer) {
|
|
156
160
|
const parentItemContainer = parentContainer.parentElement;
|
|
157
161
|
if (!parentItemContainer) break;
|
|
158
162
|
|
|
159
|
-
const parentItem = parentItemContainer.querySelector(`.${
|
|
163
|
+
const parentItem = parentItemContainer.querySelector(`.${prefix}-${NavClass.ITEM}`);
|
|
160
164
|
if (!parentItem) break;
|
|
161
165
|
|
|
162
166
|
const parentId = parentItem.getAttribute('data-id');
|
|
@@ -165,7 +169,7 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
165
169
|
path.unshift(parentId);
|
|
166
170
|
|
|
167
171
|
// Move up to next level
|
|
168
|
-
parentContainer = parentItemContainer.closest(`.${
|
|
172
|
+
parentContainer = parentItemContainer.closest(`.${prefix}-${NavClass.NESTED_CONTAINER}`);
|
|
169
173
|
}
|
|
170
174
|
|
|
171
175
|
return path;
|
|
@@ -189,7 +193,7 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
189
193
|
addItem(itemConfig: NavItemConfig) {
|
|
190
194
|
if (items.has(itemConfig.id)) return this;
|
|
191
195
|
|
|
192
|
-
const item = createNavItem(itemConfig, component.element,
|
|
196
|
+
const item = createNavItem(itemConfig, component.element, prefix);
|
|
193
197
|
storeItem(itemConfig, item);
|
|
194
198
|
|
|
195
199
|
if (itemConfig.active) {
|
|
@@ -210,7 +214,7 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
210
214
|
if (!item) return this;
|
|
211
215
|
|
|
212
216
|
// Remove all nested items first
|
|
213
|
-
const nestedItems = getAllNestedItems(item.element,
|
|
217
|
+
const nestedItems = getAllNestedItems(item.element, prefix);
|
|
214
218
|
nestedItems.forEach(nestedItem => {
|
|
215
219
|
const nestedId = nestedItem.dataset.id;
|
|
216
220
|
if (nestedId) items.delete(nestedId);
|
|
@@ -221,7 +225,7 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
// Remove the entire item container
|
|
224
|
-
const container = item.element.closest(`.${
|
|
228
|
+
const container = item.element.closest(`.${prefix}-${NavClass.ITEM_CONTAINER}`);
|
|
225
229
|
if (container) {
|
|
226
230
|
container.remove();
|
|
227
231
|
}
|
|
@@ -255,9 +259,9 @@ export const withNavItems = (config: NavigationConfig) => (component: Component)
|
|
|
255
259
|
const parentItem = items.get(parentId);
|
|
256
260
|
if (parentItem) {
|
|
257
261
|
const parentButton = parentItem.element;
|
|
258
|
-
const container = parentButton.closest(`.${
|
|
262
|
+
const container = parentButton.closest(`.${prefix}-${NavClass.ITEM_CONTAINER}`);
|
|
259
263
|
if (container) {
|
|
260
|
-
const nestedContainer = container.querySelector(`.${
|
|
264
|
+
const nestedContainer = container.querySelector(`.${prefix}-${NavClass.NESTED_CONTAINER}`);
|
|
261
265
|
if (nestedContainer) {
|
|
262
266
|
parentButton.setAttribute('aria-expanded', 'true');
|
|
263
267
|
nestedContainer.hidden = false;
|