UNPKG

9.9 kBJavaScriptView Raw
1import { Application } from '../application';
2import { notifyAccessibilityFocusState } from './accessibility-common';
3import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState, AccessibilityTrait } from './accessibility-types';
4export * from './accessibility-common';
5export * from './accessibility-types';
6export * from './font-scale';
7function enforceArray(val) {
8 if (Array.isArray(val)) {
9 return val;
10 }
11 if (typeof val === 'string') {
12 return val.split(/[, ]/g).filter((v) => !!v);
13 }
14 return [];
15}
16/**
17 * Convert array of values into a bitmask.
18 *
19 * @param values string values
20 * @param map map lower-case name to integer value.
21 */
22function inputArrayToBitMask(values, map) {
23 return (enforceArray(values)
24 .filter((value) => !!value)
25 .map((value) => `${value}`.toLocaleLowerCase())
26 .filter((value) => map.has(value))
27 .reduce((res, value) => res | map.get(value), 0) || 0);
28}
29let AccessibilityTraitsMap;
30let RoleTypeMap;
31let nativeFocusedNotificationObserver;
32let lastFocusedView;
33function ensureNativeClasses() {
34 if (AccessibilityTraitsMap && nativeFocusedNotificationObserver) {
35 return;
36 }
37 AccessibilityTraitsMap = new Map([
38 [AccessibilityTrait.AllowsDirectInteraction, UIAccessibilityTraitAllowsDirectInteraction],
39 [AccessibilityTrait.CausesPageTurn, UIAccessibilityTraitCausesPageTurn],
40 [AccessibilityTrait.NotEnabled, UIAccessibilityTraitNotEnabled],
41 [AccessibilityTrait.Selected, UIAccessibilityTraitSelected],
42 [AccessibilityTrait.UpdatesFrequently, UIAccessibilityTraitUpdatesFrequently],
43 ]);
44 RoleTypeMap = new Map([
45 [AccessibilityRole.Adjustable, UIAccessibilityTraitAdjustable],
46 [AccessibilityRole.Button, UIAccessibilityTraitButton],
47 [AccessibilityRole.Checkbox, UIAccessibilityTraitButton],
48 [AccessibilityRole.Header, UIAccessibilityTraitHeader],
49 [AccessibilityRole.KeyboardKey, UIAccessibilityTraitKeyboardKey],
50 [AccessibilityRole.Image, UIAccessibilityTraitImage],
51 [AccessibilityRole.ImageButton, UIAccessibilityTraitImage | UIAccessibilityTraitButton],
52 [AccessibilityRole.Link, UIAccessibilityTraitLink],
53 [AccessibilityRole.None, UIAccessibilityTraitNone],
54 [AccessibilityRole.PlaysSound, UIAccessibilityTraitPlaysSound],
55 [AccessibilityRole.RadioButton, UIAccessibilityTraitButton],
56 [AccessibilityRole.Search, UIAccessibilityTraitSearchField],
57 [AccessibilityRole.StaticText, UIAccessibilityTraitStaticText],
58 [AccessibilityRole.StartsMediaSession, UIAccessibilityTraitStartsMediaSession],
59 [AccessibilityRole.Summary, UIAccessibilityTraitSummaryElement],
60 [AccessibilityRole.Switch, UIAccessibilityTraitButton],
61 ]);
62 nativeFocusedNotificationObserver = Application.ios.addNotificationObserver(UIAccessibilityElementFocusedNotification, (args) => {
63 const uiView = args.userInfo?.objectForKey(UIAccessibilityFocusedElementKey);
64 if (!uiView?.tag) {
65 return;
66 }
67 const rootView = Application.getRootView();
68 // We use the UIView's tag to find the NativeScript View by its domId.
69 let view = rootView.getViewByDomId(uiView?.tag);
70 if (!view) {
71 for (const modalView of rootView._getRootModalViews()) {
72 view = modalView.getViewByDomId(uiView?.tag);
73 if (view) {
74 break;
75 }
76 }
77 }
78 if (!view) {
79 return;
80 }
81 const lastView = lastFocusedView?.deref();
82 if (lastView && view !== lastView) {
83 const lastFocusedUIView = lastView.nativeViewProtected;
84 if (lastFocusedUIView) {
85 lastFocusedView = null;
86 notifyAccessibilityFocusState(lastView, false, true);
87 }
88 }
89 lastFocusedView = new WeakRef(view);
90 notifyAccessibilityFocusState(view, true, false);
91 });
92 Application.on(Application.exitEvent, () => {
93 if (nativeFocusedNotificationObserver) {
94 Application.ios.removeNotificationObserver(nativeFocusedNotificationObserver, UIAccessibilityElementFocusedNotification);
95 }
96 nativeFocusedNotificationObserver = null;
97 lastFocusedView = null;
98 });
99}
100export function setupAccessibleView(view) {
101 const uiView = view.nativeViewProtected;
102 if (!uiView) {
103 return;
104 }
105 /**
106 * We need to map back from the UIView to the NativeScript View.
107 *
108 * We do that by setting the uiView's tag to the View's domId.
109 * This way we can do reverse lookup.
110 */
111 uiView.tag = view._domId;
112}
113export function updateAccessibilityProperties(view) {
114 const uiView = view.nativeViewProtected;
115 if (!uiView) {
116 return;
117 }
118 ensureNativeClasses();
119 const accessibilityRole = view.accessibilityRole;
120 const accessibilityState = view.accessibilityState;
121 if (!view.accessible || view.accessibilityHidden) {
122 uiView.accessibilityTraits = UIAccessibilityTraitNone;
123 return;
124 }
125 // NOTE: left here for various core inspection passes while running the toolbox app
126 // console.log('--- Accessible element: ', view.constructor.name);
127 // console.log('accessibilityLabel: ', view.accessibilityLabel);
128 // console.log('accessibilityRole: ', accessibilityRole);
129 // console.log('accessibilityState: ', accessibilityState);
130 // console.log('accessibilityValue: ', view.accessibilityValue);
131 let a11yTraits = UIAccessibilityTraitNone;
132 if (RoleTypeMap.has(accessibilityRole)) {
133 a11yTraits |= RoleTypeMap.get(accessibilityRole);
134 }
135 switch (accessibilityRole) {
136 case AccessibilityRole.Checkbox:
137 case AccessibilityRole.RadioButton:
138 case AccessibilityRole.Switch: {
139 if (accessibilityState === AccessibilityState.Checked) {
140 a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.Selected);
141 }
142 break;
143 }
144 default: {
145 if (accessibilityState === AccessibilityState.Selected) {
146 a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.Selected);
147 }
148 if (accessibilityState === AccessibilityState.Disabled) {
149 a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.NotEnabled);
150 }
151 break;
152 }
153 }
154 const UpdatesFrequentlyTrait = AccessibilityTraitsMap.get(AccessibilityTrait.UpdatesFrequently);
155 switch (view.accessibilityLiveRegion) {
156 case AccessibilityLiveRegion.Polite:
157 case AccessibilityLiveRegion.Assertive: {
158 a11yTraits |= UpdatesFrequentlyTrait;
159 break;
160 }
161 default: {
162 a11yTraits &= ~UpdatesFrequentlyTrait;
163 break;
164 }
165 }
166 // NOTE: left here for various core inspection passes while running the toolbox app
167 // if (view.accessibilityLiveRegion) {
168 // console.log('accessibilityLiveRegion:', view.accessibilityLiveRegion);
169 // }
170 if (view.accessibilityMediaSession) {
171 a11yTraits |= RoleTypeMap.get(AccessibilityRole.StartsMediaSession);
172 }
173 // NOTE: There were duplicated types in traits and roles previously which we conslidated
174 // not sure if this is still needed
175 // accessibilityTraits used to be stored on {N} view component but if the above
176 // is combining all traits fresh each time through, don't believe we need to keep track or previous traits
177 // if (view.accessibilityTraits) {
178 // a11yTraits |= inputArrayToBitMask(view.accessibilityTraits, AccessibilityTraitsMap);
179 // }
180 // NOTE: left here for various core inspection passes while running the toolbox app
181 // console.log('a11yTraits:', a11yTraits);
182 // console.log(' ');
183 uiView.accessibilityTraits = a11yTraits;
184}
185export const sendAccessibilityEvent = () => { };
186export const updateContentDescription = () => null;
187let accessibilityServiceEnabled;
188let nativeObserver;
189export function isAccessibilityServiceEnabled() {
190 if (typeof accessibilityServiceEnabled === 'boolean') {
191 return accessibilityServiceEnabled;
192 }
193 let isVoiceOverRunning;
194 if (typeof UIAccessibilityIsVoiceOverRunning === 'function') {
195 isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning;
196 }
197 else {
198 // iOS is too old to tell us if voice over is enabled
199 if (typeof UIAccessibilityIsVoiceOverRunning !== 'function') {
200 accessibilityServiceEnabled = false;
201 return accessibilityServiceEnabled;
202 }
203 }
204 accessibilityServiceEnabled = isVoiceOverRunning();
205 let voiceOverStatusChangedNotificationName = null;
206 if (typeof UIAccessibilityVoiceOverStatusDidChangeNotification !== 'undefined') {
207 voiceOverStatusChangedNotificationName = UIAccessibilityVoiceOverStatusDidChangeNotification;
208 }
209 else if (typeof UIAccessibilityVoiceOverStatusChanged !== 'undefined') {
210 voiceOverStatusChangedNotificationName = UIAccessibilityVoiceOverStatusChanged;
211 }
212 if (voiceOverStatusChangedNotificationName) {
213 nativeObserver = Application.ios.addNotificationObserver(voiceOverStatusChangedNotificationName, () => {
214 accessibilityServiceEnabled = isVoiceOverRunning();
215 });
216 Application.on(Application.exitEvent, () => {
217 if (nativeObserver) {
218 Application.ios.removeNotificationObserver(nativeObserver, voiceOverStatusChangedNotificationName);
219 }
220 accessibilityServiceEnabled = undefined;
221 nativeObserver = null;
222 });
223 }
224 Application.on(Application.resumeEvent, () => {
225 accessibilityServiceEnabled = isVoiceOverRunning();
226 });
227 return accessibilityServiceEnabled;
228}
229//# sourceMappingURL=index.ios.js.map
\No newline at end of file