зеркало из https://github.com/microsoft/reactxp.git
Added code to disable mouse gestures in main view when a modal dialog is overlaying the main view. (#846)
This commit is contained in:
Родитель
b1c84e932e
Коммит
42183e21cd
|
@ -33,12 +33,39 @@ const _styles = {
|
||||||
height: 50,
|
height: 50,
|
||||||
width: 50,
|
width: 50,
|
||||||
backgroundColor: 'blue'
|
backgroundColor: 'blue'
|
||||||
|
}),
|
||||||
|
button: RX.Styles.createButtonStyle({
|
||||||
|
borderColor: 'black',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 8,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4
|
||||||
|
}),
|
||||||
|
modalDialog: RX.Styles.createTextStyle({
|
||||||
|
flex: 1,
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}),
|
||||||
|
modalBox1: RX.Styles.createTextStyle({
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: '#eee',
|
||||||
|
borderColor: 'black',
|
||||||
|
borderWidth: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}),
|
||||||
|
modalText: RX.Styles.createViewStyle({
|
||||||
|
margin: 8
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const _colors = ['red', 'green', 'blue'];
|
const _colors = ['red', 'green', 'blue'];
|
||||||
const _shades = ['#000', '#333', '#666', '#999', '#CCC', '#FFF'];
|
const _shades = ['#000', '#333', '#666', '#999', '#CCC', '#FFF'];
|
||||||
|
|
||||||
|
const modal1Id = 'modal1';
|
||||||
|
|
||||||
interface GestureViewState {
|
interface GestureViewState {
|
||||||
test1ColorIndex: number;
|
test1ColorIndex: number;
|
||||||
test2ColorIndex: number;
|
test2ColorIndex: number;
|
||||||
|
@ -196,10 +223,39 @@ class GestureViewView extends RX.Component<RX.CommonProps, GestureViewState> {
|
||||||
style={ [_styles.smallBox, test4ColorStyle, this._test4AnimatedStyle] }
|
style={ [_styles.smallBox, test4ColorStyle, this._test4AnimatedStyle] }
|
||||||
/>
|
/>
|
||||||
</RX.GestureView>
|
</RX.GestureView>
|
||||||
|
|
||||||
|
<RX.View style={ _styles.explainTextContainer } key={ 'explanation5' }>
|
||||||
|
<RX.Button style={ _styles.button } onPress={ this._onShowModal }>
|
||||||
|
<RX.Text style={ _styles.explainText }>
|
||||||
|
{ 'Show Modal Dialog' }
|
||||||
|
</RX.Text>
|
||||||
|
</RX.Button>
|
||||||
|
</RX.View>
|
||||||
</RX.View>
|
</RX.View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _onShowModal = (e: RX.Types.SyntheticEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
RX.Modal.show((
|
||||||
|
<RX.View style={ _styles.modalDialog }>
|
||||||
|
<RX.View style={ _styles.modalBox1 } onPress={ this._onDismissDialog }>
|
||||||
|
<RX.Text style={ _styles.modalText }>
|
||||||
|
{ 'Gesture targets should be disabled' }
|
||||||
|
</RX.Text>
|
||||||
|
<RX.Text style={ _styles.modalText }>
|
||||||
|
{ 'Click here to dismiss dialog' }
|
||||||
|
</RX.Text>
|
||||||
|
</RX.View>
|
||||||
|
</RX.View>
|
||||||
|
),
|
||||||
|
modal1Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDismissDialog = (e: RX.Types.SyntheticEvent) => {
|
||||||
|
RX.Modal.dismiss(modal1Id);
|
||||||
|
}
|
||||||
|
|
||||||
private _onPinchZoomTest1(state: RX.Types.MultiTouchGestureState) {
|
private _onPinchZoomTest1(state: RX.Types.MultiTouchGestureState) {
|
||||||
// Determine ratio of distance to initial distance.
|
// Determine ratio of distance to initial distance.
|
||||||
let zoomRatio = state.distance / state.initialDistance;
|
let zoomRatio = state.distance / state.initialDistance;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import * as ReactDOM from 'react-dom';
|
||||||
import { PopupDescriptor, RootView } from './RootView';
|
import { PopupDescriptor, RootView } from './RootView';
|
||||||
import { Types } from '../common/Interfaces';
|
import { Types } from '../common/Interfaces';
|
||||||
import Timers from '../common/utils/Timers';
|
import Timers from '../common/utils/Timers';
|
||||||
|
import MouseResponder from './utils/MouseResponder';
|
||||||
|
|
||||||
const MAX_CACHED_POPUPS = 4;
|
const MAX_CACHED_POPUPS = 4;
|
||||||
|
|
||||||
|
@ -75,6 +76,10 @@ export class FrontLayerViewManager {
|
||||||
this._activePopupOptions!!!.getAnchor() === options.getAnchor();
|
this._activePopupOptions!!!.getAnchor() === options.getAnchor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _updateModalDisplayedState() {
|
||||||
|
MouseResponder.setModalIsDisplayed(this.isModalDisplayed());
|
||||||
|
}
|
||||||
|
|
||||||
showPopup(options: Types.PopupOptions, popupId: string, showDelay?: number): boolean {
|
showPopup(options: Types.PopupOptions, popupId: string, showDelay?: number): boolean {
|
||||||
// If options.dismissIfShown is true, calling this method will behave like a toggle.
|
// If options.dismissIfShown is true, calling this method will behave like a toggle.
|
||||||
// On one call, it will open the popup. If it is called when pop up is seen, it will
|
// On one call, it will open the popup. If it is called when pop up is seen, it will
|
||||||
|
@ -180,6 +185,8 @@ export class FrontLayerViewManager {
|
||||||
const activePopup = (!this._activePopupOptions || this._activePopupShowDelay > 0) ? undefined :
|
const activePopup = (!this._activePopupOptions || this._activePopupShowDelay > 0) ? undefined :
|
||||||
{ popupOptions: this._activePopupOptions, popupId: this._activePopupId!!! };
|
{ popupOptions: this._activePopupOptions, popupId: this._activePopupId!!! };
|
||||||
|
|
||||||
|
this._updateModalDisplayedState();
|
||||||
|
|
||||||
let rootView = (
|
let rootView = (
|
||||||
<RootView
|
<RootView
|
||||||
mainView={ this._mainView }
|
mainView={ this._mainView }
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
* It provides support for the scroll wheel, clicks and double clicks.
|
* It provides support for the scroll wheel, clicks and double clicks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { clone, isUndefined } from './utils/lodashMini';
|
import { clone, isUndefined } from './utils/lodashMini';
|
||||||
|
@ -43,6 +44,10 @@ enum GestureType {
|
||||||
PanHorizontal
|
PanHorizontal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GestureViewContext {
|
||||||
|
isInRxMainView?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
let _idCounter = 1;
|
let _idCounter = 1;
|
||||||
|
|
||||||
export class GestureView extends React.Component<Types.GestureViewProps, Types.Stateless> {
|
export class GestureView extends React.Component<Types.GestureViewProps, Types.Stateless> {
|
||||||
|
@ -65,6 +70,10 @@ export class GestureView extends React.Component<Types.GestureViewProps, Types.S
|
||||||
private _gestureTypeLocked = false;
|
private _gestureTypeLocked = false;
|
||||||
private _skipNextTap = false;
|
private _skipNextTap = false;
|
||||||
|
|
||||||
|
static contextTypes: React.ValidationMap<any> = {
|
||||||
|
isInRxMainView: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
// Dispose of timer before the component goes away.
|
// Dispose of timer before the component goes away.
|
||||||
this._cancelDoubleTapTimer();
|
this._cancelDoubleTapTimer();
|
||||||
|
@ -98,6 +107,7 @@ export class GestureView extends React.Component<Types.GestureViewProps, Types.S
|
||||||
this._responder = MouseResponder.create({
|
this._responder = MouseResponder.create({
|
||||||
id: this._id,
|
id: this._id,
|
||||||
target: container,
|
target: container,
|
||||||
|
disableWhenModal: !!this.context.isInRxMainView,
|
||||||
shouldBecomeFirstResponder: (event: MouseEvent) => {
|
shouldBecomeFirstResponder: (event: MouseEvent) => {
|
||||||
if (!this.props.onPan && !this.props.onPanHorizontal && !this.props.onPanVertical) {
|
if (!this.props.onPan && !this.props.onPanHorizontal && !this.props.onPanVertical) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class PopupDescriptor {
|
||||||
constructor(public popupId: string, public popupOptions: Types.PopupOptions) {}
|
constructor(public popupId: string, public popupOptions: Types.PopupOptions) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RootViewProps {
|
export interface RootViewProps extends Types.CommonProps {
|
||||||
mainView?: React.ReactNode;
|
mainView?: React.ReactNode;
|
||||||
modal?: React.ReactElement<Types.ViewProps>;
|
modal?: React.ReactElement<Types.ViewProps>;
|
||||||
activePopup?: PopupDescriptor;
|
activePopup?: PopupDescriptor;
|
||||||
|
@ -88,6 +88,32 @@ if (typeof document !== 'undefined') {
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MainViewContext {
|
||||||
|
isInRxMainView?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This helper class wraps the main view and passes a boolean value
|
||||||
|
// "isInRxMainView" to all children found within it. This is used to
|
||||||
|
// prevent gesture handling within the main view when a modal is displayed.
|
||||||
|
export class MainViewContainer extends React.Component<Types.CommonProps, Types.Stateless>
|
||||||
|
implements React.ChildContextProvider<MainViewContext> {
|
||||||
|
static childContextTypes: React.ValidationMap<any> = {
|
||||||
|
isInRxMainView: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
getChildContext(): MainViewContext {
|
||||||
|
return {
|
||||||
|
isInRxMainView: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
this.props.children
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class RootView extends React.Component<RootViewProps, RootViewState> {
|
export class RootView extends React.Component<RootViewProps, RootViewState> {
|
||||||
static childContextTypes: React.ValidationMap<any> = {
|
static childContextTypes: React.ValidationMap<any> = {
|
||||||
focusManager: PropTypes.object
|
focusManager: PropTypes.object
|
||||||
|
@ -255,7 +281,7 @@ export class RootView extends React.Component<RootViewProps, RootViewState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let rootViewStyle = {
|
const rootViewStyle = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -285,7 +311,9 @@ export class RootView extends React.Component<RootViewProps, RootViewState> {
|
||||||
style={ rootViewStyle }
|
style={ rootViewStyle }
|
||||||
dir={ this.props.writingDirection }
|
dir={ this.props.writingDirection }
|
||||||
>
|
>
|
||||||
{ this.props.mainView }
|
<MainViewContainer>
|
||||||
|
{ this.props.mainView }
|
||||||
|
</MainViewContainer>
|
||||||
{ optionalModal }
|
{ optionalModal }
|
||||||
{ optionalPopups }
|
{ optionalPopups }
|
||||||
<AccessibilityAnnouncer />
|
<AccessibilityAnnouncer />
|
||||||
|
|
|
@ -29,6 +29,7 @@ interface Responder {
|
||||||
export interface MouseResponderConfig {
|
export interface MouseResponderConfig {
|
||||||
id: number;
|
id: number;
|
||||||
target: HTMLElement;
|
target: HTMLElement;
|
||||||
|
disableWhenModal: boolean;
|
||||||
shouldBecomeFirstResponder?: (event: MouseEvent, gestureState: Types.PanGestureState) => boolean;
|
shouldBecomeFirstResponder?: (event: MouseEvent, gestureState: Types.PanGestureState) => boolean;
|
||||||
onMove?: (event: MouseEvent, gestureState: Types.PanGestureState) => void;
|
onMove?: (event: MouseEvent, gestureState: Types.PanGestureState) => void;
|
||||||
onTerminate?: (event: MouseEvent, gestureState: Types.PanGestureState) => void;
|
onTerminate?: (event: MouseEvent, gestureState: Types.PanGestureState) => void;
|
||||||
|
@ -42,9 +43,14 @@ export default class MouseResponder {
|
||||||
private static _currentResponder: Responder | null = null;
|
private static _currentResponder: Responder | null = null;
|
||||||
private static _pendingGestureState: Types.PanGestureState | null = null;
|
private static _pendingGestureState: Types.PanGestureState | null = null;
|
||||||
private static _initialized = false;
|
private static _initialized = false;
|
||||||
|
private static _isModalDisplayed = false;
|
||||||
|
|
||||||
private static _responders: Responder[];
|
private static _responders: Responder[];
|
||||||
|
|
||||||
|
static setModalIsDisplayed(isDisplayed: boolean) {
|
||||||
|
MouseResponder._isModalDisplayed = isDisplayed;
|
||||||
|
}
|
||||||
|
|
||||||
static create(config: MouseResponderConfig): MouseResponderSubscription {
|
static create(config: MouseResponderConfig): MouseResponderSubscription {
|
||||||
MouseResponder._initializeEventHandlers();
|
MouseResponder._initializeEventHandlers();
|
||||||
|
|
||||||
|
@ -54,6 +60,10 @@ export default class MouseResponder {
|
||||||
id: config.id,
|
id: config.id,
|
||||||
target: config.target,
|
target: config.target,
|
||||||
shouldBecomeFirstResponder(event: MouseEvent, gestureState: Types.PanGestureState) {
|
shouldBecomeFirstResponder(event: MouseEvent, gestureState: Types.PanGestureState) {
|
||||||
|
if (MouseResponder._isModalDisplayed && config.disableWhenModal) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!config.shouldBecomeFirstResponder) {
|
if (!config.shouldBecomeFirstResponder) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче