mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-28 00:32:17 +00:00
[change] better Touchable support for keyboards
Problem: Although 'Touchable' supports basic keyboard usage, it doesn't support delays or interaction via the Space key. Solution: Extend the 'Touchable' mixin to better support keyboard interactions. All touchable callbacks and delays are now supported when interacted with via a keyboard's Enter and Space keys (as would be expected of native 'button' elements). However, events are not normalized to mimic touch events. Minor upstream changes to the Touchables in React Native are also included.
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
|
||||
import CustomStyleOverrides from './examples/CustomStyleOverrides';
|
||||
import DelayEvents from './examples/DelayEvents';
|
||||
import FeedbackEvents from './examples/FeedbackEvents';
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@kadira/storybook';
|
||||
import { TouchableHighlightDisabled } from './examples/PropDisabled';
|
||||
@@ -45,13 +47,27 @@ const sections = [
|
||||
title: 'More examples',
|
||||
entries: [
|
||||
<DocItem
|
||||
description="Disabled TouchableHighlight"
|
||||
description="Disabled"
|
||||
example={{
|
||||
code: '',
|
||||
render: () => <TouchableHighlightDisabled />
|
||||
}}
|
||||
/>,
|
||||
|
||||
<DocItem
|
||||
description="Feedback events"
|
||||
example={{
|
||||
render: () => <FeedbackEvents touchable="highlight" />
|
||||
}}
|
||||
/>,
|
||||
|
||||
<DocItem
|
||||
description="Delay events"
|
||||
example={{
|
||||
render: () => <DelayEvents touchable="highlight" />
|
||||
}}
|
||||
/>,
|
||||
|
||||
<DocItem
|
||||
description="Custom style overrides"
|
||||
example={{
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import DelayEvents from './examples/DelayEvents';
|
||||
import FeedbackEvents from './examples/FeedbackEvents';
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@kadira/storybook';
|
||||
import { TouchableOpacityDisabled } from './examples/PropDisabled';
|
||||
@@ -44,9 +46,22 @@ const sections = [
|
||||
<DocItem
|
||||
description="Disabled TouchableOpacity"
|
||||
example={{
|
||||
code: '',
|
||||
render: () => <TouchableOpacityDisabled />
|
||||
}}
|
||||
/>,
|
||||
|
||||
<DocItem
|
||||
description="Feedback events"
|
||||
example={{
|
||||
render: () => <FeedbackEvents touchable="opacity" />
|
||||
}}
|
||||
/>,
|
||||
|
||||
<DocItem
|
||||
description="Delay events"
|
||||
example={{
|
||||
render: () => <DelayEvents touchable="opacity" />
|
||||
}}
|
||||
/>
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import DelayEvents from './examples/DelayEvents';
|
||||
import FeedbackEvents from './examples/FeedbackEvents';
|
||||
import React from 'react';
|
||||
import PropHitSlop from './examples/PropHitSlop';
|
||||
import { storiesOf } from '@kadira/storybook';
|
||||
import { TouchableWithoutFeedbackDisabled } from './examples/PropDisabled';
|
||||
import UIExplorer, { AppText, Code, DocItem } from '../../ui-explorer';
|
||||
|
||||
const sections = [
|
||||
@@ -53,6 +56,9 @@ const sections = [
|
||||
If <Code>true</Code>, disable all interactions for this component.
|
||||
</AppText>
|
||||
}
|
||||
example={{
|
||||
render: () => <TouchableWithoutFeedbackDisabled />
|
||||
}}
|
||||
/>,
|
||||
|
||||
<DocItem name="onLongPress" typeInfo="?function" />,
|
||||
@@ -83,7 +89,23 @@ constant to reduce memory allocations.`}
|
||||
|
||||
{
|
||||
title: 'More examples',
|
||||
entries: [<DocItem description="Hit slop" example={{ render: () => <PropHitSlop /> }} />]
|
||||
entries: [
|
||||
<DocItem
|
||||
description="Feedback events"
|
||||
example={{
|
||||
render: () => <FeedbackEvents touchable="withoutFeedback" />
|
||||
}}
|
||||
/>,
|
||||
|
||||
<DocItem
|
||||
description="Delay events"
|
||||
example={{
|
||||
render: () => <DelayEvents touchable="withoutFeedback" />
|
||||
}}
|
||||
/>,
|
||||
|
||||
<DocItem description="Hit slop" example={{ render: () => <PropHitSlop /> }} />
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import { oneOf } from 'prop-types';
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
const Touchables = {
|
||||
highlight: TouchableHighlight,
|
||||
opacity: TouchableOpacity,
|
||||
withoutFeedback: TouchableWithoutFeedback
|
||||
};
|
||||
|
||||
export default class TouchableDelayEvents extends PureComponent {
|
||||
static propTypes = {
|
||||
touchable: oneOf(['highlight', 'opacity', 'withoutFeedback'])
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
touchable: 'highlight'
|
||||
};
|
||||
|
||||
state = { eventLog: [] };
|
||||
|
||||
render() {
|
||||
const Touchable = Touchables[this.props.touchable];
|
||||
const { displayName } = Touchable;
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Touchable
|
||||
delayLongPress={800}
|
||||
delayPressIn={400}
|
||||
delayPressOut={1000}
|
||||
onLongPress={this._createPressHandler('longPress: 800ms delay')}
|
||||
onPress={this._createPressHandler('press')}
|
||||
onPressIn={this._createPressHandler('pressIn: 400ms delay')}
|
||||
onPressOut={this._createPressHandler('pressOut: 1000ms delay')}
|
||||
>
|
||||
<Text style={styles.touchableText}>
|
||||
{displayName}
|
||||
</Text>
|
||||
</Touchable>
|
||||
</View>
|
||||
<View style={styles.eventLogBox}>
|
||||
{this.state.eventLog.map((e, ii) =>
|
||||
<Text key={ii}>
|
||||
{e}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_createPressHandler = eventName => {
|
||||
return () => {
|
||||
const limit = 6;
|
||||
this.setState(state => {
|
||||
const eventLog = state.eventLog.slice(0, limit - 1);
|
||||
eventLog.unshift(eventName);
|
||||
return { eventLog };
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
touchableText: {
|
||||
borderRadius: 8,
|
||||
padding: 5,
|
||||
borderWidth: 1,
|
||||
borderColor: 'black',
|
||||
color: '#007AFF',
|
||||
borderStyle: 'solid',
|
||||
textAlign: 'center'
|
||||
},
|
||||
logBox: {
|
||||
padding: 20,
|
||||
margin: 10,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9'
|
||||
},
|
||||
eventLogBox: {
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
height: 120,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9'
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import { oneOf } from 'prop-types';
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
const Touchables = {
|
||||
highlight: TouchableHighlight,
|
||||
opacity: TouchableOpacity,
|
||||
withoutFeedback: TouchableWithoutFeedback
|
||||
};
|
||||
|
||||
export default class TouchableFeedbackEvents extends PureComponent {
|
||||
static propTypes = {
|
||||
touchable: oneOf(['highlight', 'opacity', 'withoutFeedback'])
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
touchable: 'highlight'
|
||||
};
|
||||
|
||||
state = { eventLog: [] };
|
||||
|
||||
render() {
|
||||
const Touchable = Touchables[this.props.touchable];
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Touchable
|
||||
onLongPress={this._createPressHandler('longPress')}
|
||||
onPress={this._createPressHandler('press')}
|
||||
onPressIn={this._createPressHandler('pressIn')}
|
||||
onPressOut={this._createPressHandler('pressOut')}
|
||||
>
|
||||
<Text style={styles.touchableText}>Press Me</Text>
|
||||
</Touchable>
|
||||
</View>
|
||||
<View style={styles.eventLogBox}>
|
||||
{this.state.eventLog.map((e, ii) =>
|
||||
<Text key={ii}>
|
||||
{e}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_createPressHandler = eventName => {
|
||||
return () => {
|
||||
const limit = 6;
|
||||
this.setState(state => {
|
||||
const eventLog = state.eventLog.slice(0, limit - 1);
|
||||
eventLog.unshift(eventName);
|
||||
return { eventLog };
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
touchableText: {
|
||||
borderRadius: 8,
|
||||
padding: 5,
|
||||
borderWidth: 1,
|
||||
borderColor: 'black',
|
||||
color: '#007AFF',
|
||||
borderStyle: 'solid',
|
||||
textAlign: 'center'
|
||||
},
|
||||
logBox: {
|
||||
padding: 20,
|
||||
margin: 10,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9'
|
||||
},
|
||||
eventLogBox: {
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
height: 120,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9'
|
||||
}
|
||||
});
|
||||
@@ -4,7 +4,14 @@
|
||||
|
||||
import React from 'react';
|
||||
import { action } from '@kadira/storybook';
|
||||
import { StyleSheet, View, Text, TouchableHighlight, TouchableOpacity } from 'react-native';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback
|
||||
} from 'react-native';
|
||||
|
||||
class TouchableHighlightDisabled extends React.Component {
|
||||
render() {
|
||||
@@ -57,7 +64,27 @@ class TouchableOpacityDisabled extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export { TouchableHighlightDisabled, TouchableOpacityDisabled };
|
||||
class TouchableWithoutFeedbackDisabled extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableWithoutFeedback disabled={true} onPress={action('TouchableWithoutFeedback')}>
|
||||
<View style={[styles.row, styles.block]}>
|
||||
<Text style={styles.disabledButton}>Disabled TouchableWithoutFeedback</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
<TouchableWithoutFeedback disabled={false} onPress={action('TouchableWithoutFeedback')}>
|
||||
<View style={[styles.row, styles.block]}>
|
||||
<Text style={styles.button}>Enabled TouchableWithoutFeedback</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { TouchableHighlightDisabled, TouchableOpacityDisabled, TouchableWithoutFeedbackDisabled };
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
@@ -66,12 +93,5 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
block: {
|
||||
padding: 10
|
||||
},
|
||||
button: {
|
||||
color: '#007AFF'
|
||||
},
|
||||
disabledButton: {
|
||||
color: '#007AFF',
|
||||
opacity: 0.5
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,92 +17,6 @@ import {
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
class TouchableFeedbackEvents extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { eventLog: [] };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View testID="touchable_feedback_events">
|
||||
<View style={[styles.row, { justifyContent: 'center' }]}>
|
||||
<TouchableOpacity
|
||||
accessibilityComponentType="button"
|
||||
accessibilityLabel="touchable feedback events"
|
||||
accessibilityTraits="button"
|
||||
onLongPress={this._createPressHandler('longPress')}
|
||||
onPress={this._createPressHandler('press')}
|
||||
onPressIn={this._createPressHandler('pressIn')}
|
||||
onPressOut={this._createPressHandler('pressOut')}
|
||||
style={styles.wrapper}
|
||||
testID="touchable_feedback_events_button"
|
||||
>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.eventLogBox} testID="touchable_feedback_events_console">
|
||||
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_createPressHandler = eventName => {
|
||||
return () => {
|
||||
const limit = 6;
|
||||
const eventLog = this.state.eventLog.slice(0, limit - 1);
|
||||
eventLog.unshift(eventName);
|
||||
this.setState({ eventLog });
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class TouchableDelayEvents extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { eventLog: [] };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View testID="touchable_delay_events">
|
||||
<View style={[styles.row, { justifyContent: 'center' }]}>
|
||||
<TouchableOpacity
|
||||
delayLongPress={800}
|
||||
delayPressIn={400}
|
||||
delayPressOut={1000}
|
||||
onLongPress={this._createPressHandler('longPress - 800ms delay')}
|
||||
onPress={this._createPressHandler('press')}
|
||||
onPressIn={this._createPressHandler('pressIn - 400ms delay')}
|
||||
onPressOut={this._createPressHandler('pressOut - 1000ms delay')}
|
||||
style={styles.wrapper}
|
||||
testID="touchable_delay_events_button"
|
||||
>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.eventLogBox} testID="touchable_delay_events_console">
|
||||
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_createPressHandler = eventName => {
|
||||
return () => {
|
||||
const limit = 6;
|
||||
const eventLog = this.state.eventLog.slice(0, limit - 1);
|
||||
eventLog.unshift(eventName);
|
||||
this.setState({ eventLog });
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const heartImage = { uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small' };
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -214,33 +128,5 @@ const examples = [
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Touchable feedback events',
|
||||
description:
|
||||
'<Touchable*> components accept onPress, onPressIn, ' +
|
||||
'onPressOut, and onLongPress as props.',
|
||||
render() {
|
||||
return <TouchableFeedbackEvents />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Touchable delay for events',
|
||||
description:
|
||||
'<Touchable*> components also accept delayPressIn, ' +
|
||||
'delayPressOut, and delayLongPress as props. These props impact the ' +
|
||||
'timing of feedback events.',
|
||||
render() {
|
||||
return <TouchableDelayEvents />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Disabled Touchable*',
|
||||
description:
|
||||
'<Touchable*> components accept disabled prop which prevents ' +
|
||||
'any interaction with component',
|
||||
render() {
|
||||
return <TouchableDisabled />;
|
||||
}
|
||||
}
|
||||
];
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user