react-roving-tabindex
Advanced tools
Comparing version 2.0.0-alpha.1 to 2.0.0-alpha.2
@@ -1,2 +0,2 @@ | ||
import React, { useRef, useContext, useEffect, useCallback, useLayoutEffect } from 'react'; | ||
import React, { createContext, useReducer, useEffect, useMemo, useRef, useContext, useCallback, useLayoutEffect } from 'react'; | ||
import warning from 'warning'; | ||
@@ -30,10 +30,2 @@ | ||
function __spreadArrays() { | ||
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; | ||
for (var r = Array(s), k = 0, i = 0; i < il; i++) | ||
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) | ||
r[k] = a[j]; | ||
return r; | ||
} | ||
var EventKey; | ||
@@ -99,24 +91,28 @@ (function (EventKey) { | ||
// the desired behaviour for the page. | ||
// | ||
// Note: The rowStartMap is only created if row-related | ||
// navigation occurs (e.g., move to row start or end), so | ||
// non-grid usage of this library does not pay the price | ||
// (minimal as it is) of constructing this map. The map | ||
// gets cleared if registering, unregistering, or updating. | ||
function reducer(state, action) { | ||
switch (action.type) { | ||
case ActionType.REGISTER_TAB_STOP: { | ||
var newTabStop_1 = action.payload; | ||
if (!newTabStop_1.domElementRef.current) { | ||
var newTabStop = action.payload; | ||
if (!newTabStop.domElementRef.current) { | ||
return state; | ||
} | ||
var index = state.tabStops.findIndex(function (tabStop) { return tabStop.id === newTabStop_1.id; }); | ||
if (index !== -1) { | ||
warning(false, "'" + newTabStop_1.id + "' tab stop already registered"); | ||
return state; | ||
var indexToInsertAt = -1; | ||
for (var i = 0; i < state.tabStops.length; ++i) { | ||
var loopTabStop = state.tabStops[i]; | ||
if (loopTabStop.id === newTabStop.id) { | ||
warning(false, "'" + newTabStop.id + "' tab stop already registered"); | ||
return state; | ||
} | ||
if (indexToInsertAt === -1 && | ||
loopTabStop.domElementRef.current && | ||
!!(loopTabStop.domElementRef.current.compareDocumentPosition(newTabStop.domElementRef.current) & DOCUMENT_POSITION_PRECEDING)) { | ||
indexToInsertAt = i; | ||
} | ||
} | ||
var indexToInsertAt = state.tabStops.findIndex(function (tabStop) { | ||
// This mess is for TypeScript: | ||
if (!tabStop.domElementRef.current || | ||
!newTabStop_1.domElementRef.current) { | ||
return -1; | ||
} | ||
// Returns true if newTabStop's element is located earlier | ||
// in the DOM than tabStop's element, else returns false: | ||
return !!(tabStop.domElementRef.current.compareDocumentPosition(newTabStop_1.domElementRef.current) & DOCUMENT_POSITION_PRECEDING); | ||
}); | ||
// Array.findIndex returns -1 when newTabStop should be inserted | ||
@@ -128,6 +124,5 @@ // at the end of tabStops (the compareDocumentPosition test | ||
} | ||
var newTabStops = __spreadArrays(state.tabStops.slice(0, indexToInsertAt), [ | ||
newTabStop_1 | ||
], state.tabStops.slice(indexToInsertAt)); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops }); | ||
var newTabStops = state.tabStops.slice(); | ||
newTabStops.splice(indexToInsertAt, 0, newTabStop); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops, rowStartMap: null }); | ||
} | ||
@@ -141,3 +136,3 @@ case ActionType.UNREGISTER_TAB_STOP: { | ||
} | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops }); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops, rowStartMap: null }); | ||
} | ||
@@ -156,6 +151,6 @@ case ActionType.TAB_STOP_UPDATED: { | ||
} | ||
var newTabStop = __assign(__assign({}, tabStop), { rowIndex: rowIndex, disabled: disabled }); | ||
var newTabStops = state.tabStops.slice(); | ||
var newTabStop = __assign(__assign({}, tabStop), { rowIndex: rowIndex, disabled: disabled }); | ||
newTabStops.splice(index, 1, newTabStop); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops }); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops, rowStartMap: null }); | ||
} | ||
@@ -187,3 +182,3 @@ case ActionType.KEY_DOWN: { | ||
if (!tabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id }); | ||
return selectTabStop(state, tabStop); | ||
} | ||
@@ -201,3 +196,3 @@ } | ||
if (!tabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id }); | ||
return selectTabStop(state, tabStop); | ||
} | ||
@@ -212,3 +207,3 @@ } | ||
if (!tabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id }); | ||
return selectTabStop(state, tabStop); | ||
} | ||
@@ -223,3 +218,3 @@ } | ||
if (!tabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id }); | ||
return selectTabStop(state, tabStop); | ||
} | ||
@@ -235,44 +230,46 @@ } | ||
} | ||
var rowStartIndexes_1 = {}; | ||
state.tabStops.forEach(function (_a, index) { | ||
var rowIndex = _a.rowIndex; | ||
if (rowIndex !== null && | ||
rowStartIndexes_1[rowIndex] === undefined) { | ||
rowStartIndexes_1[rowIndex] = index; | ||
var rowStartMap = state.rowStartMap || createRowStartMap(state); | ||
var rowStartIndex = rowStartMap.get(currentTabStop.rowIndex); | ||
if (rowStartIndex === undefined) { | ||
return state; | ||
} | ||
var columnOffset = index - rowStartIndex; | ||
for (var i = currentTabStop.rowIndex - 1; i >= 0; --i) { | ||
var rowStartIndex_1 = rowStartMap.get(i); | ||
if (rowStartIndex_1 === undefined) { | ||
return state; | ||
} | ||
}); | ||
var columnOffset = index - rowStartIndexes_1[currentTabStop.rowIndex]; | ||
for (var i = currentTabStop.rowIndex - 1; i >= 0; --i) { | ||
var rowTabStop = state.tabStops[rowStartIndexes_1[i] + columnOffset]; | ||
var rowTabStop = state.tabStops[rowStartIndex_1 + columnOffset]; | ||
if (!rowTabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: rowTabStop.id }); | ||
return selectTabStop(state, rowTabStop, rowStartMap); | ||
} | ||
} | ||
return __assign(__assign({}, state), { allowFocusing: true, rowStartMap: rowStartMap }); | ||
} | ||
break; | ||
case Navigation.NEXT_ROW: | ||
{ | ||
var maxRowIndex = state.tabStops[state.tabStops.length - 1].rowIndex; | ||
if (currentTabStop.rowIndex === null || | ||
currentTabStop.rowIndex === | ||
state.tabStops[state.tabStops.length - 1].rowIndex) { | ||
maxRowIndex === null || | ||
currentTabStop.rowIndex === maxRowIndex) { | ||
return state; | ||
} | ||
var rowStartIndexes_2 = {}; | ||
state.tabStops.forEach(function (_a, index) { | ||
var rowIndex = _a.rowIndex; | ||
if (rowIndex !== null && | ||
rowStartIndexes_2[rowIndex] === undefined) { | ||
rowStartIndexes_2[rowIndex] = index; | ||
var rowStartMap = state.rowStartMap || createRowStartMap(state); | ||
var rowStartIndex = rowStartMap.get(currentTabStop.rowIndex); | ||
if (rowStartIndex === undefined) { | ||
return state; | ||
} | ||
var columnOffset = index - rowStartIndex; | ||
for (var i = currentTabStop.rowIndex + 1; i <= maxRowIndex; ++i) { | ||
var rowStartIndex_2 = rowStartMap.get(i); | ||
if (rowStartIndex_2 === undefined) { | ||
return state; | ||
} | ||
}); | ||
var columnOffset = index - rowStartIndexes_2[currentTabStop.rowIndex]; | ||
var maxRowIndex = state.tabStops[state.tabStops.length - 1].rowIndex || 0; | ||
for (var i = currentTabStop.rowIndex + 1; i <= maxRowIndex; ++i) { | ||
var rowTabStop = state.tabStops[rowStartIndexes_2[i] + columnOffset]; | ||
var rowTabStop = state.tabStops[rowStartIndex_2 + columnOffset]; | ||
if (!rowTabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: rowTabStop.id }); | ||
return selectTabStop(state, rowTabStop, rowStartMap); | ||
} | ||
} | ||
return __assign(__assign({}, state), { allowFocusing: true, rowStartMap: rowStartMap }); | ||
} | ||
break; | ||
case Navigation.FIRST_IN_ROW: | ||
@@ -283,4 +280,8 @@ { | ||
} | ||
var newIndex = null; | ||
for (var i = index - 1; i >= 0; --i) { | ||
var rowStartMap = state.rowStartMap || createRowStartMap(state); | ||
var rowStartIndex = rowStartMap.get(currentTabStop.rowIndex); | ||
if (rowStartIndex === undefined) { | ||
return state; | ||
} | ||
for (var i = rowStartIndex; i < state.tabStops.length; ++i) { | ||
var tabStop = state.tabStops[i]; | ||
@@ -291,8 +292,5 @@ if (tabStop.rowIndex !== currentTabStop.rowIndex) { | ||
else if (!tabStop.disabled) { | ||
newIndex = i; | ||
return selectTabStop(state, state.tabStops[i], rowStartMap); | ||
} | ||
} | ||
if (newIndex !== null) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: state.tabStops[newIndex].id }); | ||
} | ||
} | ||
@@ -305,4 +303,7 @@ break; | ||
} | ||
var newIndex = null; | ||
for (var i = index + 1; i < state.tabStops.length; ++i) { | ||
var rowStartMap = state.rowStartMap || createRowStartMap(state); | ||
var rowEndIndex = rowStartMap.has(currentTabStop.rowIndex + 1) | ||
? (rowStartMap.get(currentTabStop.rowIndex + 1) || 0) - 1 | ||
: state.tabStops.length - 1; | ||
for (var i = rowEndIndex; i >= 0; --i) { | ||
var tabStop = state.tabStops[i]; | ||
@@ -313,8 +314,5 @@ if (tabStop.rowIndex !== currentTabStop.rowIndex) { | ||
else if (!tabStop.disabled) { | ||
newIndex = i; | ||
return selectTabStop(state, state.tabStops[i], rowStartMap); | ||
} | ||
} | ||
if (newIndex !== null) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: state.tabStops[newIndex].id }); | ||
} | ||
} | ||
@@ -335,3 +333,3 @@ break; | ||
? state | ||
: __assign(__assign({}, state), { allowFocusing: true, selectedId: id_4 }); | ||
: selectTabStop(state, currentTabStop); | ||
} | ||
@@ -360,3 +358,3 @@ case ActionType.KEY_CONFIG_UPDATED: { | ||
} | ||
// Find the first tab stop that is not disabled and return | ||
// Finds the first tab stop that is not disabled and return | ||
// its id, otherwise return null. | ||
@@ -366,3 +364,3 @@ index = tabStops.findIndex(function (tabStop) { return !tabStop.disabled; }); | ||
} | ||
// Translate the user key down event info into a navigation instruction. | ||
// Translates the user key down event info into a navigation instruction. | ||
function getNavigationValue(key, ctrlKey, keyConfig) { | ||
@@ -386,9 +384,27 @@ var translatedKey = null; | ||
} | ||
var RovingTabIndexContext = React.createContext({ | ||
state: { | ||
selectedId: null, | ||
allowFocusing: false, | ||
tabStops: [], | ||
keyConfig: DEFAULT_KEY_CONFIG | ||
}, | ||
// Creates the new state for a tab stop when it becomes the selected one. | ||
function selectTabStop(state, tabStop, rowStartMap) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id, rowStartMap: rowStartMap || state.rowStartMap }); | ||
} | ||
// Creates the row start index lookup map | ||
// for the currently registered tab stops. | ||
function createRowStartMap(state) { | ||
var map = new Map(); | ||
for (var i = 0; i < state.tabStops.length; ++i) { | ||
var rowIndex = state.tabStops[i].rowIndex; | ||
if (rowIndex !== null && !map.has(rowIndex)) { | ||
map.set(rowIndex, i); | ||
} | ||
} | ||
return map; | ||
} | ||
var INITIAL_STATE = { | ||
selectedId: null, | ||
allowFocusing: false, | ||
tabStops: [], | ||
keyConfig: DEFAULT_KEY_CONFIG, | ||
rowStartMap: null | ||
}; | ||
var RovingTabIndexContext = createContext({ | ||
state: INITIAL_STATE, | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
@@ -399,3 +415,3 @@ dispatch: function () { } | ||
* Creates a roving tabindex context. | ||
* @param {React.ReactNode} children The child content, which will | ||
* @param {ReactNode} children The child content, which will | ||
* include the DOM elements to rove between using the tab key. | ||
@@ -412,14 +428,9 @@ * @param {keyConfig} keyConfig An optional key navigation configuration | ||
var children = _a.children, _b = _a.keyConfig, keyConfig = _b === void 0 ? DEFAULT_KEY_CONFIG : _b; | ||
var _c = React.useReducer(reducer, { | ||
selectedId: null, | ||
allowFocusing: false, | ||
tabStops: [], | ||
keyConfig: keyConfig | ||
}), state = _c[0], dispatch = _c[1]; | ||
// Update the keyConfig whenever it is changed: | ||
React.useEffect(function () { | ||
var _c = useReducer(reducer, __assign(__assign({}, INITIAL_STATE), { keyConfig: keyConfig })), state = _c[0], dispatch = _c[1]; | ||
// Update the keyConfig whenever it changes: | ||
useEffect(function () { | ||
dispatch({ type: ActionType.KEY_CONFIG_UPDATED, payload: { keyConfig: keyConfig } }); | ||
}, [keyConfig]); | ||
// Create a cached object to use as the context value: | ||
var context = React.useMemo(function () { return ({ state: state, dispatch: dispatch }); }, [state]); | ||
var context = useMemo(function () { return ({ state: state, dispatch: dispatch }); }, [state]); | ||
return (React.createElement(RovingTabIndexContext.Provider, { value: context }, children)); | ||
@@ -466,2 +477,3 @@ }; | ||
} | ||
var isMounted = useRef(false); | ||
var context = useContext(RovingTabIndexContext); | ||
@@ -487,17 +499,20 @@ // Register the tab stop on mount and unregister it on unmount: | ||
}, []); | ||
// Update the tab stop data if rowIndex or disabled change: | ||
// Note: A TAB_STOP_UPDATED event is dispatched directly | ||
// after the REGISTER_TAB_STOP event on mount. This is okay | ||
// because the values of rowIndex and disabled will not | ||
// have changed, and in that case the reducer treats | ||
// the TAB_STOP_UPDATED event as a no-op. | ||
// Update the tab stop data if rowIndex or disabled change. | ||
// The isMounted flag is used to prevent this effect running | ||
// on mount, which would be benign but is redundant as the | ||
// REGISTER_TAB_STOP action would have just been dispatched. | ||
useEffect(function () { | ||
context.dispatch({ | ||
type: ActionType.TAB_STOP_UPDATED, | ||
payload: { | ||
id: getId(), | ||
rowIndex: getRowIndexFromOptions(options), | ||
disabled: disabled | ||
} | ||
}); | ||
if (isMounted.current) { | ||
context.dispatch({ | ||
type: ActionType.TAB_STOP_UPDATED, | ||
payload: { | ||
id: getId(), | ||
rowIndex: getRowIndexFromOptions(options), | ||
disabled: disabled | ||
} | ||
}); | ||
} | ||
else { | ||
isMounted.current = true; | ||
} | ||
}, [options === null || options === void 0 ? void 0 : options.rowIndex, disabled]); | ||
@@ -504,0 +519,0 @@ // Create a stable callback function for handling key down events: |
@@ -39,10 +39,2 @@ 'use strict'; | ||
function __spreadArrays() { | ||
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; | ||
for (var r = Array(s), k = 0, i = 0; i < il; i++) | ||
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) | ||
r[k] = a[j]; | ||
return r; | ||
} | ||
(function (EventKey) { | ||
@@ -104,24 +96,28 @@ EventKey["ArrowLeft"] = "ArrowLeft"; | ||
// the desired behaviour for the page. | ||
// | ||
// Note: The rowStartMap is only created if row-related | ||
// navigation occurs (e.g., move to row start or end), so | ||
// non-grid usage of this library does not pay the price | ||
// (minimal as it is) of constructing this map. The map | ||
// gets cleared if registering, unregistering, or updating. | ||
function reducer(state, action) { | ||
switch (action.type) { | ||
case exports.ActionType.REGISTER_TAB_STOP: { | ||
var newTabStop_1 = action.payload; | ||
if (!newTabStop_1.domElementRef.current) { | ||
var newTabStop = action.payload; | ||
if (!newTabStop.domElementRef.current) { | ||
return state; | ||
} | ||
var index = state.tabStops.findIndex(function (tabStop) { return tabStop.id === newTabStop_1.id; }); | ||
if (index !== -1) { | ||
warning__default['default'](false, "'" + newTabStop_1.id + "' tab stop already registered"); | ||
return state; | ||
var indexToInsertAt = -1; | ||
for (var i = 0; i < state.tabStops.length; ++i) { | ||
var loopTabStop = state.tabStops[i]; | ||
if (loopTabStop.id === newTabStop.id) { | ||
warning__default['default'](false, "'" + newTabStop.id + "' tab stop already registered"); | ||
return state; | ||
} | ||
if (indexToInsertAt === -1 && | ||
loopTabStop.domElementRef.current && | ||
!!(loopTabStop.domElementRef.current.compareDocumentPosition(newTabStop.domElementRef.current) & DOCUMENT_POSITION_PRECEDING)) { | ||
indexToInsertAt = i; | ||
} | ||
} | ||
var indexToInsertAt = state.tabStops.findIndex(function (tabStop) { | ||
// This mess is for TypeScript: | ||
if (!tabStop.domElementRef.current || | ||
!newTabStop_1.domElementRef.current) { | ||
return -1; | ||
} | ||
// Returns true if newTabStop's element is located earlier | ||
// in the DOM than tabStop's element, else returns false: | ||
return !!(tabStop.domElementRef.current.compareDocumentPosition(newTabStop_1.domElementRef.current) & DOCUMENT_POSITION_PRECEDING); | ||
}); | ||
// Array.findIndex returns -1 when newTabStop should be inserted | ||
@@ -133,6 +129,5 @@ // at the end of tabStops (the compareDocumentPosition test | ||
} | ||
var newTabStops = __spreadArrays(state.tabStops.slice(0, indexToInsertAt), [ | ||
newTabStop_1 | ||
], state.tabStops.slice(indexToInsertAt)); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops }); | ||
var newTabStops = state.tabStops.slice(); | ||
newTabStops.splice(indexToInsertAt, 0, newTabStop); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops, rowStartMap: null }); | ||
} | ||
@@ -146,3 +141,3 @@ case exports.ActionType.UNREGISTER_TAB_STOP: { | ||
} | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops }); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops, rowStartMap: null }); | ||
} | ||
@@ -161,6 +156,6 @@ case exports.ActionType.TAB_STOP_UPDATED: { | ||
} | ||
var newTabStop = __assign(__assign({}, tabStop), { rowIndex: rowIndex, disabled: disabled }); | ||
var newTabStops = state.tabStops.slice(); | ||
var newTabStop = __assign(__assign({}, tabStop), { rowIndex: rowIndex, disabled: disabled }); | ||
newTabStops.splice(index, 1, newTabStop); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops }); | ||
return __assign(__assign({}, state), { selectedId: getUpdatedSelectedId(newTabStops, state.selectedId), tabStops: newTabStops, rowStartMap: null }); | ||
} | ||
@@ -192,3 +187,3 @@ case exports.ActionType.KEY_DOWN: { | ||
if (!tabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id }); | ||
return selectTabStop(state, tabStop); | ||
} | ||
@@ -206,3 +201,3 @@ } | ||
if (!tabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id }); | ||
return selectTabStop(state, tabStop); | ||
} | ||
@@ -217,3 +212,3 @@ } | ||
if (!tabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id }); | ||
return selectTabStop(state, tabStop); | ||
} | ||
@@ -228,3 +223,3 @@ } | ||
if (!tabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id }); | ||
return selectTabStop(state, tabStop); | ||
} | ||
@@ -240,44 +235,46 @@ } | ||
} | ||
var rowStartIndexes_1 = {}; | ||
state.tabStops.forEach(function (_a, index) { | ||
var rowIndex = _a.rowIndex; | ||
if (rowIndex !== null && | ||
rowStartIndexes_1[rowIndex] === undefined) { | ||
rowStartIndexes_1[rowIndex] = index; | ||
var rowStartMap = state.rowStartMap || createRowStartMap(state); | ||
var rowStartIndex = rowStartMap.get(currentTabStop.rowIndex); | ||
if (rowStartIndex === undefined) { | ||
return state; | ||
} | ||
var columnOffset = index - rowStartIndex; | ||
for (var i = currentTabStop.rowIndex - 1; i >= 0; --i) { | ||
var rowStartIndex_1 = rowStartMap.get(i); | ||
if (rowStartIndex_1 === undefined) { | ||
return state; | ||
} | ||
}); | ||
var columnOffset = index - rowStartIndexes_1[currentTabStop.rowIndex]; | ||
for (var i = currentTabStop.rowIndex - 1; i >= 0; --i) { | ||
var rowTabStop = state.tabStops[rowStartIndexes_1[i] + columnOffset]; | ||
var rowTabStop = state.tabStops[rowStartIndex_1 + columnOffset]; | ||
if (!rowTabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: rowTabStop.id }); | ||
return selectTabStop(state, rowTabStop, rowStartMap); | ||
} | ||
} | ||
return __assign(__assign({}, state), { allowFocusing: true, rowStartMap: rowStartMap }); | ||
} | ||
break; | ||
case exports.Navigation.NEXT_ROW: | ||
{ | ||
var maxRowIndex = state.tabStops[state.tabStops.length - 1].rowIndex; | ||
if (currentTabStop.rowIndex === null || | ||
currentTabStop.rowIndex === | ||
state.tabStops[state.tabStops.length - 1].rowIndex) { | ||
maxRowIndex === null || | ||
currentTabStop.rowIndex === maxRowIndex) { | ||
return state; | ||
} | ||
var rowStartIndexes_2 = {}; | ||
state.tabStops.forEach(function (_a, index) { | ||
var rowIndex = _a.rowIndex; | ||
if (rowIndex !== null && | ||
rowStartIndexes_2[rowIndex] === undefined) { | ||
rowStartIndexes_2[rowIndex] = index; | ||
var rowStartMap = state.rowStartMap || createRowStartMap(state); | ||
var rowStartIndex = rowStartMap.get(currentTabStop.rowIndex); | ||
if (rowStartIndex === undefined) { | ||
return state; | ||
} | ||
var columnOffset = index - rowStartIndex; | ||
for (var i = currentTabStop.rowIndex + 1; i <= maxRowIndex; ++i) { | ||
var rowStartIndex_2 = rowStartMap.get(i); | ||
if (rowStartIndex_2 === undefined) { | ||
return state; | ||
} | ||
}); | ||
var columnOffset = index - rowStartIndexes_2[currentTabStop.rowIndex]; | ||
var maxRowIndex = state.tabStops[state.tabStops.length - 1].rowIndex || 0; | ||
for (var i = currentTabStop.rowIndex + 1; i <= maxRowIndex; ++i) { | ||
var rowTabStop = state.tabStops[rowStartIndexes_2[i] + columnOffset]; | ||
var rowTabStop = state.tabStops[rowStartIndex_2 + columnOffset]; | ||
if (!rowTabStop.disabled) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: rowTabStop.id }); | ||
return selectTabStop(state, rowTabStop, rowStartMap); | ||
} | ||
} | ||
return __assign(__assign({}, state), { allowFocusing: true, rowStartMap: rowStartMap }); | ||
} | ||
break; | ||
case exports.Navigation.FIRST_IN_ROW: | ||
@@ -288,4 +285,8 @@ { | ||
} | ||
var newIndex = null; | ||
for (var i = index - 1; i >= 0; --i) { | ||
var rowStartMap = state.rowStartMap || createRowStartMap(state); | ||
var rowStartIndex = rowStartMap.get(currentTabStop.rowIndex); | ||
if (rowStartIndex === undefined) { | ||
return state; | ||
} | ||
for (var i = rowStartIndex; i < state.tabStops.length; ++i) { | ||
var tabStop = state.tabStops[i]; | ||
@@ -296,8 +297,5 @@ if (tabStop.rowIndex !== currentTabStop.rowIndex) { | ||
else if (!tabStop.disabled) { | ||
newIndex = i; | ||
return selectTabStop(state, state.tabStops[i], rowStartMap); | ||
} | ||
} | ||
if (newIndex !== null) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: state.tabStops[newIndex].id }); | ||
} | ||
} | ||
@@ -310,4 +308,7 @@ break; | ||
} | ||
var newIndex = null; | ||
for (var i = index + 1; i < state.tabStops.length; ++i) { | ||
var rowStartMap = state.rowStartMap || createRowStartMap(state); | ||
var rowEndIndex = rowStartMap.has(currentTabStop.rowIndex + 1) | ||
? (rowStartMap.get(currentTabStop.rowIndex + 1) || 0) - 1 | ||
: state.tabStops.length - 1; | ||
for (var i = rowEndIndex; i >= 0; --i) { | ||
var tabStop = state.tabStops[i]; | ||
@@ -318,8 +319,5 @@ if (tabStop.rowIndex !== currentTabStop.rowIndex) { | ||
else if (!tabStop.disabled) { | ||
newIndex = i; | ||
return selectTabStop(state, state.tabStops[i], rowStartMap); | ||
} | ||
} | ||
if (newIndex !== null) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: state.tabStops[newIndex].id }); | ||
} | ||
} | ||
@@ -340,3 +338,3 @@ break; | ||
? state | ||
: __assign(__assign({}, state), { allowFocusing: true, selectedId: id_4 }); | ||
: selectTabStop(state, currentTabStop); | ||
} | ||
@@ -365,3 +363,3 @@ case exports.ActionType.KEY_CONFIG_UPDATED: { | ||
} | ||
// Find the first tab stop that is not disabled and return | ||
// Finds the first tab stop that is not disabled and return | ||
// its id, otherwise return null. | ||
@@ -371,3 +369,3 @@ index = tabStops.findIndex(function (tabStop) { return !tabStop.disabled; }); | ||
} | ||
// Translate the user key down event info into a navigation instruction. | ||
// Translates the user key down event info into a navigation instruction. | ||
function getNavigationValue(key, ctrlKey, keyConfig) { | ||
@@ -391,9 +389,27 @@ var translatedKey = null; | ||
} | ||
var RovingTabIndexContext = React__default['default'].createContext({ | ||
state: { | ||
selectedId: null, | ||
allowFocusing: false, | ||
tabStops: [], | ||
keyConfig: DEFAULT_KEY_CONFIG | ||
}, | ||
// Creates the new state for a tab stop when it becomes the selected one. | ||
function selectTabStop(state, tabStop, rowStartMap) { | ||
return __assign(__assign({}, state), { allowFocusing: true, selectedId: tabStop.id, rowStartMap: rowStartMap || state.rowStartMap }); | ||
} | ||
// Creates the row start index lookup map | ||
// for the currently registered tab stops. | ||
function createRowStartMap(state) { | ||
var map = new Map(); | ||
for (var i = 0; i < state.tabStops.length; ++i) { | ||
var rowIndex = state.tabStops[i].rowIndex; | ||
if (rowIndex !== null && !map.has(rowIndex)) { | ||
map.set(rowIndex, i); | ||
} | ||
} | ||
return map; | ||
} | ||
var INITIAL_STATE = { | ||
selectedId: null, | ||
allowFocusing: false, | ||
tabStops: [], | ||
keyConfig: DEFAULT_KEY_CONFIG, | ||
rowStartMap: null | ||
}; | ||
var RovingTabIndexContext = React.createContext({ | ||
state: INITIAL_STATE, | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
@@ -404,3 +420,3 @@ dispatch: function () { } | ||
* Creates a roving tabindex context. | ||
* @param {React.ReactNode} children The child content, which will | ||
* @param {ReactNode} children The child content, which will | ||
* include the DOM elements to rove between using the tab key. | ||
@@ -417,14 +433,9 @@ * @param {keyConfig} keyConfig An optional key navigation configuration | ||
var children = _a.children, _b = _a.keyConfig, keyConfig = _b === void 0 ? DEFAULT_KEY_CONFIG : _b; | ||
var _c = React__default['default'].useReducer(reducer, { | ||
selectedId: null, | ||
allowFocusing: false, | ||
tabStops: [], | ||
keyConfig: keyConfig | ||
}), state = _c[0], dispatch = _c[1]; | ||
// Update the keyConfig whenever it is changed: | ||
React__default['default'].useEffect(function () { | ||
var _c = React.useReducer(reducer, __assign(__assign({}, INITIAL_STATE), { keyConfig: keyConfig })), state = _c[0], dispatch = _c[1]; | ||
// Update the keyConfig whenever it changes: | ||
React.useEffect(function () { | ||
dispatch({ type: exports.ActionType.KEY_CONFIG_UPDATED, payload: { keyConfig: keyConfig } }); | ||
}, [keyConfig]); | ||
// Create a cached object to use as the context value: | ||
var context = React__default['default'].useMemo(function () { return ({ state: state, dispatch: dispatch }); }, [state]); | ||
var context = React.useMemo(function () { return ({ state: state, dispatch: dispatch }); }, [state]); | ||
return (React__default['default'].createElement(RovingTabIndexContext.Provider, { value: context }, children)); | ||
@@ -471,2 +482,3 @@ }; | ||
} | ||
var isMounted = React.useRef(false); | ||
var context = React.useContext(RovingTabIndexContext); | ||
@@ -492,17 +504,20 @@ // Register the tab stop on mount and unregister it on unmount: | ||
}, []); | ||
// Update the tab stop data if rowIndex or disabled change: | ||
// Note: A TAB_STOP_UPDATED event is dispatched directly | ||
// after the REGISTER_TAB_STOP event on mount. This is okay | ||
// because the values of rowIndex and disabled will not | ||
// have changed, and in that case the reducer treats | ||
// the TAB_STOP_UPDATED event as a no-op. | ||
// Update the tab stop data if rowIndex or disabled change. | ||
// The isMounted flag is used to prevent this effect running | ||
// on mount, which would be benign but is redundant as the | ||
// REGISTER_TAB_STOP action would have just been dispatched. | ||
React.useEffect(function () { | ||
context.dispatch({ | ||
type: exports.ActionType.TAB_STOP_UPDATED, | ||
payload: { | ||
id: getId(), | ||
rowIndex: getRowIndexFromOptions(options), | ||
disabled: disabled | ||
} | ||
}); | ||
if (isMounted.current) { | ||
context.dispatch({ | ||
type: exports.ActionType.TAB_STOP_UPDATED, | ||
payload: { | ||
id: getId(), | ||
rowIndex: getRowIndexFromOptions(options), | ||
disabled: disabled | ||
} | ||
}); | ||
} | ||
else { | ||
isMounted.current = true; | ||
} | ||
}, [options === null || options === void 0 ? void 0 : options.rowIndex, disabled]); | ||
@@ -509,0 +524,0 @@ // Create a stable callback function for handling key down events: |
@@ -1,3 +0,3 @@ | ||
import React from "react"; | ||
import { Action, KeyConfig, State } from "./types"; | ||
import React, { ReactElement, ReactNode } from "react"; | ||
import { Action, KeyConfig, RowStartMap, State } from "./types"; | ||
export declare const DEFAULT_KEY_CONFIG: KeyConfig; | ||
@@ -16,2 +16,3 @@ export declare function reducer(state: State, action: Action): State; | ||
keyConfig: KeyConfig; | ||
rowStartMap: RowStartMap | null; | ||
}>; | ||
@@ -22,3 +23,3 @@ dispatch: React.Dispatch<Action>; | ||
* Creates a roving tabindex context. | ||
* @param {React.ReactNode} children The child content, which will | ||
* @param {ReactNode} children The child content, which will | ||
* include the DOM elements to rove between using the tab key. | ||
@@ -34,4 +35,4 @@ * @param {keyConfig} keyConfig An optional key navigation configuration | ||
export declare const Provider: ({ children, keyConfig }: { | ||
children: React.ReactNode; | ||
children: ReactNode; | ||
keyConfig?: KeyConfig | undefined; | ||
}) => React.ReactElement; | ||
}) => ReactElement; |
@@ -31,10 +31,10 @@ /// <reference types="react" /> | ||
export declare type KeyConfig = { | ||
[Key.ARROW_LEFT]?: Navigation.PREVIOUS; | ||
[Key.ARROW_RIGHT]?: Navigation.NEXT; | ||
[Key.ARROW_UP]?: Navigation.PREVIOUS | Navigation.PREVIOUS_ROW; | ||
[Key.ARROW_DOWN]?: Navigation.NEXT | Navigation.NEXT_ROW; | ||
[Key.HOME]?: Navigation.FIRST | Navigation.FIRST_IN_ROW; | ||
[Key.END]?: Navigation.LAST | Navigation.LAST_IN_ROW; | ||
[Key.HOME_WITH_CTRL]?: Navigation.FIRST; | ||
[Key.END_WITH_CTRL]?: Navigation.LAST; | ||
[Key.ARROW_LEFT]?: Navigation.PREVIOUS | null; | ||
[Key.ARROW_RIGHT]?: Navigation.NEXT | null; | ||
[Key.ARROW_UP]?: Navigation.PREVIOUS | Navigation.PREVIOUS_ROW | null; | ||
[Key.ARROW_DOWN]?: Navigation.NEXT | Navigation.NEXT_ROW | null; | ||
[Key.HOME]?: Navigation.FIRST | Navigation.FIRST_IN_ROW | null; | ||
[Key.END]?: Navigation.LAST | Navigation.LAST_IN_ROW | null; | ||
[Key.HOME_WITH_CTRL]?: Navigation.FIRST | null; | ||
[Key.END_WITH_CTRL]?: Navigation.LAST | null; | ||
}; | ||
@@ -47,2 +47,3 @@ export declare type TabStop = Readonly<{ | ||
}>; | ||
export declare type RowStartMap = Map<Exclude<TabStop["rowIndex"], null>, number>; | ||
export declare type State = Readonly<{ | ||
@@ -53,2 +54,3 @@ selectedId: string | null; | ||
keyConfig: KeyConfig; | ||
rowStartMap: RowStartMap | null; | ||
}>; | ||
@@ -107,4 +109,4 @@ export declare enum ActionType { | ||
boolean, | ||
(event: React.KeyboardEvent<Element>) => void, | ||
(event: React.KeyboardEvent) => void, | ||
() => void | ||
]; |
{ | ||
"name": "react-roving-tabindex", | ||
"version": "2.0.0-alpha.1", | ||
"description": "React implementation of a roving tabindex", | ||
"version": "2.0.0-alpha.2", | ||
"description": "React implementation of a roving tabindex, now with grid support", | ||
"author": "stevejay", | ||
@@ -68,4 +68,5 @@ "license": "MIT", | ||
"@testing-library/react": "^11.1.0", | ||
"@testing-library/react-hooks": "^3.4.2", | ||
"@types/array-find-index": "^1.0.0", | ||
"@types/jest": "^26.0.14", | ||
"@types/jest": "^26.0.15", | ||
"@types/jsdom": "^16.2.4", | ||
@@ -81,2 +82,3 @@ "@types/lodash.uniqueid": "^4.0.6", | ||
"babel-loader": "^8.1.0", | ||
"coveralls": "^3.1.0", | ||
"cross-env": "^7.0.2", | ||
@@ -86,3 +88,3 @@ "eslint": "^7.11.0", | ||
"eslint-plugin-prettier": "^3.1.4", | ||
"eslint-plugin-react": "^7.21.4", | ||
"eslint-plugin-react": "^7.21.5", | ||
"fork-ts-checker-webpack-plugin": "^5.2.0", | ||
@@ -100,3 +102,2 @@ "gh-pages": "^3.1.0", | ||
"react-dom": "^16.14.0", | ||
"react-hooks-testing-library": "^0.6.0", | ||
"react-test-renderer": "^16.14.0", | ||
@@ -107,3 +108,3 @@ "rollup": "^2.32.0", | ||
"rollup-plugin-node-resolve": "^5.2.0", | ||
"rollup-plugin-peer-deps-external": "^2.2.3", | ||
"rollup-plugin-peer-deps-external": "^2.2.4", | ||
"rollup-plugin-postcss": "^3.1.8", | ||
@@ -113,3 +114,3 @@ "rollup-plugin-typescript2": "^0.28.0", | ||
"styled-components": "^5.2.0", | ||
"ts-loader": "^8.0.5", | ||
"ts-loader": "^8.0.6", | ||
"typescript": "^4.0.3" | ||
@@ -116,0 +117,0 @@ }, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
167142
1248
0
53