@ariakit/react-core
Version:
Ariakit React core
361 lines (358 loc) • 12 kB
JavaScript
"use client";
import {
focusSilently,
getEnabledItem,
isItem,
selectTextField
} from "./5VQZOHHZ.js";
import {
useCollectionItem
} from "./Y62RTBST.js";
import {
CompositeItemContext,
CompositeRowContext,
useCompositeContext
} from "./APTFW6PT.js";
import {
useCommand
} from "./2W3RN7C5.js";
import {
useStoreStateObject
} from "./RTNCFSKZ.js";
import {
createElement,
createHook,
forwardRef,
memo
} from "./VOQWLFSQ.js";
import {
useBooleanEvent,
useEvent,
useId,
useMergeRefs,
useWrapElement
} from "./5GGHRIN3.js";
import {
__objRest,
__spreadProps,
__spreadValues
} from "./3YLGPPWQ.js";
// src/composite/composite-item.tsx
import {
getScrollingElement,
getTextboxSelection,
getTextboxValue,
isButton,
isTextbox,
isTextField
} from "@ariakit/core/utils/dom";
import { isPortalEvent, isSelfTarget } from "@ariakit/core/utils/events";
import {
disabledFromProps,
removeUndefinedValues
} from "@ariakit/core/utils/misc";
import { isSafari } from "@ariakit/core/utils/platform";
import { useCallback, useContext, useMemo, useRef } from "react";
import { jsx } from "react/jsx-runtime";
var TagName = "button";
function isEditableElement(element) {
if (isTextbox(element)) return true;
return element.tagName === "INPUT" && !isButton(element);
}
function getNextPageOffset(scrollingElement, pageUp = false) {
const height = scrollingElement.clientHeight;
const { top } = scrollingElement.getBoundingClientRect();
const pageSize = Math.max(height * 0.875, height - 40) * 1.5;
const pageOffset = pageUp ? height - pageSize + top : pageSize + top;
if (scrollingElement.tagName === "HTML") {
return pageOffset + scrollingElement.scrollTop;
}
return pageOffset;
}
function getItemOffset(itemElement, pageUp = false) {
const { top } = itemElement.getBoundingClientRect();
if (pageUp) {
return top + itemElement.clientHeight;
}
return top;
}
function findNextPageItemId(element, store, next, pageUp = false) {
var _a;
if (!store) return;
if (!next) return;
const { renderedItems } = store.getState();
const scrollingElement = getScrollingElement(element);
if (!scrollingElement) return;
const nextPageOffset = getNextPageOffset(scrollingElement, pageUp);
let id;
let prevDifference;
for (let i = 0; i < renderedItems.length; i += 1) {
const previousId = id;
id = next(i);
if (!id) break;
if (id === previousId) continue;
const itemElement = (_a = getEnabledItem(store, id)) == null ? void 0 : _a.element;
if (!itemElement) continue;
const itemOffset = getItemOffset(itemElement, pageUp);
const difference = itemOffset - nextPageOffset;
const absDifference = Math.abs(difference);
if (pageUp && difference <= 0 || !pageUp && difference >= 0) {
if (prevDifference !== void 0 && prevDifference < absDifference) {
id = previousId;
}
break;
}
prevDifference = absDifference;
}
return id;
}
function targetIsAnotherItem(event, store) {
if (isSelfTarget(event)) return false;
return isItem(store, event.target);
}
var useCompositeItem = createHook(
function useCompositeItem2(_a) {
var _b = _a, {
store,
rowId: rowIdProp,
preventScrollOnKeyDown = false,
moveOnKeyPress = true,
tabbable = false,
getItem: getItemProp,
"aria-setsize": ariaSetSizeProp,
"aria-posinset": ariaPosInSetProp
} = _b, props = __objRest(_b, [
"store",
"rowId",
"preventScrollOnKeyDown",
"moveOnKeyPress",
"tabbable",
"getItem",
"aria-setsize",
"aria-posinset"
]);
const context = useCompositeContext();
store = store || context;
const id = useId(props.id);
const ref = useRef(null);
const row = useContext(CompositeRowContext);
const disabled = disabledFromProps(props);
const trulyDisabled = disabled && !props.accessibleWhenDisabled;
const {
rowId,
baseElement,
isActiveItem,
ariaSetSize,
ariaPosInSet,
isTabbable
} = useStoreStateObject(store, {
rowId(state) {
if (rowIdProp) return rowIdProp;
if (!state) return;
if (!(row == null ? void 0 : row.baseElement)) return;
if (row.baseElement !== state.baseElement) return;
return row.id;
},
baseElement(state) {
return (state == null ? void 0 : state.baseElement) || void 0;
},
isActiveItem(state) {
return !!state && state.activeId === id;
},
ariaSetSize(state) {
if (ariaSetSizeProp != null) return ariaSetSizeProp;
if (!state) return;
if (!(row == null ? void 0 : row.ariaSetSize)) return;
if (row.baseElement !== state.baseElement) return;
return row.ariaSetSize;
},
ariaPosInSet(state) {
if (ariaPosInSetProp != null) return ariaPosInSetProp;
if (!state) return;
if (!(row == null ? void 0 : row.ariaPosInSet)) return;
if (row.baseElement !== state.baseElement) return;
const itemsInRow = state.renderedItems.filter(
(item) => item.rowId === rowId
);
return row.ariaPosInSet + itemsInRow.findIndex((item) => item.id === id);
},
isTabbable(state) {
if (!(state == null ? void 0 : state.renderedItems.length)) return true;
if (state.virtualFocus) return false;
if (tabbable) return true;
if (state.activeId === null) return false;
const item = store == null ? void 0 : store.item(state.activeId);
if (item == null ? void 0 : item.disabled) return true;
if (!(item == null ? void 0 : item.element)) return true;
return state.activeId === id;
}
});
const getItem = useCallback(
(item) => {
var _a2;
const nextItem = __spreadProps(__spreadValues({}, item), {
id: id || item.id,
rowId,
disabled: !!trulyDisabled,
children: (_a2 = item.element) == null ? void 0 : _a2.textContent
});
if (getItemProp) {
return getItemProp(nextItem);
}
return nextItem;
},
[id, rowId, trulyDisabled, getItemProp]
);
const onFocusProp = props.onFocus;
const hasFocusedComposite = useRef(false);
const onFocus = useEvent((event) => {
onFocusProp == null ? void 0 : onFocusProp(event);
if (event.defaultPrevented) return;
if (isPortalEvent(event)) return;
if (!id) return;
if (!store) return;
if (targetIsAnotherItem(event, store)) return;
const { virtualFocus, baseElement: baseElement2 } = store.getState();
store.setActiveId(id);
if (isTextbox(event.currentTarget)) {
selectTextField(event.currentTarget);
}
if (!virtualFocus) return;
if (!isSelfTarget(event)) return;
if (isEditableElement(event.currentTarget)) return;
if (!(baseElement2 == null ? void 0 : baseElement2.isConnected)) return;
if (isSafari() && event.currentTarget.hasAttribute("data-autofocus")) {
event.currentTarget.scrollIntoView({
block: "nearest",
inline: "nearest"
});
}
hasFocusedComposite.current = true;
const fromComposite = event.relatedTarget === baseElement2 || isItem(store, event.relatedTarget);
if (fromComposite) {
focusSilently(baseElement2);
} else {
baseElement2.focus();
}
});
const onBlurCaptureProp = props.onBlurCapture;
const onBlurCapture = useEvent((event) => {
onBlurCaptureProp == null ? void 0 : onBlurCaptureProp(event);
if (event.defaultPrevented) return;
const state = store == null ? void 0 : store.getState();
if ((state == null ? void 0 : state.virtualFocus) && hasFocusedComposite.current) {
hasFocusedComposite.current = false;
event.preventDefault();
event.stopPropagation();
}
});
const onKeyDownProp = props.onKeyDown;
const preventScrollOnKeyDownProp = useBooleanEvent(preventScrollOnKeyDown);
const moveOnKeyPressProp = useBooleanEvent(moveOnKeyPress);
const onKeyDown = useEvent((event) => {
onKeyDownProp == null ? void 0 : onKeyDownProp(event);
if (event.defaultPrevented) return;
if (!isSelfTarget(event)) return;
if (!store) return;
const { currentTarget } = event;
const state = store.getState();
const item = store.item(id);
const isGrid = !!(item == null ? void 0 : item.rowId);
const isVertical = state.orientation !== "horizontal";
const isHorizontal = state.orientation !== "vertical";
const canHomeEnd = () => {
if (isGrid) return true;
if (isHorizontal) return true;
if (!state.baseElement) return true;
if (!isTextField(state.baseElement)) return true;
return false;
};
const keyMap = {
ArrowUp: (isGrid || isVertical) && store.up,
ArrowRight: (isGrid || isHorizontal) && store.next,
ArrowDown: (isGrid || isVertical) && store.down,
ArrowLeft: (isGrid || isHorizontal) && store.previous,
Home: () => {
if (!canHomeEnd()) return;
if (!isGrid || event.ctrlKey) {
return store == null ? void 0 : store.first();
}
return store == null ? void 0 : store.previous(-1);
},
End: () => {
if (!canHomeEnd()) return;
if (!isGrid || event.ctrlKey) {
return store == null ? void 0 : store.last();
}
return store == null ? void 0 : store.next(-1);
},
PageUp: () => {
return findNextPageItemId(currentTarget, store, store == null ? void 0 : store.up, true);
},
PageDown: () => {
return findNextPageItemId(currentTarget, store, store == null ? void 0 : store.down);
}
};
const action = keyMap[event.key];
if (action) {
if (isTextbox(currentTarget)) {
const selection = getTextboxSelection(currentTarget);
const isLeft = isHorizontal && event.key === "ArrowLeft";
const isRight = isHorizontal && event.key === "ArrowRight";
const isUp = isVertical && event.key === "ArrowUp";
const isDown = isVertical && event.key === "ArrowDown";
if (isRight || isDown) {
const { length: valueLength } = getTextboxValue(currentTarget);
if (selection.end !== valueLength) return;
} else if ((isLeft || isUp) && selection.start !== 0) return;
}
const nextId = action();
if (preventScrollOnKeyDownProp(event) || nextId !== void 0) {
if (!moveOnKeyPressProp(event)) return;
event.preventDefault();
store.move(nextId);
}
}
});
const providerValue = useMemo(
() => ({ id, baseElement }),
[id, baseElement]
);
props = useWrapElement(
props,
(element) => /* @__PURE__ */ jsx(CompositeItemContext.Provider, { value: providerValue, children: element }),
[providerValue]
);
props = __spreadProps(__spreadValues({
id,
"data-active-item": isActiveItem || void 0
}, props), {
ref: useMergeRefs(ref, props.ref),
tabIndex: isTabbable ? props.tabIndex : -1,
onFocus,
onBlurCapture,
onKeyDown
});
props = useCommand(props);
props = useCollectionItem(__spreadProps(__spreadValues({
store
}, props), {
getItem,
shouldRegisterItem: id ? props.shouldRegisterItem : false
}));
return removeUndefinedValues(__spreadProps(__spreadValues({}, props), {
"aria-setsize": ariaSetSize,
"aria-posinset": ariaPosInSet
}));
}
);
var CompositeItem = memo(
forwardRef(function CompositeItem2(props) {
const htmlProps = useCompositeItem(props);
return createElement(TagName, htmlProps);
})
);
export {
useCompositeItem,
CompositeItem
};