react-roving-tabindex
Advanced tools
Comparing version 2.1.0 to 2.2.0-beta-1
# Changelog | ||
## 2.2.0 | ||
- Add an optional `initialTabElementSelector` string prop to the provider. | ||
- Add an optional `onTabElementSelected` callback prop to the provider. | ||
## 2.1.0 | ||
@@ -4,0 +9,0 @@ |
@@ -58,2 +58,3 @@ import React, { createContext, useReducer, useEffect, useMemo, useRef, useContext, useCallback } from 'react'; | ||
ActionType["DIRECTION_UPDATED"] = "DIRECTION_UPDATED"; | ||
ActionType["SET_INITIAL_TAB_ELEMENT"] = "SET_INITIAL_TAB_ELEMENT"; | ||
})(ActionType || (ActionType = {})); | ||
@@ -310,2 +311,8 @@ | ||
} | ||
case ActionType.SET_INITIAL_TAB_ELEMENT: { | ||
var selector_1 = action.payload.selector; | ||
var tabStop = state.tabStops.find(function (tabStop) { var _a; return (_a = tabStop.domElementRef.current) === null || _a === void 0 ? void 0 : _a.matches(selector_1); }); | ||
return tabStop && !tabStop.disabled | ||
? __assign(__assign({}, state), { selectedId: tabStop.id }) : state; | ||
} | ||
default: | ||
@@ -375,3 +382,3 @@ return state; | ||
function selectTabStop(state, tabStop, rowStartMap) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id, rowStartMap: rowStartMap || state.rowStartMap }); | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id, lastSelectedElement: { domElementRef: tabStop.domElementRef }, rowStartMap: rowStartMap || state.rowStartMap }); | ||
} | ||
@@ -392,2 +399,3 @@ // Creates the row start index lookup map | ||
selectedId: null, | ||
lastSelectedElement: null, | ||
allowFocusing: false, | ||
@@ -420,5 +428,24 @@ tabStops: [], | ||
* at any time. | ||
* @param {string} initialTabElementSelector An optional selector for | ||
* selecting the element in the roving tab index that will be | ||
* the initially selected one. Normally the first registered | ||
* element will be the initially tabbable element in the index. | ||
* However, you might want to save the last tabbed-to element | ||
* so that you can restore it the next time the index is mounted. | ||
* The selector will be passed to the | ||
* [`Element.matches()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) | ||
* method, so the likeliest value would be an ID (e.g., `'#bar'`) | ||
* or a data selector (e.g., `'[data-foo-id="bar"]'`). | ||
* The value of this prop should remain the same for the lifetime | ||
* of this component. | ||
* @param {(element: Element) => void} onTabElementSelected | ||
* An optional callback so you can be notified when the user | ||
* changes the selected element in the roving tab index. This is useful to | ||
* extract the information you would need to subsequently pass as the | ||
* `initialTabElementSelector` prop. The value of this prop should remain | ||
* the same for the lifetime of this component. This might require | ||
* use of `React.useCallback` to create a stable callback function. | ||
*/ | ||
var Provider = function (_a) { | ||
var children = _a.children, _b = _a.direction, direction = _b === void 0 ? "horizontal" : _b; | ||
var children = _a.children, _b = _a.direction, direction = _b === void 0 ? "horizontal" : _b, initialTabElementSelector = _a.initialTabElementSelector, onTabElementSelected = _a.onTabElementSelected; | ||
var _c = useReducer(reducer, __assign(__assign({}, INITIAL_STATE), { direction: direction })), state = _c[0], dispatch = _c[1]; | ||
@@ -429,3 +456,23 @@ // Update the direction whenever it changes: | ||
}, [direction]); | ||
// Create a cached object to use as the context value: | ||
// If given, set the initial tab element: | ||
useEffect(function () { | ||
if (initialTabElementSelector) { | ||
dispatch({ | ||
type: ActionType.SET_INITIAL_TAB_ELEMENT, | ||
payload: { selector: initialTabElementSelector } | ||
}); | ||
} | ||
}, [initialTabElementSelector]); | ||
// Invoke the onTabElementSelected callback when the user | ||
// selects an element in the index: | ||
useEffect(function () { | ||
var _a; | ||
if (!onTabElementSelected || | ||
!((_a = state.lastSelectedElement) === null || _a === void 0 ? void 0 : _a.domElementRef.current)) { | ||
return; | ||
} | ||
onTabElementSelected(state.lastSelectedElement.domElementRef.current); | ||
}, [onTabElementSelected, state.lastSelectedElement]); | ||
// Create a memoized object to use as the context's value. | ||
// This prevents unnecessary renders. | ||
var context = useMemo(function () { return ({ state: state, dispatch: dispatch }); }, [state]); | ||
@@ -432,0 +479,0 @@ return (React.createElement(RovingTabIndexContext.Provider, { value: context }, children)); |
@@ -64,2 +64,3 @@ 'use strict'; | ||
ActionType["DIRECTION_UPDATED"] = "DIRECTION_UPDATED"; | ||
ActionType["SET_INITIAL_TAB_ELEMENT"] = "SET_INITIAL_TAB_ELEMENT"; | ||
})(exports.ActionType || (exports.ActionType = {})); | ||
@@ -316,2 +317,8 @@ | ||
} | ||
case exports.ActionType.SET_INITIAL_TAB_ELEMENT: { | ||
var selector_1 = action.payload.selector; | ||
var tabStop = state.tabStops.find(function (tabStop) { var _a; return (_a = tabStop.domElementRef.current) === null || _a === void 0 ? void 0 : _a.matches(selector_1); }); | ||
return tabStop && !tabStop.disabled | ||
? __assign(__assign({}, state), { selectedId: tabStop.id }) : state; | ||
} | ||
default: | ||
@@ -381,3 +388,3 @@ return state; | ||
function selectTabStop(state, tabStop, rowStartMap) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id, rowStartMap: rowStartMap || state.rowStartMap }); | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id, lastSelectedElement: { domElementRef: tabStop.domElementRef }, rowStartMap: rowStartMap || state.rowStartMap }); | ||
} | ||
@@ -398,2 +405,3 @@ // Creates the row start index lookup map | ||
selectedId: null, | ||
lastSelectedElement: null, | ||
allowFocusing: false, | ||
@@ -426,5 +434,24 @@ tabStops: [], | ||
* at any time. | ||
* @param {string} initialTabElementSelector An optional selector for | ||
* selecting the element in the roving tab index that will be | ||
* the initially selected one. Normally the first registered | ||
* element will be the initially tabbable element in the index. | ||
* However, you might want to save the last tabbed-to element | ||
* so that you can restore it the next time the index is mounted. | ||
* The selector will be passed to the | ||
* [`Element.matches()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) | ||
* method, so the likeliest value would be an ID (e.g., `'#bar'`) | ||
* or a data selector (e.g., `'[data-foo-id="bar"]'`). | ||
* The value of this prop should remain the same for the lifetime | ||
* of this component. | ||
* @param {(element: Element) => void} onTabElementSelected | ||
* An optional callback so you can be notified when the user | ||
* changes the selected element in the roving tab index. This is useful to | ||
* extract the information you would need to subsequently pass as the | ||
* `initialTabElementSelector` prop. The value of this prop should remain | ||
* the same for the lifetime of this component. This might require | ||
* use of `React.useCallback` to create a stable callback function. | ||
*/ | ||
var Provider = function (_a) { | ||
var children = _a.children, _b = _a.direction, direction = _b === void 0 ? "horizontal" : _b; | ||
var children = _a.children, _b = _a.direction, direction = _b === void 0 ? "horizontal" : _b, initialTabElementSelector = _a.initialTabElementSelector, onTabElementSelected = _a.onTabElementSelected; | ||
var _c = React.useReducer(reducer, __assign(__assign({}, INITIAL_STATE), { direction: direction })), state = _c[0], dispatch = _c[1]; | ||
@@ -435,3 +462,23 @@ // Update the direction whenever it changes: | ||
}, [direction]); | ||
// Create a cached object to use as the context value: | ||
// If given, set the initial tab element: | ||
React.useEffect(function () { | ||
if (initialTabElementSelector) { | ||
dispatch({ | ||
type: exports.ActionType.SET_INITIAL_TAB_ELEMENT, | ||
payload: { selector: initialTabElementSelector } | ||
}); | ||
} | ||
}, [initialTabElementSelector]); | ||
// Invoke the onTabElementSelected callback when the user | ||
// selects an element in the index: | ||
React.useEffect(function () { | ||
var _a; | ||
if (!onTabElementSelected || | ||
!((_a = state.lastSelectedElement) === null || _a === void 0 ? void 0 : _a.domElementRef.current)) { | ||
return; | ||
} | ||
onTabElementSelected(state.lastSelectedElement.domElementRef.current); | ||
}, [onTabElementSelected, state.lastSelectedElement]); | ||
// Create a memoized object to use as the context's value. | ||
// This prevents unnecessary renders. | ||
var context = React.useMemo(function () { return ({ state: state, dispatch: dispatch }); }, [state]); | ||
@@ -438,0 +485,0 @@ return (React__default['default'].createElement(RovingTabIndexContext.Provider, { value: context }, children)); |
@@ -1,3 +0,3 @@ | ||
import React, { ReactElement, ReactNode } from "react"; | ||
import { Action, KeyDirection, RowStartMap, State } from "./types"; | ||
import React, { ReactElement } from "react"; | ||
import { Action, ProviderProps, RowStartMap, State } from "./types"; | ||
export declare function reducer(state: State, action: Action): State; | ||
@@ -7,2 +7,5 @@ export declare const RovingTabIndexContext: React.Context<Readonly<{ | ||
selectedId: string | null; | ||
lastSelectedElement: { | ||
domElementRef: React.RefObject<Element>; | ||
} | null; | ||
allowFocusing: boolean; | ||
@@ -15,3 +18,3 @@ tabStops: readonly Readonly<{ | ||
}>[]; | ||
direction: KeyDirection; | ||
direction: import("./types").KeyDirection; | ||
rowStartMap: RowStartMap | null; | ||
@@ -38,6 +41,22 @@ }>; | ||
* at any time. | ||
* @param {string} initialTabElementSelector An optional selector for | ||
* selecting the element in the roving tab index that will be | ||
* the initially selected one. Normally the first registered | ||
* element will be the initially tabbable element in the index. | ||
* However, you might want to save the last tabbed-to element | ||
* so that you can restore it the next time the index is mounted. | ||
* The selector will be passed to the | ||
* [`Element.matches()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) | ||
* method, so the likeliest value would be an ID (e.g., `'#bar'`) | ||
* or a data selector (e.g., `'[data-foo-id="bar"]'`). | ||
* The value of this prop should remain the same for the lifetime | ||
* of this component. | ||
* @param {(element: Element) => void} onTabElementSelected | ||
* An optional callback so you can be notified when the user | ||
* changes the selected element in the roving tab index. This is useful to | ||
* extract the information you would need to subsequently pass as the | ||
* `initialTabElementSelector` prop. The value of this prop should remain | ||
* the same for the lifetime of this component. This might require | ||
* use of `React.useCallback` to create a stable callback function. | ||
*/ | ||
export declare const Provider: ({ children, direction }: { | ||
children: ReactNode; | ||
direction?: "horizontal" | "vertical" | "both" | undefined; | ||
}) => ReactElement; | ||
export declare const Provider: ({ children, direction, initialTabElementSelector, onTabElementSelected }: ProviderProps) => ReactElement; |
@@ -1,2 +0,2 @@ | ||
/// <reference types="react" /> | ||
import type { ReactNode } from "react"; | ||
export declare enum EventKey { | ||
@@ -30,2 +30,5 @@ ArrowLeft = "ArrowLeft", | ||
selectedId: string | null; | ||
lastSelectedElement: { | ||
domElementRef: TabStop["domElementRef"]; | ||
} | null; | ||
allowFocusing: boolean; | ||
@@ -42,3 +45,4 @@ tabStops: readonly TabStop[]; | ||
TAB_STOP_UPDATED = "TAB_STOP_UPDATED", | ||
DIRECTION_UPDATED = "DIRECTION_UPDATED" | ||
DIRECTION_UPDATED = "DIRECTION_UPDATED", | ||
SET_INITIAL_TAB_ELEMENT = "SET_INITIAL_TAB_ELEMENT" | ||
} | ||
@@ -77,2 +81,7 @@ export declare type Action = { | ||
}; | ||
} | { | ||
type: ActionType.SET_INITIAL_TAB_ELEMENT; | ||
payload: { | ||
selector: string; | ||
}; | ||
}; | ||
@@ -83,2 +92,8 @@ export declare type Context = Readonly<{ | ||
}>; | ||
export declare type ProviderProps = { | ||
children: ReactNode; | ||
direction?: KeyDirection; | ||
initialTabElementSelector?: string | null; | ||
onTabElementSelected?: (element: Element) => void; | ||
}; | ||
export declare type HookResponse = [ | ||
@@ -85,0 +100,0 @@ number, |
{ | ||
"name": "react-roving-tabindex", | ||
"version": "2.1.0", | ||
"version": "2.2.0-beta-1", | ||
"description": "React implementation of a roving tabindex, now with grid support", | ||
@@ -34,19 +34,8 @@ "author": "stevejay", | ||
"deploy-storybook": "storybook-to-ghpages", | ||
"eslint": "eslint --max-warnings 0 \"src/**/*.{js,jsx,ts,tsx}\"", | ||
"lint": "yarn eslint", | ||
"lint": "yarn lint:code && yarn lint:ts && yarn lint:prettier", | ||
"lint:ts": "tsc --strict --noEmit --project ./tsconfig.json", | ||
"lint:code": "eslint --max-warnings 0 --ignore-path .gitignore ./src", | ||
"lint:prettier": "prettier --check --ignore-path .gitignore ./src", | ||
"lint-staged": "lint-staged" | ||
}, | ||
"prettier": { | ||
"trailingComma": "none" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"lint-staged": { | ||
"src/**/*.{js,jsx,tsx,ts}": [ | ||
"eslint --fix --max-warnings 0 \"src/**/*.{js,jsx,ts,tsx}\"" | ||
] | ||
}, | ||
"dependencies": { | ||
@@ -124,4 +113,5 @@ "warning": "^4.0.3" | ||
"/dist/" | ||
] | ||
], | ||
"clearMocks": true | ||
} | ||
} |
@@ -145,2 +145,10 @@ # react-roving-tabindex | ||
#### Initial tab element | ||
You may want to set a particular element in the roving tabindex to be the initially tabbable element (i.e., the element that has a `tabindex` of `0`). This could be because you remember the state of the UI and restore it when the user next uses the app. | ||
To facilitate this, the `RovingTabIndexProvider` has an optional `initialTabElementSelector` prop. This takes a selector string that is used to identify the tab element that should be the initially tabbable element. Each tab element is tested using [`Element.matches()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches). This requires that the selector identifies the tabbable element itself (rather than, say, a child element). Thus the selector is most likely to be an ID (e.g., `'#bar'`) or a data selector (e.g., `'[data-foo-id="bar"]'`). If used, the value of this prop should remain the same for the lifetime of the component. | ||
To help with tracking the currently tabbable element in the roving tabindex, the `RovingTabIndexProvider` offers an optional `onTabElementSelected` callback prop. This callback is invoked whenever the user clicks on or uses the keyboard to select a tab element in the roving tabindex. The callback is invoked with that element. This allows you to get some information from that element, e.g., its ID or a data attribute. This information could be used later to provide the value for the `initialTabElementSelector` prop. If used, the value of this prop should remain the same for the lifetime of the component. This might require that you use `React.useCallback` to create a stable callback function. | ||
### Grid usage | ||
@@ -198,2 +206,2 @@ | ||
- For beta versions: `npm publish --tag next`. | ||
- For releases: `npm publish`. | ||
- For releases: `npm publish --tag latest`. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
192140
24
1446
206
2