@stack-spot/portal-components 2.7.0 → 2.8.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.8.0](https://github.com/stack-spot/portal-commons/compare/portal-components@v2.7.0...portal-components@v2.8.0) (2024-10-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * adds new features to the keyboard hook ([#459](https://github.com/stack-spot/portal-commons/issues/459)) ([39b669c](https://github.com/stack-spot/portal-commons/commit/39b669c4ea4b551b399be50678399e14607ce167))
9
+
3
10
  ## [2.7.0](https://github.com/stack-spot/portal-commons/compare/portal-components@v2.6.1...portal-components@v2.7.0) (2024-10-18)
4
11
 
5
12
 
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- interface Props {
2
+ interface Options<T extends HTMLElement = HTMLDivElement> {
3
3
  /**
4
4
  * A query selector that returns every html element that must be navigable through the keyboard.
5
5
  */
@@ -11,8 +11,29 @@ interface Props {
11
11
  /**
12
12
  * Function to call when TAB is pressed at the last item in the list of items returned by the query selector. Will be the same as
13
13
  * onPressEscape if not specified.
14
+ *
15
+ * Attention: has no effect if `disableTabBehavior` is true.
14
16
  */
15
17
  onPressLastTab?: () => void;
18
+ /**
19
+ * Pass this function if you want any behavior when the user presses the arrow left.
20
+ */
21
+ onPressArrowLeft?: () => void;
22
+ /**
23
+ * Pass this function if you want any behavior when the user presses the arrow right.
24
+ */
25
+ onPressArrowRight?: () => void;
26
+ /**
27
+ * Disables any alteration to the tab key.
28
+ * @default false
29
+ */
30
+ disableTabBehavior?: boolean;
31
+ /**
32
+ * If you already have a ref to the element you want to attach the events to, you can pass it in this prop.
33
+ *
34
+ * If you pass a ref. The events won't be attached to the document, instead, they will be attached to the element referred by the ref.
35
+ */
36
+ ref?: React.RefObject<T>;
16
37
  }
17
38
  /**
18
39
  * Creates listeners for controlling a Menu UI through the keyboard.
@@ -20,11 +41,22 @@ interface Props {
20
41
  * - Arrow up: previous element in the iterator returned by the query selectors. Last element, if the current element is the first.
21
42
  * - Tab: same as Arrow down, but has a different behavior if the element is the last (see onPressLastTab).
22
43
  * - Esc: determined by onPressEscape.
23
- * @param props {@link Props}.
44
+ * @param props {@link Options}.
24
45
  * @returns an object with the element controlled by the keyboard (useRef); a function to attach the keyboard events and a function to
25
46
  * detach the keyboard events.
26
47
  */
27
- export declare function useKeyboardControls<T extends HTMLElement = HTMLDivElement>({ querySelectors, onPressEscape, onPressLastTab }: Props): {
48
+ export declare function useKeyboardControls<T extends HTMLElement = HTMLDivElement>(
49
+ /**
50
+ * Options for the keyboard controls.
51
+ */
52
+ { querySelectors, onPressEscape, onPressLastTab, onPressArrowLeft, onPressArrowRight, disableTabBehavior, ref, }: Options<T>,
53
+ /**
54
+ * Calls `attachKeyboardListeners` (mount) and `detachKeyboardListeners` (unmount) whenever the deps passed as parameter changes.
55
+ *
56
+ * If deps are undefined, this component doesn't automatically add these listeners and you have to use the functions returned in the
57
+ * result.
58
+ */
59
+ deps?: any[]): {
28
60
  keyboardControlledElement: import("react").RefObject<T>;
29
61
  attachKeyboardListeners: () => void;
30
62
  detachKeyboardListeners: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../src/hooks/keyboard.tsx"],"names":[],"mappings":";AAEA,UAAU,KAAK;IACb;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc,EACxE,EAAE,cAAc,EAAE,aAAa,EAAE,cAA8B,EAAE,EAAE,KAAK;;;;EAoDzE"}
1
+ {"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../src/hooks/keyboard.tsx"],"names":[],"mappings":";AAEA,UAAU,OAAO,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc;IACtD;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,GAAG,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc;AACxE;;GAEG;AACH,EACE,cAAc,EAAE,aAAa,EAAE,cAA8B,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,GAAG,GAC5H,EAAE,OAAO,CAAC,CAAC,CAAC;AACb;;;;;GAKG;AACH,IAAI,CAAC,EAAE,GAAG,EAAE;;;;EAiEb"}
@@ -1,24 +1,37 @@
1
- import { useCallback, useRef } from 'react';
1
+ import { useCallback, useEffect, useRef } from 'react';
2
2
  /**
3
3
  * Creates listeners for controlling a Menu UI through the keyboard.
4
4
  * - Arrow down: next element in the iterator returned by the query selectors. First element, if the current element is the last.
5
5
  * - Arrow up: previous element in the iterator returned by the query selectors. Last element, if the current element is the first.
6
6
  * - Tab: same as Arrow down, but has a different behavior if the element is the last (see onPressLastTab).
7
7
  * - Esc: determined by onPressEscape.
8
- * @param props {@link Props}.
8
+ * @param props {@link Options}.
9
9
  * @returns an object with the element controlled by the keyboard (useRef); a function to attach the keyboard events and a function to
10
10
  * detach the keyboard events.
11
11
  */
12
- export function useKeyboardControls({ querySelectors, onPressEscape, onPressLastTab = onPressEscape }) {
13
- const keyboardControlledElement = useRef(null);
12
+ export function useKeyboardControls(
13
+ /**
14
+ * Options for the keyboard controls.
15
+ */
16
+ { querySelectors, onPressEscape, onPressLastTab = onPressEscape, onPressArrowLeft, onPressArrowRight, disableTabBehavior, ref, },
17
+ /**
18
+ * Calls `attachKeyboardListeners` (mount) and `detachKeyboardListeners` (unmount) whenever the deps passed as parameter changes.
19
+ *
20
+ * If deps are undefined, this component doesn't automatically add these listeners and you have to use the functions returned in the
21
+ * result.
22
+ */
23
+ deps) {
24
+ const localRef = useRef(null);
25
+ const keyboardControlledElement = ref ?? localRef;
14
26
  const listeners = useRef({});
15
27
  listeners.current = { onPressEscape, onPressLastTab };
16
28
  const keyboardControls = useCallback((event) => {
29
+ const eventKey = event.key;
17
30
  const target = event?.target;
18
31
  function getSelectableAnchors() {
19
32
  return keyboardControlledElement.current?.querySelectorAll(querySelectors) ?? [];
20
33
  }
21
- function handleArrows(key = event.key) {
34
+ function handleArrows(key = eventKey) {
22
35
  const anchors = getSelectableAnchors();
23
36
  let i = 0;
24
37
  while (i < anchors.length && document.activeElement !== anchors[i])
@@ -36,6 +49,8 @@ export function useKeyboardControls({ querySelectors, onPressEscape, onPressLast
36
49
  target?.click();
37
50
  },
38
51
  Tab: () => {
52
+ if (disableTabBehavior)
53
+ return;
39
54
  const anchors = getSelectableAnchors();
40
55
  if (document.activeElement === anchors[anchors.length - 1])
41
56
  listeners.current.onPressLastTab?.();
@@ -45,15 +60,25 @@ export function useKeyboardControls({ querySelectors, onPressEscape, onPressLast
45
60
  },
46
61
  ArrowUp: handleArrows,
47
62
  ArrowDown: handleArrows,
63
+ ArrowLeft: onPressArrowLeft,
64
+ ArrowRight: onPressArrowRight,
48
65
  };
49
- handlers[event.key]?.();
66
+ handlers[eventKey]?.();
50
67
  }, []);
51
68
  const attachKeyboardListeners = useCallback(() => {
52
- document.addEventListener('keydown', keyboardControls);
69
+ const element = ref?.current ?? document;
70
+ element.addEventListener('keydown', keyboardControls);
53
71
  }, []);
54
72
  const detachKeyboardListeners = useCallback(() => {
55
- document.removeEventListener('keydown', keyboardControls);
73
+ const element = ref?.current ?? document;
74
+ element.removeEventListener('keydown', keyboardControls);
56
75
  }, []);
76
+ useEffect(() => {
77
+ if (!deps)
78
+ return;
79
+ attachKeyboardListeners();
80
+ return detachKeyboardListeners;
81
+ }, deps);
57
82
  return { keyboardControlledElement, attachKeyboardListeners, detachKeyboardListeners };
58
83
  }
59
84
  //# sourceMappingURL=keyboard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard.js","sourceRoot":"","sources":["../../src/hooks/keyboard.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAkB3C;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,GAAG,aAAa,EAAS;IAExE,MAAM,yBAAyB,GAAG,MAAM,CAAI,IAAI,CAAC,CAAA;IACjD,MAAM,SAAS,GAAG,MAAM,CAAkD,EAAE,CAAC,CAAA;IAC7E,SAAS,CAAC,OAAO,GAAG,EAAE,aAAa,EAAE,cAAc,EAAE,CAAA;IAErD,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,KAAoB,EAAE,EAAE;QAC5D,MAAM,MAAM,GAAG,KAAK,EAAE,MAA4B,CAAA;QAElD,SAAS,oBAAoB;YAC3B,OAAO,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;QAClF,CAAC;QAED,SAAS,YAAY,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG;YACnC,MAAM,OAAO,GAAG,oBAAoB,EAAE,CAAA;YACtC,IAAI,CAAC,GAAG,CAAC,CAAA;YACT,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC;gBAAE,CAAC,EAAE,CAAA;YACvE,MAAM,IAAI,GAAQ,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;YACxH,IAAI,EAAE,KAAK,EAAE,EAAE,CAAA;QACjB,CAAC;QAED,MAAM,QAAQ,GAA6C;YACzD,MAAM,EAAE,GAAG,EAAE;gBACX,SAAS,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAA;gBACnC,KAAK,CAAC,eAAe,EAAE,CAAA;gBACvB,KAAK,CAAC,cAAc,EAAE,CAAA;YACxB,CAAC;YACD,KAAK,EAAE,GAAG,EAAE;gBACV,MAAM,EAAE,KAAK,EAAE,CAAA;YACjB,CAAC;YACD,GAAG,EAAE,GAAG,EAAE;gBACR,MAAM,OAAO,GAAG,oBAAoB,EAAE,CAAA;gBACtC,IAAI,QAAQ,CAAC,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;oBAAE,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAA;;oBAC1F,YAAY,CAAC,WAAW,CAAC,CAAA;gBAC/B,KAAK,CAAC,cAAc,EAAE,CAAA;YACxB,CAAC;YACD,OAAO,EAAE,YAAY;YACrB,SAAS,EAAE,YAAY;SACxB,CAAA;QAED,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,CAAA;IACzB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,uBAAuB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/C,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAA;IACxD,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,uBAAuB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/C,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAA;IAC3D,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;AACxF,CAAC"}
1
+ {"version":3,"file":"keyboard.js","sourceRoot":"","sources":["../../src/hooks/keyboard.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAuCtD;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB;AACjC;;GAEG;AACH,EACE,cAAc,EAAE,aAAa,EAAE,cAAc,GAAG,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,GAAG,GAChH;AACb;;;;;GAKG;AACH,IAAY;IAEZ,MAAM,QAAQ,GAAG,MAAM,CAAI,IAAI,CAAC,CAAA;IAChC,MAAM,yBAAyB,GAAG,GAAG,IAAI,QAAQ,CAAA;IACjD,MAAM,SAAS,GAAG,MAAM,CAAoD,EAAE,CAAC,CAAA;IAC/E,SAAS,CAAC,OAAO,GAAG,EAAE,aAAa,EAAE,cAAc,EAAE,CAAA;IAErD,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,KAAY,EAAE,EAAE;QACpD,MAAM,QAAQ,GAAI,KAAuB,CAAC,GAAG,CAAA;QAC7C,MAAM,MAAM,GAAG,KAAK,EAAE,MAA4B,CAAA;QAElD,SAAS,oBAAoB;YAC3B,OAAO,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;QAClF,CAAC;QAED,SAAS,YAAY,CAAC,GAAG,GAAG,QAAQ;YAClC,MAAM,OAAO,GAAG,oBAAoB,EAAE,CAAA;YACtC,IAAI,CAAC,GAAG,CAAC,CAAA;YACT,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC;gBAAE,CAAC,EAAE,CAAA;YACvE,MAAM,IAAI,GAAQ,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;YACxH,IAAI,EAAE,KAAK,EAAE,EAAE,CAAA;QACjB,CAAC;QAED,MAAM,QAAQ,GAA6C;YACzD,MAAM,EAAE,GAAG,EAAE;gBACX,SAAS,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAA;gBACnC,KAAK,CAAC,eAAe,EAAE,CAAA;gBACvB,KAAK,CAAC,cAAc,EAAE,CAAA;YACxB,CAAC;YACD,KAAK,EAAE,GAAG,EAAE;gBACV,MAAM,EAAE,KAAK,EAAE,CAAA;YACjB,CAAC;YACD,GAAG,EAAE,GAAG,EAAE;gBACR,IAAI,kBAAkB;oBAAE,OAAM;gBAC9B,MAAM,OAAO,GAAG,oBAAoB,EAAE,CAAA;gBACtC,IAAI,QAAQ,CAAC,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;oBAAE,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAA;;oBAC1F,YAAY,CAAC,WAAW,CAAC,CAAA;gBAC/B,KAAK,CAAC,cAAc,EAAE,CAAA;YACxB,CAAC;YACD,OAAO,EAAE,YAAY;YACrB,SAAS,EAAE,YAAY;YACvB,SAAS,EAAE,gBAAgB;YAC3B,UAAU,EAAE,iBAAiB;SAC9B,CAAA;QAED,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAA;IACxB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,uBAAuB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/C,MAAM,OAAO,GAAG,GAAG,EAAE,OAAO,IAAI,QAAQ,CAAA;QACxC,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAA;IACvD,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,uBAAuB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/C,MAAM,OAAO,GAAG,GAAG,EAAE,OAAO,IAAI,QAAQ,CAAA;QACxC,OAAO,CAAC,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAA;IAC1D,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,uBAAuB,EAAE,CAAA;QACzB,OAAO,uBAAuB,CAAA;IAChC,CAAC,EAAE,IAAI,CAAC,CAAA;IAER,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;AACxF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stack-spot/portal-components",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,6 +1,6 @@
1
- import { useCallback, useRef } from 'react'
1
+ import { useCallback, useEffect, useRef } from 'react'
2
2
 
3
- interface Props {
3
+ interface Options<T extends HTMLElement = HTMLDivElement> {
4
4
  /**
5
5
  * A query selector that returns every html element that must be navigable through the keyboard.
6
6
  */
@@ -12,8 +12,29 @@ interface Props {
12
12
  /**
13
13
  * Function to call when TAB is pressed at the last item in the list of items returned by the query selector. Will be the same as
14
14
  * onPressEscape if not specified.
15
+ *
16
+ * Attention: has no effect if `disableTabBehavior` is true.
15
17
  */
16
18
  onPressLastTab?: () => void,
19
+ /**
20
+ * Pass this function if you want any behavior when the user presses the arrow left.
21
+ */
22
+ onPressArrowLeft?: () => void,
23
+ /**
24
+ * Pass this function if you want any behavior when the user presses the arrow right.
25
+ */
26
+ onPressArrowRight?: () => void,
27
+ /**
28
+ * Disables any alteration to the tab key.
29
+ * @default false
30
+ */
31
+ disableTabBehavior?: boolean,
32
+ /**
33
+ * If you already have a ref to the element you want to attach the events to, you can pass it in this prop.
34
+ *
35
+ * If you pass a ref. The events won't be attached to the document, instead, they will be attached to the element referred by the ref.
36
+ */
37
+ ref?: React.RefObject<T>,
17
38
  }
18
39
 
19
40
  /**
@@ -22,25 +43,39 @@ interface Props {
22
43
  * - Arrow up: previous element in the iterator returned by the query selectors. Last element, if the current element is the first.
23
44
  * - Tab: same as Arrow down, but has a different behavior if the element is the last (see onPressLastTab).
24
45
  * - Esc: determined by onPressEscape.
25
- * @param props {@link Props}.
46
+ * @param props {@link Options}.
26
47
  * @returns an object with the element controlled by the keyboard (useRef); a function to attach the keyboard events and a function to
27
48
  * detach the keyboard events.
28
49
  */
29
50
  export function useKeyboardControls<T extends HTMLElement = HTMLDivElement>(
30
- { querySelectors, onPressEscape, onPressLastTab = onPressEscape }: Props,
51
+ /**
52
+ * Options for the keyboard controls.
53
+ */
54
+ {
55
+ querySelectors, onPressEscape, onPressLastTab = onPressEscape, onPressArrowLeft, onPressArrowRight, disableTabBehavior, ref,
56
+ }: Options<T>,
57
+ /**
58
+ * Calls `attachKeyboardListeners` (mount) and `detachKeyboardListeners` (unmount) whenever the deps passed as parameter changes.
59
+ *
60
+ * If deps are undefined, this component doesn't automatically add these listeners and you have to use the functions returned in the
61
+ * result.
62
+ */
63
+ deps?: any[],
31
64
  ) {
32
- const keyboardControlledElement = useRef<T>(null)
33
- const listeners = useRef<Pick<Props, 'onPressEscape' | 'onPressLastTab'>>({})
65
+ const localRef = useRef<T>(null)
66
+ const keyboardControlledElement = ref ?? localRef
67
+ const listeners = useRef<Pick<Options, 'onPressEscape' | 'onPressLastTab'>>({})
34
68
  listeners.current = { onPressEscape, onPressLastTab }
35
69
 
36
- const keyboardControls = useCallback((event: KeyboardEvent) => {
70
+ const keyboardControls = useCallback((event: Event) => {
71
+ const eventKey = (event as KeyboardEvent).key
37
72
  const target = event?.target as HTMLElement | null
38
73
 
39
74
  function getSelectableAnchors() {
40
75
  return keyboardControlledElement.current?.querySelectorAll(querySelectors) ?? []
41
76
  }
42
77
 
43
- function handleArrows(key = event.key) {
78
+ function handleArrows(key = eventKey) {
44
79
  const anchors = getSelectableAnchors()
45
80
  let i = 0
46
81
  while (i < anchors.length && document.activeElement !== anchors[i]) i++
@@ -58,6 +93,7 @@ export function useKeyboardControls<T extends HTMLElement = HTMLDivElement>(
58
93
  target?.click()
59
94
  },
60
95
  Tab: () => {
96
+ if (disableTabBehavior) return
61
97
  const anchors = getSelectableAnchors()
62
98
  if (document.activeElement === anchors[anchors.length - 1]) listeners.current.onPressLastTab?.()
63
99
  else handleArrows('ArrowDown')
@@ -65,18 +101,28 @@ export function useKeyboardControls<T extends HTMLElement = HTMLDivElement>(
65
101
  },
66
102
  ArrowUp: handleArrows,
67
103
  ArrowDown: handleArrows,
104
+ ArrowLeft: onPressArrowLeft,
105
+ ArrowRight: onPressArrowRight,
68
106
  }
69
107
 
70
- handlers[event.key]?.()
108
+ handlers[eventKey]?.()
71
109
  }, [])
72
110
 
73
111
  const attachKeyboardListeners = useCallback(() => {
74
- document.addEventListener('keydown', keyboardControls)
112
+ const element = ref?.current ?? document
113
+ element.addEventListener('keydown', keyboardControls)
75
114
  }, [])
76
115
 
77
116
  const detachKeyboardListeners = useCallback(() => {
78
- document.removeEventListener('keydown', keyboardControls)
117
+ const element = ref?.current ?? document
118
+ element.removeEventListener('keydown', keyboardControls)
79
119
  }, [])
80
120
 
121
+ useEffect(() => {
122
+ if (!deps) return
123
+ attachKeyboardListeners()
124
+ return detachKeyboardListeners
125
+ }, deps)
126
+
81
127
  return { keyboardControlledElement, attachKeyboardListeners, detachKeyboardListeners }
82
128
  }