mtrl 0.2.8 → 0.3.0
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 +4 -0
- package/package.json +1 -1
- package/src/components/button/button.ts +34 -5
- 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/core.ts +302 -0
- package/src/components/navigation/system/events.ts +240 -0
- package/src/components/navigation/system/index.ts +184 -0
- package/src/components/navigation/system/mobile.ts +278 -0
- package/src/components/navigation/system/state.ts +77 -0
- package/src/components/navigation/system/types.ts +364 -0
- package/src/components/slider/config.ts +20 -2
- package/src/components/slider/features/controller.ts +737 -0
- package/src/components/slider/features/handlers.ts +18 -16
- package/src/components/slider/features/index.ts +3 -2
- package/src/components/slider/features/range.ts +104 -0
- package/src/components/slider/schema.ts +141 -0
- package/src/components/slider/slider.ts +34 -13
- package/src/components/switch/api.ts +16 -0
- package/src/components/switch/config.ts +1 -18
- package/src/components/switch/features.ts +198 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.ts +3 -3
- package/src/components/switch/types.ts +14 -2
- 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 +45 -0
- package/src/core/composition/features/icon.ts +131 -0
- package/src/core/composition/features/index.ts +12 -0
- package/src/core/composition/features/label.ts +155 -0
- package/src/core/composition/features/layout.ts +47 -0
- package/src/core/composition/index.ts +26 -0
- package/src/core/index.ts +1 -1
- package/src/core/layout/README.md +350 -0
- package/src/core/layout/array.ts +181 -0
- package/src/core/layout/create.ts +55 -0
- package/src/core/layout/index.ts +26 -0
- package/src/core/layout/object.ts +124 -0
- package/src/core/layout/processor.ts +58 -0
- package/src/core/layout/result.ts +85 -0
- package/src/core/layout/types.ts +125 -0
- package/src/core/layout/utils.ts +136 -0
- package/src/index.ts +1 -0
- package/src/styles/abstract/_variables.scss +28 -0
- package/src/styles/components/_navigation-mobile.scss +244 -0
- package/src/styles/components/_navigation-system.scss +151 -0
- package/src/styles/components/_switch.scss +133 -69
- package/src/styles/components/_textfield.scss +259 -27
- 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
- package/src/core/layout/index.js +0 -95
|
@@ -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,302 @@
|
|
|
1
|
+
// src/components/navigation/system/core.ts
|
|
2
|
+
|
|
3
|
+
import { NavigationSystemState, NavigationSystemConfig, NavigationItem } from './types';
|
|
4
|
+
import { isMobileDevice } from '../../../core/utils/mobile';
|
|
5
|
+
import createNavigation from '../navigation';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Update drawer content for a specific section WITHOUT changing visibility
|
|
9
|
+
*
|
|
10
|
+
* @param state - System state
|
|
11
|
+
* @param sectionId - Section ID to display
|
|
12
|
+
* @param showDrawer - Function to show the drawer
|
|
13
|
+
* @param hideDrawer - Function to hide the drawer
|
|
14
|
+
*/
|
|
15
|
+
export const updateDrawerContent = (
|
|
16
|
+
state: NavigationSystemState,
|
|
17
|
+
sectionId: string,
|
|
18
|
+
showDrawer: () => void,
|
|
19
|
+
hideDrawer: () => void
|
|
20
|
+
): void => {
|
|
21
|
+
if (!state.drawer || !sectionId || !state.items[sectionId]) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Get section items
|
|
26
|
+
const sectionData = state.items[sectionId];
|
|
27
|
+
const items = sectionData.items || [];
|
|
28
|
+
|
|
29
|
+
// If no items, hide drawer and exit
|
|
30
|
+
if (items.length === 0) {
|
|
31
|
+
hideDrawer();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Clear existing drawer items first using the API
|
|
36
|
+
const currentItems = state.drawer.getAllItems();
|
|
37
|
+
if (currentItems?.length > 0) {
|
|
38
|
+
currentItems.forEach((item: any) => {
|
|
39
|
+
state.drawer.removeItem(item.config.id);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Add new items to drawer through the API
|
|
44
|
+
items.forEach((item: NavigationItem) => {
|
|
45
|
+
state.drawer.addItem(item);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Show the drawer
|
|
49
|
+
showDrawer();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates the rail navigation component
|
|
54
|
+
*
|
|
55
|
+
* @param state - System state
|
|
56
|
+
* @param config - System configuration
|
|
57
|
+
* @returns Rail navigation component
|
|
58
|
+
*/
|
|
59
|
+
export const createRailNavigation = (
|
|
60
|
+
state: NavigationSystemState,
|
|
61
|
+
config: any
|
|
62
|
+
): any => {
|
|
63
|
+
// Build rail items from sections
|
|
64
|
+
const railItems = Object.keys(state.items || {}).map(sectionId => ({
|
|
65
|
+
id: sectionId,
|
|
66
|
+
label: state.items[sectionId]?.label || sectionId,
|
|
67
|
+
icon: state.items[sectionId]?.icon || '',
|
|
68
|
+
active: sectionId === state.activeSection
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
// Create the rail component
|
|
72
|
+
const rail = createNavigation({
|
|
73
|
+
variant: 'rail',
|
|
74
|
+
position: 'left',
|
|
75
|
+
showLabels: config.showLabelsOnRail,
|
|
76
|
+
items: railItems,
|
|
77
|
+
...config.railOptions
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
document.body.appendChild(rail.element);
|
|
81
|
+
return rail;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Creates the drawer navigation component
|
|
86
|
+
*
|
|
87
|
+
* @param state - System state
|
|
88
|
+
* @param config - System configuration
|
|
89
|
+
* @returns Drawer navigation component
|
|
90
|
+
*/
|
|
91
|
+
export const createDrawerNavigation = (
|
|
92
|
+
state: NavigationSystemState,
|
|
93
|
+
config: any
|
|
94
|
+
): any => {
|
|
95
|
+
// Create the drawer component (initially empty)
|
|
96
|
+
const drawer = createNavigation({
|
|
97
|
+
variant: 'drawer',
|
|
98
|
+
position: 'left',
|
|
99
|
+
items: [], // Start empty
|
|
100
|
+
...config.drawerOptions
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
document.body.appendChild(drawer.element);
|
|
104
|
+
|
|
105
|
+
// Mark drawer with identifier
|
|
106
|
+
drawer.element.dataset.id = 'drawer';
|
|
107
|
+
|
|
108
|
+
// IMPORTANT: Make drawer initially hidden unless explicitly expanded
|
|
109
|
+
if (!config.expanded) {
|
|
110
|
+
drawer.element.classList.add('mtrl-nav--hidden');
|
|
111
|
+
drawer.element.setAttribute('aria-hidden', 'true');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return drawer;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Shows the drawer with mobile-specific behaviors
|
|
119
|
+
*
|
|
120
|
+
* @param state - System state
|
|
121
|
+
* @param mobileConfig - Mobile configuration
|
|
122
|
+
*/
|
|
123
|
+
export const showDrawer = (
|
|
124
|
+
state: NavigationSystemState,
|
|
125
|
+
mobileConfig: any
|
|
126
|
+
): void => {
|
|
127
|
+
if (!state.drawer) return;
|
|
128
|
+
|
|
129
|
+
state.drawer.element.classList.remove('mtrl-nav--hidden');
|
|
130
|
+
state.drawer.element.setAttribute('aria-hidden', 'false');
|
|
131
|
+
|
|
132
|
+
// Apply mobile-specific behaviors
|
|
133
|
+
if (state.isMobile) {
|
|
134
|
+
if (state.overlayElement) {
|
|
135
|
+
state.overlayElement.classList.add('active');
|
|
136
|
+
state.overlayElement.setAttribute('aria-hidden', 'false');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Lock body scroll if enabled
|
|
140
|
+
if (mobileConfig.lockBodyScroll) {
|
|
141
|
+
document.body.classList.add(mobileConfig.bodyLockClass);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Ensure close button is visible
|
|
145
|
+
if (state.closeButtonElement) {
|
|
146
|
+
state.closeButtonElement.style.display = 'flex';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Hides the drawer with mobile-specific behaviors
|
|
153
|
+
*
|
|
154
|
+
* @param state - System state
|
|
155
|
+
* @param mobileConfig - Mobile configuration
|
|
156
|
+
*/
|
|
157
|
+
export const hideDrawer = (
|
|
158
|
+
state: NavigationSystemState,
|
|
159
|
+
mobileConfig: any
|
|
160
|
+
): void => {
|
|
161
|
+
if (!state.drawer) return;
|
|
162
|
+
|
|
163
|
+
state.drawer.element.classList.add('mtrl-nav--hidden');
|
|
164
|
+
state.drawer.element.setAttribute('aria-hidden', 'true');
|
|
165
|
+
|
|
166
|
+
// Remove mobile-specific effects
|
|
167
|
+
if (state.overlayElement) {
|
|
168
|
+
state.overlayElement.classList.remove('active');
|
|
169
|
+
state.overlayElement.setAttribute('aria-hidden', 'true');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Unlock body scroll
|
|
173
|
+
if (mobileConfig.lockBodyScroll) {
|
|
174
|
+
document.body.classList.remove(mobileConfig.bodyLockClass);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Checks if drawer is visible
|
|
180
|
+
*
|
|
181
|
+
* @param state - System state
|
|
182
|
+
* @returns true if drawer is visible
|
|
183
|
+
*/
|
|
184
|
+
export const isDrawerVisible = (
|
|
185
|
+
state: NavigationSystemState
|
|
186
|
+
): boolean => {
|
|
187
|
+
if (!state.drawer) return false;
|
|
188
|
+
return !state.drawer.element.classList.contains('mtrl-nav--hidden');
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Checks and updates the mobile state
|
|
193
|
+
*
|
|
194
|
+
* @param state - System state
|
|
195
|
+
* @param mobileConfig - Mobile configuration
|
|
196
|
+
* @param setupMobileMode - Function to set up mobile mode
|
|
197
|
+
* @param teardownMobileMode - Function to tear down mobile mode
|
|
198
|
+
* @param systemApi - Reference to the public system API
|
|
199
|
+
*/
|
|
200
|
+
export const checkMobileState = (
|
|
201
|
+
state: NavigationSystemState,
|
|
202
|
+
mobileConfig: any,
|
|
203
|
+
setupMobileMode: () => void,
|
|
204
|
+
teardownMobileMode: () => void,
|
|
205
|
+
systemApi: any
|
|
206
|
+
): void => {
|
|
207
|
+
const prevState = state.isMobile;
|
|
208
|
+
state.isMobile = window.innerWidth <= mobileConfig.breakpoint || isMobileDevice();
|
|
209
|
+
|
|
210
|
+
// If state changed, adjust UI
|
|
211
|
+
if (prevState !== state.isMobile) {
|
|
212
|
+
if (state.isMobile) {
|
|
213
|
+
// Switched to mobile mode
|
|
214
|
+
setupMobileMode();
|
|
215
|
+
} else {
|
|
216
|
+
// Switched to desktop mode
|
|
217
|
+
teardownMobileMode();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Emit a view change event
|
|
221
|
+
if (systemApi.onViewChange) {
|
|
222
|
+
systemApi.onViewChange({
|
|
223
|
+
mobile: state.isMobile,
|
|
224
|
+
previousMobile: prevState,
|
|
225
|
+
width: window.innerWidth
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Clean up resources when the system is destroyed
|
|
233
|
+
*
|
|
234
|
+
* @param state - System state
|
|
235
|
+
*/
|
|
236
|
+
export const cleanupResources = (
|
|
237
|
+
state: NavigationSystemState
|
|
238
|
+
): void => {
|
|
239
|
+
// Clean up overlay
|
|
240
|
+
if (state.overlayElement && state.overlayElement.parentNode) {
|
|
241
|
+
state.overlayElement.parentNode.removeChild(state.overlayElement);
|
|
242
|
+
state.overlayElement = null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Destroy components
|
|
246
|
+
if (state.rail) {
|
|
247
|
+
state.rail.destroy();
|
|
248
|
+
state.rail = null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (state.drawer) {
|
|
252
|
+
state.drawer.destroy();
|
|
253
|
+
state.drawer = null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Reset state
|
|
257
|
+
state.activeSection = null;
|
|
258
|
+
state.activeSubsection = null;
|
|
259
|
+
state.mouseInDrawer = false;
|
|
260
|
+
state.mouseInRail = false;
|
|
261
|
+
state.processingChange = false;
|
|
262
|
+
state.isMobile = false;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Navigate to a specific section and subsection
|
|
267
|
+
*
|
|
268
|
+
* @param state - System state
|
|
269
|
+
* @param section - Section ID
|
|
270
|
+
* @param subsection - Subsection ID (optional)
|
|
271
|
+
* @param silent - Whether to suppress change events
|
|
272
|
+
*/
|
|
273
|
+
export const navigateTo = (
|
|
274
|
+
state: NavigationSystemState,
|
|
275
|
+
section: string,
|
|
276
|
+
subsection?: string,
|
|
277
|
+
silent?: boolean
|
|
278
|
+
): void => {
|
|
279
|
+
// Skip if section doesn't exist
|
|
280
|
+
if (!section || !state.items[section]) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check if we're already on this section and subsection
|
|
285
|
+
if (state.activeSection === section && state.activeSubsection === subsection) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Update active section
|
|
290
|
+
state.activeSection = section;
|
|
291
|
+
|
|
292
|
+
// Update rail if it exists
|
|
293
|
+
if (state.rail) {
|
|
294
|
+
state.rail.setActive(section, silent);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Update active subsection if specified
|
|
298
|
+
if (subsection && state.drawer) {
|
|
299
|
+
state.activeSubsection = subsection;
|
|
300
|
+
state.drawer.setActive(subsection, silent);
|
|
301
|
+
}
|
|
302
|
+
};
|