@ariakit/react-core
Version:
Ariakit React core
140 lines (137 loc) • 4.74 kB
JavaScript
"use client";
import {
flipItems
} from "./5VQZOHHZ.js";
import {
useCompositeContext
} from "./APTFW6PT.js";
import {
createElement,
createHook,
forwardRef
} from "./VOQWLFSQ.js";
import {
useEvent
} from "./5GGHRIN3.js";
import {
__objRest,
__spreadProps,
__spreadValues
} from "./3YLGPPWQ.js";
// src/composite/composite-typeahead.tsx
import {
getDocument,
isTextField,
sortBasedOnDOMPosition
} from "@ariakit/core/utils/dom";
import { isSelfTarget } from "@ariakit/core/utils/events";
import {
invariant,
normalizeString,
removeUndefinedValues
} from "@ariakit/core/utils/misc";
import { useRef } from "react";
var TagName = "div";
var chars = "";
function clearChars() {
chars = "";
}
function isValidTypeaheadEvent(event) {
const target = event.target;
if (target && isTextField(target)) return false;
if (event.key === " " && chars.length) return true;
return event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey && /^[\p{Letter}\p{Number}]$/u.test(event.key);
}
function isSelfTargetOrItem(event, items) {
if (isSelfTarget(event)) return true;
const target = event.target;
if (!target) return false;
const isItem = items.some((item) => item.element === target);
return isItem;
}
function getEnabledItems(items) {
return items.filter((item) => !item.disabled);
}
function itemTextStartsWith(item, text) {
var _a;
const itemText = ((_a = item.element) == null ? void 0 : _a.textContent) || item.children || // The composite item object itself doesn't include a value property, but
// other components like Select do. Since CompositeTypeahead is a generic
// component that can be used with those as well, we also consider the value
// property as a fallback for the typeahead text content.
"value" in item && item.value;
if (!itemText) return false;
return normalizeString(itemText).trim().toLowerCase().startsWith(text.toLowerCase());
}
function getSameInitialItems(items, char, activeId) {
if (!activeId) return items;
const activeItem = items.find((item) => item.id === activeId);
if (!activeItem) return items;
if (!itemTextStartsWith(activeItem, char)) return items;
if (chars !== char && itemTextStartsWith(activeItem, chars)) return items;
chars = char;
return flipItems(
items.filter((item) => itemTextStartsWith(item, chars)),
activeId
).filter((item) => item.id !== activeId);
}
var useCompositeTypeahead = createHook(function useCompositeTypeahead2(_a) {
var _b = _a, { store, typeahead = true } = _b, props = __objRest(_b, ["store", "typeahead"]);
const context = useCompositeContext();
store = store || context;
invariant(
store,
process.env.NODE_ENV !== "production" && "CompositeTypeahead must be a Composite component"
);
const onKeyDownCaptureProp = props.onKeyDownCapture;
const cleanupTimeoutRef = useRef(0);
const onKeyDownCapture = useEvent((event) => {
onKeyDownCaptureProp == null ? void 0 : onKeyDownCaptureProp(event);
if (event.defaultPrevented) return;
if (!typeahead) return;
if (!store) return;
if (!isValidTypeaheadEvent(event)) {
return clearChars();
}
const { renderedItems, items, activeId, id } = store.getState();
let enabledItems = getEnabledItems(
items.length > renderedItems.length ? items : renderedItems
);
const document = getDocument(event.currentTarget);
const selector = `[data-offscreen-id="${id}"]`;
const offscreenItems = document.querySelectorAll(selector);
for (const element of offscreenItems) {
const disabled = element.ariaDisabled === "true" || "disabled" in element && !!element.disabled;
enabledItems.push({ id: element.id, element, disabled });
}
if (offscreenItems.length) {
enabledItems = sortBasedOnDOMPosition(enabledItems, (i) => i.element);
}
if (!isSelfTargetOrItem(event, enabledItems)) return clearChars();
event.preventDefault();
window.clearTimeout(cleanupTimeoutRef.current);
cleanupTimeoutRef.current = window.setTimeout(() => {
chars = "";
}, 500);
const char = event.key.toLowerCase();
chars += char;
enabledItems = getSameInitialItems(enabledItems, char, activeId);
const item = enabledItems.find((item2) => itemTextStartsWith(item2, chars));
if (item) {
store.move(item.id);
} else {
clearChars();
}
});
props = __spreadProps(__spreadValues({}, props), {
onKeyDownCapture
});
return removeUndefinedValues(props);
});
var CompositeTypeahead = forwardRef(function CompositeTypeahead2(props) {
const htmlProps = useCompositeTypeahead(props);
return createElement(TagName, htmlProps);
});
export {
useCompositeTypeahead,
CompositeTypeahead
};