react-focus-lock
Advanced tools
Comparing version 2.10.1 to 2.11.0
@@ -13,4 +13,3 @@ "use strict"; | ||
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); | ||
var _react = _interopRequireWildcard(require("react")); | ||
var React = _react; | ||
var React = _interopRequireWildcard(require("react")); | ||
var _propTypes = require("prop-types"); | ||
@@ -69,4 +68,12 @@ var constants = _interopRequireWildcard(require("focus-lock/constants")); | ||
id = _React$useState6[0]; | ||
var onActivation = React.useCallback(function () { | ||
originalFocusedElement.current = originalFocusedElement.current || document && document.activeElement; | ||
var onActivation = React.useCallback(function (_ref) { | ||
var captureFocusRestore = _ref.captureFocusRestore; | ||
if (!originalFocusedElement.current) { | ||
var _document; | ||
var activeElement = (_document = document) === null || _document === void 0 ? void 0 : _document.activeElement; | ||
originalFocusedElement.current = activeElement; | ||
if (activeElement !== document.body) { | ||
originalFocusedElement.current = captureFocusRestore(activeElement); | ||
} | ||
} | ||
if (observed.current && onActivationCallback) { | ||
@@ -85,10 +92,6 @@ onActivationCallback(observed.current); | ||
}, [onDeactivationCallback]); | ||
(0, _react.useEffect)(function () { | ||
if (!disabled) { | ||
originalFocusedElement.current = null; | ||
} | ||
}, []); | ||
var returnFocus = React.useCallback(function (allowDefer) { | ||
var returnFocusTo = originalFocusedElement.current; | ||
if (returnFocusTo && returnFocusTo.focus) { | ||
var focusRestore = originalFocusedElement.current; | ||
if (focusRestore) { | ||
var returnFocusTo = (typeof focusRestore === 'function' ? focusRestore() : focusRestore) || document.body; | ||
var howToReturnFocus = typeof shouldReturnFocus === 'function' ? shouldReturnFocus(returnFocusTo) : shouldReturnFocus; | ||
@@ -95,0 +98,0 @@ if (howToReturnFocus) { |
@@ -187,2 +187,11 @@ "use strict"; | ||
} | ||
var focusLockAPI = { | ||
moveFocusInside: _focusLock.moveFocusInside, | ||
focusInside: _focusLock.focusInside, | ||
focusNextElement: _focusLock.focusNextElement, | ||
focusPrevElement: _focusLock.focusPrevElement, | ||
focusFirstElement: _focusLock.focusFirstElement, | ||
focusLastElement: _focusLock.focusLastElement, | ||
captureFocusRestore: _focusLock.captureFocusRestore | ||
}; | ||
function handleStateChangeOnClient(traps) { | ||
@@ -208,3 +217,3 @@ var trap = traps.slice(-1)[0]; | ||
if (!sameTrap || lastTrap.observed !== trap.observed) { | ||
trap.onActivation(); | ||
trap.onActivation(focusLockAPI); | ||
} | ||
@@ -221,9 +230,4 @@ activateTrap(true); | ||
_medium.mediumEffect.assignMedium(function (cb) { | ||
return cb({ | ||
moveFocusInside: _focusLock.moveFocusInside, | ||
focusInside: _focusLock.focusInside, | ||
focusNextElement: _focusLock.focusNextElement, | ||
focusPrevElement: _focusLock.focusPrevElement | ||
}); | ||
return cb(focusLockAPI); | ||
}); | ||
var _default = exports["default"] = (0, _reactClientsideEffect["default"])(reducePropsToState, handleStateChangeOnClient)(FocusWatcher); |
@@ -58,2 +58,12 @@ "use strict"; | ||
}); | ||
}, | ||
focusFirst: function focusFirst(options) { | ||
return withMedium(function (car) { | ||
car.focusFirstElement(collapseRefs(ref.current), options); | ||
}); | ||
}, | ||
focusLast: function focusLast(options) { | ||
return withMedium(function (car) { | ||
car.focusLastElement(collapseRefs(ref.current), options); | ||
}); | ||
} | ||
@@ -60,0 +70,0 @@ }; |
@@ -45,2 +45,3 @@ "use strict"; | ||
var useFocusState = exports.useFocusState = function useFocusState() { | ||
var callbacks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var _useState = (0, _react.useState)(false), | ||
@@ -56,6 +57,11 @@ _useState2 = (0, _slicedToArray2["default"])(_useState, 2), | ||
var focusState = (0, _react.useRef)({}); | ||
var stateTracker = (0, _react.useRef)(false); | ||
(0, _react.useEffect)(function () { | ||
if (ref.current) { | ||
setActive(ref.current === document.activeElement || ref.current.contains(document.activeElement)); | ||
var isAlreadyFocused = ref.current === document.activeElement || ref.current.contains(document.activeElement); | ||
setActive(isAlreadyFocused); | ||
setState(getFocusState(document.activeElement, ref.current)); | ||
if (isAlreadyFocused && callbacks.onFocus) { | ||
callbacks.onFocus(); | ||
} | ||
} | ||
@@ -75,4 +81,13 @@ }, []); | ||
var fin = mainbus.on('assign', function () { | ||
setActive(focusState.current.focused || false); | ||
var newState = focusState.current.focused || false; | ||
setActive(newState); | ||
setState(focusState.current.state || ''); | ||
if (newState !== stateTracker.current) { | ||
stateTracker.current = newState; | ||
if (newState) { | ||
callbacks.onFocus && callbacks.onFocus(); | ||
} else { | ||
callbacks.onBlur && callbacks.onBlur(); | ||
} | ||
} | ||
}); | ||
@@ -79,0 +94,0 @@ return function () { |
@@ -6,3 +6,2 @@ import _extends from "@babel/runtime/helpers/esm/extends"; | ||
import { useMergeRefs } from 'use-callback-ref'; | ||
import { useEffect } from 'react'; | ||
import { hiddenGuard } from './FocusGuard'; | ||
@@ -52,4 +51,12 @@ import { mediumFocus, mediumBlur, mediumSidecar } from './medium'; | ||
id = _React$useState3[0]; | ||
var onActivation = React.useCallback(function () { | ||
originalFocusedElement.current = originalFocusedElement.current || document && document.activeElement; | ||
var onActivation = React.useCallback(function (_ref) { | ||
var captureFocusRestore = _ref.captureFocusRestore; | ||
if (!originalFocusedElement.current) { | ||
var _document; | ||
var activeElement = (_document = document) == null ? void 0 : _document.activeElement; | ||
originalFocusedElement.current = activeElement; | ||
if (activeElement !== document.body) { | ||
originalFocusedElement.current = captureFocusRestore(activeElement); | ||
} | ||
} | ||
if (observed.current && onActivationCallback) { | ||
@@ -68,10 +75,6 @@ onActivationCallback(observed.current); | ||
}, [onDeactivationCallback]); | ||
useEffect(function () { | ||
if (!disabled) { | ||
originalFocusedElement.current = null; | ||
} | ||
}, []); | ||
var returnFocus = React.useCallback(function (allowDefer) { | ||
var returnFocusTo = originalFocusedElement.current; | ||
if (returnFocusTo && returnFocusTo.focus) { | ||
var focusRestore = originalFocusedElement.current; | ||
if (focusRestore) { | ||
var returnFocusTo = (typeof focusRestore === 'function' ? focusRestore() : focusRestore) || document.body; | ||
var howToReturnFocus = typeof shouldReturnFocus === 'function' ? shouldReturnFocus(returnFocusTo) : shouldReturnFocus; | ||
@@ -78,0 +81,0 @@ if (howToReturnFocus) { |
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import withSideEffect from 'react-clientside-effect'; | ||
import { moveFocusInside, focusInside, focusIsHidden, expandFocusableNodes, focusNextElement, focusPrevElement } from 'focus-lock'; | ||
import { moveFocusInside, focusInside, focusIsHidden, expandFocusableNodes, focusNextElement, focusPrevElement, focusFirstElement, focusLastElement, captureFocusRestore } from 'focus-lock'; | ||
import { deferAction, extractRef } from './util'; | ||
@@ -176,2 +176,11 @@ import { mediumFocus, mediumBlur, mediumEffect } from './medium'; | ||
} | ||
var focusLockAPI = { | ||
moveFocusInside: moveFocusInside, | ||
focusInside: focusInside, | ||
focusNextElement: focusNextElement, | ||
focusPrevElement: focusPrevElement, | ||
focusFirstElement: focusFirstElement, | ||
focusLastElement: focusLastElement, | ||
captureFocusRestore: captureFocusRestore | ||
}; | ||
function handleStateChangeOnClient(traps) { | ||
@@ -197,3 +206,3 @@ var trap = traps.slice(-1)[0]; | ||
if (!sameTrap || lastTrap.observed !== trap.observed) { | ||
trap.onActivation(); | ||
trap.onActivation(focusLockAPI); | ||
} | ||
@@ -210,9 +219,4 @@ activateTrap(true); | ||
mediumEffect.assignMedium(function (cb) { | ||
return cb({ | ||
moveFocusInside: moveFocusInside, | ||
focusInside: focusInside, | ||
focusNextElement: focusNextElement, | ||
focusPrevElement: focusPrevElement | ||
}); | ||
return cb(focusLockAPI); | ||
}); | ||
export default withSideEffect(reducePropsToState, handleStateChangeOnClient)(FocusWatcher); |
@@ -50,2 +50,12 @@ import _extends from "@babel/runtime/helpers/esm/extends"; | ||
}); | ||
}, | ||
focusFirst: function focusFirst(options) { | ||
return withMedium(function (car) { | ||
car.focusFirstElement(collapseRefs(ref.current), options); | ||
}); | ||
}, | ||
focusLast: function focusLast(options) { | ||
return withMedium(function (car) { | ||
car.focusLastElement(collapseRefs(ref.current), options); | ||
}); | ||
} | ||
@@ -52,0 +62,0 @@ }; |
@@ -36,3 +36,6 @@ import { useCallback, useRef, useState, useEffect } from 'react'; | ||
}; | ||
export var useFocusState = function useFocusState() { | ||
export var useFocusState = function useFocusState(callbacks) { | ||
if (callbacks === void 0) { | ||
callbacks = {}; | ||
} | ||
var _useState = useState(false), | ||
@@ -46,6 +49,11 @@ active = _useState[0], | ||
var focusState = useRef({}); | ||
var stateTracker = useRef(false); | ||
useEffect(function () { | ||
if (ref.current) { | ||
setActive(ref.current === document.activeElement || ref.current.contains(document.activeElement)); | ||
var isAlreadyFocused = ref.current === document.activeElement || ref.current.contains(document.activeElement); | ||
setActive(isAlreadyFocused); | ||
setState(getFocusState(document.activeElement, ref.current)); | ||
if (isAlreadyFocused && callbacks.onFocus) { | ||
callbacks.onFocus(); | ||
} | ||
} | ||
@@ -65,4 +73,13 @@ }, []); | ||
var fin = mainbus.on('assign', function () { | ||
setActive(focusState.current.focused || false); | ||
var newState = focusState.current.focused || false; | ||
setActive(newState); | ||
setState(focusState.current.state || ''); | ||
if (newState !== stateTracker.current) { | ||
stateTracker.current = newState; | ||
if (newState) { | ||
callbacks.onFocus && callbacks.onFocus(); | ||
} else { | ||
callbacks.onBlur && callbacks.onBlur(); | ||
} | ||
} | ||
}); | ||
@@ -69,0 +86,0 @@ return function () { |
{ | ||
"name": "react-focus-lock", | ||
"version": "2.10.1", | ||
"version": "2.11.0", | ||
"description": "It is a trap! (for a focus)", | ||
@@ -74,2 +74,3 @@ "main": "dist/cjs/index.js", | ||
"@testing-library/react": "^12.0.0", | ||
"@testing-library/user-event": "^12.0.0", | ||
"@types/react": "^18.0.8", | ||
@@ -76,0 +77,0 @@ "babel-eslint": "^10.0.1", |
@@ -33,2 +33,89 @@ import * as React from 'react'; | ||
export class InFocusGuard extends React.Component<InFocusGuardProps> { | ||
} | ||
/** | ||
* Moves focus inside a given node | ||
*/ | ||
export function useFocusInside(node: React.RefObject<HTMLElement>): void; | ||
export type FocusOptions = { | ||
/** | ||
* enables focus cycle | ||
* @default true | ||
*/ | ||
cycle?: boolean; | ||
/** | ||
* limits focusables to tabbables (tabindex>=0) elements only | ||
* @default true | ||
*/ | ||
onlyTabbable?:boolean | ||
} | ||
export type FocusControl = { | ||
/** | ||
* moves focus to the current scope, can be considered as autofocus | ||
*/ | ||
autoFocus():Promise<void>; | ||
/** | ||
* focuses the next element in the scope. | ||
* If active element is not in the scope, autofocus will be triggered first | ||
*/ | ||
focusNext(options:FocusOptions):Promise<void>; | ||
/** | ||
* focuses the prev element in the scope. | ||
* If active element is not in the scope, autofocus will be triggered first | ||
*/ | ||
focusPrev():Promise<void>; | ||
/** | ||
* focused the first element in the scope | ||
*/ | ||
focusFirst(options: Pick<FocusOptions,'onlyTabbable'>):Promise<void>; | ||
/** | ||
* focused the last element in the scope | ||
*/ | ||
focusLast(options: Pick<FocusOptions,'onlyTabbable'>):Promise<void>; | ||
} | ||
/** | ||
* returns FocusControl over the union given elements, one or many | ||
* - can be used outside of FocusLock | ||
* @see {@link useFocusScope} for use cases inside of FocusLock | ||
*/ | ||
export function useFocusController(...shards: HTMLElement[]):FocusControl; | ||
/** | ||
* returns FocusControl over the current FocusLock | ||
* - can be used only within FocusLock | ||
* - can be used by disabled FocusLock | ||
* @see {@link useFocusController} for use cases outside of FocusLock | ||
*/ | ||
export function useFocusScope():FocusControl | ||
export type FocusCallbacks = { | ||
onFocus():void; | ||
onBlur():void; | ||
} | ||
/** | ||
* returns information about FocusState of a given node | ||
* @example | ||
* ```tsx | ||
* const {active, ref, onFocus} = useFocusState(); | ||
* return <div ref={ref} onFocus={onFocus}>{active ? 'is focused' : 'not focused'}</div> | ||
* ``` | ||
*/ | ||
export function useFocusState<T extends Element>(callbacks?: FocusCallbacks ):{ | ||
/** | ||
* is currently focused, or is focus is inside | ||
*/ | ||
active: boolean; | ||
/** | ||
* focus handled. SHALL be passed to the node down | ||
*/ | ||
onFocus: React.FocusEventHandler<T>; | ||
/** | ||
* reference to the node | ||
* only required to capture current status of the node | ||
*/ | ||
ref: React.RefObject<T>; | ||
} |
@@ -64,4 +64,5 @@ <div align="left"> | ||
- `className`, to set the `className` of the internal wrapper. | ||
- `returnFocus`, to return focus into initial position on unmount(not disable). | ||
> By default `returnFocus` is disabled, so FocusLock will __not__ restore original focus on deactivation. | ||
- `returnFocus`, to return focus into initial position on unmount | ||
> By default `returnFocus` is disabled, so FocusLock __will not__ restore original focus on deactivation. | ||
> This was done mostly to avoid breaking changes. We __strong recommend enabling it__, to provide a better user experience. | ||
@@ -333,2 +334,28 @@ This is expected behavior for Modals, but it is better to implement it by your self. See [unmounting and focus management](https://github.com/theKashey/react-focus-lock#unmounting-and-focus-management) for details | ||
## Return focus to another node | ||
In some cases the original node that was focused before the lock was activated is not the desired node to return focus to. | ||
Some times this node might not exists at all. | ||
- first of all, FocusLock need a moment to record this node, please do not hide it onClick, but hide onBlur (Dropdown, looking at you) | ||
- second, you may specify a callback as `returnFocus`, letting you decide where to return focus to. | ||
```tsx | ||
<FocusLock | ||
returnFocus={(suggestedNode) => { | ||
// somehow activeElement should not be changed | ||
if(document.activeElement.hasAttributes('main-content')) { | ||
// opt out from default behavior | ||
return false; | ||
} | ||
if (someCondition(suggestedNode)) { | ||
// proceed with the suggested node | ||
return true; | ||
} | ||
// handle return focus manually | ||
document.getElementById('the-button').focus(); | ||
// opt out from default behavior | ||
return false; | ||
}} | ||
/> | ||
```` | ||
## Return focus with no scroll | ||
@@ -335,0 +362,0 @@ > read more at the [issue #83](https://github.com/theKashey/react-focus-lock/issues/83) or |
@@ -68,2 +68,10 @@ import * as React from 'react'; | ||
focusPrev():Promise<void>; | ||
/** | ||
* focused the first element in the scope | ||
*/ | ||
focusFirst(options: Pick<FocusOptions,'onlyTabbable'>):Promise<void>; | ||
/** | ||
* focused the last element in the scope | ||
*/ | ||
focusLast(options: Pick<FocusOptions,'onlyTabbable'>):Promise<void>; | ||
} | ||
@@ -87,2 +95,7 @@ | ||
export type FocusCallbacks = { | ||
onFocus():void; | ||
onBlur():void; | ||
} | ||
/** | ||
@@ -96,3 +109,3 @@ * returns information about FocusState of a given node | ||
*/ | ||
export function useFocusState<T extends Element>():{ | ||
export function useFocusState<T extends Element>(callbacks?: FocusCallbacks ):{ | ||
/** | ||
@@ -99,0 +112,0 @@ * is currently focused, or is focus is inside |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
106511
2107
434
41