ivt
Version:
Ivt Components Library
215 lines (212 loc) • 9.58 kB
JavaScript
import * as React from 'react';
import { P as Primitive, c as composeEventHandlers } from './index-Dsw02Eup.mjs';
import { createCollection } from '@radix-ui/react-collection';
import { c as createContextScope, u as useComposedRefs } from './index-D4jPaBZ2.mjs';
import { u as useId } from './index-gX0V59dZ.mjs';
import { u as useCallbackRef } from './index-DC-gAYjS.mjs';
import { u as useControllableState } from './index-1toTCGyr.mjs';
import { u as useDirection } from './index-UxWlwQlk.mjs';
import { jsx } from 'react/jsx-runtime';
var ENTRY_FOCUS = "rovingFocusGroup.onEntryFocus";
var EVENT_OPTIONS = {
bubbles: false,
cancelable: true
};
var GROUP_NAME = "RovingFocusGroup";
var [Collection, useCollection, createCollectionScope] = createCollection(GROUP_NAME);
var [createRovingFocusGroupContext, createRovingFocusGroupScope] = createContextScope(GROUP_NAME, [
createCollectionScope
]);
var [RovingFocusProvider, useRovingFocusContext] = createRovingFocusGroupContext(GROUP_NAME);
var RovingFocusGroup = React.forwardRef((props, forwardedRef)=>{
return /* @__PURE__ */ jsx(Collection.Provider, {
scope: props.__scopeRovingFocusGroup,
children: /* @__PURE__ */ jsx(Collection.Slot, {
scope: props.__scopeRovingFocusGroup,
children: /* @__PURE__ */ jsx(RovingFocusGroupImpl, {
...props,
ref: forwardedRef
})
})
});
});
RovingFocusGroup.displayName = GROUP_NAME;
var RovingFocusGroupImpl = React.forwardRef((props, forwardedRef)=>{
const { __scopeRovingFocusGroup, orientation, loop = false, dir, currentTabStopId: currentTabStopIdProp, defaultCurrentTabStopId, onCurrentTabStopIdChange, onEntryFocus, preventScrollOnEntryFocus = false, ...groupProps } = props;
const ref = React.useRef(null);
const composedRefs = useComposedRefs(forwardedRef, ref);
const direction = useDirection(dir);
const [currentTabStopId, setCurrentTabStopId] = useControllableState({
prop: currentTabStopIdProp,
defaultProp: defaultCurrentTabStopId ?? null,
onChange: onCurrentTabStopIdChange,
caller: GROUP_NAME
});
const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);
const handleEntryFocus = useCallbackRef(onEntryFocus);
const getItems = useCollection(__scopeRovingFocusGroup);
const isClickFocusRef = React.useRef(false);
const [focusableItemsCount, setFocusableItemsCount] = React.useState(0);
React.useEffect(()=>{
const node = ref.current;
if (node) {
node.addEventListener(ENTRY_FOCUS, handleEntryFocus);
return ()=>node.removeEventListener(ENTRY_FOCUS, handleEntryFocus);
}
}, [
handleEntryFocus
]);
return /* @__PURE__ */ jsx(RovingFocusProvider, {
scope: __scopeRovingFocusGroup,
orientation,
dir: direction,
loop,
currentTabStopId,
onItemFocus: React.useCallback((tabStopId)=>setCurrentTabStopId(tabStopId), [
setCurrentTabStopId
]),
onItemShiftTab: React.useCallback(()=>setIsTabbingBackOut(true), []),
onFocusableItemAdd: React.useCallback(()=>setFocusableItemsCount((prevCount)=>prevCount + 1), []),
onFocusableItemRemove: React.useCallback(()=>setFocusableItemsCount((prevCount)=>prevCount - 1), []),
children: /* @__PURE__ */ jsx(Primitive.div, {
tabIndex: isTabbingBackOut || focusableItemsCount === 0 ? -1 : 0,
"data-orientation": orientation,
...groupProps,
ref: composedRefs,
style: {
outline: "none",
...props.style
},
onMouseDown: composeEventHandlers(props.onMouseDown, ()=>{
isClickFocusRef.current = true;
}),
onFocus: composeEventHandlers(props.onFocus, (event)=>{
const isKeyboardFocus = !isClickFocusRef.current;
if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {
const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);
event.currentTarget.dispatchEvent(entryFocusEvent);
if (!entryFocusEvent.defaultPrevented) {
const items = getItems().filter((item)=>item.focusable);
const activeItem = items.find((item)=>item.active);
const currentItem = items.find((item)=>item.id === currentTabStopId);
const candidateItems = [
activeItem,
currentItem,
...items
].filter(Boolean);
const candidateNodes = candidateItems.map((item)=>item.ref.current);
focusFirst(candidateNodes, preventScrollOnEntryFocus);
}
}
isClickFocusRef.current = false;
}),
onBlur: composeEventHandlers(props.onBlur, ()=>setIsTabbingBackOut(false))
})
});
});
var ITEM_NAME = "RovingFocusGroupItem";
var RovingFocusGroupItem = React.forwardRef((props, forwardedRef)=>{
const { __scopeRovingFocusGroup, focusable = true, active = false, tabStopId, children, ...itemProps } = props;
const autoId = useId();
const id = tabStopId || autoId;
const context = useRovingFocusContext(ITEM_NAME, __scopeRovingFocusGroup);
const isCurrentTabStop = context.currentTabStopId === id;
const getItems = useCollection(__scopeRovingFocusGroup);
const { onFocusableItemAdd, onFocusableItemRemove, currentTabStopId } = context;
React.useEffect(()=>{
if (focusable) {
onFocusableItemAdd();
return ()=>onFocusableItemRemove();
}
}, [
focusable,
onFocusableItemAdd,
onFocusableItemRemove
]);
return /* @__PURE__ */ jsx(Collection.ItemSlot, {
scope: __scopeRovingFocusGroup,
id,
focusable,
active,
children: /* @__PURE__ */ jsx(Primitive.span, {
tabIndex: isCurrentTabStop ? 0 : -1,
"data-orientation": context.orientation,
...itemProps,
ref: forwardedRef,
onMouseDown: composeEventHandlers(props.onMouseDown, (event)=>{
if (!focusable) event.preventDefault();
else context.onItemFocus(id);
}),
onFocus: composeEventHandlers(props.onFocus, ()=>context.onItemFocus(id)),
onKeyDown: composeEventHandlers(props.onKeyDown, (event)=>{
if (event.key === "Tab" && event.shiftKey) {
context.onItemShiftTab();
return;
}
if (event.target !== event.currentTarget) return;
const focusIntent = getFocusIntent(event, context.orientation, context.dir);
if (focusIntent !== void 0) {
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;
event.preventDefault();
const items = getItems().filter((item)=>item.focusable);
let candidateNodes = items.map((item)=>item.ref.current);
if (focusIntent === "last") candidateNodes.reverse();
else if (focusIntent === "prev" || focusIntent === "next") {
if (focusIntent === "prev") candidateNodes.reverse();
const currentIndex = candidateNodes.indexOf(event.currentTarget);
candidateNodes = context.loop ? wrapArray(candidateNodes, currentIndex + 1) : candidateNodes.slice(currentIndex + 1);
}
setTimeout(()=>focusFirst(candidateNodes));
}
}),
children: typeof children === "function" ? children({
isCurrentTabStop,
hasTabStop: currentTabStopId != null
}) : children
})
});
});
RovingFocusGroupItem.displayName = ITEM_NAME;
var MAP_KEY_TO_FOCUS_INTENT = {
ArrowLeft: "prev",
ArrowUp: "prev",
ArrowRight: "next",
ArrowDown: "next",
PageUp: "first",
Home: "first",
PageDown: "last",
End: "last"
};
function getDirectionAwareKey(key, dir) {
if (dir !== "rtl") return key;
return key === "ArrowLeft" ? "ArrowRight" : key === "ArrowRight" ? "ArrowLeft" : key;
}
function getFocusIntent(event, orientation, dir) {
const key = getDirectionAwareKey(event.key, dir);
if (orientation === "vertical" && [
"ArrowLeft",
"ArrowRight"
].includes(key)) return void 0;
if (orientation === "horizontal" && [
"ArrowUp",
"ArrowDown"
].includes(key)) return void 0;
return MAP_KEY_TO_FOCUS_INTENT[key];
}
function focusFirst(candidates, preventScroll = false) {
const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
for (const candidate of candidates){
if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
candidate.focus({
preventScroll
});
if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
}
}
function wrapArray(array, startIndex) {
return array.map((_, index)=>array[(startIndex + index) % array.length]);
}
var Root = RovingFocusGroup;
var Item = RovingFocusGroupItem;
export { Item as I, Root as R, createRovingFocusGroupScope as c };
//# sourceMappingURL=index-DIHpzffL.mjs.map