From 486a7e08428487286d09ef7bfeaa701e8376318d Mon Sep 17 00:00:00 2001 From: Anton Korzunov Date: Fri, 17 Apr 2020 13:25:53 +1000 Subject: [PATCH] feat: add crossFrame property to control iframe behaviour, fixes #104 --- interfaces.d.ts | 8 ++++++++ src/Lock.js | 4 ++++ src/Trap.js | 39 ++++++++++++++++++++++++++------------- stories/Default.js | 6 +++++- stories/Iframe.js | 7 ++++--- stories/Jump.js | 2 ++ stories/index.js | 5 +++-- 7 files changed, 52 insertions(+), 19 deletions(-) diff --git a/interfaces.d.ts b/interfaces.d.ts index 20d21e3..b88a92c 100644 --- a/interfaces.d.ts +++ b/interfaces.d.ts @@ -24,6 +24,14 @@ export interface ReactFocusLockProps { disabled, noFocusGuards, persistentFocus, + crossFrame, autoFocus, allowTextSelection, group, @@ -132,6 +133,7 @@ const FocusLock = React.forwardRef((props, parentRef) => { observed={realObserved} disabled={disabled} persistentFocus={persistentFocus} + crossFrame={crossFrame} autoFocus={autoFocus} whiteList={whiteList} shards={shards} @@ -166,6 +168,7 @@ FocusLock.propTypes = { allowTextSelection: bool, autoFocus: bool, persistentFocus: bool, + crossFrame: bool, group: string, className: string, @@ -189,6 +192,7 @@ FocusLock.defaultProps = { noFocusGuards: false, autoFocus: true, persistentFocus: false, + crossFrame: true, allowTextSelection: undefined, group: undefined, className: undefined, diff --git a/src/Trap.js b/src/Trap.js index ade15ac..d3a7da3 100644 --- a/src/Trap.js +++ b/src/Trap.js @@ -1,9 +1,9 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import withSideEffect from 'react-clientside-effect'; -import moveFocusInside, { focusInside, focusIsHidden, getFocusabledIn } from 'focus-lock'; -import { deferAction } from './util'; -import { mediumFocus, mediumBlur, mediumEffect } from './medium'; +import moveFocusInside, {focusInside, focusIsHidden, getFocusabledIn} from 'focus-lock'; +import {deferAction} from './util'; +import {mediumFocus, mediumBlur, mediumEffect} from './medium'; const focusOnBody = () => ( document && document.activeElement === document.body @@ -25,7 +25,7 @@ const focusWhitelisted = activeElement => ( ); const recordPortal = (observerNode, portaledElement) => { - lastPortaledElement = { observerNode, portaledElement }; + lastPortaledElement = {observerNode, portaledElement}; }; const focusIsPortaledPair = element => ( @@ -58,11 +58,20 @@ function autoGuard(startIndex, end, step, allNodes) { const extractRef = ref => ((ref && 'current' in ref) ? ref.current : ref); +const focusWasOutside = (crossFrameOption) => { + if(crossFrameOption){ + // with cross frame return true for any value + return Boolean(focusWasOutsideWindow); + } + // in other case return only of focus went a while aho + return focusWasOutsideWindow === "meanwhile" +} + const activateTrap = () => { let result = false; if (lastActiveTrap) { const { - observed, persistentFocus, autoFocus, shards, + observed, persistentFocus, autoFocus, shards, crossFrame, } = lastActiveTrap; const workingNode = observed || (lastPortaledElement && lastPortaledElement.portaledElement); const activeElement = document && document.activeElement; @@ -74,7 +83,7 @@ const activateTrap = () => { if (!activeElement || focusWhitelisted(activeElement)) { if ( - (persistentFocus || focusWasOutsideWindow) + (persistentFocus || focusWasOutside(crossFrame)) || !isFreeFocus() || (!lastActiveFocus && autoFocus) ) { @@ -101,12 +110,12 @@ const activateTrap = () => { if (document) { const newActiveElement = document && document.activeElement; const allNodes = getFocusabledIn(workingArea); - const focusedItem = allNodes.find(({ node }) => node === newActiveElement); + const focusedItem = allNodes.find(({node}) => node === newActiveElement); if (focusedItem) { // remove old focus allNodes - .filter(({ guard, node }) => guard && node.dataset.focusAutoGuard) - .forEach(({ node }) => node.removeAttribute('tabIndex')); + .filter(({guard, node}) => guard && node.dataset.focusAutoGuard) + .forEach(({node}) => node.removeAttribute('tabIndex')); const focusedIndex = allNodes.indexOf(focusedItem); autoGuard(focusedIndex, allNodes.length, +1, allNodes); @@ -141,7 +150,7 @@ const onFocus = (event) => { const FocusWatcher = () => null; -const FocusTrap = ({ children }) => ( +const FocusTrap = ({children}) => (
{children}
@@ -152,7 +161,11 @@ FocusTrap.propTypes = { }; const onWindowBlur = () => { - focusWasOutsideWindow = true; + focusWasOutsideWindow = "just"; + // using setTimeout to set this variable after React/sidecar reaction + setTimeout(() => { + focusWasOutsideWindow = "meanwhile"; + }, 0); }; const attachHandler = () => { @@ -169,7 +182,7 @@ const detachHandler = () => { function reducePropsToState(propsList) { return propsList - .filter(({ disabled }) => !disabled); + .filter(({disabled}) => !disabled); } function handleStateChangeOnClient(traps) { @@ -186,7 +199,7 @@ function handleStateChangeOnClient(traps) { if (lastTrap && !sameTrap) { lastTrap.onDeactivation(); // return focus only of last trap was removed - if (!traps.filter(({ id }) => id === lastTrap.id).length) { + if (!traps.filter(({id}) => id === lastTrap.id).length) { // allow defer is no other trap is awaiting restore lastTrap.returnFocus(!trap); } diff --git a/stories/Default.js b/stories/Default.js index 936e7ab..38215ec 100644 --- a/stories/Default.js +++ b/stories/Default.js @@ -21,8 +21,12 @@ class Trap extends Component { render() { const {disabled} = this.state; + const query = (new URL(document.location)).searchParams; return ( - + {disabled &&
! this is a real trap.
We will steal your focus !

diff --git a/stories/Iframe.js b/stories/Iframe.js index 4c48b27..e3da063 100644 --- a/stories/Iframe.js +++ b/stories/Iframe.js @@ -21,6 +21,7 @@ class Trap extends Component { render() { const {disabled} = this.state; + const {crossFrame} = this.props; return ( {disabled &&
@@ -34,7 +35,7 @@ class Trap extends Component { You will cycle over this. Never leaving
-