UNPKG

muspe-cli

Version:

MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo

590 lines (474 loc) 13.9 kB
// MusPE Advanced Component System - React/Vue-like components with unique mobile-first approach class MusPEComponent { constructor(props = {}) { this.props = reactive(props); this.state = reactive({}); this.refs = {}; this.effects = []; this.watchers = []; this.isMounted = false; this.isDestroyed = false; this.element = null; this.vnode = null; this.prevVNode = null; // Unique MusPE features this.touchHandlers = new Map(); this.gestureRecognizers = new Map(); this.animations = new Set(); // Auto-bind methods this.autoBind(); // Call setup if available (Vue 3-like composition API) if (this.setup) { const setupResult = this.setup(this.props); if (setupResult && typeof setupResult === 'object') { Object.assign(this.state, setupResult); } } } // Auto-bind all methods to this instance autoBind() { const proto = Object.getPrototypeOf(this); Object.getOwnPropertyNames(proto).forEach(key => { if (key !== 'constructor' && typeof this[key] === 'function') { this[key] = this[key].bind(this); } }); } // Lifecycle: Before mount (like Vue beforeMount) beforeMount() { // Override in subclasses } // Lifecycle: After mount (like React componentDidMount) mounted() { // Override in subclasses } // Lifecycle: Before update beforeUpdate() { // Override in subclasses } // Lifecycle: After update updated() { // Override in subclasses } // Lifecycle: Before unmount beforeUnmount() { // Override in subclasses } // Lifecycle: After unmount unmounted() { // Override in subclasses } // Error handling errorCaptured(error, instance, info) { console.error('Component error:', error, info); return false; } // Render method - must be implemented by subclasses render() { throw new Error('Component must implement render() method'); } // Mount component to container mount(container) { if (this.isMounted) return this; if (typeof container === 'string') { container = document.querySelector(container); } if (!container) { throw new Error('Invalid container for mounting component'); } try { this.beforeMount(); // Render virtual DOM this.vnode = this.render(); // Create real DOM this.element = vdom.createElement(this.vnode); // Mount to container container.appendChild(this.element); this.isMounted = true; // Set up reactivity this.setupReactivity(); // Mobile-specific setup this.setupMobileFeatures(); this.mounted(); } catch (error) { this.errorCaptured(error, this, 'mount'); } return this; } // Unmount component unmount() { if (!this.isMounted || this.isDestroyed) return this; try { this.beforeUnmount(); // Cleanup effects and watchers this.cleanup(); // Cleanup mobile features this.cleanupMobileFeatures(); // Remove from DOM if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); } this.isMounted = false; this.isDestroyed = true; this.unmounted(); } catch (error) { this.errorCaptured(error, this, 'unmount'); } return this; } // Update component (re-render) update() { if (!this.isMounted || this.isDestroyed) return this; try { this.beforeUpdate(); // Store previous vnode this.prevVNode = this.vnode; // Render new vnode this.vnode = this.render(); // Patch DOM vdom.patch(this.prevVNode, this.vnode, this.element.parentNode); this.updated(); } catch (error) { this.errorCaptured(error, this, 'update'); } return this; } // Setup reactivity for automatic updates setupReactivity() { // Watch for state changes this.effects.push( effect(() => { if (this.isMounted && !this.isDestroyed) { this.update(); } }, { scheduler: this.scheduleUpdate.bind(this) }) ); } // Schedule update (for batching) scheduleUpdate() { if (this.updateScheduled) return; this.updateScheduled = true; // Use requestAnimationFrame for smooth updates requestAnimationFrame(() => { this.updateScheduled = false; this.update(); }); } // Mobile-specific features setup setupMobileFeatures() { if (!MusPE.env.isMobile) return; // Setup touch events this.setupTouchEvents(); // Setup gesture recognition this.setupGestureRecognition(); // Setup haptic feedback this.setupHapticFeedback(); } // Touch events setup setupTouchEvents() { const element = this.element; if (!element) return; // Prevent default touch behaviors element.addEventListener('touchstart', this.handleTouchStart, { passive: false }); element.addEventListener('touchmove', this.handleTouchMove, { passive: false }); element.addEventListener('touchend', this.handleTouchEnd, { passive: false }); } handleTouchStart(e) { this.touchStartTime = Date.now(); this.touchStartPos = { x: e.touches[0].clientX, y: e.touches[0].clientY }; // Call onTouchStart if available if (this.onTouchStart) { this.onTouchStart(e); } } handleTouchMove(e) { if (this.onTouchMove) { this.onTouchMove(e); } } handleTouchEnd(e) { const touchDuration = Date.now() - this.touchStartTime; const touchEndPos = { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY }; // Detect tap if (touchDuration < 300 && this.getDistance(this.touchStartPos, touchEndPos) < 10) { if (this.onTap) { this.onTap(e); } } // Call onTouchEnd if available if (this.onTouchEnd) { this.onTouchEnd(e); } } // Gesture recognition setup setupGestureRecognition() { // Swipe detection this.recognizeSwipe(); // Pinch detection this.recognizePinch(); // Long press detection this.recognizeLongPress(); } recognizeSwipe() { let startX, startY, startTime; this.element.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; startY = e.touches[0].clientY; startTime = Date.now(); }); this.element.addEventListener('touchend', (e) => { const endX = e.changedTouches[0].clientX; const endY = e.changedTouches[0].clientY; const endTime = Date.now(); const diffX = endX - startX; const diffY = endY - startY; const duration = endTime - startTime; if (duration < 500 && Math.abs(diffX) > 50) { const direction = diffX > 0 ? 'right' : 'left'; if (this.onSwipe) { this.onSwipe(direction, { diffX, diffY, duration }); } } }); } recognizePinch() { let initialDistance = 0; this.element.addEventListener('touchstart', (e) => { if (e.touches.length === 2) { initialDistance = this.getDistance( { x: e.touches[0].clientX, y: e.touches[0].clientY }, { x: e.touches[1].clientX, y: e.touches[1].clientY } ); } }); this.element.addEventListener('touchmove', (e) => { if (e.touches.length === 2) { const currentDistance = this.getDistance( { x: e.touches[0].clientX, y: e.touches[0].clientY }, { x: e.touches[1].clientX, y: e.touches[1].clientY } ); const scale = currentDistance / initialDistance; if (this.onPinch) { this.onPinch(scale, e); } } }); } recognizeLongPress() { let pressTimer; this.element.addEventListener('touchstart', (e) => { pressTimer = setTimeout(() => { if (this.onLongPress) { this.onLongPress(e); } }, 750); }); this.element.addEventListener('touchend', () => { clearTimeout(pressTimer); }); this.element.addEventListener('touchmove', () => { clearTimeout(pressTimer); }); } // Haptic feedback setup setupHapticFeedback() { if ('vibrate' in navigator) { this.haptic = { light: () => navigator.vibrate(10), medium: () => navigator.vibrate(20), heavy: () => navigator.vibrate(50), success: () => navigator.vibrate([10, 50, 10]), error: () => navigator.vibrate([50, 50, 50]) }; } } // Cleanup mobile features cleanupMobileFeatures() { // Stop all animations this.animations.forEach(animation => { if (animation.cancel) animation.cancel(); }); this.animations.clear(); // Clear touch handlers this.touchHandlers.clear(); // Clear gesture recognizers this.gestureRecognizers.clear(); } // Utility methods getDistance(pos1, pos2) { return Math.sqrt( Math.pow(pos2.x - pos1.x, 2) + Math.pow(pos2.y - pos1.y, 2) ); } // Animation helpers animate(element, keyframes, options = {}) { const animation = element.animate(keyframes, { duration: 300, easing: 'ease-out', fill: 'forwards', ...options }); this.animations.add(animation); animation.addEventListener('finish', () => { this.animations.delete(animation); }); return animation; } // Fade animations fadeIn(duration = 300) { return this.animate(this.element, [ { opacity: 0 }, { opacity: 1 } ], { duration }); } fadeOut(duration = 300) { return this.animate(this.element, [ { opacity: 1 }, { opacity: 0 } ], { duration }); } // Slide animations slideIn(direction = 'left', duration = 300) { const transforms = { left: ['translateX(-100%)', 'translateX(0)'], right: ['translateX(100%)', 'translateX(0)'], up: ['translateY(-100%)', 'translateY(0)'], down: ['translateY(100%)', 'translateY(0)'] }; return this.animate(this.element, [ { transform: transforms[direction][0] }, { transform: transforms[direction][1] } ], { duration }); } // State helpers setState(newState) { Object.assign(this.state, newState); } // Ref helpers ref(name) { return (element) => { this.refs[name] = element; }; } // Event helpers emit(event, data) { MusPE.emit(event, { component: this, data }); } // Cleanup effects and watchers cleanup() { this.effects.forEach(effect => { if (effect.stop) effect.stop(); }); this.effects = []; this.watchers.forEach(watcher => { if (watcher.stop) watcher.stop(); }); this.watchers = []; } // Hook-like methods useState(initialValue) { const state = ref(initialValue); return [ () => state.value, (newValue) => { state.value = newValue; } ]; } useEffect(fn, deps = []) { const effectFn = effect(fn, { scheduler: (effect) => { // Check dependencies if (deps.length === 0 || this.dependenciesChanged(deps)) { effect.run(); } } }); this.effects.push(effectFn); return effectFn; } useWatch(source, callback, options = {}) { const watcher = watch(source, callback, options); this.watchers.push(watcher); return watcher; } // Dependency tracking for useEffect dependenciesChanged(deps) { if (!this.prevDeps) { this.prevDeps = deps.slice(); return true; } const changed = deps.some((dep, index) => dep !== this.prevDeps[index]); this.prevDeps = deps.slice(); return changed; } } // Component factory function function defineComponent(options) { return class extends MusPEComponent { constructor(props) { super(props); // Apply options if (options.data && typeof options.data === 'function') { Object.assign(this.state, options.data.call(this)); } if (options.methods) { Object.assign(this, options.methods); } if (options.computed) { for (const [key, computeFn] of Object.entries(options.computed)) { this[key] = computed(computeFn.bind(this)); } } if (options.watch) { for (const [key, watchFn] of Object.entries(options.watch)) { this.useWatch(() => this.state[key], watchFn.bind(this)); } } } render() { if (options.render) { return options.render.call(this); } if (options.template) { return jsx`${options.template}`; } throw new Error('Component must have render method or template'); } // Apply lifecycle hooks beforeMount() { if (options.beforeMount) options.beforeMount.call(this); } mounted() { if (options.mounted) options.mounted.call(this); } beforeUpdate() { if (options.beforeUpdate) options.beforeUpdate.call(this); } updated() { if (options.updated) options.updated.call(this); } beforeUnmount() { if (options.beforeUnmount) options.beforeUnmount.call(this); } unmounted() { if (options.unmounted) options.unmounted.call(this); } }; } // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = { MusPEComponent, defineComponent }; } // Make available globally if (typeof window !== 'undefined') { window.MusPEComponent = MusPEComponent; window.defineComponent = defineComponent; }