mtrl 0.2.9 → 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 +2 -0
- package/package.json +1 -1
- package/src/components/button/button.ts +34 -5
- 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 +2 -2
- package/src/components/slider/features/controller.ts +1 -25
- package/src/components/slider/features/handlers.ts +0 -1
- package/src/components/slider/features/range.ts +7 -7
- package/src/components/slider/{structure.ts → schema.ts} +2 -13
- package/src/components/slider/slider.ts +3 -2
- 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/core/composition/features/dom.ts +26 -14
- package/src/core/composition/features/icon.ts +18 -18
- package/src/core/composition/features/index.ts +3 -2
- package/src/core/composition/features/label.ts +16 -17
- package/src/core/composition/features/layout.ts +47 -0
- package/src/core/composition/index.ts +4 -4
- 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/styles/abstract/_variables.scss +28 -0
- package/src/styles/components/_switch.scss +133 -69
- package/src/styles/components/_textfield.scss +9 -16
- package/src/components/navigation/system-types.ts +0 -124
- package/src/components/navigation/system.ts +0 -776
- package/src/core/composition/features/structure.ts +0 -22
- package/src/core/layout/index.js +0 -95
- package/src/core/structure.ts +0 -288
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// src/components/navigation/system/index.ts
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
NavigationSystemConfig,
|
|
5
|
+
NavigationSystemState,
|
|
6
|
+
NavigationSystem,
|
|
7
|
+
ViewChangeEvent
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createInitialState,
|
|
12
|
+
createConfig,
|
|
13
|
+
createMobileConfig
|
|
14
|
+
} from './state';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createRailNavigation,
|
|
18
|
+
createDrawerNavigation,
|
|
19
|
+
updateDrawerContent,
|
|
20
|
+
showDrawer as showDrawerCore,
|
|
21
|
+
hideDrawer as hideDrawerCore,
|
|
22
|
+
isDrawerVisible as isDrawerVisibleCore,
|
|
23
|
+
checkMobileState as checkMobileStateCore,
|
|
24
|
+
cleanupResources,
|
|
25
|
+
navigateTo as navigateToCore
|
|
26
|
+
} from './core';
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
registerRailEvents,
|
|
30
|
+
registerDrawerEvents,
|
|
31
|
+
setupResponsiveHandling,
|
|
32
|
+
cleanupEvents
|
|
33
|
+
} from './events';
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
setupMobileMode as setupMobileModeCore,
|
|
37
|
+
teardownMobileMode
|
|
38
|
+
} from './mobile';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a complete navigation system with synchronized rail and drawer components
|
|
42
|
+
*
|
|
43
|
+
* @param options - System configuration options
|
|
44
|
+
* @returns Navigation system API
|
|
45
|
+
*/
|
|
46
|
+
export const createNavigationSystem = (options: NavigationSystemConfig = {}): NavigationSystem => {
|
|
47
|
+
// Initialize state and configuration
|
|
48
|
+
const state = createInitialState(options);
|
|
49
|
+
const config = createConfig(options);
|
|
50
|
+
const mobileConfig = createMobileConfig(options);
|
|
51
|
+
|
|
52
|
+
// Create system API object with placeholders
|
|
53
|
+
const system: NavigationSystem = {
|
|
54
|
+
initialize: () => system,
|
|
55
|
+
cleanup: () => {},
|
|
56
|
+
navigateTo: () => {},
|
|
57
|
+
getRail: () => state.rail,
|
|
58
|
+
getDrawer: () => state.drawer,
|
|
59
|
+
getActiveSection: () => state.activeSection,
|
|
60
|
+
getActiveSubsection: () => state.activeSubsection,
|
|
61
|
+
showDrawer: () => {},
|
|
62
|
+
hideDrawer: () => {},
|
|
63
|
+
isDrawerVisible: () => false,
|
|
64
|
+
configure: () => system,
|
|
65
|
+
setProcessingChange: () => {},
|
|
66
|
+
isProcessingChange: () => false,
|
|
67
|
+
isMobile: () => state.isMobile,
|
|
68
|
+
checkMobileState: () => {},
|
|
69
|
+
onSectionChange: undefined,
|
|
70
|
+
onItemSelect: undefined,
|
|
71
|
+
onViewChange: undefined
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Implementation functions that use the state
|
|
75
|
+
const showDrawer = () => showDrawerCore(state, mobileConfig);
|
|
76
|
+
const hideDrawer = () => hideDrawerCore(state, mobileConfig);
|
|
77
|
+
const isDrawerVisible = () => isDrawerVisibleCore(state);
|
|
78
|
+
|
|
79
|
+
const updateDrawerContentWrapper = (sectionId: string) => {
|
|
80
|
+
updateDrawerContent(state, sectionId, showDrawer, hideDrawer);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const setupMobileMode = () => {
|
|
84
|
+
setupMobileModeCore(
|
|
85
|
+
state,
|
|
86
|
+
mobileConfig,
|
|
87
|
+
hideDrawer,
|
|
88
|
+
isDrawerVisible,
|
|
89
|
+
showDrawer
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const checkMobileState = () => {
|
|
94
|
+
checkMobileStateCore(
|
|
95
|
+
state,
|
|
96
|
+
mobileConfig,
|
|
97
|
+
setupMobileMode,
|
|
98
|
+
() => teardownMobileMode(state, mobileConfig),
|
|
99
|
+
system
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const navigateTo = (section: string, subsection?: string, silent?: boolean) => {
|
|
104
|
+
navigateToCore(state, section, subsection, silent);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Implementation of the initialize method
|
|
108
|
+
const initialize = (): NavigationSystem => {
|
|
109
|
+
// Create rail component
|
|
110
|
+
state.rail = createRailNavigation(state, config);
|
|
111
|
+
|
|
112
|
+
// Create drawer component
|
|
113
|
+
state.drawer = createDrawerNavigation(state, config);
|
|
114
|
+
|
|
115
|
+
// Register event handlers
|
|
116
|
+
registerRailEvents(state, config, updateDrawerContentWrapper, showDrawer, hideDrawer, system);
|
|
117
|
+
registerDrawerEvents(state, config, hideDrawer, system);
|
|
118
|
+
|
|
119
|
+
// Set up responsive behavior
|
|
120
|
+
setupResponsiveHandling(state, checkMobileState);
|
|
121
|
+
|
|
122
|
+
// Set active section if specified
|
|
123
|
+
if (options.activeSection && state.items[options.activeSection]) {
|
|
124
|
+
state.activeSection = options.activeSection;
|
|
125
|
+
|
|
126
|
+
if (state.rail) {
|
|
127
|
+
state.rail.setActive(options.activeSection);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Update drawer content without showing it
|
|
131
|
+
updateDrawerContentWrapper(options.activeSection);
|
|
132
|
+
|
|
133
|
+
// Only show drawer if expanded is explicitly true
|
|
134
|
+
if (options.expanded === true) {
|
|
135
|
+
showDrawer();
|
|
136
|
+
} else {
|
|
137
|
+
// Explicitly ensure drawer is hidden
|
|
138
|
+
hideDrawer();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check initial mobile state
|
|
143
|
+
checkMobileState();
|
|
144
|
+
|
|
145
|
+
return system;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Implementation of the cleanup method
|
|
149
|
+
const cleanup = (): void => {
|
|
150
|
+
cleanupEvents(state, checkMobileState);
|
|
151
|
+
cleanupResources(state);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Configure method implementation
|
|
155
|
+
const configure = (newConfig: Partial<NavigationSystemConfig>): NavigationSystem => {
|
|
156
|
+
Object.assign(options, newConfig);
|
|
157
|
+
Object.assign(config, createConfig({...options, ...newConfig}));
|
|
158
|
+
Object.assign(mobileConfig, createMobileConfig({...options, ...newConfig}));
|
|
159
|
+
return system;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Assign implementations to system object
|
|
163
|
+
system.initialize = initialize;
|
|
164
|
+
system.cleanup = cleanup;
|
|
165
|
+
system.navigateTo = navigateTo;
|
|
166
|
+
system.showDrawer = showDrawer;
|
|
167
|
+
system.hideDrawer = hideDrawer;
|
|
168
|
+
system.isDrawerVisible = isDrawerVisible;
|
|
169
|
+
system.configure = configure;
|
|
170
|
+
system.setProcessingChange = (isProcessing: boolean) => {
|
|
171
|
+
state.processingChange = isProcessing;
|
|
172
|
+
};
|
|
173
|
+
system.isProcessingChange = () => state.processingChange;
|
|
174
|
+
system.isMobile = () => state.isMobile;
|
|
175
|
+
system.checkMobileState = checkMobileState;
|
|
176
|
+
|
|
177
|
+
// Return the uninitialized system
|
|
178
|
+
return system;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export default createNavigationSystem;
|
|
182
|
+
|
|
183
|
+
// Re-export types for external use
|
|
184
|
+
export * from './types';
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// src/components/navigation/system/mobile.ts
|
|
2
|
+
|
|
3
|
+
import { NavigationSystemState } from './types';
|
|
4
|
+
import {
|
|
5
|
+
hasTouchSupport,
|
|
6
|
+
normalizeEvent,
|
|
7
|
+
TOUCH_CONFIG,
|
|
8
|
+
TOUCH_TARGETS
|
|
9
|
+
} from '../../../core/utils/mobile';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates and appends overlay element for mobile
|
|
13
|
+
*
|
|
14
|
+
* @param state - System state
|
|
15
|
+
* @param mobileConfig - Mobile configuration
|
|
16
|
+
* @param hideDrawer - Function to hide the drawer
|
|
17
|
+
* @returns Overlay element
|
|
18
|
+
*/
|
|
19
|
+
export const createOverlay = (
|
|
20
|
+
state: NavigationSystemState,
|
|
21
|
+
mobileConfig: any,
|
|
22
|
+
hideDrawer: () => void
|
|
23
|
+
): HTMLElement => {
|
|
24
|
+
if (state.overlayElement) return state.overlayElement;
|
|
25
|
+
|
|
26
|
+
state.overlayElement = document.createElement('div');
|
|
27
|
+
state.overlayElement.className = mobileConfig.overlayClass;
|
|
28
|
+
state.overlayElement.setAttribute('aria-hidden', 'true');
|
|
29
|
+
document.body.appendChild(state.overlayElement);
|
|
30
|
+
|
|
31
|
+
state.overlayElement.addEventListener('click', (event) => {
|
|
32
|
+
if (event.target === state.overlayElement) {
|
|
33
|
+
hideDrawer();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return state.overlayElement;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates and adds close button to the drawer
|
|
42
|
+
*
|
|
43
|
+
* @param state - System state
|
|
44
|
+
* @param mobileConfig - Mobile configuration
|
|
45
|
+
* @param hideDrawer - Function to hide the drawer
|
|
46
|
+
* @returns Close button element
|
|
47
|
+
*/
|
|
48
|
+
export const createCloseButton = (
|
|
49
|
+
state: NavigationSystemState,
|
|
50
|
+
mobileConfig: any,
|
|
51
|
+
hideDrawer: () => void
|
|
52
|
+
): HTMLElement | null => {
|
|
53
|
+
if (!state.drawer || state.closeButtonElement) return null;
|
|
54
|
+
|
|
55
|
+
state.closeButtonElement = document.createElement('button');
|
|
56
|
+
state.closeButtonElement.className = mobileConfig.closeButtonClass;
|
|
57
|
+
state.closeButtonElement.setAttribute('aria-label', 'Close navigation');
|
|
58
|
+
state.closeButtonElement.innerHTML = `
|
|
59
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
60
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
61
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
62
|
+
</svg>
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
// Handle click event
|
|
66
|
+
state.closeButtonElement.addEventListener('click', () => {
|
|
67
|
+
hideDrawer();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Apply touch-friendly styles if needed
|
|
71
|
+
if (hasTouchSupport() && mobileConfig.optimizeForTouch) {
|
|
72
|
+
state.closeButtonElement.style.minWidth = `${TOUCH_TARGETS.COMFORTABLE}px`;
|
|
73
|
+
state.closeButtonElement.style.minHeight = `${TOUCH_TARGETS.COMFORTABLE}px`;
|
|
74
|
+
|
|
75
|
+
// Add touch feedback
|
|
76
|
+
state.closeButtonElement.addEventListener('touchstart', () => {
|
|
77
|
+
state.closeButtonElement.classList.add('active');
|
|
78
|
+
}, { passive: true });
|
|
79
|
+
|
|
80
|
+
state.closeButtonElement.addEventListener('touchend', () => {
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
state.closeButtonElement.classList.remove('active');
|
|
83
|
+
}, TOUCH_CONFIG.FEEDBACK_DURATION);
|
|
84
|
+
}, { passive: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
state.drawer.element.appendChild(state.closeButtonElement);
|
|
88
|
+
return state.closeButtonElement;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Sets up mobile mode features
|
|
93
|
+
*
|
|
94
|
+
* @param state - System state
|
|
95
|
+
* @param mobileConfig - Mobile configuration
|
|
96
|
+
* @param hideDrawer - Function to hide the drawer
|
|
97
|
+
* @param isDrawerVisible - Function to check if drawer is visible
|
|
98
|
+
*/
|
|
99
|
+
export const setupMobileMode = (
|
|
100
|
+
state: NavigationSystemState,
|
|
101
|
+
mobileConfig: any,
|
|
102
|
+
hideDrawer: () => void,
|
|
103
|
+
isDrawerVisible: () => boolean
|
|
104
|
+
): void => {
|
|
105
|
+
const drawer = state.drawer;
|
|
106
|
+
const rail = state.rail;
|
|
107
|
+
|
|
108
|
+
if (!drawer || !rail) return;
|
|
109
|
+
|
|
110
|
+
// Create mobile UI elements
|
|
111
|
+
createOverlay(state, mobileConfig, hideDrawer);
|
|
112
|
+
createCloseButton(state, mobileConfig, hideDrawer);
|
|
113
|
+
|
|
114
|
+
// Setup outside click handling
|
|
115
|
+
setupOutsideClickHandling(state, mobileConfig, hideDrawer, isDrawerVisible);
|
|
116
|
+
|
|
117
|
+
// Setup touch gestures if enabled
|
|
118
|
+
if (mobileConfig.enableSwipeGestures && hasTouchSupport()) {
|
|
119
|
+
setupTouchGestures(state, hideDrawer, isDrawerVisible);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Hide drawer initially in mobile mode
|
|
123
|
+
hideDrawer();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Sets up outside click handling for mobile
|
|
128
|
+
*
|
|
129
|
+
* @param state - System state
|
|
130
|
+
* @param mobileConfig - Mobile configuration
|
|
131
|
+
* @param hideDrawer - Function to hide the drawer
|
|
132
|
+
* @param isDrawerVisible - Function to check if drawer is visible
|
|
133
|
+
*/
|
|
134
|
+
export const setupOutsideClickHandling = (
|
|
135
|
+
state: NavigationSystemState,
|
|
136
|
+
mobileConfig: any,
|
|
137
|
+
hideDrawer: () => void,
|
|
138
|
+
isDrawerVisible: () => boolean
|
|
139
|
+
): void => {
|
|
140
|
+
if (!mobileConfig.hideOnClickOutside) return;
|
|
141
|
+
|
|
142
|
+
// Only set up once
|
|
143
|
+
if (state.outsideClickHandlerSet) return;
|
|
144
|
+
state.outsideClickHandlerSet = true;
|
|
145
|
+
|
|
146
|
+
// Use either click or touchend event depending on device capability
|
|
147
|
+
const eventType = hasTouchSupport() ? 'touchend' : 'click';
|
|
148
|
+
|
|
149
|
+
// The handler function
|
|
150
|
+
const handleOutsideClick = (event: Event) => {
|
|
151
|
+
if (!state.isMobile || !isDrawerVisible()) return;
|
|
152
|
+
|
|
153
|
+
const normalizedEvent = normalizeEvent(event);
|
|
154
|
+
const target = normalizedEvent.target as HTMLElement;
|
|
155
|
+
|
|
156
|
+
// Don't close if clicking on drawer, rail, or excluded elements
|
|
157
|
+
if (state.drawer.element.contains(target) ||
|
|
158
|
+
state.rail.element.contains(target)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Close drawer - it's an outside click/touch
|
|
163
|
+
hideDrawer();
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Store handler for cleanup
|
|
167
|
+
state.outsideClickHandler = handleOutsideClick;
|
|
168
|
+
|
|
169
|
+
// Add listener
|
|
170
|
+
document.addEventListener(eventType, handleOutsideClick,
|
|
171
|
+
hasTouchSupport() ? { passive: true } : false);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Sets up touch gestures for mobile
|
|
176
|
+
*
|
|
177
|
+
* @param state - System state
|
|
178
|
+
* @param hideDrawer - Function to hide the drawer
|
|
179
|
+
* @param isDrawerVisible - Function to check if drawer is visible
|
|
180
|
+
*/
|
|
181
|
+
export const setupTouchGestures = (
|
|
182
|
+
state: NavigationSystemState,
|
|
183
|
+
hideDrawer: () => void,
|
|
184
|
+
isDrawerVisible: () => boolean,
|
|
185
|
+
showDrawer?: () => void
|
|
186
|
+
): void => {
|
|
187
|
+
const drawer = state.drawer;
|
|
188
|
+
const rail = state.rail;
|
|
189
|
+
|
|
190
|
+
if (!drawer || !rail) return;
|
|
191
|
+
|
|
192
|
+
let touchStartX = 0;
|
|
193
|
+
let touchStartY = 0;
|
|
194
|
+
|
|
195
|
+
// Rail swipe right to open drawer
|
|
196
|
+
rail.element.addEventListener('touchstart', (event: TouchEvent) => {
|
|
197
|
+
const touch = event.touches[0];
|
|
198
|
+
touchStartX = touch.clientX;
|
|
199
|
+
touchStartY = touch.clientY;
|
|
200
|
+
}, { passive: true });
|
|
201
|
+
|
|
202
|
+
rail.element.addEventListener('touchmove', (event: TouchEvent) => {
|
|
203
|
+
if (!state.isMobile || isDrawerVisible() || !showDrawer) return;
|
|
204
|
+
|
|
205
|
+
const touch = event.touches[0];
|
|
206
|
+
const deltaX = touch.clientX - touchStartX;
|
|
207
|
+
const deltaY = touch.clientY - touchStartY;
|
|
208
|
+
|
|
209
|
+
// Only consider horizontal swipes
|
|
210
|
+
if (Math.abs(deltaX) > Math.abs(deltaY) &&
|
|
211
|
+
deltaX > TOUCH_CONFIG.SWIPE_THRESHOLD) {
|
|
212
|
+
showDrawer();
|
|
213
|
+
}
|
|
214
|
+
}, { passive: true });
|
|
215
|
+
|
|
216
|
+
// Drawer swipe left to close
|
|
217
|
+
drawer.element.addEventListener('touchstart', (event: TouchEvent) => {
|
|
218
|
+
const touch = event.touches[0];
|
|
219
|
+
touchStartX = touch.clientX;
|
|
220
|
+
touchStartY = touch.clientY;
|
|
221
|
+
}, { passive: true });
|
|
222
|
+
|
|
223
|
+
// Use touchmove with transform for visual feedback
|
|
224
|
+
drawer.element.addEventListener('touchmove', (event: TouchEvent) => {
|
|
225
|
+
if (!state.isMobile || !isDrawerVisible()) return;
|
|
226
|
+
|
|
227
|
+
const touch = event.touches[0];
|
|
228
|
+
const deltaX = touch.clientX - touchStartX;
|
|
229
|
+
|
|
230
|
+
// Only apply transform for leftward swipes
|
|
231
|
+
if (deltaX < 0) {
|
|
232
|
+
// Apply transform with resistance
|
|
233
|
+
drawer.element.style.transform = `translateX(${deltaX / 2}px)`;
|
|
234
|
+
|
|
235
|
+
// Close if threshold reached
|
|
236
|
+
if (deltaX < -TOUCH_CONFIG.SWIPE_THRESHOLD) {
|
|
237
|
+
hideDrawer();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}, { passive: true });
|
|
241
|
+
|
|
242
|
+
// Reset transforms when touch ends
|
|
243
|
+
drawer.element.addEventListener('touchend', () => {
|
|
244
|
+
if (drawer.element.style.transform) {
|
|
245
|
+
drawer.element.style.transition = 'transform 0.2s ease';
|
|
246
|
+
drawer.element.style.transform = '';
|
|
247
|
+
|
|
248
|
+
setTimeout(() => {
|
|
249
|
+
drawer.element.style.transition = '';
|
|
250
|
+
}, 200);
|
|
251
|
+
}
|
|
252
|
+
}, { passive: true });
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Tears down mobile-specific features
|
|
257
|
+
*
|
|
258
|
+
* @param state - System state
|
|
259
|
+
* @param mobileConfig - Mobile configuration
|
|
260
|
+
*/
|
|
261
|
+
export const teardownMobileMode = (
|
|
262
|
+
state: NavigationSystemState,
|
|
263
|
+
mobileConfig: any
|
|
264
|
+
): void => {
|
|
265
|
+
// Hide overlay
|
|
266
|
+
if (state.overlayElement) {
|
|
267
|
+
state.overlayElement.classList.remove('active');
|
|
268
|
+
state.overlayElement.setAttribute('aria-hidden', 'true');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Hide close button
|
|
272
|
+
if (state.closeButtonElement) {
|
|
273
|
+
state.closeButtonElement.style.display = 'none';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Remove body scroll lock if applied
|
|
277
|
+
document.body.classList.remove(mobileConfig.bodyLockClass);
|
|
278
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/components/navigation/system/state.ts
|
|
2
|
+
|
|
3
|
+
import { NavigationSystemConfig, NavigationSystemState } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create the initial state for the navigation system
|
|
7
|
+
*
|
|
8
|
+
* @param options - Configuration options
|
|
9
|
+
* @returns Initial system state
|
|
10
|
+
*/
|
|
11
|
+
export const createInitialState = (options: NavigationSystemConfig = {}): NavigationSystemState => {
|
|
12
|
+
return {
|
|
13
|
+
rail: null,
|
|
14
|
+
drawer: null,
|
|
15
|
+
activeSection: options.activeSection || null,
|
|
16
|
+
activeSubsection: options.activeSubsection || null,
|
|
17
|
+
items: options.items || {},
|
|
18
|
+
mouseInDrawer: false,
|
|
19
|
+
mouseInRail: false,
|
|
20
|
+
hoverTimer: null,
|
|
21
|
+
closeTimer: null,
|
|
22
|
+
processingChange: false,
|
|
23
|
+
isMobile: false,
|
|
24
|
+
overlayElement: null,
|
|
25
|
+
closeButtonElement: null,
|
|
26
|
+
resizeObserver: null,
|
|
27
|
+
outsideClickHandler: null,
|
|
28
|
+
outsideClickHandlerSet: false
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create default configuration with sensible defaults
|
|
34
|
+
*
|
|
35
|
+
* @param options - User-provided configuration
|
|
36
|
+
* @returns Complete configuration with defaults
|
|
37
|
+
*/
|
|
38
|
+
export const createConfig = (options: NavigationSystemConfig = {}): Required<Pick<NavigationSystemConfig,
|
|
39
|
+
'animateDrawer' | 'showLabelsOnRail' | 'hideDrawerOnClick' | 'expanded' |
|
|
40
|
+
'hoverDelay' | 'closeDelay' | 'railOptions' | 'drawerOptions'>> => {
|
|
41
|
+
return {
|
|
42
|
+
// Display options
|
|
43
|
+
animateDrawer: options.animateDrawer !== false,
|
|
44
|
+
showLabelsOnRail: options.showLabelsOnRail !== false,
|
|
45
|
+
hideDrawerOnClick: options.hideDrawerOnClick || false,
|
|
46
|
+
expanded: options.expanded === true,
|
|
47
|
+
|
|
48
|
+
// Timing options (ms)
|
|
49
|
+
hoverDelay: options.hoverDelay || 200,
|
|
50
|
+
closeDelay: options.closeDelay || 100,
|
|
51
|
+
|
|
52
|
+
// Component options
|
|
53
|
+
railOptions: options.railOptions || {},
|
|
54
|
+
drawerOptions: options.drawerOptions || {}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create mobile configuration with defaults
|
|
60
|
+
*
|
|
61
|
+
* @param options - User-provided configuration
|
|
62
|
+
* @returns Mobile-specific configuration
|
|
63
|
+
*/
|
|
64
|
+
export const createMobileConfig = (options: NavigationSystemConfig = {}): Required<Pick<NavigationSystemConfig,
|
|
65
|
+
'breakpoint' | 'lockBodyScroll' | 'hideOnClickOutside' | 'enableSwipeGestures' |
|
|
66
|
+
'optimizeForTouch' | 'overlayClass' | 'closeButtonClass' | 'bodyLockClass'>> => {
|
|
67
|
+
return {
|
|
68
|
+
breakpoint: options.breakpoint || 960,
|
|
69
|
+
lockBodyScroll: options.lockBodyScroll !== false,
|
|
70
|
+
hideOnClickOutside: options.hideOnClickOutside !== false,
|
|
71
|
+
enableSwipeGestures: options.enableSwipeGestures !== false,
|
|
72
|
+
optimizeForTouch: options.optimizeForTouch !== false,
|
|
73
|
+
overlayClass: options.overlayClass || 'mtrl-nav-overlay',
|
|
74
|
+
closeButtonClass: options.closeButtonClass || 'mtrl-nav-close-btn',
|
|
75
|
+
bodyLockClass: options.bodyLockClass || 'mtrl-body-drawer-open'
|
|
76
|
+
};
|
|
77
|
+
};
|