index.mjs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import $98Iye$babelruntimehelpersesmextends from "@babel/runtime/helpers/esm/extends";
  2. import {forwardRef as $98Iye$forwardRef, createElement as $98Iye$createElement, useRef as $98Iye$useRef, useState as $98Iye$useState, useEffect as $98Iye$useEffect, useCallback as $98Iye$useCallback} from "react";
  3. import {composeEventHandlers as $98Iye$composeEventHandlers} from "@radix-ui/primitive";
  4. import {createCollection as $98Iye$createCollection} from "@radix-ui/react-collection";
  5. import {useComposedRefs as $98Iye$useComposedRefs} from "@radix-ui/react-compose-refs";
  6. import {createContextScope as $98Iye$createContextScope} from "@radix-ui/react-context";
  7. import {useId as $98Iye$useId} from "@radix-ui/react-id";
  8. import {Primitive as $98Iye$Primitive} from "@radix-ui/react-primitive";
  9. import {useCallbackRef as $98Iye$useCallbackRef} from "@radix-ui/react-use-callback-ref";
  10. import {useControllableState as $98Iye$useControllableState} from "@radix-ui/react-use-controllable-state";
  11. import {useDirection as $98Iye$useDirection} from "@radix-ui/react-direction";
  12. const $d7bdfb9eb0fdf311$var$ENTRY_FOCUS = 'rovingFocusGroup.onEntryFocus';
  13. const $d7bdfb9eb0fdf311$var$EVENT_OPTIONS = {
  14. bubbles: false,
  15. cancelable: true
  16. };
  17. /* -------------------------------------------------------------------------------------------------
  18. * RovingFocusGroup
  19. * -----------------------------------------------------------------------------------------------*/ const $d7bdfb9eb0fdf311$var$GROUP_NAME = 'RovingFocusGroup';
  20. const [$d7bdfb9eb0fdf311$var$Collection, $d7bdfb9eb0fdf311$var$useCollection, $d7bdfb9eb0fdf311$var$createCollectionScope] = $98Iye$createCollection($d7bdfb9eb0fdf311$var$GROUP_NAME);
  21. const [$d7bdfb9eb0fdf311$var$createRovingFocusGroupContext, $d7bdfb9eb0fdf311$export$c7109489551a4f4] = $98Iye$createContextScope($d7bdfb9eb0fdf311$var$GROUP_NAME, [
  22. $d7bdfb9eb0fdf311$var$createCollectionScope
  23. ]);
  24. const [$d7bdfb9eb0fdf311$var$RovingFocusProvider, $d7bdfb9eb0fdf311$var$useRovingFocusContext] = $d7bdfb9eb0fdf311$var$createRovingFocusGroupContext($d7bdfb9eb0fdf311$var$GROUP_NAME);
  25. const $d7bdfb9eb0fdf311$export$8699f7c8af148338 = /*#__PURE__*/ $98Iye$forwardRef((props, forwardedRef)=>{
  26. return /*#__PURE__*/ $98Iye$createElement($d7bdfb9eb0fdf311$var$Collection.Provider, {
  27. scope: props.__scopeRovingFocusGroup
  28. }, /*#__PURE__*/ $98Iye$createElement($d7bdfb9eb0fdf311$var$Collection.Slot, {
  29. scope: props.__scopeRovingFocusGroup
  30. }, /*#__PURE__*/ $98Iye$createElement($d7bdfb9eb0fdf311$var$RovingFocusGroupImpl, $98Iye$babelruntimehelpersesmextends({}, props, {
  31. ref: forwardedRef
  32. }))));
  33. });
  34. /*#__PURE__*/ Object.assign($d7bdfb9eb0fdf311$export$8699f7c8af148338, {
  35. displayName: $d7bdfb9eb0fdf311$var$GROUP_NAME
  36. });
  37. /* -----------------------------------------------------------------------------------------------*/ const $d7bdfb9eb0fdf311$var$RovingFocusGroupImpl = /*#__PURE__*/ $98Iye$forwardRef((props, forwardedRef)=>{
  38. const { __scopeRovingFocusGroup: __scopeRovingFocusGroup , orientation: orientation , loop: loop = false , dir: dir , currentTabStopId: currentTabStopIdProp , defaultCurrentTabStopId: defaultCurrentTabStopId , onCurrentTabStopIdChange: onCurrentTabStopIdChange , onEntryFocus: onEntryFocus , ...groupProps } = props;
  39. const ref = $98Iye$useRef(null);
  40. const composedRefs = $98Iye$useComposedRefs(forwardedRef, ref);
  41. const direction = $98Iye$useDirection(dir);
  42. const [currentTabStopId = null, setCurrentTabStopId] = $98Iye$useControllableState({
  43. prop: currentTabStopIdProp,
  44. defaultProp: defaultCurrentTabStopId,
  45. onChange: onCurrentTabStopIdChange
  46. });
  47. const [isTabbingBackOut, setIsTabbingBackOut] = $98Iye$useState(false);
  48. const handleEntryFocus = $98Iye$useCallbackRef(onEntryFocus);
  49. const getItems = $d7bdfb9eb0fdf311$var$useCollection(__scopeRovingFocusGroup);
  50. const isClickFocusRef = $98Iye$useRef(false);
  51. const [focusableItemsCount, setFocusableItemsCount] = $98Iye$useState(0);
  52. $98Iye$useEffect(()=>{
  53. const node = ref.current;
  54. if (node) {
  55. node.addEventListener($d7bdfb9eb0fdf311$var$ENTRY_FOCUS, handleEntryFocus);
  56. return ()=>node.removeEventListener($d7bdfb9eb0fdf311$var$ENTRY_FOCUS, handleEntryFocus)
  57. ;
  58. }
  59. }, [
  60. handleEntryFocus
  61. ]);
  62. return /*#__PURE__*/ $98Iye$createElement($d7bdfb9eb0fdf311$var$RovingFocusProvider, {
  63. scope: __scopeRovingFocusGroup,
  64. orientation: orientation,
  65. dir: direction,
  66. loop: loop,
  67. currentTabStopId: currentTabStopId,
  68. onItemFocus: $98Iye$useCallback((tabStopId)=>setCurrentTabStopId(tabStopId)
  69. , [
  70. setCurrentTabStopId
  71. ]),
  72. onItemShiftTab: $98Iye$useCallback(()=>setIsTabbingBackOut(true)
  73. , []),
  74. onFocusableItemAdd: $98Iye$useCallback(()=>setFocusableItemsCount((prevCount)=>prevCount + 1
  75. )
  76. , []),
  77. onFocusableItemRemove: $98Iye$useCallback(()=>setFocusableItemsCount((prevCount)=>prevCount - 1
  78. )
  79. , [])
  80. }, /*#__PURE__*/ $98Iye$createElement($98Iye$Primitive.div, $98Iye$babelruntimehelpersesmextends({
  81. tabIndex: isTabbingBackOut || focusableItemsCount === 0 ? -1 : 0,
  82. "data-orientation": orientation
  83. }, groupProps, {
  84. ref: composedRefs,
  85. style: {
  86. outline: 'none',
  87. ...props.style
  88. },
  89. onMouseDown: $98Iye$composeEventHandlers(props.onMouseDown, ()=>{
  90. isClickFocusRef.current = true;
  91. }),
  92. onFocus: $98Iye$composeEventHandlers(props.onFocus, (event)=>{
  93. // We normally wouldn't need this check, because we already check
  94. // that the focus is on the current target and not bubbling to it.
  95. // We do this because Safari doesn't focus buttons when clicked, and
  96. // instead, the wrapper will get focused and not through a bubbling event.
  97. const isKeyboardFocus = !isClickFocusRef.current;
  98. if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {
  99. const entryFocusEvent = new CustomEvent($d7bdfb9eb0fdf311$var$ENTRY_FOCUS, $d7bdfb9eb0fdf311$var$EVENT_OPTIONS);
  100. event.currentTarget.dispatchEvent(entryFocusEvent);
  101. if (!entryFocusEvent.defaultPrevented) {
  102. const items = getItems().filter((item)=>item.focusable
  103. );
  104. const activeItem = items.find((item)=>item.active
  105. );
  106. const currentItem = items.find((item)=>item.id === currentTabStopId
  107. );
  108. const candidateItems = [
  109. activeItem,
  110. currentItem,
  111. ...items
  112. ].filter(Boolean);
  113. const candidateNodes = candidateItems.map((item)=>item.ref.current
  114. );
  115. $d7bdfb9eb0fdf311$var$focusFirst(candidateNodes);
  116. }
  117. }
  118. isClickFocusRef.current = false;
  119. }),
  120. onBlur: $98Iye$composeEventHandlers(props.onBlur, ()=>setIsTabbingBackOut(false)
  121. )
  122. })));
  123. });
  124. /* -------------------------------------------------------------------------------------------------
  125. * RovingFocusGroupItem
  126. * -----------------------------------------------------------------------------------------------*/ const $d7bdfb9eb0fdf311$var$ITEM_NAME = 'RovingFocusGroupItem';
  127. const $d7bdfb9eb0fdf311$export$ab9df7c53fe8454 = /*#__PURE__*/ $98Iye$forwardRef((props, forwardedRef)=>{
  128. const { __scopeRovingFocusGroup: __scopeRovingFocusGroup , focusable: focusable = true , active: active = false , tabStopId: tabStopId , ...itemProps } = props;
  129. const autoId = $98Iye$useId();
  130. const id = tabStopId || autoId;
  131. const context = $d7bdfb9eb0fdf311$var$useRovingFocusContext($d7bdfb9eb0fdf311$var$ITEM_NAME, __scopeRovingFocusGroup);
  132. const isCurrentTabStop = context.currentTabStopId === id;
  133. const getItems = $d7bdfb9eb0fdf311$var$useCollection(__scopeRovingFocusGroup);
  134. const { onFocusableItemAdd: onFocusableItemAdd , onFocusableItemRemove: onFocusableItemRemove } = context;
  135. $98Iye$useEffect(()=>{
  136. if (focusable) {
  137. onFocusableItemAdd();
  138. return ()=>onFocusableItemRemove()
  139. ;
  140. }
  141. }, [
  142. focusable,
  143. onFocusableItemAdd,
  144. onFocusableItemRemove
  145. ]);
  146. return /*#__PURE__*/ $98Iye$createElement($d7bdfb9eb0fdf311$var$Collection.ItemSlot, {
  147. scope: __scopeRovingFocusGroup,
  148. id: id,
  149. focusable: focusable,
  150. active: active
  151. }, /*#__PURE__*/ $98Iye$createElement($98Iye$Primitive.span, $98Iye$babelruntimehelpersesmextends({
  152. tabIndex: isCurrentTabStop ? 0 : -1,
  153. "data-orientation": context.orientation
  154. }, itemProps, {
  155. ref: forwardedRef,
  156. onMouseDown: $98Iye$composeEventHandlers(props.onMouseDown, (event)=>{
  157. // We prevent focusing non-focusable items on `mousedown`.
  158. // Even though the item has tabIndex={-1}, that only means take it out of the tab order.
  159. if (!focusable) event.preventDefault(); // Safari doesn't focus a button when clicked so we run our logic on mousedown also
  160. else context.onItemFocus(id);
  161. }),
  162. onFocus: $98Iye$composeEventHandlers(props.onFocus, ()=>context.onItemFocus(id)
  163. ),
  164. onKeyDown: $98Iye$composeEventHandlers(props.onKeyDown, (event)=>{
  165. if (event.key === 'Tab' && event.shiftKey) {
  166. context.onItemShiftTab();
  167. return;
  168. }
  169. if (event.target !== event.currentTarget) return;
  170. const focusIntent = $d7bdfb9eb0fdf311$var$getFocusIntent(event, context.orientation, context.dir);
  171. if (focusIntent !== undefined) {
  172. event.preventDefault();
  173. const items = getItems().filter((item)=>item.focusable
  174. );
  175. let candidateNodes = items.map((item)=>item.ref.current
  176. );
  177. if (focusIntent === 'last') candidateNodes.reverse();
  178. else if (focusIntent === 'prev' || focusIntent === 'next') {
  179. if (focusIntent === 'prev') candidateNodes.reverse();
  180. const currentIndex = candidateNodes.indexOf(event.currentTarget);
  181. candidateNodes = context.loop ? $d7bdfb9eb0fdf311$var$wrapArray(candidateNodes, currentIndex + 1) : candidateNodes.slice(currentIndex + 1);
  182. }
  183. /**
  184. * Imperative focus during keydown is risky so we prevent React's batching updates
  185. * to avoid potential bugs. See: https://github.com/facebook/react/issues/20332
  186. */ setTimeout(()=>$d7bdfb9eb0fdf311$var$focusFirst(candidateNodes)
  187. );
  188. }
  189. })
  190. })));
  191. });
  192. /*#__PURE__*/ Object.assign($d7bdfb9eb0fdf311$export$ab9df7c53fe8454, {
  193. displayName: $d7bdfb9eb0fdf311$var$ITEM_NAME
  194. });
  195. /* -----------------------------------------------------------------------------------------------*/ // prettier-ignore
  196. const $d7bdfb9eb0fdf311$var$MAP_KEY_TO_FOCUS_INTENT = {
  197. ArrowLeft: 'prev',
  198. ArrowUp: 'prev',
  199. ArrowRight: 'next',
  200. ArrowDown: 'next',
  201. PageUp: 'first',
  202. Home: 'first',
  203. PageDown: 'last',
  204. End: 'last'
  205. };
  206. function $d7bdfb9eb0fdf311$var$getDirectionAwareKey(key, dir) {
  207. if (dir !== 'rtl') return key;
  208. return key === 'ArrowLeft' ? 'ArrowRight' : key === 'ArrowRight' ? 'ArrowLeft' : key;
  209. }
  210. function $d7bdfb9eb0fdf311$var$getFocusIntent(event, orientation, dir) {
  211. const key = $d7bdfb9eb0fdf311$var$getDirectionAwareKey(event.key, dir);
  212. if (orientation === 'vertical' && [
  213. 'ArrowLeft',
  214. 'ArrowRight'
  215. ].includes(key)) return undefined;
  216. if (orientation === 'horizontal' && [
  217. 'ArrowUp',
  218. 'ArrowDown'
  219. ].includes(key)) return undefined;
  220. return $d7bdfb9eb0fdf311$var$MAP_KEY_TO_FOCUS_INTENT[key];
  221. }
  222. function $d7bdfb9eb0fdf311$var$focusFirst(candidates) {
  223. const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
  224. for (const candidate of candidates){
  225. // if focus is already where we want to go, we don't want to keep going through the candidates
  226. if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
  227. candidate.focus();
  228. if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
  229. }
  230. }
  231. /**
  232. * Wraps an array around itself at a given start index
  233. * Example: `wrapArray(['a', 'b', 'c', 'd'], 2) === ['c', 'd', 'a', 'b']`
  234. */ function $d7bdfb9eb0fdf311$var$wrapArray(array, startIndex) {
  235. return array.map((_, index)=>array[(startIndex + index) % array.length]
  236. );
  237. }
  238. const $d7bdfb9eb0fdf311$export$be92b6f5f03c0fe9 = $d7bdfb9eb0fdf311$export$8699f7c8af148338;
  239. const $d7bdfb9eb0fdf311$export$6d08773d2e66f8f2 = $d7bdfb9eb0fdf311$export$ab9df7c53fe8454;
  240. export {$d7bdfb9eb0fdf311$export$c7109489551a4f4 as createRovingFocusGroupScope, $d7bdfb9eb0fdf311$export$8699f7c8af148338 as RovingFocusGroup, $d7bdfb9eb0fdf311$export$ab9df7c53fe8454 as RovingFocusGroupItem, $d7bdfb9eb0fdf311$export$be92b6f5f03c0fe9 as Root, $d7bdfb9eb0fdf311$export$6d08773d2e66f8f2 as Item};
  241. //# sourceMappingURL=index.mjs.map