mtrl 0.2.7 → 0.2.9
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/index.ts +2 -0
- package/package.json +14 -3
- 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/api.ts +131 -96
- package/src/components/navigation/config.ts +22 -10
- package/src/components/navigation/features/controller.ts +273 -0
- package/src/components/navigation/features/items.ts +160 -87
- package/src/components/navigation/index.ts +0 -6
- package/src/components/navigation/nav-item.ts +12 -24
- package/src/components/navigation/navigation.ts +21 -8
- package/src/components/navigation/system-types.ts +124 -0
- package/src/components/navigation/system.ts +776 -0
- 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/config.ts +20 -2
- package/src/components/slider/features/controller.ts +761 -0
- package/src/components/slider/features/handlers.ts +18 -15
- package/src/components/slider/features/index.ts +3 -2
- package/src/components/slider/features/range.ts +104 -0
- package/src/components/slider/slider.ts +34 -14
- package/src/components/slider/structure.ts +152 -0
- 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/api.ts +53 -0
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/features.ts +322 -0
- package/src/components/textfield/index.ts +0 -1
- package/src/components/textfield/textfield.ts +8 -0
- package/src/components/textfield/types.ts +29 -6
- package/src/components/timepicker/api.ts +1 -1
- package/src/components/timepicker/clockdial.ts +2 -5
- 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/textinput.ts +15 -2
- package/src/core/compose/features/textlabel.ts +0 -3
- package/src/core/composition/features/dom.ts +33 -0
- package/src/core/composition/features/icon.ts +131 -0
- package/src/core/composition/features/index.ts +11 -0
- package/src/core/composition/features/label.ts +156 -0
- package/src/core/composition/features/structure.ts +22 -0
- package/src/core/composition/index.ts +26 -0
- package/src/core/index.ts +1 -1
- package/src/core/structure.ts +288 -0
- package/src/index.ts +1 -0
- package/src/styles/components/_navigation-mobile.scss +244 -0
- package/src/styles/components/_navigation-system.scss +151 -0
- package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +1 -1
- package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +314 -72
- 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/slider/features/slider.ts +0 -318
- package/src/components/slider/features/structure.ts +0 -181
- package/src/components/slider/features/ui.ts +0 -388
- package/src/components/switch/constants.ts +0 -80
- package/src/components/tabs/constants.ts +0 -89
- package/src/components/textfield/constants.ts +0 -100
- 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
|
+
};
|
|
@@ -1,107 +1,142 @@
|
|
|
1
|
-
// src/components/
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
interface ApiOptions {
|
|
5
|
-
disabled: {
|
|
6
|
-
enable: () => void;
|
|
7
|
-
disable: () => void;
|
|
8
|
-
};
|
|
9
|
-
lifecycle: {
|
|
10
|
-
destroy: () => void;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface ComponentWithElements {
|
|
15
|
-
element: HTMLElement;
|
|
16
|
-
text: {
|
|
17
|
-
setText: (content: string) => any;
|
|
18
|
-
getText: () => string;
|
|
19
|
-
getElement: () => HTMLElement | null;
|
|
20
|
-
};
|
|
21
|
-
icon: {
|
|
22
|
-
setIcon: (html: string) => any;
|
|
23
|
-
getIcon: () => string;
|
|
24
|
-
getElement: () => HTMLElement | null;
|
|
25
|
-
};
|
|
26
|
-
getClass: (name: string) => string;
|
|
27
|
-
}
|
|
1
|
+
// src/components/navigation/api.ts
|
|
2
|
+
import { NavigationComponent, NavItemConfig, NavItemData, BaseComponent, ApiOptions } from './types';
|
|
28
3
|
|
|
29
4
|
/**
|
|
30
|
-
* Enhances a
|
|
5
|
+
* Enhances a component with navigation-specific API methods
|
|
31
6
|
* @param {ApiOptions} options - API configuration options
|
|
32
7
|
* @returns {Function} Higher-order function that adds API methods to component
|
|
33
|
-
* @internal This is an internal utility for the Button component
|
|
34
8
|
*/
|
|
35
|
-
export const withAPI = (
|
|
36
|
-
(component:
|
|
37
|
-
...component as any,
|
|
38
|
-
element: component.element as HTMLButtonElement,
|
|
9
|
+
export const withAPI = (options: ApiOptions) =>
|
|
10
|
+
(component: BaseComponent): NavigationComponent => {
|
|
39
11
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
component.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
12
|
+
const navComponent = {
|
|
13
|
+
...component,
|
|
14
|
+
element: component.element,
|
|
15
|
+
items: component.items || new Map(),
|
|
16
|
+
|
|
17
|
+
// Basic item operations
|
|
18
|
+
addItem(config: NavItemConfig): NavigationComponent {
|
|
19
|
+
if (typeof component.addItem === 'function') {
|
|
20
|
+
component.addItem(config);
|
|
21
|
+
}
|
|
22
|
+
return this;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
removeItem(id: string): NavigationComponent {
|
|
26
|
+
if (typeof component.removeItem === 'function') {
|
|
27
|
+
component.removeItem(id);
|
|
28
|
+
}
|
|
29
|
+
return this;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
getItem(id: string): NavItemData | undefined {
|
|
33
|
+
if (typeof component.getItem === 'function') {
|
|
34
|
+
return component.getItem(id);
|
|
35
|
+
}
|
|
36
|
+
return this.items.get(id);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
getAllItems(): NavItemData[] {
|
|
40
|
+
if (typeof component.getAllItems === 'function') {
|
|
41
|
+
return component.getAllItems();
|
|
42
|
+
}
|
|
43
|
+
return Array.from(this.items.values());
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Path and active item management
|
|
47
|
+
getActive(): NavItemData | null {
|
|
48
|
+
if (typeof component.getActive === 'function') {
|
|
49
|
+
return component.getActive();
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
getItemPath(id: string): string[] {
|
|
55
|
+
if (typeof component.getItemPath === 'function') {
|
|
56
|
+
return component.getItemPath(id);
|
|
57
|
+
}
|
|
58
|
+
return [];
|
|
59
|
+
},
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
setActive(id: string, silent): NavigationComponent {
|
|
62
|
+
// Use the controller if available for consistent handling
|
|
63
|
+
if (typeof component.handleItemClick === 'function') {
|
|
64
|
+
component.handleItemClick(id);
|
|
65
|
+
} else if (typeof component.setActive === 'function') {
|
|
66
|
+
component.setActive(id);
|
|
67
|
+
} else {
|
|
68
|
+
// Fallback if setActive is not available
|
|
69
|
+
const item = this.items.get(id);
|
|
70
|
+
if (item && item.element) {
|
|
71
|
+
// Emit a change event to propagate the state change
|
|
72
|
+
if (component.emit) {
|
|
73
|
+
component.emit('change', {
|
|
74
|
+
id,
|
|
75
|
+
item,
|
|
76
|
+
source: 'api'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
69
79
|
}
|
|
70
80
|
}
|
|
71
|
-
|
|
81
|
+
return this;
|
|
82
|
+
},
|
|
72
83
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
84
|
+
// Navigation state management
|
|
85
|
+
enable(): NavigationComponent {
|
|
86
|
+
if (options.disabled.enable) {
|
|
87
|
+
options.disabled.enable();
|
|
88
|
+
}
|
|
89
|
+
return this;
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
disable(): NavigationComponent {
|
|
93
|
+
if (options.disabled.disable) {
|
|
94
|
+
options.disabled.disable();
|
|
95
|
+
}
|
|
96
|
+
return this;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
expand(): NavigationComponent {
|
|
100
|
+
this.element.classList.remove(`${this.element.className.split(' ')[0]}--hidden`);
|
|
101
|
+
this.element.setAttribute('aria-hidden', 'false');
|
|
102
|
+
|
|
103
|
+
if (component.emit) {
|
|
104
|
+
component.emit('expanded', { source: 'api' });
|
|
105
|
+
}
|
|
106
|
+
return this;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
collapse(): NavigationComponent {
|
|
110
|
+
this.element.classList.add(`${this.element.className.split(' ')[0]}--hidden`);
|
|
111
|
+
this.element.setAttribute('aria-hidden', 'true');
|
|
112
|
+
|
|
113
|
+
if (component.emit) {
|
|
114
|
+
component.emit('collapsed', { source: 'api' });
|
|
115
|
+
}
|
|
116
|
+
return this;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
isExpanded(): boolean {
|
|
120
|
+
return !this.element.classList.contains(`${this.element.className.split(' ')[0]}--hidden`);
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
toggle(): NavigationComponent {
|
|
124
|
+
return this.isExpanded() ? this.collapse() : this.expand();
|
|
125
|
+
},
|
|
126
|
+
// on: component.on,
|
|
127
|
+
// off: component.off,
|
|
128
|
+
// emit: component.emit,
|
|
129
|
+
|
|
130
|
+
// Destruction
|
|
131
|
+
destroy(): void {
|
|
132
|
+
if (options.lifecycle.destroy) {
|
|
133
|
+
options.lifecycle.destroy();
|
|
134
|
+
}
|
|
105
135
|
}
|
|
106
|
-
}
|
|
107
|
-
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Return the enhanced component
|
|
139
|
+
return navComponent;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export default withAPI;
|