mtrl 0.2.8 → 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 +1 -1
- package/src/components/navigation/api.ts +131 -96
- package/src/components/navigation/features/controller.ts +273 -0
- package/src/components/navigation/features/items.ts +133 -64
- package/src/components/navigation/navigation.ts +17 -2
- package/src/components/navigation/system-types.ts +124 -0
- package/src/components/navigation/system.ts +776 -0
- 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/textfield/api.ts +53 -0
- package/src/components/textfield/features.ts +322 -0
- package/src/components/textfield/textfield.ts +8 -0
- package/src/components/textfield/types.ts +12 -3
- package/src/components/timepicker/clockdial.ts +1 -4
- package/src/core/compose/features/textinput.ts +15 -2
- 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/styles/components/_textfield.scss +250 -11
- package/demo/build.ts +0 -349
- package/demo/index.html +0 -110
- package/demo/main.js +0 -448
- package/demo/styles.css +0 -239
- package/server.ts +0 -86
- 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/textfield/constants.ts +0 -100
|
@@ -24,9 +24,28 @@ interface ItemsComponent extends BaseComponent {
|
|
|
24
24
|
interface NavigationConfig {
|
|
25
25
|
prefix?: string;
|
|
26
26
|
items?: NavItemConfig[];
|
|
27
|
+
debug?: boolean;
|
|
27
28
|
[key: string]: any;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Helper to get element ID or component type
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
function getElementId(element: HTMLElement | null, prefix: string): string | null {
|
|
36
|
+
if (!element) return null;
|
|
37
|
+
|
|
38
|
+
// Try to get data-id
|
|
39
|
+
const id = element.getAttribute('data-id');
|
|
40
|
+
if (id) return id;
|
|
41
|
+
|
|
42
|
+
// Try to identify by component class
|
|
43
|
+
if (element.classList.contains(`${prefix}-nav--rail`)) return 'rail';
|
|
44
|
+
if (element.classList.contains(`${prefix}-nav--drawer`)) return 'drawer';
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
/**
|
|
31
50
|
* Adds navigation items management to a component
|
|
32
51
|
* @param {NavigationConfig} config - Navigation configuration
|
|
@@ -108,72 +127,52 @@ export const withNavItems = (config: NavigationConfig) => (component: BaseCompon
|
|
|
108
127
|
});
|
|
109
128
|
}
|
|
110
129
|
|
|
111
|
-
//
|
|
112
|
-
component.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
updateActiveState(item, itemData, true);
|
|
134
|
-
activeItem = itemData;
|
|
130
|
+
// Set up enhanced event handling for mouse events
|
|
131
|
+
if (component.emit) {
|
|
132
|
+
// Mouse over event handler
|
|
133
|
+
component.element.addEventListener('mouseover', (event: MouseEvent) => {
|
|
134
|
+
// Find the closest item with data-id
|
|
135
|
+
const target = event.target as HTMLElement;
|
|
136
|
+
const item = target.closest(`[data-id]`) as HTMLElement;
|
|
137
|
+
if (item) {
|
|
138
|
+
const id = item.dataset.id;
|
|
139
|
+
if (id) {
|
|
140
|
+
component.emit('mouseover', {
|
|
141
|
+
id,
|
|
142
|
+
clientX: event.clientX,
|
|
143
|
+
clientY: event.clientY,
|
|
144
|
+
target: item,
|
|
145
|
+
item: items.get(id)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}, { passive: true });
|
|
135
150
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
component.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
// Mouse enter event
|
|
152
|
+
component.element.addEventListener('mouseenter', (event: MouseEvent) => {
|
|
153
|
+
const componentId = component.element.dataset.id || component.componentName || 'nav';
|
|
154
|
+
|
|
155
|
+
component.emit('mouseenter', {
|
|
156
|
+
clientX: event.clientX,
|
|
157
|
+
clientY: event.clientY,
|
|
158
|
+
id: componentId
|
|
143
159
|
});
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Gets the path to an item (parent IDs)
|
|
149
|
-
* @param {string} id - Item ID to get path for
|
|
150
|
-
* @returns {Array<string>} Array of parent item IDs
|
|
151
|
-
*/
|
|
152
|
-
const getItemPath = (id: string): string[] => {
|
|
153
|
-
const path: string[] = [];
|
|
154
|
-
let currentItem = items.get(id);
|
|
155
|
-
|
|
156
|
-
if (!currentItem) return path;
|
|
157
|
-
|
|
158
|
-
let parentContainer = currentItem.element.closest(`.${prefix}-${NavClass.NESTED_CONTAINER}`);
|
|
159
|
-
while (parentContainer) {
|
|
160
|
-
const parentItemContainer = parentContainer.parentElement;
|
|
161
|
-
if (!parentItemContainer) break;
|
|
160
|
+
}, { passive: true });
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
path.unshift(parentId);
|
|
162
|
+
// Mouse leave event
|
|
163
|
+
component.element.addEventListener('mouseleave', (event: MouseEvent) => {
|
|
164
|
+
const relatedTarget = event.relatedTarget as HTMLElement;
|
|
165
|
+
const relatedTargetId = getElementId(relatedTarget, prefix);
|
|
166
|
+
const componentId = component.element.dataset.id || component.componentName || 'nav';
|
|
170
167
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
168
|
+
component.emit('mouseleave', {
|
|
169
|
+
clientX: event.clientX,
|
|
170
|
+
clientY: event.clientY,
|
|
171
|
+
relatedTargetId,
|
|
172
|
+
id: componentId
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
177
176
|
|
|
178
177
|
// Clean up when component is destroyed
|
|
179
178
|
if (component.lifecycle) {
|
|
@@ -186,6 +185,76 @@ export const withNavItems = (config: NavigationConfig) => (component: BaseCompon
|
|
|
186
185
|
};
|
|
187
186
|
}
|
|
188
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Gets the path to an item (parent IDs and the item's own ID)
|
|
190
|
+
* @param {string} id - Item ID to get path for
|
|
191
|
+
* @returns {Array<string>} Array of parent item IDs including the item's own ID
|
|
192
|
+
*/
|
|
193
|
+
const getItemPath = (id: string): string[] => {
|
|
194
|
+
// Always include the item's own ID in the path
|
|
195
|
+
const path: string[] = [id];
|
|
196
|
+
const currentItem = items.get(id);
|
|
197
|
+
|
|
198
|
+
if (!currentItem) {
|
|
199
|
+
return path; // Still return [id] even if item not found in map
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// First, find the item container that contains this element
|
|
203
|
+
const itemContainer = currentItem.element.closest(`.${prefix}-${NavClass.ITEM_CONTAINER}`);
|
|
204
|
+
if (!itemContainer) {
|
|
205
|
+
return path;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Then find the parent element of the item container
|
|
209
|
+
const parentElement = itemContainer.parentElement;
|
|
210
|
+
|
|
211
|
+
// If parent element is not a nested container, this is a root-level item
|
|
212
|
+
// Just return the item's own ID
|
|
213
|
+
if (!parentElement || !parentElement.classList.contains(`${prefix}-${NavClass.NESTED_CONTAINER}`)) {
|
|
214
|
+
return path;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// We're dealing with a nested item - find its ancestors
|
|
218
|
+
let currentNestedContainer = parentElement;
|
|
219
|
+
|
|
220
|
+
while (currentNestedContainer) {
|
|
221
|
+
// Find the parent item container
|
|
222
|
+
const parentItemContainer = currentNestedContainer.parentElement;
|
|
223
|
+
|
|
224
|
+
if (!parentItemContainer || !parentItemContainer.classList.contains(`${prefix}-${NavClass.ITEM_CONTAINER}`)) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Find the parent item button/element
|
|
229
|
+
const parentItem = parentItemContainer.querySelector(`.${prefix}-${NavClass.ITEM}[data-id]`);
|
|
230
|
+
|
|
231
|
+
if (!parentItem) {
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const parentId = parentItem.getAttribute('data-id');
|
|
236
|
+
|
|
237
|
+
if (!parentId) {
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Add to the beginning of path (since we're going up the tree)
|
|
242
|
+
// This puts ancestors before the item's own ID
|
|
243
|
+
path.unshift(parentId);
|
|
244
|
+
|
|
245
|
+
// Move up to the next level - find the container of this parent's container
|
|
246
|
+
const grandparentElement = parentItemContainer.parentElement;
|
|
247
|
+
|
|
248
|
+
if (!grandparentElement || !grandparentElement.classList.contains(`${prefix}-${NavClass.NESTED_CONTAINER}`)) {
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
currentNestedContainer = grandparentElement;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return path;
|
|
256
|
+
};
|
|
257
|
+
|
|
189
258
|
return {
|
|
190
259
|
...component,
|
|
191
260
|
items,
|
|
@@ -212,7 +281,7 @@ export const withNavItems = (config: NavigationConfig) => (component: BaseCompon
|
|
|
212
281
|
removeItem(id: string) {
|
|
213
282
|
const item = items.get(id);
|
|
214
283
|
if (!item) return this;
|
|
215
|
-
|
|
284
|
+
|
|
216
285
|
// Remove all nested items first
|
|
217
286
|
const nestedItems = getAllNestedItems(item.element, prefix);
|
|
218
287
|
nestedItems.forEach(nestedItem => {
|
|
@@ -245,7 +314,7 @@ export const withNavItems = (config: NavigationConfig) => (component: BaseCompon
|
|
|
245
314
|
setActive(id: string) {
|
|
246
315
|
const item = items.get(id);
|
|
247
316
|
if (!item || item.config.disabled) return this;
|
|
248
|
-
|
|
317
|
+
|
|
249
318
|
if (activeItem) {
|
|
250
319
|
updateActiveState(activeItem.element, activeItem, false);
|
|
251
320
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '../../core/compose/features';
|
|
11
11
|
import { withAPI } from './api';
|
|
12
12
|
import { withNavItems } from './features/items';
|
|
13
|
+
import { withController } from './features/controller';
|
|
13
14
|
import { NavigationConfig, NavigationComponent, NavVariant } from './types';
|
|
14
15
|
import {
|
|
15
16
|
createBaseConfig,
|
|
@@ -68,15 +69,21 @@ const createNavigation = (config: NavigationConfig = {}): NavigationComponent =>
|
|
|
68
69
|
try {
|
|
69
70
|
const navigation = pipe(
|
|
70
71
|
createBase,
|
|
71
|
-
// First add events system
|
|
72
|
+
// First add events system - MUST be before other features that use events
|
|
72
73
|
withEvents(),
|
|
73
74
|
// Then add the element and other features
|
|
74
75
|
withElement(getElementConfig(baseConfig)),
|
|
76
|
+
// Add core features
|
|
75
77
|
withVariant(baseConfig),
|
|
76
78
|
withPosition(baseConfig),
|
|
79
|
+
// Add navigation-specific features
|
|
77
80
|
withNavItems(baseConfig),
|
|
81
|
+
// Add controller for event delegation AFTER items are set up
|
|
82
|
+
withController(baseConfig),
|
|
83
|
+
// Add standard component features
|
|
78
84
|
withDisabled(baseConfig),
|
|
79
85
|
withLifecycle(),
|
|
86
|
+
// Finally add the API - this must be last to include all features
|
|
80
87
|
comp => withAPI(getApiConfig(comp))(comp)
|
|
81
88
|
)(baseConfig);
|
|
82
89
|
|
|
@@ -89,10 +96,18 @@ const createNavigation = (config: NavigationConfig = {}): NavigationComponent =>
|
|
|
89
96
|
if (baseConfig.disabled) {
|
|
90
97
|
nav.disable();
|
|
91
98
|
}
|
|
99
|
+
|
|
100
|
+
// Set component variant property for component identification
|
|
101
|
+
nav.variant = baseConfig.variant;
|
|
102
|
+
|
|
103
|
+
// Add explicit component identifier for debugging
|
|
104
|
+
nav.element.dataset.componentType = 'navigation';
|
|
105
|
+
if (baseConfig.variant) {
|
|
106
|
+
nav.element.dataset.variant = baseConfig.variant;
|
|
107
|
+
}
|
|
92
108
|
|
|
93
109
|
return nav;
|
|
94
110
|
} catch (error) {
|
|
95
|
-
console.error('Navigation creation error:', error instanceof Error ? error.message : String(error));
|
|
96
111
|
throw new Error(`Failed to create navigation: ${error instanceof Error ? error.message : String(error)}`);
|
|
97
112
|
}
|
|
98
113
|
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// src/components/navigation/system-types.ts
|
|
2
|
+
|
|
3
|
+
import { NavigationComponent, NavItemConfig } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Structure for navigation section with its items
|
|
7
|
+
*/
|
|
8
|
+
export interface NavigationSection {
|
|
9
|
+
/** Section label */
|
|
10
|
+
label: string;
|
|
11
|
+
|
|
12
|
+
/** Section icon HTML */
|
|
13
|
+
icon?: string;
|
|
14
|
+
|
|
15
|
+
/** Navigation items within this section */
|
|
16
|
+
items: NavItemConfig[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configuration for the navigation system
|
|
21
|
+
*/
|
|
22
|
+
export interface NavigationSystemConfig {
|
|
23
|
+
/** Item structure by section ID */
|
|
24
|
+
items: Record<string, NavigationSection>;
|
|
25
|
+
|
|
26
|
+
/** Initial active section */
|
|
27
|
+
activeSection?: string;
|
|
28
|
+
|
|
29
|
+
/** Initial active subsection */
|
|
30
|
+
activeSubsection?: string;
|
|
31
|
+
|
|
32
|
+
/** Whether to animate drawer opening/closing */
|
|
33
|
+
animateDrawer?: boolean;
|
|
34
|
+
|
|
35
|
+
/** Whether to show labels on rail */
|
|
36
|
+
showLabelsOnRail?: boolean;
|
|
37
|
+
|
|
38
|
+
/** Whether to hide drawer when an item is clicked */
|
|
39
|
+
hideDrawerOnClick?: boolean;
|
|
40
|
+
|
|
41
|
+
/** Delay before showing drawer on hover (ms) */
|
|
42
|
+
hoverDelay?: number;
|
|
43
|
+
|
|
44
|
+
/** Delay before hiding drawer on mouse leave (ms) */
|
|
45
|
+
closeDelay?: number;
|
|
46
|
+
|
|
47
|
+
/** Configuration options passed to rail component */
|
|
48
|
+
railOptions?: Record<string, any>;
|
|
49
|
+
|
|
50
|
+
/** Configuration options passed to drawer component */
|
|
51
|
+
drawerOptions?: Record<string, any>;
|
|
52
|
+
|
|
53
|
+
/** Enable debug logging */
|
|
54
|
+
debug?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Event data for section change events
|
|
59
|
+
*/
|
|
60
|
+
export interface SectionChangeEvent {
|
|
61
|
+
/** ID of the selected section */
|
|
62
|
+
section: string;
|
|
63
|
+
|
|
64
|
+
/** Section data */
|
|
65
|
+
sectionData: NavigationSection;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Event data for item selection events
|
|
70
|
+
*/
|
|
71
|
+
export interface ItemSelectEvent {
|
|
72
|
+
/** ID of the parent section */
|
|
73
|
+
section: string;
|
|
74
|
+
|
|
75
|
+
/** ID of the selected subsection */
|
|
76
|
+
subsection: string;
|
|
77
|
+
|
|
78
|
+
/** Selected item data */
|
|
79
|
+
item: any;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Navigation system API
|
|
84
|
+
*/
|
|
85
|
+
export interface NavigationSystem {
|
|
86
|
+
/** Initialize the navigation system */
|
|
87
|
+
initialize(): NavigationSystem;
|
|
88
|
+
|
|
89
|
+
/** Clean up resources */
|
|
90
|
+
cleanup(): void;
|
|
91
|
+
|
|
92
|
+
/** Navigate to a specific section and subsection */
|
|
93
|
+
navigateTo(section: string, subsection?: string): void;
|
|
94
|
+
|
|
95
|
+
/** Get the rail component */
|
|
96
|
+
getRail(): NavigationComponent | null;
|
|
97
|
+
|
|
98
|
+
/** Get the drawer component */
|
|
99
|
+
getDrawer(): NavigationComponent | null;
|
|
100
|
+
|
|
101
|
+
/** Get the active section */
|
|
102
|
+
getActiveSection(): string | null;
|
|
103
|
+
|
|
104
|
+
/** Get the active subsection */
|
|
105
|
+
getActiveSubsection(): string | null;
|
|
106
|
+
|
|
107
|
+
/** Show the drawer */
|
|
108
|
+
showDrawer(): void;
|
|
109
|
+
|
|
110
|
+
/** Hide the drawer */
|
|
111
|
+
hideDrawer(): void;
|
|
112
|
+
|
|
113
|
+
/** Check if drawer is visible */
|
|
114
|
+
isDrawerVisible(): boolean;
|
|
115
|
+
|
|
116
|
+
/** Configure the navigation system */
|
|
117
|
+
configure(config: Partial<NavigationSystemConfig>): NavigationSystem;
|
|
118
|
+
|
|
119
|
+
/** Event handler for section changes */
|
|
120
|
+
onSectionChange: ((section: string, subsection?: string) => void) | null;
|
|
121
|
+
|
|
122
|
+
/** Event handler for item selection */
|
|
123
|
+
onItemSelect: ((event: ItemSelectEvent) => void) | null;
|
|
124
|
+
}
|