react-focus-lock
Advanced tools
Comparing version 2.9.8 to 2.10.0
@@ -20,2 +20,3 @@ "use strict"; | ||
var _medium = require("./medium"); | ||
var _scope = require("./scope"); | ||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } | ||
@@ -34,2 +35,5 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof3(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } | ||
var originalFocusedElement = React.useRef(null); | ||
var _React$useState3 = React.useState({}), | ||
_React$useState4 = (0, _slicedToArray2["default"])(_React$useState3, 2), | ||
update = _React$useState4[1]; | ||
var children = props.children, | ||
@@ -63,5 +67,5 @@ _props$disabled = props.disabled, | ||
onDeactivationCallback = props.onDeactivation; | ||
var _React$useState3 = React.useState({}), | ||
_React$useState4 = (0, _slicedToArray2["default"])(_React$useState3, 1), | ||
id = _React$useState4[0]; | ||
var _React$useState5 = React.useState({}), | ||
_React$useState6 = (0, _slicedToArray2["default"])(_React$useState5, 1), | ||
id = _React$useState6[0]; | ||
var onActivation = React.useCallback(function () { | ||
@@ -73,2 +77,3 @@ originalFocusedElement.current = originalFocusedElement.current || document && document.activeElement; | ||
isActive.current = true; | ||
update(); | ||
}, [onActivationCallback]); | ||
@@ -80,2 +85,3 @@ var onDeactivation = React.useCallback(function () { | ||
} | ||
update(); | ||
}, [onDeactivationCallback]); | ||
@@ -130,2 +136,10 @@ (0, _react.useEffect)(function () { | ||
var mergedRef = (0, _useCallbackRef.useMergeRefs)([parentRef, setObserveNode]); | ||
var focusScopeValue = React.useMemo(function () { | ||
return { | ||
observed: observed, | ||
shards: shards, | ||
enabled: !disabled, | ||
active: isActive.current | ||
}; | ||
}, [disabled, isActive.current, shards, realObserved]); | ||
return /*#__PURE__*/React.createElement(React.Fragment, null, hasLeadingGuards && [ | ||
@@ -163,3 +177,5 @@ /*#__PURE__*/ | ||
onFocus: onFocus | ||
}), children), hasTailingGuards && /*#__PURE__*/React.createElement("div", { | ||
}), /*#__PURE__*/React.createElement(_scope.focusScope.Provider, { | ||
value: focusScopeValue | ||
}, children)), hasTailingGuards && /*#__PURE__*/React.createElement("div", { | ||
"data-focus-guard": true, | ||
@@ -166,0 +182,0 @@ tabIndex: disabled ? -1 : 0, |
@@ -19,3 +19,4 @@ "use strict"; | ||
var mediumSidecar = exports.mediumSidecar = (0, _useSidecar.createSidecarMedium)({ | ||
async: true | ||
async: true, | ||
ssr: typeof document !== 'undefined' | ||
}); |
@@ -65,5 +65,2 @@ "use strict"; | ||
} | ||
var extractRef = function extractRef(ref) { | ||
return ref && 'current' in ref ? ref.current : ref; | ||
}; | ||
var focusWasOutside = function focusWasOutside(crossFrameOption) { | ||
@@ -96,3 +93,3 @@ if (crossFrameOption) { | ||
if (workingNode) { | ||
var workingArea = [workingNode].concat((0, _toConsumableArray2["default"])(shards.map(extractRef).filter(Boolean))); | ||
var workingArea = [workingNode].concat((0, _toConsumableArray2["default"])(shards.map(_util.extractRef).filter(Boolean))); | ||
if (!activeElement || focusWhitelisted(activeElement)) { | ||
@@ -226,5 +223,7 @@ if (persistentFocus || focusWasOutside(crossFrame) || !isFreeFocus() || !lastActiveFocus && autoFocus) { | ||
moveFocusInside: _focusLock.moveFocusInside, | ||
focusInside: _focusLock.focusInside | ||
focusInside: _focusLock.focusInside, | ||
focusNextElement: _focusLock.focusNextElement, | ||
focusPrevElement: _focusLock.focusPrevElement | ||
}); | ||
}); | ||
var _default = exports["default"] = (0, _reactClientsideEffect["default"])(reducePropsToState, handleStateChangeOnClient)(FocusWatcher); |
@@ -39,2 +39,8 @@ "use strict"; | ||
exports["default"] = void 0; | ||
Object.defineProperty(exports, "useFocusController", { | ||
enumerable: true, | ||
get: function get() { | ||
return _useFocusScope.useFocusController; | ||
} | ||
}); | ||
Object.defineProperty(exports, "useFocusInside", { | ||
@@ -46,2 +52,14 @@ enumerable: true, | ||
}); | ||
Object.defineProperty(exports, "useFocusScope", { | ||
enumerable: true, | ||
get: function get() { | ||
return _useFocusScope.useFocusScope; | ||
} | ||
}); | ||
Object.defineProperty(exports, "useFocusState", { | ||
enumerable: true, | ||
get: function get() { | ||
return _useFocusState.useFocusState; | ||
} | ||
}); | ||
var _Lock = _interopRequireDefault(require("./Lock")); | ||
@@ -52,4 +70,6 @@ var _AutoFocusInside = _interopRequireDefault(require("./AutoFocusInside")); | ||
var _FocusGuard = _interopRequireDefault(require("./FocusGuard")); | ||
var _useFocusScope = require("./use-focus-scope"); | ||
var _useFocusState = require("./use-focus-state"); | ||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } | ||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } | ||
var _default = exports["default"] = _Lock["default"]; |
@@ -7,3 +7,3 @@ "use strict"; | ||
exports.deferAction = deferAction; | ||
exports.inlineProp = void 0; | ||
exports.inlineProp = exports.extractRef = void 0; | ||
function deferAction(action) { | ||
@@ -16,2 +16,5 @@ setTimeout(action, 1); | ||
return obj; | ||
}; | ||
var extractRef = exports.extractRef = function extractRef(ref) { | ||
return ref && 'current' in ref ? ref.current : ref; | ||
}; |
@@ -9,2 +9,3 @@ import _extends from "@babel/runtime/helpers/esm/extends"; | ||
import { mediumFocus, mediumBlur, mediumSidecar } from './medium'; | ||
import { focusScope } from './scope'; | ||
var emptyArray = []; | ||
@@ -19,2 +20,4 @@ var FocusLock = /*#__PURE__*/React.forwardRef(function FocusLockUI(props, parentRef) { | ||
var originalFocusedElement = React.useRef(null); | ||
var _React$useState2 = React.useState({}), | ||
update = _React$useState2[1]; | ||
var children = props.children, | ||
@@ -48,4 +51,4 @@ _props$disabled = props.disabled, | ||
onDeactivationCallback = props.onDeactivation; | ||
var _React$useState2 = React.useState({}), | ||
id = _React$useState2[0]; | ||
var _React$useState3 = React.useState({}), | ||
id = _React$useState3[0]; | ||
var onActivation = React.useCallback(function () { | ||
@@ -57,2 +60,3 @@ originalFocusedElement.current = originalFocusedElement.current || document && document.activeElement; | ||
isActive.current = true; | ||
update(); | ||
}, [onActivationCallback]); | ||
@@ -64,2 +68,3 @@ var onDeactivation = React.useCallback(function () { | ||
} | ||
update(); | ||
}, [onDeactivationCallback]); | ||
@@ -114,2 +119,10 @@ useEffect(function () { | ||
var mergedRef = useMergeRefs([parentRef, setObserveNode]); | ||
var focusScopeValue = React.useMemo(function () { | ||
return { | ||
observed: observed, | ||
shards: shards, | ||
enabled: !disabled, | ||
active: isActive.current | ||
}; | ||
}, [disabled, isActive.current, shards, realObserved]); | ||
return /*#__PURE__*/React.createElement(React.Fragment, null, hasLeadingGuards && [ | ||
@@ -147,3 +160,5 @@ /*#__PURE__*/ | ||
onFocus: onFocus | ||
}), children), hasTailingGuards && /*#__PURE__*/React.createElement("div", { | ||
}), /*#__PURE__*/React.createElement(focusScope.Provider, { | ||
value: focusScopeValue | ||
}, children)), hasTailingGuards && /*#__PURE__*/React.createElement("div", { | ||
"data-focus-guard": true, | ||
@@ -150,0 +165,0 @@ tabIndex: disabled ? -1 : 0, |
@@ -13,3 +13,4 @@ import { createMedium, createSidecarMedium } from 'use-sidecar'; | ||
export var mediumSidecar = createSidecarMedium({ | ||
async: true | ||
async: true, | ||
ssr: typeof document !== 'undefined' | ||
}); |
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import withSideEffect from 'react-clientside-effect'; | ||
import { moveFocusInside, focusInside, focusIsHidden, expandFocusableNodes } from 'focus-lock'; | ||
import { deferAction } from './util'; | ||
import { moveFocusInside, focusInside, focusIsHidden, expandFocusableNodes, focusNextElement, focusPrevElement } from 'focus-lock'; | ||
import { deferAction, extractRef } from './util'; | ||
import { mediumFocus, mediumBlur, mediumEffect } from './medium'; | ||
@@ -54,5 +54,2 @@ var focusOnBody = function focusOnBody() { | ||
} | ||
var extractRef = function extractRef(ref) { | ||
return ref && 'current' in ref ? ref.current : ref; | ||
}; | ||
var focusWasOutside = function focusWasOutside(crossFrameOption) { | ||
@@ -214,5 +211,7 @@ if (crossFrameOption) { | ||
moveFocusInside: moveFocusInside, | ||
focusInside: focusInside | ||
focusInside: focusInside, | ||
focusNextElement: focusNextElement, | ||
focusPrevElement: focusPrevElement | ||
}); | ||
}); | ||
export default withSideEffect(reducePropsToState, handleStateChangeOnClient)(FocusWatcher); |
@@ -6,3 +6,5 @@ import FocusLockUI from './Lock'; | ||
import InFocusGuard from './FocusGuard'; | ||
export { AutoFocusInside, MoveFocusInside, FreeFocusInside, InFocusGuard, FocusLockUI, useFocusInside }; | ||
import { useFocusController, useFocusScope } from './use-focus-scope'; | ||
import { useFocusState } from './use-focus-state'; | ||
export { AutoFocusInside, MoveFocusInside, FreeFocusInside, InFocusGuard, FocusLockUI, useFocusInside, useFocusController, useFocusScope, useFocusState }; | ||
export default FocusLockUI; |
@@ -8,2 +8,5 @@ export function deferAction(action) { | ||
return obj; | ||
}; | ||
export var extractRef = function extractRef(ref) { | ||
return ref && 'current' in ref ? ref.current : ref; | ||
}; |
{ | ||
"name": "react-focus-lock", | ||
"version": "2.9.8", | ||
"version": "2.10.0", | ||
"description": "It is a trap! (for a focus)", | ||
@@ -104,3 +104,3 @@ "main": "dist/cjs/index.js", | ||
"@babel/runtime": "^7.0.0", | ||
"focus-lock": "^1.0.1", | ||
"focus-lock": "^1.1.0", | ||
"prop-types": "^15.6.2", | ||
@@ -107,0 +107,0 @@ "react-clientside-effect": "^1.2.6", |
120
README.md
@@ -23,2 +23,3 @@ <div align="left"> | ||
- Any any other case, when you have to lock user _intention_ and _focus_, if that's what `a11y` is asking for. | ||
- Including programatic focus management and smart return focus | ||
@@ -34,9 +35,9 @@ ### Trusted | ||
# Features | ||
- no keyboard control, everything is done watching a __focus behavior__, not emulating it. Thus works always and everywhere. | ||
- no keyboard control, everything is done watching a __focus behavior__, not emulating it. Focus-Locks works for all cases including positive tab indexes. | ||
- React __Portals__ support. Even if some data is in outer space - it is [still in lock](https://github.com/theKashey/react-focus-lock/issues/19). | ||
- _Scattered_ locks, or focus lock groups - you can setup different isolated locks, and _tab_ from one to another. | ||
- Controllable isolation level. | ||
- variable size bundle. Uses sidecar to trim UI part to 1.5kb. | ||
- variable size bundle. Uses _sidecar_ to trim UI part down to 1.5kb. | ||
> 💡 __focus__ locks is only the first part, there are also __scroll lock__ and __text-to-speech__ lock | ||
> 💡 __focus__ locks is part of a bigger whole, consider __scroll lock__ and __text-to-speech__ lock | ||
you have to use to really "lock" the user. | ||
@@ -59,11 +60,2 @@ Try [react-focus-on](https://github.com/theKashey/react-focus-on) to archive everything above, assembled in the right order. | ||
# WHY? | ||
From [MDN Article about accessible dialogs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_dialog_role): | ||
- The dialog must be properly labeled | ||
- Keyboard __focus must be managed__ correctly | ||
This one is about managing the focus. | ||
I've got a good [article about focus management, dialogs and WAI-ARIA](https://medium.com/@antonkorzunov/its-a-focus-trap-699a04d66fb5). | ||
# API | ||
@@ -90,2 +82,21 @@ > FocusLock would work perfectly even with no props set. | ||
## Programmatic control | ||
Focus lock exposes a few methods to control focus programmatically. | ||
### Imperative API | ||
- `useFocusInside(nodeRef)` - to move focus inside the given node | ||
- `useFocusScope():{autofocus, focusNext, focusPrev}` - provides API to manage focus within the current lock | ||
- `useFocusState()` - manages focus state of a given node | ||
- `useFocusController(nodeRef)` - low level version of `useFocusScope` working without FocusLock | ||
### Declarative API | ||
- `<AutoFocusInside/>` - causes autofocus to look inside the component | ||
- `<MoveFocusInside/>` - wrapper around `useFocusInside`, forcibly moves focus inside on mount | ||
- `<FreeFocusInside/>` - hides internals from FocusLock allowing unmanaged focus | ||
#### Indirect API | ||
Focus-lock behavior can be controlled via data-attributes. Declarative API above is working by setting them for you. | ||
See [corresponding section in focus-lock](https://github.com/theKashey/focus-lock#declarative-control) for details | ||
### Focusing in OSX (Safari/Firefox) is strange! | ||
@@ -225,2 +236,52 @@ By default `tabbing` in OSX `sees` only controls, but not links or anything else `tabbable`. This is system settings, and Safari/Firefox obey. | ||
### Programmatic Control | ||
Let's take a look at the `Rowing Focus` as an example. | ||
```tsx | ||
// Will set tabindex to -1 when is not focused | ||
const FocusTrackingButton = ({ children }) => { | ||
const { active, onFocus, ref } = useFocusState(); | ||
return ( | ||
<button tabIndex={active ? undefined : -1} onFocus={onFocus} ref={ref}> | ||
{children} | ||
</button> | ||
); | ||
}; | ||
const RowingFocusInternalTrap = () => { | ||
const { autoFocus, focusNext, focusPrev } = useFocusScope(); | ||
// use useFocusController(divRef) if there is no FocusLock around | ||
useEffect(() => { | ||
autoFocus(); | ||
}, []); | ||
const onKey = (event) => { | ||
if (event.key === 'ArrowDown') { | ||
focusNext({ onlyTabbable: false }); | ||
} | ||
if (event.key === 'ArrowUp') { | ||
focusPrev({ onlyTabbable: false }); | ||
} | ||
}; | ||
return ( | ||
<div | ||
onKeyDown={onKey} | ||
// ref={divRef} for useFocusController | ||
> | ||
<FocusButton>Button1</FocusButton> | ||
<FocusButton>Button2</FocusButton> | ||
<FocusButton>Button3</FocusButton> | ||
<FocusButton>Button4</FocusButton> | ||
</div> | ||
); | ||
}; | ||
// FocusLock, even disabled one | ||
const RowingFocusTrap = () => ( | ||
<FocusLock disabled> | ||
<RowingFocusInternalTrap /> | ||
</FocusLock> | ||
); | ||
``` | ||
### Guarding | ||
@@ -288,14 +349,11 @@ As you may know - FocusLock is adding `Focus Guards` before and after lock to remove some side effects, like page scrolling. | ||
# Not only for React | ||
Uses [focus-lock](https://github.com/theKashey/focus-lock/) under the hood. It does also provide support for Vue.js and Vanilla DOM solutions | ||
# Warning! | ||
Two different _focus-lock-managers_ or even different version of a single one, active | ||
simultaneously will FIGHT! | ||
## Focus fighting | ||
Two different _focus-lock-managers_ or even different version of a single one, being active | ||
simultaneously will FIGHT for the focus. This usually totally breaks user experience. | ||
__Focus-lock will surrender__, as long any other focus management library will not. | ||
__React-Focus-Lock will automatically surrender__, letting another library to take the lead. | ||
## Focus fighting | ||
### Resolving focus fighting | ||
You may wrap some render branch with `FreeFocusInside`, and react-focus-lock __will ignore__ | ||
any focus inside marked node, thus landing a peace. | ||
any focus inside marked node. So in case focus moves to _uncontrolled location_ focus-lock will not trigger letting another library to act without interference in that another location. | ||
@@ -311,4 +369,4 @@ ```js | ||
``` | ||
Even the better is to `whiteList` FocusLock areas - for example "you should handle only React Stuff in React Root" | ||
Another option for hybrid applications is to `whiteList` area where Focus-Lock should act, automatically allowing other managers in other areas. | ||
The code below will scope Focus-Lock on inside the (react)`root` element, so anything jQuery can add to the body will be ignored. | ||
```js | ||
@@ -320,3 +378,5 @@ <FocusLock whiteList={node => document.getElementById('root').contains(node)}> | ||
PS: __please use webpack or yarn resolution for force one version of react-focus-lock used__ | ||
### Two Focus-Locks | ||
React-Focus-Lock is expected to be a singlentone. | ||
__Use webpack or yarn resolution for force only one version of react-focus-lock used. | ||
@@ -331,2 +391,14 @@ > webpack.conf | ||
# WHY? | ||
From [MDN Article about accessible dialogs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_dialog_role): | ||
- The dialog must be properly labeled | ||
- Keyboard __focus must be managed__ correctly | ||
This one is about managing the focus. | ||
I've got a good [article about focus management, dialogs and WAI-ARIA](https://medium.com/@antonkorzunov/its-a-focus-trap-699a04d66fb5). | ||
# Not only for React | ||
Uses [focus-lock](https://github.com/theKashey/focus-lock/) under the hood. It does also provide support for Vue.js and Vanilla DOM solutions | ||
# More | ||
@@ -333,0 +405,0 @@ To create a "right" modal dialog you have to: |
@@ -38,2 +38,72 @@ import * as React from 'react'; | ||
*/ | ||
export function useFocusInside(node: React.RefObject<HTMLElement>): void; | ||
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>; | ||
} | ||
/** | ||
* 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 | ||
/** | ||
* 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>():{ | ||
/** | ||
* 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>; | ||
} |
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
99704
43
1949
407
Updatedfocus-lock@^1.1.0