@neuroequality/neuroadapt-mobile
Version:
Mobile accessibility features for React Native and cross-platform apps
342 lines (341 loc) • 10.2 kB
JavaScript
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
};