UNPKG

react-select-async-paginate

Version:
542 lines (524 loc) 17.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { AsyncPaginate: () => AsyncPaginate, checkIsResponse: () => checkIsResponse, reduceGroupedOptions: () => reduceGroupedOptions, useAsyncPaginate: () => useAsyncPaginate, useAsyncPaginateBase: () => useAsyncPaginateBase, useComponents: () => useComponents, validateResponse: () => validateResponse, withAsyncPaginate: () => withAsyncPaginate, wrapMenuList: () => wrapMenuList }); module.exports = __toCommonJS(index_exports); var import_react_select2 = __toESM(require("react-select")); // src/components/useComponents.ts var import_react2 = require("react"); var import_react_select = require("react-select"); // src/components/wrapMenuList.tsx var import_compose_react_refs = __toESM(require("@seznam/compose-react-refs")); var import_react = require("react"); var import_jsx_runtime = require("react/jsx-runtime"); var CHECK_TIMEOUT = 300; function wrapMenuList(MenuList2) { function WrappedMenuList(props) { const { selectProps, innerRef } = props; const { handleScrolledToBottom, shouldLoadMore } = selectProps; const checkTimeoutRef = (0, import_react.useRef)(null); const menuListRef = (0, import_react.useRef)(null); const shouldHandle = (0, import_react.useCallback)(() => { const el = menuListRef.current; if (!el) { return false; } const { scrollTop, scrollHeight, clientHeight } = el; return shouldLoadMore(scrollHeight, clientHeight, scrollTop); }, [shouldLoadMore]); const checkAndHandle = (0, import_react.useCallback)(() => { if (shouldHandle()) { if (handleScrolledToBottom) { handleScrolledToBottom(); } } }, [shouldHandle, handleScrolledToBottom]); const setCheckAndHandleTimeout = (0, import_react.useMemo)(() => { const res = () => { checkAndHandle(); checkTimeoutRef.current = setTimeout( res, CHECK_TIMEOUT ); }; return res; }, [checkAndHandle]); (0, import_react.useEffect)(() => { setCheckAndHandleTimeout(); return () => { if (checkTimeoutRef.current) { clearTimeout(checkTimeoutRef.current); } }; }, []); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( MenuList2, { ...props, innerRef: (0, import_compose_react_refs.default)(innerRef, menuListRef) } ); } return WrappedMenuList; } // src/components/useComponents.ts var MenuList = wrapMenuList( // biome-ignore lint/suspicious/noExplicitAny: fix types import_react_select.components.MenuList ); var useComponents = (components) => (0, import_react2.useMemo)( () => ({ MenuList, ...components }), [components] ); // src/useAsyncPaginate.ts var import_react4 = require("react"); // src/useAsyncPaginateBase.ts var import_use_lazy_ref = require("@vtaits/use-lazy-ref"); var import_react3 = require("react"); var import_use_is_mounted_ref = __toESM(require("use-is-mounted-ref")); var import_use_latest = __toESM(require("use-latest")); // src/defaultReduceOptions.ts var defaultReduceOptions = (prevOptions, loadedOptions) => [...prevOptions, ...loadedOptions]; // src/defaultShouldLoadMore.ts var AVAILABLE_DELTA = 10; var defaultShouldLoadMore = (scrollHeight, clientHeight, scrollTop) => { const bottomBorder = scrollHeight - clientHeight - AVAILABLE_DELTA; return bottomBorder < scrollTop; }; // src/getInitialCache.ts var getInitialCache = (params) => ({ isFirstLoad: true, options: [], hasMore: true, isLoading: false, lockedUntil: 0, additional: params.additional }); // src/getInitialOptionsCache.ts var getInitialOptionsCache = ({ options, defaultOptions, additional, defaultAdditional }) => { const initialOptions = defaultOptions === true ? null : Array.isArray(defaultOptions) ? defaultOptions : options; if (initialOptions) { return { "": { isFirstLoad: false, isLoading: false, options: initialOptions, hasMore: true, lockedUntil: 0, additional: defaultAdditional || additional } }; } return {}; }; // src/requestOptions.ts var import_krustykrab = require("krustykrab"); var import_sleep_promise = __toESM(require("sleep-promise")); // src/validateResponse.ts var errorText = '[react-select-async-paginate] response of "loadOptions" should be an object with "options" prop, which contains array of options.'; var checkIsResponse = (response) => { if (!response) { return false; } const { options, hasMore } = response; if (!Array.isArray(options)) { return false; } if (typeof hasMore !== "boolean" && typeof hasMore !== "undefined") { return false; } return true; }; var validateResponse = (response) => { if (!checkIsResponse(response)) { console.error(errorText, "Received:", response); throw new Error(errorText); } return true; }; // src/requestOptions.ts var requestOptions = async (caller, paramsRef, optionsCacheRef, debounceTimeout, setOptionsCache, reduceOptions, isMountedRef, clearCacheOnSearchChange) => { const currentInputValue = paramsRef.current.inputValue; const isCacheEmpty = !optionsCacheRef.current[currentInputValue]; const currentOptions = isCacheEmpty ? getInitialCache(paramsRef.current) : optionsCacheRef.current[currentInputValue]; if (currentOptions.isLoading || !currentOptions.hasMore || currentOptions.lockedUntil > Date.now()) { return; } setOptionsCache( (prevOptionsCache) => { if (clearCacheOnSearchChange && caller === "input-change") { return { [currentInputValue]: { ...currentOptions, isLoading: true } }; } return { ...prevOptionsCache, [currentInputValue]: { ...currentOptions, isLoading: true } }; } ); if (debounceTimeout > 0 && caller === "input-change") { await (0, import_sleep_promise.default)(debounceTimeout); const newInputValue = paramsRef.current.inputValue; if (currentInputValue !== newInputValue) { setOptionsCache((prevOptionsCache) => { if (isCacheEmpty) { const { [currentInputValue]: _itemForDelete, ...restCache } = prevOptionsCache; return restCache; } return { ...prevOptionsCache, [currentInputValue]: { ...currentOptions, isLoading: false } }; }); return; } } const { loadOptions, reloadOnErrorTimeout = 0 } = paramsRef.current; const result = await (0, import_krustykrab.getResult)( Promise.resolve().then( () => loadOptions( currentInputValue, currentOptions.options, currentOptions.additional ) ) ); if (!isMountedRef.current) { return; } if (result.isErr()) { setOptionsCache((prevOptionsCache) => ({ ...prevOptionsCache, [currentInputValue]: { ...currentOptions, isLoading: false, lockedUntil: Date.now() + reloadOnErrorTimeout } })); return; } const response = result.unwrap(); if (validateResponse(response)) { const { options, hasMore } = response; const newAdditional = Object.hasOwn(response, "additional") ? response.additional : currentOptions.additional; setOptionsCache((prevOptionsCache) => ({ ...prevOptionsCache, [currentInputValue]: { ...currentOptions, options: reduceOptions(currentOptions.options, options, newAdditional), hasMore: !!hasMore, isLoading: false, isFirstLoad: false, additional: newAdditional } })); } }; // src/useAsyncPaginateBase.ts var increaseStateId = (prevStateId) => prevStateId + 1; var useAsyncPaginateBase = (params, deps = []) => { const { clearCacheOnSearchChange = false, clearCacheOnMenuClose = false, defaultOptions, loadOptionsOnMenuOpen = true, debounceTimeout = 0, inputValue, menuIsOpen, filterOption = null, reduceOptions = defaultReduceOptions, shouldLoadMore = defaultShouldLoadMore, mapOptionsForMenu = void 0 } = params; const menuIsOpenRef = (0, import_use_latest.default)(menuIsOpen); const isMountedRef = (0, import_use_is_mounted_ref.default)(); const reduceOptionsRef = (0, import_use_latest.default)(reduceOptions); const loadOptionsOnMenuOpenRef = (0, import_use_latest.default)(loadOptionsOnMenuOpen); const isInitRef = (0, import_react3.useRef)(true); const paramsRef = (0, import_react3.useRef)(params); paramsRef.current = params; const [_stateId, setStateId] = (0, import_react3.useState)(0); const optionsCacheRef = (0, import_use_lazy_ref.useLazyRef)(() => getInitialOptionsCache(params)); const callRequestOptionsRef = (0, import_use_latest.default)( (caller) => { requestOptions( caller, paramsRef, optionsCacheRef, debounceTimeout, (reduceState) => { optionsCacheRef.current = reduceState(optionsCacheRef.current); if (isMountedRef.current) { setStateId(increaseStateId); } }, reduceOptionsRef.current, isMountedRef, clearCacheOnSearchChange ); } ); const handleScrolledToBottom = (0, import_react3.useCallback)(() => { const currentInputValue = paramsRef.current.inputValue; const currentOptions2 = optionsCacheRef.current[currentInputValue]; if (currentOptions2) { callRequestOptionsRef.current("menu-scroll"); } }, [callRequestOptionsRef, optionsCacheRef]); (0, import_react3.useEffect)(() => { if (isInitRef.current) { isInitRef.current = false; } else { optionsCacheRef.current = {}; setStateId(increaseStateId); } if (defaultOptions === true) { callRequestOptionsRef.current("autoload"); } }, deps); (0, import_react3.useEffect)(() => { if (menuIsOpenRef.current && !optionsCacheRef.current[inputValue]) { callRequestOptionsRef.current("input-change"); } }, [callRequestOptionsRef, inputValue, menuIsOpenRef, optionsCacheRef]); (0, import_react3.useEffect)(() => { if (menuIsOpen) { if (!optionsCacheRef.current[""] && loadOptionsOnMenuOpenRef.current) { callRequestOptionsRef.current("menu-toggle"); return; } return; } if (clearCacheOnMenuClose) { optionsCacheRef.current = {}; setStateId(increaseStateId); } }, [ callRequestOptionsRef, loadOptionsOnMenuOpenRef, menuIsOpen, optionsCacheRef, clearCacheOnMenuClose ]); const currentOptions = optionsCacheRef.current[inputValue] || getInitialCache(params); const options = (0, import_react3.useMemo)(() => { if (!mapOptionsForMenu) { return currentOptions.options; } return mapOptionsForMenu(currentOptions.options); }, [currentOptions.options, mapOptionsForMenu]); return { handleScrolledToBottom, shouldLoadMore, filterOption, isLoading: currentOptions.isLoading || currentOptions.lockedUntil > Date.now(), isFirstLoad: currentOptions.isFirstLoad, options }; }; // src/useAsyncPaginate.ts var useAsyncPaginate = (params, deps = []) => { const { inputValue: inputValueParam, menuIsOpen: menuIsOpenParam, defaultInputValue: defaultInputValueParam, defaultMenuIsOpen: defaultMenuIsOpenParam, onInputChange: onInputChangeParam, onMenuClose: onMenuCloseParam, onMenuOpen: onMenuOpenParam } = params; const [inputValueState, setInputValue] = (0, import_react4.useState)( defaultInputValueParam || "" ); const [menuIsOpenState, setMenuIsOpen] = (0, import_react4.useState)(!!defaultMenuIsOpenParam); const inputValue = typeof inputValueParam === "string" ? inputValueParam : inputValueState; const menuIsOpen = typeof menuIsOpenParam === "boolean" ? menuIsOpenParam : menuIsOpenState; const onInputChange = (0, import_react4.useCallback)( (nextInputValue, actionMeta) => { if (onInputChangeParam) { onInputChangeParam(nextInputValue, actionMeta); } setInputValue(nextInputValue); }, [onInputChangeParam] ); const onMenuClose = (0, import_react4.useCallback)(() => { if (onMenuCloseParam) { onMenuCloseParam(); } setMenuIsOpen(false); }, [onMenuCloseParam]); const onMenuOpen = (0, import_react4.useCallback)(() => { if (onMenuOpenParam) { onMenuOpenParam(); } setMenuIsOpen(true); }, [onMenuOpenParam]); const baseResult = useAsyncPaginateBase( { ...params, inputValue, menuIsOpen }, deps ); return { ...baseResult, inputValue, menuIsOpen, onInputChange, onMenuClose, onMenuOpen }; }; // src/withAsyncPaginate.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); var defaultCacheUniqs = []; var defaultComponents2 = {}; function withAsyncPaginate(SelectComponent) { function WithAsyncPaginate(props) { const { components = defaultComponents2, selectRef = void 0, isLoading: isLoadingProp, cacheUniqs = defaultCacheUniqs, menuPlacement, menuShouldScrollIntoView, ...rest } = props; const asyncPaginateProps = useAsyncPaginate(rest, cacheUniqs); const processedComponents = useComponents( components ); const isLoading = typeof isLoadingProp === "boolean" ? isLoadingProp : asyncPaginateProps.isLoading; return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( SelectComponent, { ...props, ...asyncPaginateProps, menuPlacement, menuShouldScrollIntoView: menuPlacement === "auto" ? isLoading ? false : menuShouldScrollIntoView : menuShouldScrollIntoView, isLoading, components: processedComponents, ref: selectRef } ); } return WithAsyncPaginate; } // src/reduceGroupedOptions.ts var checkGroup = (group) => { if (!group) { return false; } const { label, options } = group; if (typeof label !== "string" && typeof label !== "undefined") { return false; } if (!Array.isArray(options)) { return false; } return true; }; var reduceGroupedOptions = (prevOptions, loadedOptions) => { const res = prevOptions.slice(); const mapLabelToIndex = {}; let prevOptionsIndex = 0; const prevOptionsLength = prevOptions.length; for (const optionOrGroup of loadedOptions) { const group = checkGroup(optionOrGroup) ? optionOrGroup : { options: [optionOrGroup] }; const { label = "" } = group; let groupIndex = mapLabelToIndex[label]; if (typeof groupIndex !== "number") { for (; prevOptionsIndex < prevOptionsLength && typeof mapLabelToIndex[label] !== "number"; ++prevOptionsIndex) { const prevGroup = prevOptions[prevOptionsIndex]; if (checkGroup(prevGroup)) { mapLabelToIndex[prevGroup.label || ""] = prevOptionsIndex; } } groupIndex = mapLabelToIndex[label]; } if (typeof groupIndex !== "number") { mapLabelToIndex[label] = res.length; res.push(group); } else { res[groupIndex] = { ...res[groupIndex], options: [...res[groupIndex].options, ...group.options] }; } } return res; }; // src/index.ts var AsyncPaginate = withAsyncPaginate(import_react_select2.default); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AsyncPaginate, checkIsResponse, reduceGroupedOptions, useAsyncPaginate, useAsyncPaginateBase, useComponents, validateResponse, withAsyncPaginate, wrapMenuList }); //# sourceMappingURL=index.js.map