diff --git a/samples/RXPTest/src/Tests/GestureViewTest.tsx b/samples/RXPTest/src/Tests/GestureViewTest.tsx index f5e7682c..7ec26056 100644 --- a/samples/RXPTest/src/Tests/GestureViewTest.tsx +++ b/samples/RXPTest/src/Tests/GestureViewTest.tsx @@ -33,12 +33,39 @@ const _styles = { height: 50, width: 50, 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 _shades = ['#000', '#333', '#666', '#999', '#CCC', '#FFF']; +const modal1Id = 'modal1'; + interface GestureViewState { test1ColorIndex: number; test2ColorIndex: number; @@ -196,10 +223,39 @@ class GestureViewView extends RX.Component { style={ [_styles.smallBox, test4ColorStyle, this._test4AnimatedStyle] } /> + + + + + { 'Show Modal Dialog' } + + + ); } + private _onShowModal = (e: RX.Types.SyntheticEvent) => { + e.stopPropagation(); + RX.Modal.show(( + + + + { 'Gesture targets should be disabled' } + + + { 'Click here to dismiss dialog' } + + + + ), + modal1Id); + } + + private _onDismissDialog = (e: RX.Types.SyntheticEvent) => { + RX.Modal.dismiss(modal1Id); + } + private _onPinchZoomTest1(state: RX.Types.MultiTouchGestureState) { // Determine ratio of distance to initial distance. let zoomRatio = state.distance / state.initialDistance; diff --git a/src/web/FrontLayerViewManager.tsx b/src/web/FrontLayerViewManager.tsx index a786e57d..13c1e019 100644 --- a/src/web/FrontLayerViewManager.tsx +++ b/src/web/FrontLayerViewManager.tsx @@ -13,6 +13,7 @@ import * as ReactDOM from 'react-dom'; import { PopupDescriptor, RootView } from './RootView'; import { Types } from '../common/Interfaces'; import Timers from '../common/utils/Timers'; +import MouseResponder from './utils/MouseResponder'; const MAX_CACHED_POPUPS = 4; @@ -75,6 +76,10 @@ export class FrontLayerViewManager { this._activePopupOptions!!!.getAnchor() === options.getAnchor(); } + private _updateModalDisplayedState() { + MouseResponder.setModalIsDisplayed(this.isModalDisplayed()); + } + showPopup(options: Types.PopupOptions, popupId: string, showDelay?: number): boolean { // 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 @@ -180,6 +185,8 @@ export class FrontLayerViewManager { const activePopup = (!this._activePopupOptions || this._activePopupShowDelay > 0) ? undefined : { popupOptions: this._activePopupOptions, popupId: this._activePopupId!!! }; + this._updateModalDisplayedState(); + let rootView = ( { @@ -65,6 +70,10 @@ export class GestureView extends React.Component = { + isInRxMainView: PropTypes.bool + }; + componentWillUnmount() { // Dispose of timer before the component goes away. this._cancelDoubleTapTimer(); @@ -98,6 +107,7 @@ export class GestureView extends React.Component { if (!this.props.onPan && !this.props.onPanHorizontal && !this.props.onPanVertical) { return false; diff --git a/src/web/RootView.tsx b/src/web/RootView.tsx index 932b93fe..04f728b3 100644 --- a/src/web/RootView.tsx +++ b/src/web/RootView.tsx @@ -27,7 +27,7 @@ export class PopupDescriptor { constructor(public popupId: string, public popupOptions: Types.PopupOptions) {} } -export interface RootViewProps { +export interface RootViewProps extends Types.CommonProps { mainView?: React.ReactNode; modal?: React.ReactElement; activePopup?: PopupDescriptor; @@ -88,6 +88,32 @@ if (typeof document !== 'undefined') { 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 + implements React.ChildContextProvider { + static childContextTypes: React.ValidationMap = { + isInRxMainView: PropTypes.bool + }; + + getChildContext(): MainViewContext { + return { + isInRxMainView: true + }; + } + + render() { + return ( + this.props.children + ); + } +} + export class RootView extends React.Component { static childContextTypes: React.ValidationMap = { focusManager: PropTypes.object @@ -255,7 +281,7 @@ export class RootView extends React.Component { } render() { - let rootViewStyle = { + const rootViewStyle = { width: '100%', height: '100%', display: 'flex', @@ -285,7 +311,9 @@ export class RootView extends React.Component { style={ rootViewStyle } dir={ this.props.writingDirection } > - { this.props.mainView } + + { this.props.mainView } + { optionalModal } { optionalPopups } diff --git a/src/web/utils/MouseResponder.ts b/src/web/utils/MouseResponder.ts index a735e183..29d75e7d 100644 --- a/src/web/utils/MouseResponder.ts +++ b/src/web/utils/MouseResponder.ts @@ -29,6 +29,7 @@ interface Responder { export interface MouseResponderConfig { id: number; target: HTMLElement; + disableWhenModal: boolean; shouldBecomeFirstResponder?: (event: MouseEvent, gestureState: Types.PanGestureState) => boolean; onMove?: (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 _pendingGestureState: Types.PanGestureState | null = null; private static _initialized = false; + private static _isModalDisplayed = false; private static _responders: Responder[]; + static setModalIsDisplayed(isDisplayed: boolean) { + MouseResponder._isModalDisplayed = isDisplayed; + } + static create(config: MouseResponderConfig): MouseResponderSubscription { MouseResponder._initializeEventHandlers(); @@ -54,6 +60,10 @@ export default class MouseResponder { id: config.id, target: config.target, shouldBecomeFirstResponder(event: MouseEvent, gestureState: Types.PanGestureState) { + if (MouseResponder._isModalDisplayed && config.disableWhenModal) { + return false; + } + if (!config.shouldBecomeFirstResponder) { return false; }