[change] Explicitly forward props and introduce unstable_{ariaSet,dataSet}

Rather than filtering props, they are explicitly forwarded in each component.
This makes it easier to see exactly which props are being forwarded to host
components by each React Native component. Two new props - `unstable_ariaSet`
and `unstable_dataSet` - are introduced to avoid iterating over props to find
`aria` and `data` props.

The `accessibilityValue` prop is also implemented.
This commit is contained in:
Nicolas Gallagher
2020-02-06 11:23:20 -08:00
parent 654f65e3e0
commit 8952eccf86
17 changed files with 591 additions and 217 deletions
@@ -3,12 +3,12 @@ import PropTypes, { any, bool, func, object, oneOf, string } from 'prop-types';
const ofProps = () => {};
ofProps.propTypes = {
/* test */
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: oneOf(['assertive', 'none', 'polite']),
accessibilityRelationship: object,
accessibilityRole: string,
accessibilityState: object,
accessibilityValue: object,
accessible: bool,
children: any,
forwardedRef: any,
@@ -89,6 +89,10 @@ assistive technologies of a `role` value change.
...
### accessibilityValue: ?object
...
### accessible
When `true`, indicates that the view is an accessibility element. When a view
@@ -14,6 +14,8 @@ import StyleSheet from '../StyleSheet';
import View from '../View';
import React, { forwardRef } from 'react';
const accessibilityValue = { max: 1, min: 0 };
const createSvgCircle = style => (
<circle cx="16" cy="16" fill="none" r="14" strokeWidth="4" style={style} />
);
@@ -54,8 +56,7 @@ const ActivityIndicator = forwardRef<ActivityIndicatorProps, *>((props, ref) =>
<View
{...other}
accessibilityRole="progressbar"
aria-valuemax="1"
aria-valuemin="0"
accessibilityValue={accessibilityValue}
ref={ref}
style={[styles.container, style]}
>
+5 -3
View File
@@ -48,9 +48,11 @@ const ProgressBar = forwardRef<ProgressBarProps, *>((props, ref) => {
<View
{...other}
accessibilityRole="progressbar"
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow={indeterminate ? null : percentageProgress}
accessibilityValue={{
max: 100,
min: 0,
now: indeterminate ? null : percentageProgress
}}
ref={ref}
style={[styles.track, style, { backgroundColor: trackColor }]}
>
+128 -17
View File
@@ -12,7 +12,6 @@ import type { TextProps } from './types';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import filterSupportedProps from '../View/filterSupportedProps';
import setAndForwardRef from '../../modules/setAndForwardRef';
import useElementLayout from '../../hooks/useElementLayout';
import usePlatformMethods from '../../hooks/usePlatformMethods';
@@ -21,7 +20,69 @@ import StyleSheet from '../StyleSheet';
import TextAncestorContext from './TextAncestorContext';
const Text = forwardRef<TextProps, *>((props, ref) => {
const { dir, forwardedRef, numberOfLines, onLayout, onPress, selectable } = props;
const {
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRelationship,
accessibilityRole,
accessibilityState,
children,
dir,
forwardedRef,
importantForAccessibility,
lang,
nativeID,
numberOfLines,
onBlur,
onContextMenu,
onFocus,
onLayout,
onPress,
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onScrollShouldSetResponder,
onScrollShouldSetResponderCapture,
onSelectionChangeShouldSetResponder,
onSelectionChangeShouldSetResponderCapture,
onStartShouldSetResponder,
onStartShouldSetResponderCapture,
selectable,
testID,
// unstable
onMouseDown,
onMouseEnter,
onMouseLeave,
onMouseMove,
onMouseOver,
onMouseOut,
onMouseUp,
onTouchCancel,
onTouchCancelCapture,
onTouchEnd,
onTouchEndCapture,
onTouchMove,
onTouchMoveCapture,
onTouchStart,
onTouchStartCapture,
href,
itemID,
itemRef,
itemProp,
itemScope,
itemType,
rel,
target,
unstable_ariaSet,
unstable_dataSet
} = props;
const hasTextAncestor = useContext(TextAncestorContext);
const hostRef = useRef(null);
@@ -63,22 +124,72 @@ const Text = forwardRef<TextProps, *>((props, ref) => {
};
}
const supportedProps = filterSupportedProps(props);
if (onPress) {
supportedProps.accessible = true;
supportedProps.onClick = createPressHandler(onPress);
supportedProps.onKeyDown = createEnterHandler(onPress);
}
supportedProps.classList = classList;
// allow browsers to automatically infer the language writing direction
supportedProps.dir = dir !== undefined ? dir : 'auto';
supportedProps.ref = setRef;
supportedProps.style = style;
const component = hasTextAncestor ? 'span' : 'div';
const element = createElement(component, supportedProps);
const element = createElement(component, {
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRelationship,
accessibilityRole,
accessibilityState,
accessible: onPress != null ? true : null,
children,
classList,
// allow browsers to automatically infer the language writing direction
dir: dir !== undefined ? dir : 'auto',
importantForAccessibility,
lang,
nativeID,
onBlur,
onFocus,
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onScrollShouldSetResponder,
onScrollShouldSetResponderCapture,
onSelectionChangeShouldSetResponder,
onSelectionChangeShouldSetResponderCapture,
onStartShouldSetResponder,
onStartShouldSetResponderCapture,
ref: setRef,
style,
testID,
// unstable
onClick: onPress != null ? createPressHandler(onPress) : null,
onContextMenu,
onKeyDown: onPress != null ? createEnterHandler(onPress) : null,
onMouseDown,
onMouseEnter,
onMouseLeave,
onMouseMove,
onMouseOver,
onMouseOut,
onMouseUp,
onTouchCancel,
onTouchCancelCapture,
onTouchEnd,
onTouchEndCapture,
onTouchMove,
onTouchMoveCapture,
onTouchStart,
onTouchStartCapture,
href,
itemID,
itemRef,
itemProp,
itemScope,
itemType,
rel,
target,
unstable_ariaSet,
unstable_dataSet
});
return hasTextAncestor ? (
element
+41 -2
View File
@@ -104,14 +104,53 @@ export type TextProps = {
onFocus?: (e: any) => void,
onLayout?: (e: LayoutEvent) => void,
onPress?: (e: any) => void,
onMoveShouldSetResponder?: (e: any) => boolean,
onMoveShouldSetResponderCapture?: (e: any) => boolean,
onResponderEnd?: (e: any) => void,
onResponderGrant?: (e: any) => void,
onResponderMove?: (e: any) => void,
onResponderReject?: (e: any) => void,
onResponderRelease?: (e: any) => void,
onResponderStart?: (e: any) => void,
onResponderTerminate?: (e: any) => void,
onResponderTerminationRequest?: (e: any) => void,
onScrollShouldSetResponder?: (e: any) => boolean,
onScrollShouldSetResponderCapture?: (e: any) => boolean,
onSelectionChangeShouldSetResponder?: (e: any) => boolean,
onSelectionChangeShouldSetResponderCapture?: (e: any) => boolean,
onStartShouldSetResponder?: (e: any) => boolean,
onStartShouldSetResponderCapture?: (e: any) => boolean,
selectable?: boolean,
style?: GenericStyleProp<TextStyle>,
testID?: string,
// web extensions
// unstable
onContextMenu?: (e: any) => void,
onKeyDown?: (e: any) => void,
onKeyPress?: (e: any) => void,
onKeyUp?: (e: any) => void,
onMouseDown?: (e: any) => void,
onMouseEnter?: (e: any) => void,
onMouseLeave?: (e: any) => void,
onMouseMove?: (e: any) => void,
onMouseOver?: (e: any) => void,
onMouseOut?: (e: any) => void,
onMouseUp?: (e: any) => void,
onTouchCancel?: (e: any) => void,
onTouchCancelCapture?: (e: any) => void,
onTouchEnd?: (e: any) => void,
onTouchEndCapture?: (e: any) => void,
onTouchMove?: (e: any) => void,
onTouchMoveCapture?: (e: any) => void,
onTouchStart?: (e: any) => void,
onTouchStartCapture?: (e: any) => void,
href?: string,
itemID?: string,
itemRef?: string,
itemProp?: string,
itemScope?: string,
itemType?: string
itemType?: string,
rel?: string,
target?: string,
unstable_ariaSet?: Object,
unstable_dataSet?: Object
};
+84 -32
View File
@@ -12,11 +12,11 @@ import type { TextInputProps } from './types';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import filterSupportedProps from '../View/filterSupportedProps';
import setAndForwardRef from '../../modules/setAndForwardRef';
import useElementLayout from '../../hooks/useElementLayout';
import usePlatformMethods from '../../hooks/usePlatformMethods';
import { forwardRef, useEffect, useRef } from 'react';
import useLayoutEffect from '../../hooks/useLayoutEffect';
import { usePlatformInputMethods } from '../../hooks/usePlatformMethods';
import { forwardRef, useRef } from 'react';
import StyleSheet from '../StyleSheet';
import TextInputState from '../../modules/TextInputState';
@@ -50,6 +50,9 @@ const setSelection = (node, selection) => {
const TextInput = forwardRef<TextInputProps, *>((props, ref) => {
const {
accessibilityLabel,
accessibilityRelationship,
accessibilityState,
autoCapitalize = 'sentences',
autoComplete,
autoCompleteType,
@@ -60,27 +63,58 @@ const TextInput = forwardRef<TextInputProps, *>((props, ref) => {
defaultValue,
disabled,
editable = true,
forwardedRef,
importantForAccessibility,
keyboardType = 'default',
maxLength,
multiline = false,
nativeID,
numberOfLines = 1,
onBlur,
onChange,
onChangeText,
onContentSizeChange,
onContextMenu,
onFocus,
onKeyPress,
onLayout,
onScroll,
onSelectionChange,
onSubmitEditing,
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onScrollShouldSetResponder,
onScrollShouldSetResponderCapture,
onSelectionChangeShouldSetResponder,
onSelectionChangeShouldSetResponderCapture,
onStartShouldSetResponder,
onStartShouldSetResponderCapture,
placeholder,
placeholderTextColor,
pointerEvents,
returnKeyType,
secureTextEntry = false,
selection = emptyObject,
selectTextOnFocus,
spellCheck,
value
testID,
value,
// unstable
itemID,
itemRef,
itemProp,
itemScope,
itemType,
unstable_ariaSet,
unstable_dataSet
} = props;
let type;
@@ -114,7 +148,7 @@ const TextInput = forwardRef<TextInputProps, *>((props, ref) => {
const hostRef = useRef(null);
const dimensions = useRef({ height: null, width: null });
const setRef = setAndForwardRef({
getForwardedRef: () => ref,
getForwardedRef: () => forwardedRef,
setLocalRef: c => {
hostRef.current = c;
if (hostRef.current != null) {
@@ -124,7 +158,6 @@ const TextInput = forwardRef<TextInputProps, *>((props, ref) => {
});
const component = multiline ? 'textarea' : 'input';
const supportedProps = filterSupportedProps(props);
const classList = [classes.textinput];
const style = StyleSheet.compose(
props.style,
@@ -160,7 +193,7 @@ const TextInput = forwardRef<TextInputProps, *>((props, ref) => {
}
function handleChange(e) {
const { text } = e.nativeEvent;
const text = e.target.value;
e.nativeEvent.text = text;
handleContentSizeChange();
if (onChange) {
@@ -219,7 +252,7 @@ const TextInput = forwardRef<TextInputProps, *>((props, ref) => {
e.nativeEvent = { target: e.target, text: e.target.value };
onSubmitEditing(e);
}
if (shouldBlurOnSubmit) {
if (shouldBlurOnSubmit && hostRef.current != null) {
// $FlowFixMe
hostRef.current.blur();
}
@@ -243,26 +276,21 @@ const TextInput = forwardRef<TextInputProps, *>((props, ref) => {
}
}
useEffect(() => {
setSelection(hostRef.current, selection);
if (document.activeElement === hostRef.current) {
TextInputState._currentlyFocusedNode = hostRef.current;
useLayoutEffect(() => {
const node = hostRef.current;
setSelection(node, selection);
if (document.activeElement === node) {
TextInputState._currentlyFocusedNode = node;
}
}, [hostRef, selection]);
useElementLayout(hostRef, onLayout);
usePlatformMethods(hostRef, ref, classList, style, {
clear() {
if (hostRef.current != null) {
hostRef.current.value = '';
}
},
isFocused() {
return hostRef.current != null && TextInputState.currentlyFocusedField() === hostRef.current;
}
});
usePlatformInputMethods(hostRef, ref, classList, style);
Object.assign(supportedProps, {
return createElement(component, {
accessibilityLabel,
accessibilityRelationship,
accessibilityState,
autoCapitalize,
autoComplete: autoComplete || autoCompleteType || 'on',
autoCorrect: autoCorrect ? 'on' : 'off',
@@ -272,27 +300,51 @@ const TextInput = forwardRef<TextInputProps, *>((props, ref) => {
dir: 'auto',
disabled,
enterkeyhint: returnKeyType,
importantForAccessibility,
maxLength,
nativeID,
onBlur: handleBlur,
onChange: handleChange,
onContextMenu,
onFocus: handleFocus,
onKeyDown: handleKeyDown,
onScroll,
onSelect: handleSelectionChange,
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onScrollShouldSetResponder,
onScrollShouldSetResponderCapture,
onSelectionChangeShouldSetResponder,
onSelectionChangeShouldSetResponderCapture,
onStartShouldSetResponder,
onStartShouldSetResponderCapture,
placeholder,
pointerEvents,
testID,
readOnly: !editable,
ref: setRef,
rows: multiline ? numberOfLines : undefined,
spellCheck: spellCheck != null ? spellCheck : autoCorrect,
style,
value
type: multiline ? undefined : type,
value,
// unstable
itemID,
itemRef,
itemProp,
itemScope,
itemType,
unstable_ariaSet,
unstable_dataSet
});
if (multiline) {
supportedProps.rows = numberOfLines;
} else {
supportedProps.type = type;
}
return createElement(component, supportedProps);
});
TextInput.displayName = 'TextInput';
@@ -291,7 +291,10 @@ const TouchableHighlight = ((createReactClass({
accessibilityHint={this.props.accessibilityHint}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole}
accessibilityState={this.props.accessibilityState}
accessibilityState={{
disabled: this.props.disabled,
...this.props.accessibilityState
}}
accessible={this.props.accessible !== false}
hitSlop={this.props.hitSlop}
nativeID={this.props.nativeID}
@@ -246,7 +246,10 @@ const TouchableOpacity = ((createReactClass({
accessibilityHint={this.props.accessibilityHint}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole}
accessibilityState={this.props.accessibilityState}
accessibilityState={{
disabled: this.props.disabled,
...this.props.accessibilityState
}}
accessible={this.props.accessible !== false}
hitSlop={this.props.hitSlop}
nativeID={this.props.nativeID}
@@ -148,6 +148,11 @@ const TouchableWithoutFeedback = ((createReactClass({
}
}
overrides.accessibilityState = {
disabled: this.props.disabled,
...this.props.accessibilityState
};
return (React: any).cloneElement(child, {
...overrides,
accessible: this.props.accessible !== false,
@@ -1,92 +0,0 @@
/**
* Copyright (c) Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
*/
const supportedProps = {
accessibilityLabel: true,
accessibilityLiveRegion: true,
accessibilityRelationship: true,
accessibilityRole: true,
accessibilityState: true,
accessible: true,
children: true,
disabled: true,
importantForAccessibility: true,
nativeID: true,
onBlur: true,
onContextMenu: true,
onFocus: true,
onMoveShouldSetResponder: true,
onMoveShouldSetResponderCapture: true,
onResponderEnd: true,
onResponderGrant: true,
onResponderMove: true,
onResponderReject: true,
onResponderRelease: true,
onResponderStart: true,
onResponderTerminate: true,
onResponderTerminationRequest: true,
onScrollShouldSetResponder: true,
onScrollShouldSetResponderCapture: true,
onSelectionChangeShouldSetResponder: true,
onSelectionChangeShouldSetResponderCapture: true,
onStartShouldSetResponder: true,
onStartShouldSetResponderCapture: true,
onTouchCancel: true,
onTouchCancelCapture: true,
onTouchEnd: true,
onTouchEndCapture: true,
onTouchMove: true,
onTouchMoveCapture: true,
onTouchStart: true,
onTouchStartCapture: true,
pointerEvents: true,
style: true,
testID: true,
/* @platform web */
lang: true,
onScroll: true,
onWheel: true,
// keyboard events
onKeyDown: true,
onKeyPress: true,
onKeyUp: true,
// mouse events (e.g, hover effects)
onMouseDown: true,
onMouseEnter: true,
onMouseLeave: true,
onMouseMove: true,
onMouseOver: true,
onMouseOut: true,
onMouseUp: true,
// unstable escape-hatches for web
href: true,
itemID: true,
itemRef: true,
itemProp: true,
itemScope: true,
itemType: true,
onClick: true,
onClickCapture: true,
rel: true,
target: true
};
const filterSupportedProps = props => {
const safeProps = {};
for (const prop in props) {
if (props.hasOwnProperty(prop)) {
if (supportedProps[prop] || prop.indexOf('aria-') === 0 || prop.indexOf('data-') === 0) {
safeProps[prop] = props[prop];
}
}
}
return safeProps;
};
export default filterSupportedProps;
+138 -11
View File
@@ -12,7 +12,6 @@ import type { ViewProps } from './types';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import filterSupportedProps from './filterSupportedProps';
import setAndForwardRef from '../../modules/setAndForwardRef';
import useElementLayout from '../../hooks/useElementLayout';
import usePlatformMethods from '../../hooks/usePlatformMethods';
@@ -37,7 +36,73 @@ function createHitSlopElement(hitSlop) {
}
const View = forwardRef<ViewProps, *>((props, ref) => {
const { children, forwardedRef, hitSlop, onLayout } = props;
const {
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRelationship,
accessibilityRole,
accessibilityState,
accessibilityValue,
forwardedRef,
hitSlop,
importantForAccessibility,
nativeID,
onBlur,
onContextMenu,
onFocus,
onLayout,
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onScrollShouldSetResponder,
onScrollShouldSetResponderCapture,
onSelectionChangeShouldSetResponder,
onSelectionChangeShouldSetResponderCapture,
onStartShouldSetResponder,
onStartShouldSetResponderCapture,
pointerEvents,
testID,
// unstable
onClick,
onClickCapture,
onScroll,
onWheel,
onKeyDown,
onKeyPress,
onKeyUp,
onMouseDown,
onMouseEnter,
onMouseLeave,
onMouseMove,
onMouseOver,
onMouseOut,
onMouseUp,
onTouchCancel,
onTouchCancelCapture,
onTouchEnd,
onTouchEndCapture,
onTouchMove,
onTouchMoveCapture,
onTouchStart,
onTouchStartCapture,
href,
itemID,
itemRef,
itemProp,
itemScope,
itemType,
rel,
target,
unstable_ariaSet,
unstable_dataSet
} = props;
if (process.env.NODE_ENV !== 'production') {
React.Children.toArray(props.children).forEach(item => {
@@ -56,6 +121,9 @@ const View = forwardRef<ViewProps, *>((props, ref) => {
}
});
const children = hitSlop
? React.Children.toArray([createHitSlopElement(hitSlop), props.children])
: props.children;
const classList = [classes.view];
const style = StyleSheet.compose(
hasTextAncestor && styles.inline,
@@ -65,15 +133,74 @@ const View = forwardRef<ViewProps, *>((props, ref) => {
useElementLayout(hostRef, onLayout);
usePlatformMethods(hostRef, ref, classList, style);
const supportedProps = filterSupportedProps(props);
supportedProps.children = hitSlop
? React.Children.toArray([createHitSlopElement(hitSlop), children])
: children;
supportedProps.classList = classList;
supportedProps.ref = setRef;
supportedProps.style = style;
return createElement('div', supportedProps);
return createElement('div', {
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRelationship,
accessibilityRole,
accessibilityState,
accessibilityValue,
children,
classList,
importantForAccessibility,
nativeID,
onBlur,
onContextMenu,
onFocus,
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onScrollShouldSetResponder,
onScrollShouldSetResponderCapture,
onSelectionChangeShouldSetResponder,
onSelectionChangeShouldSetResponderCapture,
onStartShouldSetResponder,
onStartShouldSetResponderCapture,
pointerEvents,
ref: setRef,
style,
testID,
// unstable
onClick,
onClickCapture,
onScroll,
onWheel,
onKeyDown,
onKeyPress,
onKeyUp,
onMouseDown,
onMouseEnter,
onMouseLeave,
onMouseMove,
onMouseOver,
onMouseOut,
onMouseUp,
onTouchCancel,
onTouchCancelCapture,
onTouchEnd,
onTouchEndCapture,
onTouchMove,
onTouchMoveCapture,
onTouchStart,
onTouchStartCapture,
href,
itemID,
itemRef,
itemProp,
itemScope,
itemType,
rel,
target,
unstable_ariaSet,
unstable_dataSet
});
});
View.displayName = 'View';
+39 -10
View File
@@ -83,6 +83,12 @@ export type ViewProps = {
required?: ?boolean,
selected?: ?boolean
},
accessibilityValue?: {
max?: ?number,
min?: ?number,
now?: ?number,
text?: ?string
},
accessible?: boolean,
children?: any,
forwardedRef?: any,
@@ -90,20 +96,43 @@ export type ViewProps = {
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants',
nativeID?: string,
onBlur?: (e: any) => void,
onClick?: (e: any) => void,
onClickCapture?: (e: any) => void,
onFocus?: (e: any) => void,
onLayout?: (e: LayoutEvent) => void,
onMoveShouldSetResponder?: (e: any) => boolean,
onMoveShouldSetResponderCapture?: (e: any) => boolean,
onResponderEnd?: (e: any) => void,
onResponderGrant?: (e: any) => void,
onResponderMove?: (e: any) => void,
onResponderReject?: (e: any) => void,
onResponderRelease?: (e: any) => void,
onResponderStart?: (e: any) => void,
onResponderTerminate?: (e: any) => void,
onResponderTerminationRequest?: (e: any) => void,
onScrollShouldSetResponder?: (e: any) => boolean,
onScrollShouldSetResponderCapture?: (e: any) => boolean,
onSelectionChangeShouldSetResponder?: (e: any) => boolean,
onSelectionChangeShouldSetResponderCapture?: (e: any) => boolean,
onStartShouldSetResponder?: (e: any) => boolean,
onStartShouldSetResponderCapture?: (e: any) => boolean,
onMoveShouldSetResponder?: (e: any) => boolean,
onMoveShouldSetResponderCapture?: (e: any) => boolean,
pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto',
style?: GenericStyleProp<ViewStyle>,
testID?: string,
// unstable
onClick?: (e: any) => void,
onClickCapture?: (e: any) => void,
onContextMenu?: (e: any) => void,
onScroll?: (e: any) => void,
onWheel?: (e: any) => void,
onKeyDown?: (e: any) => void,
onKeyPress?: (e: any) => void,
onKeyUp?: (e: any) => void,
onMouseDown?: (e: any) => void,
onMouseEnter?: (e: any) => void,
onMouseLeave?: (e: any) => void,
onMouseMove?: (e: any) => void,
onMouseOver?: (e: any) => void,
onMouseOut?: (e: any) => void,
onMouseUp?: (e: any) => void,
onTouchCancel?: (e: any) => void,
onTouchCancelCapture?: (e: any) => void,
onTouchEnd?: (e: any) => void,
@@ -112,14 +141,14 @@ export type ViewProps = {
onTouchMoveCapture?: (e: any) => void,
onTouchStart?: (e: any) => void,
onTouchStartCapture?: (e: any) => void,
pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto',
style?: GenericStyleProp<ViewStyle>,
testID?: string,
// web extensions
onContextMenu?: (e: any) => void,
href?: string,
itemID?: string,
itemRef?: string,
itemProp?: string,
itemScope?: string,
itemType?: string
itemType?: string,
rel?: string,
target?: string,
unstable_ariaSet?: Object,
unstable_dataSet?: Object
};
+83 -33
View File
@@ -13,6 +13,34 @@ import type { ElementRef } from 'react';
import UIManager from '../exports/UIManager';
import createDOMProps from '../modules/createDOMProps';
import { useImperativeHandle, useRef } from 'react';
import TextInputState from '../modules/TextInputState';
function setNativeProps(node, nativeProps, classList, style, previousStyle) {
if (node != null && nativeProps) {
const domProps = createDOMProps(null, {
...nativeProps,
classList: [nativeProps.className, classList],
style: [style, nativeProps.style]
});
const nextDomStyle = domProps.style;
if (previousStyle.current != null) {
if (domProps.style == null) {
domProps.style = {};
}
for (const styleName in previousStyle.current) {
if (domProps.style[styleName] == null) {
domProps.style[styleName] = '';
}
}
}
previousStyle.current = nextDomStyle;
UIManager.updateView(node, domProps);
}
}
export default function usePlatformMethods(
hostRef: ElementRef<any>,
@@ -26,52 +54,74 @@ export default function usePlatformMethods(
useImperativeHandle(
ref,
() => {
const hostNode = hostRef.current;
return {
blur() {
UIManager.blur(hostRef.current);
UIManager.blur(hostNode);
},
focus() {
UIManager.focus(hostRef.current);
UIManager.focus(hostNode);
},
measure(callback) {
UIManager.measure(hostRef.current, callback);
UIManager.measure(hostNode, callback);
},
measureLayout(relativeToNativeNode, onFail, onSuccess) {
UIManager.measureLayout(hostRef.current, relativeToNativeNode, onFail, onSuccess);
UIManager.measureLayout(hostNode, relativeToNativeNode, onFail, onSuccess);
},
measureInWindow(callback) {
UIManager.measureInWindow(hostRef.current, callback);
UIManager.measureInWindow(hostNode, callback);
},
setNativeProps(nativeProps) {
const node = hostRef.current;
if (node && nativeProps) {
const domProps = createDOMProps(null, {
...nativeProps,
classList: [nativeProps.className, classList],
style: [style, nativeProps.style]
});
const nextDomStyle = domProps.style;
if (previousStyle.current != null) {
if (domProps.style == null) {
domProps.style = {};
}
for (const styleName in previousStyle.current) {
if (domProps.style[styleName] == null) {
domProps.style[styleName] = '';
}
}
}
previousStyle.current = nextDomStyle;
UIManager.updateView(node, domProps);
}
},
...extras
setNativeProps(hostNode, nativeProps, classList, style, previousStyle);
}
};
},
[classList, hostRef, style, extras]
[classList, hostRef, style]
);
}
export function usePlatformInputMethods(
hostRef: ElementRef<any>,
ref: ElementRef<any>,
classList: Array<boolean | string>,
style: GenericStyleProp<any>,
extras: any
) {
const previousStyle = useRef(null);
useImperativeHandle(
ref,
() => {
const hostNode = hostRef.current;
return {
blur() {
UIManager.blur(hostNode);
},
clear() {
if (hostNode != null) {
hostNode.value = '';
}
},
focus() {
UIManager.focus(hostNode);
},
isFocused() {
return hostNode != null && TextInputState.currentlyFocusedField() === hostNode;
},
measure(callback) {
UIManager.measure(hostNode, callback);
},
measureLayout(relativeToNativeNode, onFail, onSuccess) {
UIManager.measureLayout(hostNode, relativeToNativeNode, onFail, onSuccess);
},
measureInWindow(callback) {
UIManager.measureInWindow(hostNode, callback);
},
setNativeProps(nativeProps) {
setNativeProps(hostNode, nativeProps, classList, style, previousStyle);
}
};
},
[classList, hostRef, style]
);
}
@@ -17,7 +17,10 @@ describe('modules/AccessibilityUtil/propsToAccessibilityComponent', () => {
test('when "accessibilityRole" is "heading" and "aria-level" is set', () => {
expect(
propsToAccessibilityComponent({ accessibilityRole: 'heading', 'aria-level': 3 })
propsToAccessibilityComponent({
accessibilityRole: 'heading',
unstable_ariaSet: { level: 3 }
})
).toEqual('h3');
});
@@ -34,8 +34,12 @@ const propsToAccessibilityComponent = (props: Object = emptyObject) => {
const role = propsToAriaRole(props);
if (role) {
if (role === 'heading') {
const level = props['aria-level'] || 1;
return `h${level}`;
const ariaSet = props.unstable_ariaSet;
if (ariaSet != null && ariaSet.level) {
const level = ariaSet.level;
return `h${level}`;
}
return 'h1';
}
return roleComponents[role];
}
@@ -14,6 +14,7 @@ import styleResolver from '../../exports/StyleSheet/styleResolver';
import { STYLE_GROUPS } from '../../exports/StyleSheet/constants';
const emptyObject = {};
const hasOwnProperty = Object.prototype.hasOwnProperty;
// Reset styles for heading, link, and list DOM elements
const classes = css.create(
@@ -65,8 +66,8 @@ const createDOMProps = (component, props, styleResolver) => {
accessibilityLiveRegion,
accessibilityRelationship,
accessibilityState,
accessibilityValue,
classList,
className: deprecatedClassName,
disabled: providedDisabled,
importantForAccessibility,
nativeID,
@@ -77,6 +78,8 @@ const createDOMProps = (component, props, styleResolver) => {
accessible,
accessibilityRole,
/* eslint-enable */
unstable_ariaSet,
unstable_dataSet,
...domProps
} = props;
@@ -84,6 +87,30 @@ const createDOMProps = (component, props, styleResolver) => {
(accessibilityState != null && accessibilityState.disabled === true) || providedDisabled;
const role = AccessibilityUtil.propsToAriaRole(props);
// unstable_ariaSet
if (unstable_ariaSet != null) {
for (const prop in unstable_ariaSet) {
if (hasOwnProperty.call(unstable_ariaSet, prop)) {
const value = unstable_ariaSet[prop];
if (value != null) {
domProps[`aria-${prop}`] = value;
}
}
}
}
// unstable_dataSet
if (unstable_dataSet != null) {
for (const prop in unstable_dataSet) {
if (hasOwnProperty.call(unstable_dataSet, prop)) {
const value = unstable_dataSet[prop];
if (value != null) {
domProps[`data-${prop}`] = value;
}
}
}
}
// accessibilityLabel
if (accessibilityLabel != null) {
domProps['aria-label'] = accessibilityLabel;
@@ -126,6 +153,17 @@ const createDOMProps = (component, props, styleResolver) => {
}
}
}
// accessibilityValue
if (accessibilityValue != null) {
for (const prop in accessibilityValue) {
const value = accessibilityValue[prop];
if (value != null) {
domProps[`aria-value${prop}`] = value;
}
}
}
// legacy fallbacks
if (importantForAccessibility === 'no-hide-descendants') {
domProps['aria-hidden'] = true;
@@ -182,12 +220,7 @@ const createDOMProps = (component, props, styleResolver) => {
component === 'ul' ||
role === 'heading';
// Classic CSS styles
const finalClassList = [
deprecatedClassName,
needsReset && classes.reset,
needsCursor && classes.cursor,
classList
];
const finalClassList = [needsReset && classes.reset, needsCursor && classes.cursor, classList];
// Resolve styles
const { className, style } = styleResolver(reactNativeStyle, finalClassList);
@@ -202,7 +235,7 @@ const createDOMProps = (component, props, styleResolver) => {
// OTHER
// Native element ID
if (nativeID && nativeID.constructor === String) {
if (nativeID != null) {
domProps.id = nativeID;
}
@@ -214,7 +247,7 @@ const createDOMProps = (component, props, styleResolver) => {
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
}
// Automated test IDs
if (testID && testID.constructor === String) {
if (testID != null) {
domProps['data-testid'] = testID;
}