@deque/cauldron-react 6.22.3-canary.d80b6b65 → 6.22.3-canary.e0adb4ad
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Listbox/Listbox.d.ts +1 -0
- package/lib/index.js +126 -6
- package/lib/utils/useMnemonics.d.ts +31 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ interface BaseListboxProps extends PolymorphicProps<Omit<React.HTMLAttributes<HT
|
|
|
6
6
|
navigation?: 'cycle' | 'bound';
|
|
7
7
|
focusStrategy?: 'lastSelected' | 'first' | 'last';
|
|
8
8
|
focusDisabledOptions?: boolean;
|
|
9
|
+
activeOption?: ListboxOption;
|
|
9
10
|
onActiveChange?: (option: ListboxOption) => void;
|
|
10
11
|
disabled?: boolean;
|
|
11
12
|
}
|
package/lib/index.js
CHANGED
|
@@ -1366,7 +1366,7 @@ var removeFocusTrapFromStack = function (focusTrap) {
|
|
|
1366
1366
|
var focusTrapIndex = focusTrapStack.findIndex(function (trap) { return focusTrap.targetElement === trap.targetElement; });
|
|
1367
1367
|
focusTrapStack.splice(focusTrapIndex, 1);
|
|
1368
1368
|
};
|
|
1369
|
-
function getActiveElement(target) {
|
|
1369
|
+
function getActiveElement$1(target) {
|
|
1370
1370
|
var _a;
|
|
1371
1371
|
return (((_a = target === null || target === void 0 ? void 0 : target.ownerDocument.activeElement) !== null && _a !== void 0 ? _a : document.activeElement) ||
|
|
1372
1372
|
document.body);
|
|
@@ -1502,7 +1502,7 @@ function useFocusTrap(target, options) {
|
|
|
1502
1502
|
if (!targetElement || disabled) {
|
|
1503
1503
|
return;
|
|
1504
1504
|
}
|
|
1505
|
-
returnFocusElementRef.current = getActiveElement(targetElement);
|
|
1505
|
+
returnFocusElementRef.current = getActiveElement$1(targetElement);
|
|
1506
1506
|
focusTrap.current = createFocusTrap(targetElement, initialFocusElement);
|
|
1507
1507
|
return function () {
|
|
1508
1508
|
var _a;
|
|
@@ -3705,12 +3705,13 @@ var optionMatchesValue = function (option, value) {
|
|
|
3705
3705
|
option.value === value;
|
|
3706
3706
|
};
|
|
3707
3707
|
var Listbox = React.forwardRef(function (_a, ref) {
|
|
3708
|
-
var _b = _a.as, Component = _b === void 0 ? 'ul' : _b, children = _a.children, defaultValue = _a.defaultValue, value = _a.value, _c = _a.navigation, navigation = _c === void 0 ? 'bound' : _c, _d = _a.focusStrategy, focusStrategy = _d === void 0 ? 'lastSelected' : _d, _e = _a.focusDisabledOptions, focusDisabledOptions = _e === void 0 ? false : _e, _f = _a.multiselect, multiselect = _f === void 0 ? false : _f, onKeyDown = _a.onKeyDown, onFocus = _a.onFocus, onSelectionChange = _a.onSelectionChange, onActiveChange = _a.onActiveChange, _g = _a.disabled, disabled = _g === void 0 ? false : _g, props = tslib.__rest(_a, ["as", "children", "defaultValue", "value", "navigation", "focusStrategy", "focusDisabledOptions", "multiselect", "onKeyDown", "onFocus", "onSelectionChange", "onActiveChange", "disabled"]);
|
|
3708
|
+
var _b = _a.as, Component = _b === void 0 ? 'ul' : _b, children = _a.children, defaultValue = _a.defaultValue, value = _a.value, _c = _a.navigation, navigation = _c === void 0 ? 'bound' : _c, _d = _a.focusStrategy, focusStrategy = _d === void 0 ? 'lastSelected' : _d, _e = _a.focusDisabledOptions, focusDisabledOptions = _e === void 0 ? false : _e, _f = _a.multiselect, multiselect = _f === void 0 ? false : _f, onKeyDown = _a.onKeyDown, onFocus = _a.onFocus, onSelectionChange = _a.onSelectionChange, controlledActiveOption = _a.activeOption, onActiveChange = _a.onActiveChange, _g = _a.disabled, disabled = _g === void 0 ? false : _g, props = tslib.__rest(_a, ["as", "children", "defaultValue", "value", "navigation", "focusStrategy", "focusDisabledOptions", "multiselect", "onKeyDown", "onFocus", "onSelectionChange", "activeOption", "onActiveChange", "disabled"]);
|
|
3709
3709
|
var _h = tslib.__read(React.useState([]), 2), options = _h[0], setOptions = _h[1];
|
|
3710
|
-
var _j = tslib.__read(React.useState(null), 2), activeOption = _j[0], setActiveOption = _j[1];
|
|
3710
|
+
var _j = tslib.__read(React.useState(controlledActiveOption || null), 2), activeOption = _j[0], setActiveOption = _j[1];
|
|
3711
3711
|
var _k = tslib.__read(React.useState([]), 2), selectedOptions = _k[0], setSelectedOptions = _k[1];
|
|
3712
3712
|
var listboxRef = useSharedRef(ref);
|
|
3713
3713
|
var isControlled = typeof value !== 'undefined';
|
|
3714
|
+
var isActiveControlled = typeof controlledActiveOption !== 'undefined';
|
|
3714
3715
|
React.useLayoutEffect(function () {
|
|
3715
3716
|
if (!isControlled && selectedOptions.length > 0) {
|
|
3716
3717
|
return;
|
|
@@ -3745,6 +3746,11 @@ var Listbox = React.forwardRef(function (_a, ref) {
|
|
|
3745
3746
|
onActiveChange === null || onActiveChange === void 0 ? void 0 : onActiveChange(activeOption);
|
|
3746
3747
|
}
|
|
3747
3748
|
}, [activeOption]);
|
|
3749
|
+
React.useEffect(function () {
|
|
3750
|
+
if (isActiveControlled && controlledActiveOption !== activeOption) {
|
|
3751
|
+
setActiveOption(controlledActiveOption || null);
|
|
3752
|
+
}
|
|
3753
|
+
}, [isActiveControlled, controlledActiveOption]);
|
|
3748
3754
|
var handleSelect = React.useCallback(function (option) {
|
|
3749
3755
|
var _a;
|
|
3750
3756
|
setActiveOption(option);
|
|
@@ -4961,28 +4967,142 @@ function useActionListContext() {
|
|
|
4961
4967
|
return React.useContext(ActionListContext);
|
|
4962
4968
|
}
|
|
4963
4969
|
|
|
4970
|
+
/**
|
|
4971
|
+
* Get an element's accessible name by its aria-label or text content
|
|
4972
|
+
*/
|
|
4973
|
+
function getAccessibleLabel(element) {
|
|
4974
|
+
var _a;
|
|
4975
|
+
return (
|
|
4976
|
+
// We're explicitly ignoring that we _could_ use aria-labelledby here
|
|
4977
|
+
// because of the additional complexity that is needed in order to calculate
|
|
4978
|
+
// the accessible name of an aria-labelled by idref. We're reserving that behavior
|
|
4979
|
+
// for future implementation if it is determined to be needed.
|
|
4980
|
+
element.getAttribute('aria-label') || ((_a = element.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '');
|
|
4981
|
+
}
|
|
4982
|
+
/**
|
|
4983
|
+
* Gets the active element based on the root element passed in
|
|
4984
|
+
*/
|
|
4985
|
+
function getActiveElement(root) {
|
|
4986
|
+
var activeElement;
|
|
4987
|
+
if (document.activeElement === root &&
|
|
4988
|
+
root.hasAttribute('aria-activedescendant')) {
|
|
4989
|
+
activeElement = document.getElementById(
|
|
4990
|
+
// Validating attribute above with "hasAttribute"
|
|
4991
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
4992
|
+
root.getAttribute('aria-activedescendant'));
|
|
4993
|
+
}
|
|
4994
|
+
else {
|
|
4995
|
+
activeElement = document.activeElement;
|
|
4996
|
+
}
|
|
4997
|
+
return root.contains(activeElement) ? activeElement : null;
|
|
4998
|
+
}
|
|
4999
|
+
/**
|
|
5000
|
+
* A hook that provides mnemonic navigation for keyboard users.
|
|
5001
|
+
*
|
|
5002
|
+
* Mnemonics allow users to quickly navigate to elements by typing the first
|
|
5003
|
+
* letter of the element's text content. Pressing the same letter repeatedly
|
|
5004
|
+
* cycles through all matching elements.
|
|
5005
|
+
*/
|
|
5006
|
+
function useMnemonics(_a) {
|
|
5007
|
+
var elementOrRef = _a.elementOrRef, matchingElementsSelector = _a.matchingElementsSelector, onMatch = _a.onMatch, _b = _a.enabled, enabled = _b === void 0 ? true : _b;
|
|
5008
|
+
var containerRef = React.useRef();
|
|
5009
|
+
React.useEffect(function () {
|
|
5010
|
+
if (elementOrRef instanceof HTMLElement) {
|
|
5011
|
+
containerRef.current = elementOrRef;
|
|
5012
|
+
}
|
|
5013
|
+
else if (!!elementOrRef && 'current' in elementOrRef) {
|
|
5014
|
+
containerRef.current = elementOrRef === null || elementOrRef === void 0 ? void 0 : elementOrRef.current;
|
|
5015
|
+
}
|
|
5016
|
+
}, [elementOrRef]);
|
|
5017
|
+
React.useEffect(function () {
|
|
5018
|
+
if (!enabled || !containerRef.current) {
|
|
5019
|
+
return;
|
|
5020
|
+
}
|
|
5021
|
+
var keyboardHandler = function (event) {
|
|
5022
|
+
// Ignore keyboard events where a modifier key was pressed
|
|
5023
|
+
var hasModifier = event.ctrlKey || event.altKey || event.metaKey;
|
|
5024
|
+
if (hasModifier) {
|
|
5025
|
+
return;
|
|
5026
|
+
}
|
|
5027
|
+
// Ignore keyboard events where a non-alphanumeric character was pressed
|
|
5028
|
+
if (event.key.length !== 1 || !/[a-z\d]/i.test(event.key)) {
|
|
5029
|
+
return;
|
|
5030
|
+
}
|
|
5031
|
+
var container = containerRef.current;
|
|
5032
|
+
if (!container) {
|
|
5033
|
+
return;
|
|
5034
|
+
}
|
|
5035
|
+
// Prevent default behavior and stop propagation for mnemonic keys
|
|
5036
|
+
event.preventDefault();
|
|
5037
|
+
event.stopPropagation();
|
|
5038
|
+
var elements = Array.from(container.querySelectorAll(matchingElementsSelector !== null && matchingElementsSelector !== void 0 ? matchingElementsSelector : focusable__default["default"]));
|
|
5039
|
+
var matchingElements = elements.filter(function (element) {
|
|
5040
|
+
return getAccessibleLabel(element).toLowerCase()[0] ===
|
|
5041
|
+
event.key.toLowerCase();
|
|
5042
|
+
});
|
|
5043
|
+
if (!matchingElements.length) {
|
|
5044
|
+
return;
|
|
5045
|
+
}
|
|
5046
|
+
var currentActiveElement = getActiveElement(containerRef.current);
|
|
5047
|
+
var nextActiveElement = null;
|
|
5048
|
+
if (currentActiveElement) {
|
|
5049
|
+
nextActiveElement = matchingElements.find(function (element) {
|
|
5050
|
+
// Find the next matching element that is _after_ the current active element
|
|
5051
|
+
// within the collection of identified elements
|
|
5052
|
+
return !!(element.compareDocumentPosition(currentActiveElement) &
|
|
5053
|
+
Node.DOCUMENT_POSITION_PRECEDING);
|
|
5054
|
+
});
|
|
5055
|
+
}
|
|
5056
|
+
if (typeof onMatch === 'function') {
|
|
5057
|
+
onMatch(nextActiveElement !== null && nextActiveElement !== void 0 ? nextActiveElement : matchingElements[0]);
|
|
5058
|
+
}
|
|
5059
|
+
};
|
|
5060
|
+
var container = containerRef.current;
|
|
5061
|
+
container.addEventListener('keydown', keyboardHandler);
|
|
5062
|
+
return function () { return container.removeEventListener('keydown', keyboardHandler); };
|
|
5063
|
+
}, [enabled, containerRef, matchingElementsSelector, onMatch]);
|
|
5064
|
+
return containerRef;
|
|
5065
|
+
}
|
|
5066
|
+
|
|
4964
5067
|
var ActionList = React.forwardRef(function (_a, ref) {
|
|
4965
5068
|
var _b = _a.selectionType, selectionType = _b === void 0 ? null : _b, onAction = _a.onAction, className = _a.className, children = _a.children, props = tslib.__rest(_a, ["selectionType", "onAction", "className", "children"]);
|
|
4966
5069
|
var actionListContext = useActionListContext();
|
|
4967
5070
|
var activeElement = React.useRef();
|
|
5071
|
+
var _c = tslib.__read(React.useState(), 2), activeOption = _c[0], setActiveOption = _c[1];
|
|
4968
5072
|
var handleActiveChange = React.useCallback(function (value) {
|
|
4969
5073
|
activeElement.current = value === null || value === void 0 ? void 0 : value.element;
|
|
5074
|
+
setActiveOption(value);
|
|
4970
5075
|
}, []);
|
|
4971
5076
|
var handleAction = React.useCallback(function (key, event) {
|
|
4972
5077
|
if (typeof onAction === 'function') {
|
|
4973
5078
|
onAction(key, event);
|
|
4974
5079
|
}
|
|
4975
5080
|
}, [onAction]);
|
|
5081
|
+
var containerRef = useMnemonics({
|
|
5082
|
+
onMatch: function (element) {
|
|
5083
|
+
setActiveOption({
|
|
5084
|
+
element: element
|
|
5085
|
+
});
|
|
5086
|
+
},
|
|
5087
|
+
matchingElementsSelector: props.role === 'menu'
|
|
5088
|
+
? '[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio]'
|
|
5089
|
+
: '[role=option]'
|
|
5090
|
+
});
|
|
4976
5091
|
return (
|
|
4977
5092
|
// Note: we should be able to use listbox without passing a prop
|
|
4978
5093
|
// value for "multiselect"
|
|
4979
5094
|
// see: https://github.com/dequelabs/cauldron/issues/1890
|
|
4980
5095
|
// @ts-expect-error this should be allowed
|
|
4981
|
-
React__default["default"].createElement(Listbox, tslib.__assign({ ref:
|
|
5096
|
+
React__default["default"].createElement(Listbox, tslib.__assign({ ref: function (element) {
|
|
5097
|
+
if (ref) {
|
|
5098
|
+
setRef(ref, element);
|
|
5099
|
+
}
|
|
5100
|
+
containerRef.current = element;
|
|
5101
|
+
},
|
|
4982
5102
|
/* Listbox comes with an explicit role of "listbox", but we want to either
|
|
4983
5103
|
* use the role from props, or default to the intrinsic role */
|
|
4984
5104
|
// eslint-disable-next-line jsx-a11y/aria-role
|
|
4985
|
-
role: undefined, "aria-multiselectable": actionListContext.role === 'listbox' ? undefined : null, className: classNames__default["default"]('ActionList', className) }, props, { onActiveChange: handleActiveChange, navigation: "bound" }),
|
|
5105
|
+
role: undefined, "aria-multiselectable": actionListContext.role === 'listbox' ? undefined : null, className: classNames__default["default"]('ActionList', className), activeOption: activeOption }, props, { onActiveChange: handleActiveChange, navigation: "bound" }),
|
|
4986
5106
|
React__default["default"].createElement(ActionListProvider, { role: props.role || 'list', onAction: handleAction, selectionType: selectionType }, children)));
|
|
4987
5107
|
});
|
|
4988
5108
|
ActionList.displayName = 'ActionList';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { ElementOrRef } from '../types';
|
|
3
|
+
type useMnemonicsOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* The container element or ref to use for matching elements for mnemonics.
|
|
6
|
+
* If not provided, the hook will return a ref that should be attached to a container.
|
|
7
|
+
*/
|
|
8
|
+
elementOrRef?: ElementOrRef<HTMLElement>;
|
|
9
|
+
/**
|
|
10
|
+
* CSS selector to match for elements, defaults to focusable descendants.
|
|
11
|
+
*/
|
|
12
|
+
matchingElementsSelector?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Callback fired when a matching element is found via mnemonic keyboard entry.
|
|
15
|
+
*/
|
|
16
|
+
onMatch: (element: HTMLElement) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Whether mnemonic navigation is enabled. Defaults to true.
|
|
19
|
+
*/
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
};
|
|
22
|
+
type useMnemonicsResults<T extends HTMLElement> = RefObject<T>;
|
|
23
|
+
/**
|
|
24
|
+
* A hook that provides mnemonic navigation for keyboard users.
|
|
25
|
+
*
|
|
26
|
+
* Mnemonics allow users to quickly navigate to elements by typing the first
|
|
27
|
+
* letter of the element's text content. Pressing the same letter repeatedly
|
|
28
|
+
* cycles through all matching elements.
|
|
29
|
+
*/
|
|
30
|
+
export default function useMnemonics<T extends HTMLElement>({ elementOrRef, matchingElementsSelector, onMatch, enabled }: useMnemonicsOptions): useMnemonicsResults<T>;
|
|
31
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deque/cauldron-react",
|
|
3
|
-
"version": "6.22.3-canary.
|
|
3
|
+
"version": "6.22.3-canary.e0adb4ad",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Fully accessible react components library for Deque Cauldron",
|
|
6
6
|
"homepage": "https://cauldron.dequelabs.com/",
|