UNPKG

@nativescript/core

Version:

A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.

229 lines • 9.9 kB
import { Application } from '../application'; import { notifyAccessibilityFocusState } from './accessibility-common'; import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState, AccessibilityTrait } from './accessibility-types'; export * from './accessibility-common'; export * from './accessibility-types'; export * from './font-scale'; function enforceArray(val) { if (Array.isArray(val)) { return val; } if (typeof val === 'string') { return val.split(/[, ]/g).filter((v) => !!v); } return []; } /** * Convert array of values into a bitmask. * * @param values string values * @param map map lower-case name to integer value. */ function inputArrayToBitMask(values, map) { return (enforceArray(values) .filter((value) => !!value) .map((value) => `${value}`.toLocaleLowerCase()) .filter((value) => map.has(value)) .reduce((res, value) => res | map.get(value), 0) || 0); } let AccessibilityTraitsMap; let RoleTypeMap; let nativeFocusedNotificationObserver; let lastFocusedView; function ensureNativeClasses() { if (AccessibilityTraitsMap && nativeFocusedNotificationObserver) { return; } AccessibilityTraitsMap = new Map([ [AccessibilityTrait.AllowsDirectInteraction, UIAccessibilityTraitAllowsDirectInteraction], [AccessibilityTrait.CausesPageTurn, UIAccessibilityTraitCausesPageTurn], [AccessibilityTrait.NotEnabled, UIAccessibilityTraitNotEnabled], [AccessibilityTrait.Selected, UIAccessibilityTraitSelected], [AccessibilityTrait.UpdatesFrequently, UIAccessibilityTraitUpdatesFrequently], ]); RoleTypeMap = new Map([ [AccessibilityRole.Adjustable, UIAccessibilityTraitAdjustable], [AccessibilityRole.Button, UIAccessibilityTraitButton], [AccessibilityRole.Checkbox, UIAccessibilityTraitButton], [AccessibilityRole.Header, UIAccessibilityTraitHeader], [AccessibilityRole.KeyboardKey, UIAccessibilityTraitKeyboardKey], [AccessibilityRole.Image, UIAccessibilityTraitImage], [AccessibilityRole.ImageButton, UIAccessibilityTraitImage | UIAccessibilityTraitButton], [AccessibilityRole.Link, UIAccessibilityTraitLink], [AccessibilityRole.None, UIAccessibilityTraitNone], [AccessibilityRole.PlaysSound, UIAccessibilityTraitPlaysSound], [AccessibilityRole.RadioButton, UIAccessibilityTraitButton], [AccessibilityRole.Search, UIAccessibilityTraitSearchField], [AccessibilityRole.StaticText, UIAccessibilityTraitStaticText], [AccessibilityRole.StartsMediaSession, UIAccessibilityTraitStartsMediaSession], [AccessibilityRole.Summary, UIAccessibilityTraitSummaryElement], [AccessibilityRole.Switch, UIAccessibilityTraitButton], ]); nativeFocusedNotificationObserver = Application.ios.addNotificationObserver(UIAccessibilityElementFocusedNotification, (args) => { const uiView = args.userInfo?.objectForKey(UIAccessibilityFocusedElementKey); if (!uiView?.tag) { return; } const rootView = Application.getRootView(); // We use the UIView's tag to find the NativeScript View by its domId. let view = rootView.getViewByDomId(uiView?.tag); if (!view) { for (const modalView of rootView._getRootModalViews()) { view = modalView.getViewByDomId(uiView?.tag); if (view) { break; } } } if (!view) { return; } const lastView = lastFocusedView?.deref(); if (lastView && view !== lastView) { const lastFocusedUIView = lastView.nativeViewProtected; if (lastFocusedUIView) { lastFocusedView = null; notifyAccessibilityFocusState(lastView, false, true); } } lastFocusedView = new WeakRef(view); notifyAccessibilityFocusState(view, true, false); }); Application.on(Application.exitEvent, () => { if (nativeFocusedNotificationObserver) { Application.ios.removeNotificationObserver(nativeFocusedNotificationObserver, UIAccessibilityElementFocusedNotification); } nativeFocusedNotificationObserver = null; lastFocusedView = null; }); } export function setupAccessibleView(view) { const uiView = view.nativeViewProtected; if (!uiView) { return; } /** * We need to map back from the UIView to the NativeScript View. * * We do that by setting the uiView's tag to the View's domId. * This way we can do reverse lookup. */ uiView.tag = view._domId; } export function updateAccessibilityProperties(view) { const uiView = view.nativeViewProtected; if (!uiView) { return; } ensureNativeClasses(); const accessibilityRole = view.accessibilityRole; const accessibilityState = view.accessibilityState; if (!view.accessible || view.accessibilityHidden) { uiView.accessibilityTraits = UIAccessibilityTraitNone; return; } // NOTE: left here for various core inspection passes while running the toolbox app // console.log('--- Accessible element: ', view.constructor.name); // console.log('accessibilityLabel: ', view.accessibilityLabel); // console.log('accessibilityRole: ', accessibilityRole); // console.log('accessibilityState: ', accessibilityState); // console.log('accessibilityValue: ', view.accessibilityValue); let a11yTraits = UIAccessibilityTraitNone; if (RoleTypeMap.has(accessibilityRole)) { a11yTraits |= RoleTypeMap.get(accessibilityRole); } switch (accessibilityRole) { case AccessibilityRole.Checkbox: case AccessibilityRole.RadioButton: case AccessibilityRole.Switch: { if (accessibilityState === AccessibilityState.Checked) { a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.Selected); } break; } default: { if (accessibilityState === AccessibilityState.Selected) { a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.Selected); } if (accessibilityState === AccessibilityState.Disabled) { a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.NotEnabled); } break; } } const UpdatesFrequentlyTrait = AccessibilityTraitsMap.get(AccessibilityTrait.UpdatesFrequently); switch (view.accessibilityLiveRegion) { case AccessibilityLiveRegion.Polite: case AccessibilityLiveRegion.Assertive: { a11yTraits |= UpdatesFrequentlyTrait; break; } default: { a11yTraits &= ~UpdatesFrequentlyTrait; break; } } // NOTE: left here for various core inspection passes while running the toolbox app // if (view.accessibilityLiveRegion) { // console.log('accessibilityLiveRegion:', view.accessibilityLiveRegion); // } if (view.accessibilityMediaSession) { a11yTraits |= RoleTypeMap.get(AccessibilityRole.StartsMediaSession); } // NOTE: There were duplicated types in traits and roles previously which we conslidated // not sure if this is still needed // accessibilityTraits used to be stored on {N} view component but if the above // is combining all traits fresh each time through, don't believe we need to keep track or previous traits // if (view.accessibilityTraits) { // a11yTraits |= inputArrayToBitMask(view.accessibilityTraits, AccessibilityTraitsMap); // } // NOTE: left here for various core inspection passes while running the toolbox app // console.log('a11yTraits:', a11yTraits); // console.log(' '); uiView.accessibilityTraits = a11yTraits; } export const sendAccessibilityEvent = () => { }; export const updateContentDescription = () => null; let accessibilityServiceEnabled; let nativeObserver; export function isAccessibilityServiceEnabled() { if (typeof accessibilityServiceEnabled === 'boolean') { return accessibilityServiceEnabled; } let isVoiceOverRunning; if (typeof UIAccessibilityIsVoiceOverRunning === 'function') { isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning; } else { // iOS is too old to tell us if voice over is enabled if (typeof UIAccessibilityIsVoiceOverRunning !== 'function') { accessibilityServiceEnabled = false; return accessibilityServiceEnabled; } } accessibilityServiceEnabled = isVoiceOverRunning(); let voiceOverStatusChangedNotificationName = null; if (typeof UIAccessibilityVoiceOverStatusDidChangeNotification !== 'undefined') { voiceOverStatusChangedNotificationName = UIAccessibilityVoiceOverStatusDidChangeNotification; } else if (typeof UIAccessibilityVoiceOverStatusChanged !== 'undefined') { voiceOverStatusChangedNotificationName = UIAccessibilityVoiceOverStatusChanged; } if (voiceOverStatusChangedNotificationName) { nativeObserver = Application.ios.addNotificationObserver(voiceOverStatusChangedNotificationName, () => { accessibilityServiceEnabled = isVoiceOverRunning(); }); Application.on(Application.exitEvent, () => { if (nativeObserver) { Application.ios.removeNotificationObserver(nativeObserver, voiceOverStatusChangedNotificationName); } accessibilityServiceEnabled = undefined; nativeObserver = null; }); } Application.on(Application.resumeEvent, () => { accessibilityServiceEnabled = isVoiceOverRunning(); }); return accessibilityServiceEnabled; } //# sourceMappingURL=index.ios.js.map