UNPKG

@neuroequality/neuroadapt-mobile

Version:

Mobile accessibility features for React Native and cross-platform apps

342 lines (341 loc) 10.2 kB
import { Platform } from "react-native"; class AccessibleGestureManager { constructor(customGestureMap) { this.handlers = /* @__PURE__ */ new Map(); this.isEnabled = true; this.debugMode = false; this.gestureMap = { navigation: { next: "swipe_right", previous: "swipe_left", activate: "double_tap", back: "swipe_down", home: "three_finger_tap" }, content: { scrollUp: "swipe_up", scrollDown: "swipe_down", scrollLeft: "swipe_left", scrollRight: "swipe_right", zoom: "pinch", details: "long_press" }, system: { menu: "two_finger_tap", settings: "four_finger_tap", help: "shake", emergency: "long_press" }, ...customGestureMap }; this.initializeDefaultHandlers(); } /** * Register a gesture handler */ registerHandler(id, gestureType, callback, config) { const defaultConfig = { type: gestureType, sensitivity: "medium", requiresConfirmation: false, hapticFeedback: true, audioFeedback: false, timeoutMs: 5e3, customization: this.getDefaultCustomization(gestureType) }; const handler = { id, gesture: gestureType, callback, enabled: true, config: { ...defaultConfig, ...config } }; this.handlers.set(id, handler); } /** * Unregister a gesture handler */ unregisterHandler(id) { this.handlers.delete(id); } /** * Enable or disable a gesture handler */ setHandlerEnabled(id, enabled) { const handler = this.handlers.get(id); if (handler) { handler.enabled = enabled; } } /** * Update gesture configuration */ updateGestureConfig(id, config) { const handler = this.handlers.get(id); if (handler) { handler.config = { ...handler.config, ...config }; } } /** * Process gesture event */ processGesture(event) { if (!this.isEnabled) return false; let handled = false; for (const handler of this.handlers.values()) { if (!handler.enabled || handler.gesture !== event.type) continue; if (this.validateGesture(event, handler.config)) { try { if (handler.config.hapticFeedback) { this.triggerHapticFeedback(); } if (handler.config.audioFeedback) { this.triggerAudioFeedback(event.type); } if (handler.config.requiresConfirmation) { this.requestConfirmation(handler, event); } else { handler.callback(event); } handled = true; if (this.debugMode) { console.log(`Gesture handled: ${handler.id} (${event.type})`); } } catch (error) { console.error(`Error handling gesture ${handler.id}:`, error); } } } return handled; } /** * Get accessibility gesture for action */ getGestureForAction(category, action) { const categoryMap = this.gestureMap[category]; return categoryMap[action] || null; } /** * Customize gesture mapping */ customizeGestureMap(category, action, gesture) { this.gestureMap[category][action] = gesture; } /** * Get gesture statistics */ getGestureStatistics() { const gesturesByType = {}; let enabledCount = 0; for (const handler of this.handlers.values()) { if (handler.enabled) enabledCount++; gesturesByType[handler.gesture] = (gesturesByType[handler.gesture] || 0) + 1; } return { totalHandlers: this.handlers.size, enabledHandlers: enabledCount, gesturesByType, recentActivity: [] // Would track recent gestures in production }; } /** * Enable/disable gesture system */ setEnabled(enabled) { this.isEnabled = enabled; } /** * Enable/disable debug mode */ setDebugMode(enabled) { this.debugMode = enabled; } /** * Create platform-specific gesture recognizer */ createGestureRecognizer(gestureType, config) { return { type: gestureType, config, platform: Platform.OS }; } /** * Get recommended gestures for user needs */ getRecommendedGestures(userNeeds) { const recommended = []; const discouraged = []; const alternatives = {}; if (userNeeds.hasMotorDifficulties) { recommended.push("long_press", "double_tap"); discouraged.push("pinch", "rotate", "swipe_left", "swipe_right"); alternatives["pinch"] = ["double_tap", "long_press"]; alternatives["swipe_left"] = ["tap", "double_tap"]; alternatives["swipe_right"] = ["tap", "double_tap"]; } if (userNeeds.hasVisionImpairment) { recommended.push("double_tap", "long_press", "two_finger_tap"); discouraged.push("swipe_up", "swipe_down", "pinch"); alternatives["swipe_up"] = ["double_tap"]; alternatives["swipe_down"] = ["long_press"]; } if (userNeeds.prefersSingleFinger) { recommended.push("tap", "double_tap", "long_press", "swipe_left", "swipe_right"); discouraged.push("two_finger_tap", "three_finger_tap", "four_finger_tap", "pinch"); alternatives["two_finger_tap"] = ["double_tap"]; alternatives["three_finger_tap"] = ["long_press"]; alternatives["pinch"] = ["double_tap"]; } if (userNeeds.hasHearingImpairment) { recommended.push("long_press", "double_tap", "shake"); } return { recommended, discouraged, alternatives }; } // Private methods initializeDefaultHandlers() { this.registerHandler("navigation_next", "swipe_right", () => { console.log("Navigate to next element"); }); this.registerHandler("navigation_previous", "swipe_left", () => { console.log("Navigate to previous element"); }); this.registerHandler("activate", "double_tap", () => { console.log("Activate current element"); }); this.registerHandler("context_menu", "long_press", () => { console.log("Show context menu"); }, { requiresConfirmation: true }); this.registerHandler("back", "swipe_down", () => { console.log("Go back"); }); this.registerHandler("home", "three_finger_tap", () => { console.log("Go to home"); }); } getDefaultCustomization(gestureType) { switch (gestureType) { case "tap": return { minimumDuration: 50, maximumDuration: 500, requiredFingers: 1, allowSimultaneous: false }; case "double_tap": return { minimumDuration: 50, maximumDuration: 300, requiredFingers: 1, allowSimultaneous: false }; case "long_press": return { minimumDuration: 800, maximumDuration: 5e3, requiredFingers: 1, allowSimultaneous: false }; case "swipe_left": case "swipe_right": case "swipe_up": case "swipe_down": return { minimumDistance: 50, minimumVelocity: 100, requiredFingers: 1, allowSimultaneous: false }; case "pinch": return { minimumDistance: 10, requiredFingers: 2, allowSimultaneous: false }; case "two_finger_tap": return { minimumDuration: 50, maximumDuration: 500, requiredFingers: 2, allowSimultaneous: false }; case "three_finger_tap": return { minimumDuration: 50, maximumDuration: 500, requiredFingers: 3, allowSimultaneous: false }; case "shake": return { minimumVelocity: 800, minimumDuration: 200, allowSimultaneous: false }; default: return { allowSimultaneous: false }; } } validateGesture(event, config) { const custom = config.customization; if (custom.minimumDuration && event.duration < custom.minimumDuration) return false; if (custom.maximumDuration && event.duration > custom.maximumDuration) return false; if (custom.minimumDistance && event.distance < custom.minimumDistance) return false; if (custom.maximumDistance && event.distance > custom.maximumDistance) return false; if (custom.minimumVelocity && event.velocity < custom.minimumVelocity) return false; if (custom.maximumVelocity && event.velocity > custom.maximumVelocity) return false; if (custom.requiredFingers && event.fingerCount !== custom.requiredFingers) return false; return true; } triggerHapticFeedback() { if (Platform.OS === "ios") { import("react-native").then(({ Haptics }) => { Haptics?.impactAsync?.(Haptics.ImpactFeedbackStyle?.Light); }).catch(() => { }); } else if (Platform.OS === "android") { import("react-native").then(({ Vibration }) => { Vibration.vibrate(50); }).catch(() => { }); } } triggerAudioFeedback(gestureType) { console.log(`Audio feedback for ${gestureType}`); } async requestConfirmation(handler, event) { console.log(`Requesting confirmation for ${handler.id}`); setTimeout(() => { handler.callback(event); }, 1e3); } } const useAccessibleGestures = (gestureMap) => { const [gestureManager] = React.useState(() => new AccessibleGestureManager(gestureMap)); const registerGesture = React.useCallback((id, gestureType, callback, config) => { gestureManager.registerHandler(id, gestureType, callback, config); }, [gestureManager]); const unregisterGesture = React.useCallback((id) => { gestureManager.unregisterHandler(id); }, [gestureManager]); const processGesture = React.useCallback((event) => { return gestureManager.processGesture(event); }, [gestureManager]); const getGestureForAction = React.useCallback((category, action) => { return gestureManager.getGestureForAction(category, action); }, [gestureManager]); return { registerGesture, unregisterGesture, processGesture, getGestureForAction, gestureManager }; }; export { AccessibleGestureManager as A, useAccessibleGestures as u };