UNPKG

5.44 kBJavaScriptView Raw
1"use strict";
2
3exports.__esModule = true;
4exports.useListOption = useListOption;
5exports.useFocusList = exports.FocusListContext = void 0;
6
7var _querySelectorAll = _interopRequireDefault(require("dom-helpers/querySelectorAll"));
8
9var _react = _interopRequireWildcard(require("react"));
10
11function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
12
13function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
14
15function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
17/* eslint-disable react-hooks/exhaustive-deps */
18const FocusListContext = /*#__PURE__*/_react.default.createContext(null);
19
20exports.FocusListContext = FocusListContext;
21const defaultOpts = {
22 behavior: 'stop'
23};
24
25function useListOption(dataItem) {
26 const ctx = (0, _react.useContext)(FocusListContext);
27 const prevElement = (0, _react.useRef)(null); // this is a bit convoluted because we want to use a ref object, a callback ref
28 // causes an extra render which is fine except that it means the list hook for
29 // anchor items fires before elements are processed
30
31 const ref = (0, _react.useRef)(null);
32 (0, _react.useLayoutEffect)(() => () => {
33 ctx == null ? void 0 : ctx.map.delete(ref.current);
34 }, []);
35 (0, _react.useLayoutEffect)(() => {
36 if (prevElement.current !== ref.current) {
37 ctx == null ? void 0 : ctx.map.delete(prevElement.current);
38 }
39
40 prevElement.current = ref.current;
41
42 if (ref.current && (ctx == null ? void 0 : ctx.map.get(ref.current)) !== dataItem) {
43 ctx == null ? void 0 : ctx.map.set(ref.current, dataItem);
44 }
45 });
46 const focused = dataItem === (ctx == null ? void 0 : ctx.focusedItem);
47 return [ref, focused, focused ? ctx == null ? void 0 : ctx.activeId : undefined];
48}
49
50const useFocusList = ({
51 scope: listRef,
52 anchorItem,
53 focusFirstItem: _focusFirstItem = false,
54 scopeSelector: _scopeSelector = '',
55 activeId
56}) => {
57 const map = (0, _react.useMemo)(() => new WeakMap(), []);
58 const [focusedItem, setFocusedItem] = (0, _react.useState)();
59 const itemSelector = `${_scopeSelector} [data-rw-focusable]`.trim();
60
61 const get = () => {
62 const items = (0, _querySelectorAll.default)(listRef.current, itemSelector);
63 return [items, items.find(e => e.dataset.rwFocused === '')];
64 };
65
66 const list = (0, _react.useMemo)(() => {
67 return {
68 size() {
69 const [items] = get();
70 return items.length;
71 },
72
73 get,
74 toDataItem: el => map.get(el),
75
76 first() {
77 const [[first]] = get();
78 return first;
79 },
80
81 focus(el) {
82 if (!el || map.has(el)) setFocusedItem(el ? map.get(el) : undefined);
83 },
84
85 last() {
86 const [items] = get();
87 return items[items.length - 1];
88 },
89
90 next({
91 behavior
92 } = defaultOpts) {
93 const [items, focusedItem] = get();
94 let nextIdx = items.indexOf(focusedItem) + 1;
95
96 if (nextIdx >= items.length) {
97 if (behavior === 'loop') return items[0];
98 if (behavior === 'clear') return undefined;
99 return focusedItem;
100 }
101
102 return items[nextIdx];
103 },
104
105 prev({
106 behavior
107 } = defaultOpts) {
108 const [items, focusedItem] = get();
109 let nextIdx = Math.max(0, items.indexOf(focusedItem)) - 1;
110
111 if (nextIdx < 0) {
112 if (behavior === 'loop') return items[items.length - 1];
113 if (behavior === 'clear') return undefined;
114 return focusedItem;
115 }
116
117 return items[nextIdx];
118 }
119
120 };
121 }, []);
122 (0, _react.useLayoutEffect)(() => {
123 if (!anchorItem) {
124 list.focus(null);
125 return;
126 }
127
128 const element = get()[0].find(el => list.toDataItem(el) === anchorItem);
129 list.focus(element);
130 }, [anchorItem]);
131 (0, _react.useLayoutEffect)(() => {
132 if (!listRef.current) return;
133 const [, focusedElement] = get();
134 const hasItem = focusedElement != null;
135
136 if (!hasItem && _focusFirstItem || hasItem && !listRef.current.contains(focusedElement)) {
137 if (_focusFirstItem) list.focus(list.first());else list.focus(null);
138 }
139 });
140 const context = (0, _react.useMemo)(() => ({
141 map,
142 focusedItem,
143 activeId
144 }), [focusedItem, activeId]);
145 list.context = context;
146 list.getFocused = (0, _react.useCallback)(() => focusedItem, [focusedItem]);
147
148 list.hasFocused = () => focusedItem !== undefined;
149
150 return list;
151};
152
153exports.useFocusList = useFocusList;
\No newline at end of file