Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@n3/react-autocomplete

Package Overview
Dependencies
Maintainers
5
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@n3/react-autocomplete - npm Package Compare versions

Comparing version
0.2.0
to
1.0.0
+319
dist/esm/index.js
// src/Autocomplete.tsx
import {
useCallback as useCallback2,
useState as useState2,
useMemo,
useRef as useRef2,
useEffect as useEffect2
} from "react";
// src/components/Wrapper.tsx
import styled from "styled-components";
var Wrapper = styled.div({
position: "relative"
});
// src/components/Input.tsx
import styled2 from "styled-components";
var Input = styled2.input(({
$hasError,
$hasWarning
}) => {
const res = {
width: "100%",
boxSizing: "border-box"
};
if ($hasError) {
res.borderColor = "#d64c4c";
res.backgroundColor = "#fdf6f6";
} else if ($hasWarning) {
res.borderColor = "#eea505";
res.backgroundColor = "#fdf6e6";
}
return res;
});
// src/components/Menu.tsx
import styled3 from "styled-components";
var Menu = styled3.div({
boxSizing: "border-box",
position: "absolute",
top: "100%",
width: "100%",
zIndex: 1,
marginTop: 4,
maxHeight: 200,
overflowY: "auto",
borderRadius: 4,
backgroundColor: "#fff",
border: "1px solid #d9e1e8",
boxShadow: "0 1px 0 rgba(0, 0, 0, 0.06)"
});
// src/components/MenuItem.tsx
import styled4 from "styled-components";
var MenuItem = styled4.div(({
$isHighlighted
}) => ({
display: "block",
boxSizing: "border-box",
padding: 10,
color: "#666",
backgroundColor: $isHighlighted ? "#f9f9f9" : "#fff",
cursor: "pointer"
}));
// src/components/index.ts
var components = {
Wrapper,
Input,
Menu,
MenuItem
};
// src/Dropdown.tsx
import {
useRef,
useState,
useEffect
} from "react";
import useOnClickOutside from "use-onclickoutside";
// src/DropdownItem.tsx
import {
useCallback
} from "react";
import { jsx } from "react/jsx-runtime";
function DropdownItem({
components: components2,
getOptionLabel,
formatOptionLabel,
isHighlighted,
option,
onSelect,
setHighlightedIndex,
index
}) {
const onClick = useCallback(() => {
onSelect(getOptionLabel(option), option);
}, [onSelect, option, getOptionLabel]);
const onMouseEnter = useCallback(() => {
setHighlightedIndex(index);
}, [setHighlightedIndex, index]);
const {
MenuItem: MenuItem2
} = components2;
return /* @__PURE__ */ jsx(
MenuItem2,
{
$isHighlighted: isHighlighted,
onMouseEnter,
onClick,
children: formatOptionLabel(option)
}
);
}
// src/Dropdown.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
function Dropdown({
components: components2,
getOptionLabel,
formatOptionLabel,
options,
onSelect,
closeMenu
}) {
const rootRef = useRef(null);
useOnClickOutside(rootRef, closeMenu);
const isInitRef = useRef(true);
const [highlightedIndex, setHighlightedIndex] = useState(0);
const highlightedIndexRef = useRef(highlightedIndex);
highlightedIndexRef.current = highlightedIndex;
useEffect(() => {
if (isInitRef.current) {
isInitRef.current = false;
} else {
setHighlightedIndex(0);
}
const onKeyDown = (event) => {
switch (event.key) {
case "ArrowDown":
setHighlightedIndex((prevIndex) => {
if (prevIndex === options.length - 1) {
return 0;
}
return prevIndex + 1;
});
break;
case "ArrowUp":
setHighlightedIndex((prevIndex) => {
if (prevIndex === 0) {
return options.length - 1;
}
return prevIndex - 1;
});
break;
case "Enter": {
const option = options[highlightedIndexRef.current];
onSelect(getOptionLabel(option), option);
break;
}
default:
break;
}
};
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
};
}, [
options,
getOptionLabel,
onSelect
]);
const {
Menu: Menu2
} = components2;
return /* @__PURE__ */ jsx2(
Menu2,
{
ref: rootRef,
children: options.map((option, index) => /* @__PURE__ */ jsx2(
DropdownItem,
{
components: components2,
getOptionLabel,
formatOptionLabel,
option,
isHighlighted: highlightedIndex === index,
onSelect,
setHighlightedIndex,
index
},
index
))
}
);
}
// src/Autocomplete.tsx
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
var emptyObj = {};
function Autocomplete({
loadOptions,
value,
onChange: onChangeProp = void 0,
onSelect: onSelectProp = void 0,
disabled = false,
hasError = false,
hasWarning = false,
inputProps = emptyObj,
labelKey = "label",
getOptionLabel: getOptionLabelProp = void 0,
formatOptionLabel: formatOptionLabelProp = void 0,
components: componentsProp = emptyObj
}) {
const components2 = useMemo(() => ({
...components,
...componentsProp
}), [componentsProp]);
const valueRef = useRef2(value);
valueRef.current = value;
const [options, setOptions] = useState2([]);
const [isOpen, setIsOpen] = useState2(false);
const getOptionLabel = useCallback2((option) => {
if (getOptionLabelProp) {
return getOptionLabelProp(option);
}
if (typeof option === "string") {
return option;
}
if (typeof option === "number") {
return String(option);
}
if (!option || typeof option !== "object") {
return "";
}
const label = option[labelKey];
if (typeof label === "string") {
return label;
}
if (typeof label === "number") {
return String(label);
}
return "";
}, [getOptionLabelProp, labelKey]);
const formatOptionLabel = useCallback2((option) => {
if (formatOptionLabelProp) {
return formatOptionLabelProp(option);
}
return getOptionLabel(option);
}, [formatOptionLabelProp, getOptionLabel]);
const onInputFocus = useCallback2(() => {
setIsOpen(true);
}, []);
const closeMenu = useCallback2(() => {
setIsOpen(false);
}, []);
const onSelect = useCallback2((label, option) => {
setIsOpen(false);
if (onSelectProp) {
onSelectProp(label, option);
}
}, [onSelectProp]);
const onChange = useCallback2((event) => {
if (onChangeProp) {
onChangeProp(event);
}
if (!isOpen) {
setIsOpen(true);
}
}, [onChangeProp, isOpen]);
useEffect2(() => {
(async () => {
const response = await loadOptions(value);
if (valueRef.current === value) {
setOptions(response.options);
}
})().catch((e) => {
throw e;
});
}, [
loadOptions,
value
]);
const {
Wrapper: Wrapper2,
Input: Input2
} = components2;
return /* @__PURE__ */ jsxs(Wrapper2, { children: [
/* @__PURE__ */ jsx3(
Input2,
{
...inputProps,
disabled,
$hasError: hasError,
$hasWarning: hasWarning,
value,
onChange,
onFocus: onInputFocus
}
),
isOpen && options.length > 0 && /* @__PURE__ */ jsx3(
Dropdown,
{
components: components2,
getOptionLabel,
formatOptionLabel,
options,
onSelect,
closeMenu
}
)
] });
}
export {
Autocomplete
};
//# sourceMappingURL=index.js.map
{"version":3,"sources":["../../src/Autocomplete.tsx","../../src/components/Wrapper.tsx","../../src/components/Input.tsx","../../src/components/Menu.tsx","../../src/components/MenuItem.tsx","../../src/components/index.ts","../../src/Dropdown.tsx","../../src/DropdownItem.tsx"],"sourcesContent":["import {\n useCallback,\n useState,\n useMemo,\n useRef,\n useEffect,\n} from 'react';\nimport type {\n ChangeEventHandler,\n HTMLProps,\n ReactElement,\n} from 'react';\n\nimport { components as defaultComponents } from './components';\nimport { Dropdown } from './Dropdown';\n\nimport type {\n Components,\n FormatOptionLabel,\n GetOptionLabel,\n LoadOptions,\n OnSelect,\n} from './types';\n\nexport type AutocompleteProps<Option> = {\n /**\n * Функция загрузки опций\n * @param {String} search - текущее значение поля ввода\n * @returns {Object[]} response.options - опции\n */\n loadOptions: LoadOptions<Option>;\n /**\n * Значение элемента input\n */\n value: string;\n /**\n * Обработчик изменения значения поля при ручном вводе\n */\n onChange?: ChangeEventHandler<HTMLInputElement>;\n /**\n * Обработчик изменения значения поля при выборе из меню\n * @param {String} value - текст выбранной опции\n * @param {Object} option - выбранная опция\n */\n onSelect?: OnSelect<Option>;\n /**\n * Выключено ли поле\n */\n disabled?: boolean;\n /**\n * Есть ли у поля ошибка\n */\n hasError?: boolean;\n /**\n * Есть ли у поля предупреждение\n */\n hasWarning?: boolean;\n\n /**\n * Дополнительные props элемента input\n */\n inputProps?: Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange' | 'disabled'>;\n\n /**\n * Ключ, по которому хранится текст опции\n */\n labelKey?: string;\n /**\n * Функция получения текста опции, который будет подставлен при выборе\n */\n getOptionLabel?: GetOptionLabel<Option>;\n /**\n * Функция отображения опции\n */\n formatOptionLabel?: FormatOptionLabel<Option>;\n /**\n * Переиспользуемые компоненты\n */\n components?: Partial<Components>;\n};\n\nconst emptyObj = {};\n\nexport function Autocomplete<Option>({\n loadOptions,\n value,\n onChange: onChangeProp = undefined,\n onSelect: onSelectProp = undefined,\n disabled = false,\n hasError = false,\n hasWarning = false,\n inputProps = emptyObj,\n labelKey = 'label',\n getOptionLabel: getOptionLabelProp = undefined,\n formatOptionLabel: formatOptionLabelProp = undefined,\n components: componentsProp = emptyObj,\n}: AutocompleteProps<Option>): ReactElement {\n const components = useMemo<Components>(() => ({\n ...defaultComponents,\n ...componentsProp,\n }), [componentsProp]);\n\n const valueRef = useRef<string>(value);\n valueRef.current = value;\n\n const [options, setOptions] = useState<Option[]>([]);\n const [isOpen, setIsOpen] = useState<boolean>(false);\n\n const getOptionLabel = useCallback<GetOptionLabel<Option>>((option) => {\n if (getOptionLabelProp) {\n return getOptionLabelProp(option);\n }\n\n if (typeof option === 'string') {\n return option;\n }\n\n if (typeof option === 'number') {\n return String(option);\n }\n\n if (!option || typeof option !== 'object') {\n return '';\n }\n\n const label = (option as Record<string, unknown>)[labelKey];\n\n if (typeof label === 'string') {\n return label;\n }\n\n if (typeof label === 'number') {\n return String(label);\n }\n\n return '';\n }, [getOptionLabelProp, labelKey]);\n\n const formatOptionLabel = useCallback<FormatOptionLabel<Option>>((option) => {\n if (formatOptionLabelProp) {\n return formatOptionLabelProp(option);\n }\n\n return getOptionLabel(option);\n }, [formatOptionLabelProp, getOptionLabel]);\n\n const onInputFocus = useCallback(() => {\n setIsOpen(true);\n }, []);\n\n const closeMenu = useCallback(() => {\n setIsOpen(false);\n }, []);\n\n const onSelect = useCallback<OnSelect<Option>>((label, option) => {\n setIsOpen(false);\n\n if (onSelectProp) {\n onSelectProp(label, option);\n }\n }, [onSelectProp]);\n\n const onChange = useCallback<ChangeEventHandler<HTMLInputElement>>((event) => {\n if (onChangeProp) {\n onChangeProp(event);\n }\n\n if (!isOpen) {\n setIsOpen(true);\n }\n }, [onChangeProp, isOpen]);\n\n useEffect(() => {\n (async (): Promise<void> => {\n const response = await loadOptions(value);\n\n if (valueRef.current === value) {\n setOptions(response.options);\n }\n })().catch((e) => {\n throw e;\n });\n }, [\n loadOptions,\n value,\n ]);\n\n const {\n Wrapper,\n Input,\n } = components;\n\n return (\n <Wrapper>\n <Input\n {...inputProps}\n disabled={disabled}\n $hasError={hasError}\n $hasWarning={hasWarning}\n value={value}\n onChange={onChange}\n onFocus={onInputFocus}\n />\n\n {\n isOpen && options.length > 0 && (\n <Dropdown\n components={components}\n getOptionLabel={getOptionLabel}\n formatOptionLabel={formatOptionLabel}\n options={options}\n onSelect={onSelect}\n closeMenu={closeMenu}\n />\n )\n }\n </Wrapper>\n );\n}\n","import styled from 'styled-components';\n\nimport type {\n WrapperComponent,\n} from '../types';\n\nexport const Wrapper: WrapperComponent = styled.div({\n position: 'relative',\n});\n","import styled from 'styled-components';\nimport type {\n CSSObject,\n} from 'styled-components';\n\nimport type {\n InputComponentProps,\n InputComponent,\n} from '../types';\n\nexport const Input: InputComponent = styled.input<InputComponentProps>(({\n $hasError,\n $hasWarning,\n}) => {\n const res: CSSObject = {\n width: '100%',\n boxSizing: 'border-box',\n };\n\n if ($hasError) {\n res.borderColor = '#d64c4c';\n res.backgroundColor = '#fdf6f6';\n } else if ($hasWarning) {\n res.borderColor = '#eea505';\n res.backgroundColor = '#fdf6e6';\n }\n\n return res;\n});\n","import styled from 'styled-components';\n\nimport type {\n MenuComponent,\n} from '../types';\n\nexport const Menu: MenuComponent = styled.div({\n boxSizing: 'border-box',\n position: 'absolute',\n top: '100%',\n width: '100%',\n zIndex: 1,\n marginTop: 4,\n maxHeight: 200,\n overflowY: 'auto',\n borderRadius: 4,\n backgroundColor: '#fff',\n border: '1px solid #d9e1e8',\n boxShadow: '0 1px 0 rgba(0, 0, 0, 0.06)',\n});\n","import styled from 'styled-components';\n\nimport type {\n MenuItemProps,\n MenuItemComponent,\n} from '../types';\n\nexport const MenuItem: MenuItemComponent = styled.div<MenuItemProps>(({\n $isHighlighted,\n}) => ({\n display: 'block',\n boxSizing: 'border-box',\n padding: 10,\n color: '#666',\n backgroundColor: $isHighlighted ? '#f9f9f9' : '#fff',\n cursor: 'pointer',\n}));\n","import { Wrapper } from './Wrapper';\nimport { Input } from './Input';\nimport { Menu } from './Menu';\nimport { MenuItem } from './MenuItem';\n\nimport type {\n Components,\n} from '../types';\n\nexport const components: Components = {\n Wrapper,\n Input,\n Menu,\n MenuItem,\n};\n","import {\n useRef,\n useState,\n useEffect,\n} from 'react';\nimport type {\n ReactElement,\n} from 'react';\n\nimport useOnClickOutside from 'use-onclickoutside';\n\nimport { DropdownItem } from './DropdownItem';\nimport type {\n Components,\n FormatOptionLabel,\n GetOptionLabel,\n OnSelect,\n} from './types';\n\nexport type DropdownProps<Option> = {\n components: Components;\n getOptionLabel: GetOptionLabel<Option>;\n formatOptionLabel: FormatOptionLabel<Option>;\n options: Option[];\n onSelect: OnSelect<Option>;\n closeMenu: () => void;\n};\n\nexport function Dropdown<Option>({\n components,\n getOptionLabel,\n formatOptionLabel,\n options,\n onSelect,\n closeMenu,\n}: DropdownProps<Option>): ReactElement {\n const rootRef = useRef<HTMLElement>(null);\n useOnClickOutside(rootRef, closeMenu);\n\n const isInitRef = useRef<boolean>(true);\n\n const [highlightedIndex, setHighlightedIndex] = useState<number>(0);\n\n const highlightedIndexRef = useRef<number>(highlightedIndex);\n highlightedIndexRef.current = highlightedIndex;\n\n useEffect(() => {\n if (isInitRef.current) {\n isInitRef.current = false;\n } else {\n setHighlightedIndex(0);\n }\n\n const onKeyDown = (event: KeyboardEvent): void => {\n switch (event.key) {\n case 'ArrowDown':\n setHighlightedIndex((prevIndex) => {\n if (prevIndex === options.length - 1) {\n return 0;\n }\n\n return prevIndex + 1;\n });\n break;\n\n case 'ArrowUp':\n setHighlightedIndex((prevIndex) => {\n if (prevIndex === 0) {\n return options.length - 1;\n }\n\n return prevIndex - 1;\n });\n break;\n\n case 'Enter':\n {\n const option = options[highlightedIndexRef.current];\n onSelect(getOptionLabel(option), option);\n\n break;\n }\n\n default:\n break;\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n\n return (): void => {\n document.removeEventListener('keydown', onKeyDown);\n };\n }, [\n options,\n getOptionLabel,\n onSelect,\n ]);\n\n const {\n Menu,\n } = components;\n\n return (\n <Menu\n ref={rootRef}\n >\n {\n options.map((option, index) => (\n <DropdownItem\n components={components}\n getOptionLabel={getOptionLabel}\n formatOptionLabel={formatOptionLabel}\n option={option}\n isHighlighted={highlightedIndex === index}\n onSelect={onSelect}\n setHighlightedIndex={setHighlightedIndex}\n index={index}\n key={index}\n />\n ))\n }\n </Menu>\n );\n}\n","import {\n useCallback,\n} from 'react';\nimport type {\n ReactElement,\n} from 'react';\n\nimport type {\n Components,\n FormatOptionLabel,\n GetOptionLabel,\n OnSelect,\n} from './types';\n\nexport type DropdownItemProps<Option> = {\n components: Components;\n getOptionLabel: GetOptionLabel<Option>;\n formatOptionLabel: FormatOptionLabel<Option>;\n option: Option;\n isHighlighted: boolean;\n onSelect: OnSelect<Option>;\n setHighlightedIndex: (index: number) => void;\n index: number;\n};\n\nexport function DropdownItem<Option>({\n components,\n getOptionLabel,\n formatOptionLabel,\n isHighlighted,\n option,\n onSelect,\n setHighlightedIndex,\n index,\n}: DropdownItemProps<Option>): ReactElement {\n const onClick = useCallback(() => {\n onSelect(getOptionLabel(option), option);\n }, [onSelect, option, getOptionLabel]);\n\n const onMouseEnter = useCallback(() => {\n setHighlightedIndex(index);\n }, [setHighlightedIndex, index]);\n\n const {\n MenuItem,\n } = components;\n\n return (\n <MenuItem\n $isHighlighted={isHighlighted}\n onMouseEnter={onMouseEnter}\n onClick={onClick}\n >\n {formatOptionLabel(option)}\n </MenuItem>\n );\n}\n"],"mappings":"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","names":["useCallback","useState","useRef","useEffect","styled","styled","styled","components","MenuItem","jsx","components","Menu","jsx","components","useRef","useState","useCallback","useEffect","Wrapper","Input"]}
import { ComponentType, PropsWithChildren, HTMLProps, Ref, ReactNode, ChangeEventHandler, ReactElement } from 'react';
type WrapperComponent = ComponentType<PropsWithChildren>;
type InputComponentProps = HTMLProps<HTMLInputElement> & {
$hasError: boolean;
$hasWarning: boolean;
};
type InputComponent = ComponentType<InputComponentProps>;
type MenuComponent = ComponentType<PropsWithChildren<{
ref: Ref<HTMLElement>;
}>>;
type MenuItemProps = HTMLProps<HTMLDivElement> & {
$isHighlighted: boolean;
};
type MenuItemComponent = ComponentType<MenuItemProps>;
type Components = {
Wrapper: WrapperComponent;
Input: InputComponent;
Menu: MenuComponent;
MenuItem: MenuItemComponent;
};
type LoadOptionsResponse<Option> = {
options: Option[];
};
type LoadOptions<Option> = (inputValue: string) => LoadOptionsResponse<Option> | Promise<LoadOptionsResponse<Option>>;
type GetOptionLabel<Option> = (option: Option) => string;
type FormatOptionLabel<Option> = (Option: Option) => ReactNode;
type OnSelect<Option> = (value: string, option: Option) => void;
type AutocompleteProps<Option> = {
/**
* Функция загрузки опций
* @param {String} search - текущее значение поля ввода
* @returns {Object[]} response.options - опции
*/
loadOptions: LoadOptions<Option>;
/**
* Значение элемента input
*/
value: string;
/**
* Обработчик изменения значения поля при ручном вводе
*/
onChange?: ChangeEventHandler<HTMLInputElement>;
/**
* Обработчик изменения значения поля при выборе из меню
* @param {String} value - текст выбранной опции
* @param {Object} option - выбранная опция
*/
onSelect?: OnSelect<Option>;
/**
* Выключено ли поле
*/
disabled?: boolean;
/**
* Есть ли у поля ошибка
*/
hasError?: boolean;
/**
* Есть ли у поля предупреждение
*/
hasWarning?: boolean;
/**
* Дополнительные props элемента input
*/
inputProps?: Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange' | 'disabled'>;
/**
* Ключ, по которому хранится текст опции
*/
labelKey?: string;
/**
* Функция получения текста опции, который будет подставлен при выборе
*/
getOptionLabel?: GetOptionLabel<Option>;
/**
* Функция отображения опции
*/
formatOptionLabel?: FormatOptionLabel<Option>;
/**
* Переиспользуемые компоненты
*/
components?: Partial<Components>;
};
declare function Autocomplete<Option>({ loadOptions, value, onChange: onChangeProp, onSelect: onSelectProp, disabled, hasError, hasWarning, inputProps, labelKey, getOptionLabel: getOptionLabelProp, formatOptionLabel: formatOptionLabelProp, components: componentsProp, }: AutocompleteProps<Option>): ReactElement;
export { Autocomplete, AutocompleteProps, Components, FormatOptionLabel, GetOptionLabel, InputComponent, InputComponentProps, LoadOptions, LoadOptionsResponse, MenuComponent, MenuItemComponent, MenuItemProps, OnSelect, WrapperComponent };
"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 src_exports = {};
__export(src_exports, {
Autocomplete: () => Autocomplete
});
module.exports = __toCommonJS(src_exports);
// src/Autocomplete.tsx
var import_react3 = require("react");
// src/components/Wrapper.tsx
var import_styled_components = __toESM(require("styled-components"));
var Wrapper = import_styled_components.default.div({
position: "relative"
});
// src/components/Input.tsx
var import_styled_components2 = __toESM(require("styled-components"));
var Input = import_styled_components2.default.input(({
$hasError,
$hasWarning
}) => {
const res = {
width: "100%",
boxSizing: "border-box"
};
if ($hasError) {
res.borderColor = "#d64c4c";
res.backgroundColor = "#fdf6f6";
} else if ($hasWarning) {
res.borderColor = "#eea505";
res.backgroundColor = "#fdf6e6";
}
return res;
});
// src/components/Menu.tsx
var import_styled_components3 = __toESM(require("styled-components"));
var Menu = import_styled_components3.default.div({
boxSizing: "border-box",
position: "absolute",
top: "100%",
width: "100%",
zIndex: 1,
marginTop: 4,
maxHeight: 200,
overflowY: "auto",
borderRadius: 4,
backgroundColor: "#fff",
border: "1px solid #d9e1e8",
boxShadow: "0 1px 0 rgba(0, 0, 0, 0.06)"
});
// src/components/MenuItem.tsx
var import_styled_components4 = __toESM(require("styled-components"));
var MenuItem = import_styled_components4.default.div(({
$isHighlighted
}) => ({
display: "block",
boxSizing: "border-box",
padding: 10,
color: "#666",
backgroundColor: $isHighlighted ? "#f9f9f9" : "#fff",
cursor: "pointer"
}));
// src/components/index.ts
var components = {
Wrapper,
Input,
Menu,
MenuItem
};
// src/Dropdown.tsx
var import_react2 = require("react");
var import_use_onclickoutside = __toESM(require("use-onclickoutside"));
// src/DropdownItem.tsx
var import_react = require("react");
var import_jsx_runtime = require("react/jsx-runtime");
function DropdownItem({
components: components2,
getOptionLabel,
formatOptionLabel,
isHighlighted,
option,
onSelect,
setHighlightedIndex,
index
}) {
const onClick = (0, import_react.useCallback)(() => {
onSelect(getOptionLabel(option), option);
}, [onSelect, option, getOptionLabel]);
const onMouseEnter = (0, import_react.useCallback)(() => {
setHighlightedIndex(index);
}, [setHighlightedIndex, index]);
const {
MenuItem: MenuItem2
} = components2;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
MenuItem2,
{
$isHighlighted: isHighlighted,
onMouseEnter,
onClick,
children: formatOptionLabel(option)
}
);
}
// src/Dropdown.tsx
var import_jsx_runtime2 = require("react/jsx-runtime");
function Dropdown({
components: components2,
getOptionLabel,
formatOptionLabel,
options,
onSelect,
closeMenu
}) {
const rootRef = (0, import_react2.useRef)(null);
(0, import_use_onclickoutside.default)(rootRef, closeMenu);
const isInitRef = (0, import_react2.useRef)(true);
const [highlightedIndex, setHighlightedIndex] = (0, import_react2.useState)(0);
const highlightedIndexRef = (0, import_react2.useRef)(highlightedIndex);
highlightedIndexRef.current = highlightedIndex;
(0, import_react2.useEffect)(() => {
if (isInitRef.current) {
isInitRef.current = false;
} else {
setHighlightedIndex(0);
}
const onKeyDown = (event) => {
switch (event.key) {
case "ArrowDown":
setHighlightedIndex((prevIndex) => {
if (prevIndex === options.length - 1) {
return 0;
}
return prevIndex + 1;
});
break;
case "ArrowUp":
setHighlightedIndex((prevIndex) => {
if (prevIndex === 0) {
return options.length - 1;
}
return prevIndex - 1;
});
break;
case "Enter": {
const option = options[highlightedIndexRef.current];
onSelect(getOptionLabel(option), option);
break;
}
default:
break;
}
};
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
};
}, [
options,
getOptionLabel,
onSelect
]);
const {
Menu: Menu2
} = components2;
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
Menu2,
{
ref: rootRef,
children: options.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
DropdownItem,
{
components: components2,
getOptionLabel,
formatOptionLabel,
option,
isHighlighted: highlightedIndex === index,
onSelect,
setHighlightedIndex,
index
},
index
))
}
);
}
// src/Autocomplete.tsx
var import_jsx_runtime3 = require("react/jsx-runtime");
var emptyObj = {};
function Autocomplete({
loadOptions,
value,
onChange: onChangeProp = void 0,
onSelect: onSelectProp = void 0,
disabled = false,
hasError = false,
hasWarning = false,
inputProps = emptyObj,
labelKey = "label",
getOptionLabel: getOptionLabelProp = void 0,
formatOptionLabel: formatOptionLabelProp = void 0,
components: componentsProp = emptyObj
}) {
const components2 = (0, import_react3.useMemo)(() => ({
...components,
...componentsProp
}), [componentsProp]);
const valueRef = (0, import_react3.useRef)(value);
valueRef.current = value;
const [options, setOptions] = (0, import_react3.useState)([]);
const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
const getOptionLabel = (0, import_react3.useCallback)((option) => {
if (getOptionLabelProp) {
return getOptionLabelProp(option);
}
if (typeof option === "string") {
return option;
}
if (typeof option === "number") {
return String(option);
}
if (!option || typeof option !== "object") {
return "";
}
const label = option[labelKey];
if (typeof label === "string") {
return label;
}
if (typeof label === "number") {
return String(label);
}
return "";
}, [getOptionLabelProp, labelKey]);
const formatOptionLabel = (0, import_react3.useCallback)((option) => {
if (formatOptionLabelProp) {
return formatOptionLabelProp(option);
}
return getOptionLabel(option);
}, [formatOptionLabelProp, getOptionLabel]);
const onInputFocus = (0, import_react3.useCallback)(() => {
setIsOpen(true);
}, []);
const closeMenu = (0, import_react3.useCallback)(() => {
setIsOpen(false);
}, []);
const onSelect = (0, import_react3.useCallback)((label, option) => {
setIsOpen(false);
if (onSelectProp) {
onSelectProp(label, option);
}
}, [onSelectProp]);
const onChange = (0, import_react3.useCallback)((event) => {
if (onChangeProp) {
onChangeProp(event);
}
if (!isOpen) {
setIsOpen(true);
}
}, [onChangeProp, isOpen]);
(0, import_react3.useEffect)(() => {
(async () => {
const response = await loadOptions(value);
if (valueRef.current === value) {
setOptions(response.options);
}
})().catch((e) => {
throw e;
});
}, [
loadOptions,
value
]);
const {
Wrapper: Wrapper2,
Input: Input2
} = components2;
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Wrapper2, { children: [
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
Input2,
{
...inputProps,
disabled,
$hasError: hasError,
$hasWarning: hasWarning,
value,
onChange,
onFocus: onInputFocus
}
),
isOpen && options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
Dropdown,
{
components: components2,
getOptionLabel,
formatOptionLabel,
options,
onSelect,
closeMenu
}
)
] });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Autocomplete
});
//# sourceMappingURL=index.js.map
{"version":3,"sources":["../src/index.ts","../src/Autocomplete.tsx","../src/components/Wrapper.tsx","../src/components/Input.tsx","../src/components/Menu.tsx","../src/components/MenuItem.tsx","../src/components/index.ts","../src/Dropdown.tsx","../src/DropdownItem.tsx"],"sourcesContent":["export { Autocomplete } from './Autocomplete';\nexport type {\n AutocompleteProps,\n} from './Autocomplete';\n\nexport * from './types';\n","import {\n useCallback,\n useState,\n useMemo,\n useRef,\n useEffect,\n} from 'react';\nimport type {\n ChangeEventHandler,\n HTMLProps,\n ReactElement,\n} from 'react';\n\nimport { components as defaultComponents } from './components';\nimport { Dropdown } from './Dropdown';\n\nimport type {\n Components,\n FormatOptionLabel,\n GetOptionLabel,\n LoadOptions,\n OnSelect,\n} from './types';\n\nexport type AutocompleteProps<Option> = {\n /**\n * Функция загрузки опций\n * @param {String} search - текущее значение поля ввода\n * @returns {Object[]} response.options - опции\n */\n loadOptions: LoadOptions<Option>;\n /**\n * Значение элемента input\n */\n value: string;\n /**\n * Обработчик изменения значения поля при ручном вводе\n */\n onChange?: ChangeEventHandler<HTMLInputElement>;\n /**\n * Обработчик изменения значения поля при выборе из меню\n * @param {String} value - текст выбранной опции\n * @param {Object} option - выбранная опция\n */\n onSelect?: OnSelect<Option>;\n /**\n * Выключено ли поле\n */\n disabled?: boolean;\n /**\n * Есть ли у поля ошибка\n */\n hasError?: boolean;\n /**\n * Есть ли у поля предупреждение\n */\n hasWarning?: boolean;\n\n /**\n * Дополнительные props элемента input\n */\n inputProps?: Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange' | 'disabled'>;\n\n /**\n * Ключ, по которому хранится текст опции\n */\n labelKey?: string;\n /**\n * Функция получения текста опции, который будет подставлен при выборе\n */\n getOptionLabel?: GetOptionLabel<Option>;\n /**\n * Функция отображения опции\n */\n formatOptionLabel?: FormatOptionLabel<Option>;\n /**\n * Переиспользуемые компоненты\n */\n components?: Partial<Components>;\n};\n\nconst emptyObj = {};\n\nexport function Autocomplete<Option>({\n loadOptions,\n value,\n onChange: onChangeProp = undefined,\n onSelect: onSelectProp = undefined,\n disabled = false,\n hasError = false,\n hasWarning = false,\n inputProps = emptyObj,\n labelKey = 'label',\n getOptionLabel: getOptionLabelProp = undefined,\n formatOptionLabel: formatOptionLabelProp = undefined,\n components: componentsProp = emptyObj,\n}: AutocompleteProps<Option>): ReactElement {\n const components = useMemo<Components>(() => ({\n ...defaultComponents,\n ...componentsProp,\n }), [componentsProp]);\n\n const valueRef = useRef<string>(value);\n valueRef.current = value;\n\n const [options, setOptions] = useState<Option[]>([]);\n const [isOpen, setIsOpen] = useState<boolean>(false);\n\n const getOptionLabel = useCallback<GetOptionLabel<Option>>((option) => {\n if (getOptionLabelProp) {\n return getOptionLabelProp(option);\n }\n\n if (typeof option === 'string') {\n return option;\n }\n\n if (typeof option === 'number') {\n return String(option);\n }\n\n if (!option || typeof option !== 'object') {\n return '';\n }\n\n const label = (option as Record<string, unknown>)[labelKey];\n\n if (typeof label === 'string') {\n return label;\n }\n\n if (typeof label === 'number') {\n return String(label);\n }\n\n return '';\n }, [getOptionLabelProp, labelKey]);\n\n const formatOptionLabel = useCallback<FormatOptionLabel<Option>>((option) => {\n if (formatOptionLabelProp) {\n return formatOptionLabelProp(option);\n }\n\n return getOptionLabel(option);\n }, [formatOptionLabelProp, getOptionLabel]);\n\n const onInputFocus = useCallback(() => {\n setIsOpen(true);\n }, []);\n\n const closeMenu = useCallback(() => {\n setIsOpen(false);\n }, []);\n\n const onSelect = useCallback<OnSelect<Option>>((label, option) => {\n setIsOpen(false);\n\n if (onSelectProp) {\n onSelectProp(label, option);\n }\n }, [onSelectProp]);\n\n const onChange = useCallback<ChangeEventHandler<HTMLInputElement>>((event) => {\n if (onChangeProp) {\n onChangeProp(event);\n }\n\n if (!isOpen) {\n setIsOpen(true);\n }\n }, [onChangeProp, isOpen]);\n\n useEffect(() => {\n (async (): Promise<void> => {\n const response = await loadOptions(value);\n\n if (valueRef.current === value) {\n setOptions(response.options);\n }\n })().catch((e) => {\n throw e;\n });\n }, [\n loadOptions,\n value,\n ]);\n\n const {\n Wrapper,\n Input,\n } = components;\n\n return (\n <Wrapper>\n <Input\n {...inputProps}\n disabled={disabled}\n $hasError={hasError}\n $hasWarning={hasWarning}\n value={value}\n onChange={onChange}\n onFocus={onInputFocus}\n />\n\n {\n isOpen && options.length > 0 && (\n <Dropdown\n components={components}\n getOptionLabel={getOptionLabel}\n formatOptionLabel={formatOptionLabel}\n options={options}\n onSelect={onSelect}\n closeMenu={closeMenu}\n />\n )\n }\n </Wrapper>\n );\n}\n","import styled from 'styled-components';\n\nimport type {\n WrapperComponent,\n} from '../types';\n\nexport const Wrapper: WrapperComponent = styled.div({\n position: 'relative',\n});\n","import styled from 'styled-components';\nimport type {\n CSSObject,\n} from 'styled-components';\n\nimport type {\n InputComponentProps,\n InputComponent,\n} from '../types';\n\nexport const Input: InputComponent = styled.input<InputComponentProps>(({\n $hasError,\n $hasWarning,\n}) => {\n const res: CSSObject = {\n width: '100%',\n boxSizing: 'border-box',\n };\n\n if ($hasError) {\n res.borderColor = '#d64c4c';\n res.backgroundColor = '#fdf6f6';\n } else if ($hasWarning) {\n res.borderColor = '#eea505';\n res.backgroundColor = '#fdf6e6';\n }\n\n return res;\n});\n","import styled from 'styled-components';\n\nimport type {\n MenuComponent,\n} from '../types';\n\nexport const Menu: MenuComponent = styled.div({\n boxSizing: 'border-box',\n position: 'absolute',\n top: '100%',\n width: '100%',\n zIndex: 1,\n marginTop: 4,\n maxHeight: 200,\n overflowY: 'auto',\n borderRadius: 4,\n backgroundColor: '#fff',\n border: '1px solid #d9e1e8',\n boxShadow: '0 1px 0 rgba(0, 0, 0, 0.06)',\n});\n","import styled from 'styled-components';\n\nimport type {\n MenuItemProps,\n MenuItemComponent,\n} from '../types';\n\nexport const MenuItem: MenuItemComponent = styled.div<MenuItemProps>(({\n $isHighlighted,\n}) => ({\n display: 'block',\n boxSizing: 'border-box',\n padding: 10,\n color: '#666',\n backgroundColor: $isHighlighted ? '#f9f9f9' : '#fff',\n cursor: 'pointer',\n}));\n","import { Wrapper } from './Wrapper';\nimport { Input } from './Input';\nimport { Menu } from './Menu';\nimport { MenuItem } from './MenuItem';\n\nimport type {\n Components,\n} from '../types';\n\nexport const components: Components = {\n Wrapper,\n Input,\n Menu,\n MenuItem,\n};\n","import {\n useRef,\n useState,\n useEffect,\n} from 'react';\nimport type {\n ReactElement,\n} from 'react';\n\nimport useOnClickOutside from 'use-onclickoutside';\n\nimport { DropdownItem } from './DropdownItem';\nimport type {\n Components,\n FormatOptionLabel,\n GetOptionLabel,\n OnSelect,\n} from './types';\n\nexport type DropdownProps<Option> = {\n components: Components;\n getOptionLabel: GetOptionLabel<Option>;\n formatOptionLabel: FormatOptionLabel<Option>;\n options: Option[];\n onSelect: OnSelect<Option>;\n closeMenu: () => void;\n};\n\nexport function Dropdown<Option>({\n components,\n getOptionLabel,\n formatOptionLabel,\n options,\n onSelect,\n closeMenu,\n}: DropdownProps<Option>): ReactElement {\n const rootRef = useRef<HTMLElement>(null);\n useOnClickOutside(rootRef, closeMenu);\n\n const isInitRef = useRef<boolean>(true);\n\n const [highlightedIndex, setHighlightedIndex] = useState<number>(0);\n\n const highlightedIndexRef = useRef<number>(highlightedIndex);\n highlightedIndexRef.current = highlightedIndex;\n\n useEffect(() => {\n if (isInitRef.current) {\n isInitRef.current = false;\n } else {\n setHighlightedIndex(0);\n }\n\n const onKeyDown = (event: KeyboardEvent): void => {\n switch (event.key) {\n case 'ArrowDown':\n setHighlightedIndex((prevIndex) => {\n if (prevIndex === options.length - 1) {\n return 0;\n }\n\n return prevIndex + 1;\n });\n break;\n\n case 'ArrowUp':\n setHighlightedIndex((prevIndex) => {\n if (prevIndex === 0) {\n return options.length - 1;\n }\n\n return prevIndex - 1;\n });\n break;\n\n case 'Enter':\n {\n const option = options[highlightedIndexRef.current];\n onSelect(getOptionLabel(option), option);\n\n break;\n }\n\n default:\n break;\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n\n return (): void => {\n document.removeEventListener('keydown', onKeyDown);\n };\n }, [\n options,\n getOptionLabel,\n onSelect,\n ]);\n\n const {\n Menu,\n } = components;\n\n return (\n <Menu\n ref={rootRef}\n >\n {\n options.map((option, index) => (\n <DropdownItem\n components={components}\n getOptionLabel={getOptionLabel}\n formatOptionLabel={formatOptionLabel}\n option={option}\n isHighlighted={highlightedIndex === index}\n onSelect={onSelect}\n setHighlightedIndex={setHighlightedIndex}\n index={index}\n key={index}\n />\n ))\n }\n </Menu>\n );\n}\n","import {\n useCallback,\n} from 'react';\nimport type {\n ReactElement,\n} from 'react';\n\nimport type {\n Components,\n FormatOptionLabel,\n GetOptionLabel,\n OnSelect,\n} from './types';\n\nexport type DropdownItemProps<Option> = {\n components: Components;\n getOptionLabel: GetOptionLabel<Option>;\n formatOptionLabel: FormatOptionLabel<Option>;\n option: Option;\n isHighlighted: boolean;\n onSelect: OnSelect<Option>;\n setHighlightedIndex: (index: number) => void;\n index: number;\n};\n\nexport function DropdownItem<Option>({\n components,\n getOptionLabel,\n formatOptionLabel,\n isHighlighted,\n option,\n onSelect,\n setHighlightedIndex,\n index,\n}: DropdownItemProps<Option>): ReactElement {\n const onClick = useCallback(() => {\n onSelect(getOptionLabel(option), option);\n }, [onSelect, option, getOptionLabel]);\n\n const onMouseEnter = useCallback(() => {\n setHighlightedIndex(index);\n }, [setHighlightedIndex, index]);\n\n const {\n MenuItem,\n } = components;\n\n return (\n <MenuItem\n $isHighlighted={isHighlighted}\n onMouseEnter={onMouseEnter}\n onClick={onClick}\n >\n {formatOptionLabel(option)}\n </MenuItem>\n );\n}\n"],"mappings":"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","names":["import_react","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_react","components","MenuItem","import_jsx_runtime","components","useOnClickOutside","Menu","import_jsx_runtime","components","Wrapper","Input"]}
+56
-47
{
"name": "@n3/react-autocomplete",
"version": "0.2.0",
"version": "1.0.0",
"description": "Autocomplete component for react applications",
"main": "lib/index.js",
"module": "es/index.js",
"typings": "ts/index.d.ts",
"main": "./dist/index.js",
"module": "./dist/esm/index.js",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"/es",
"/lib",
"/ts"
"/dist"
],

@@ -23,53 +28,57 @@ "repository": "git@gitlab.develop.netrika.ru:web/frontend/n3-react-autocomplete.git",

"scripts": {
"clean": "rimraf es lib ts",
"start": "BABEL_ENV=es start-storybook",
"build": "yarn build:cjs && yarn build:es && yarn build:ts",
"build:cjs": "cross-env BABEL_ENV=cjs babel --extensions '.ts,.tsx' src --out-dir lib --ignore \"src/**/__tests__\",\"src/**/__stories__\"",
"build:es": "cross-env BABEL_ENV=es babel --extensions '.ts,.tsx' src --out-dir es --ignore \"src/**/__tests__\",\"src/**/__stories__\"",
"build:ts": "tsc --declaration --emitDeclarationOnly",
"clean": "rimraf dist",
"start": "start-storybook",
"build": "tsup src/index.ts --sourcemap --format esm,cjs --dts --legacy-output",
"test:ts": "tsc -p ./tsconfig.validate.json --noEmit",
"lint": "eslint src --ext .ts,.tsx",
"test": "yarn lint && yarn test:ts",
"test:unit": "jest",
"prepare": "yarn clean && yarn build"
},
"peerDependencies": {
"react": "^16.0.0"
"react": "^17.0.0 || ^18.0.0"
},
"devDependencies": {
"@babel/cli": "^7.10.1",
"@babel/core": "^7.10.2",
"@babel/plugin-transform-runtime": "^7.10.1",
"@babel/preset-env": "^7.10.2",
"@babel/preset-react": "^7.10.1",
"@babel/preset-typescript": "^7.10.1",
"@n3/eslint-config": "^0.6.0",
"@storybook/addon-knobs": "^5.3.19",
"@storybook/react": "^5.3.19",
"@types/react": "^16.9.35",
"babel-jest": "^26.0.1",
"babel-loader": "^8.1.0",
"cross-env": "^7.0.2",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"jest-cli": "^26.0.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"rimraf": "^3.0.2",
"typescript": "^3.9.3"
"@babel/types": "^7.21.2",
"@mdx-js/react": "^2.3.0",
"@n3/eslint-config": "^0.13.3",
"@storybook/addon-controls": "^6.5.16",
"@storybook/addon-docs": "^6.5.16",
"@storybook/addons": "^6.5.16",
"@storybook/builder-vite": "^0.4.2",
"@storybook/channel-postmessage": "^6.5.16",
"@storybook/channel-websocket": "^6.5.16",
"@storybook/client-api": "^6.5.16",
"@storybook/mdx2-csf": "^0.0.4",
"@storybook/node-logger": "^6.5.16",
"@storybook/preset-create-react-app": "^4.1.2",
"@storybook/preview-web": "^6.5.16",
"@storybook/react": "^6.5.16",
"@types/react": "^18.0.28",
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"@vitejs/plugin-react-swc": "^3.2.0",
"eslint": "^8.35.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-import-resolver-typescript": "^3.5.3",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"react-refresh": "^0.14.0",
"rimraf": "^4.1.2",
"tsup": "^6.6.3",
"typescript": "^4.9.5",
"vite": "^4.1.4"
},
"dependencies": {
"@babel/runtime": "^7.10.2",
"styled-components": "^5.1.1",
"use-onclickoutside": "^0.3.1"
},
"jest": {
"modulePaths": [
"node_modules",
"src"
],
"setupFiles": [
"./setup-jest.js"
]
"styled-components": "^5.3.6",
"use-onclickoutside": "^0.4.1"
}
}

@@ -58,1 +58,17 @@ # @n3/react-autocomplete

- `MenuItem`
## Локальная разработка
Репозиторий использует стабильную версию [yarn](https://yarnpkg.com/getting-started).
[Инструкция по установке](https://yarnpkg.com/getting-started/install).
### Команды
- `yarn build` - сборка;
- `yarn clean` - удалить все собранне файлы;
- `yarn test` - валидация кода;
- `yarn start` - запуск [storybook](https://storybook.js.org/) с примерами.
## 0.2.0
### Улучшения
* Миграция на `typescript`
* Отказ от `react-autocomplete`
* Миграция на переопределяемые компоненты
### Несовместимые изменения и миграция
* Замена `getItemValue` на `getOptionLabel`
* Замена `valueRenderer` на `formatOptionLabel`
* Отказ от `onChangeValue`, вместо него должно использоваться обычное `onChange` input-элемента
* Отказ от свойств `className`, `wrapperClassName`, `inputClassName`, `disabledInputClassName`, `errorInputClassName`, `warningInputClassName`, `menuClassName`, `menuItemClassName`, `menuItemHighlightedClassName`
* Отказ от свойств `valueKey` и `uniqKey`
import _extends from "@babel/runtime/helpers/extends";
import _regeneratorRuntime from "@babel/runtime/regenerator";
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
import React, { useCallback, useState, useMemo, useRef, useEffect } from 'react';
import defaultComponents from './components';
import Dropdown from './Dropdown';
var Autocomplete = function Autocomplete(_ref) {
var loadOptions = _ref.loadOptions,
value = _ref.value,
onChangeProp = _ref.onChange,
onSelectProp = _ref.onSelect,
disabled = _ref.disabled,
hasError = _ref.hasError,
hasWarning = _ref.hasWarning,
inputProps = _ref.inputProps,
labelKey = _ref.labelKey,
getOptionLabelProp = _ref.getOptionLabel,
formatOptionLabelProp = _ref.formatOptionLabel,
componentsProp = _ref.components;
var components = useMemo(function () {
return _objectSpread(_objectSpread({}, defaultComponents), componentsProp);
}, [componentsProp]);
var valueRef = useRef(value);
valueRef.current = value;
var _useState = useState([]),
_useState2 = _slicedToArray(_useState, 2),
options = _useState2[0],
setOptions = _useState2[1];
var _useState3 = useState(false),
_useState4 = _slicedToArray(_useState3, 2),
isOpen = _useState4[0],
setIsOpen = _useState4[1];
var getOptionLabel = useCallback(function (option) {
if (getOptionLabelProp) {
return getOptionLabelProp(option);
}
return option[labelKey];
}, [getOptionLabelProp, labelKey]);
var formatOptionLabel = useCallback(function (option) {
if (formatOptionLabelProp) {
return formatOptionLabelProp(option);
}
return getOptionLabel(option);
}, [formatOptionLabelProp, getOptionLabel]);
var onInputFocus = useCallback(function () {
setIsOpen(true);
}, []);
var closeMenu = useCallback(function () {
setIsOpen(false);
}, []);
var onSelect = useCallback(function (label, option) {
setIsOpen(false);
onSelectProp(label, option);
}, [onSelectProp]);
var onChange = useCallback(function (event) {
onChangeProp(event);
if (!isOpen) {
setIsOpen(true);
}
}, [onChangeProp, isOpen]);
useEffect(function () {
_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
var response;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return loadOptions(value);
case 2:
response = _context.sent;
if (valueRef.current === value) {
setOptions(response.options);
}
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}))(); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
var Wrapper = components.Wrapper,
Input = components.Input;
return /*#__PURE__*/React.createElement(Wrapper, null, /*#__PURE__*/React.createElement(Input, _extends({}, inputProps, {
disabled: disabled,
$hasError: hasError,
$hasWarning: hasWarning,
value: value,
onChange: onChange,
onFocus: onInputFocus
})), isOpen && options.length > 0 && /*#__PURE__*/React.createElement(Dropdown, {
components: components,
getOptionLabel: getOptionLabel,
formatOptionLabel: formatOptionLabel,
options: options,
onSelect: onSelect,
closeMenu: closeMenu
}));
};
Autocomplete.defaultProps = {
onChange: function onChange() {},
onSelect: function onSelect() {},
disabled: false,
hasError: false,
hasWarning: false,
inputProps: {},
labelKey: 'label',
getOptionLabel: null,
formatOptionLabel: null,
components: {}
};
export default Autocomplete;
import Wrapper from './Wrapper';
import Input from './Input';
import Menu from './Menu';
import MenuItem from './MenuItem';
var components = {
Wrapper: Wrapper,
Input: Input,
Menu: Menu,
MenuItem: MenuItem
};
export default components;
import styled from 'styled-components';
var Input = styled.input(function (_ref) {
var $hasError = _ref.$hasError,
$hasWarning = _ref.$hasWarning;
var res = {
width: '100%',
boxSizing: 'border-box'
};
if ($hasError) {
res.borderColor = '#eea505';
res.backgroundColor = '#fdf6e6';
} else if ($hasWarning) {
res.borderColor = '#d64c4c';
res.backgroundColor = '#fdf6f6';
}
return res;
});
export default Input;
import styled from 'styled-components';
var Menu = styled.div({
boxSizing: 'border-box',
position: 'absolute',
top: '100%',
width: '100%',
zIndex: 1,
marginTop: 4,
maxHeight: 200,
overflowY: 'auto',
borderRadius: 4,
backgroundColor: '#fff',
border: '1px solid #d9e1e8',
boxShadow: '0 1px 0 rgba(0, 0, 0, 0.06)'
});
export default Menu;
import styled from 'styled-components';
var MenuItem = styled.div(function (_ref) {
var $isHighlighted = _ref.$isHighlighted;
return {
display: 'block',
boxSizing: 'border-box',
padding: 10,
color: '#666',
backgroundColor: $isHighlighted ? '#f9f9f9' : '#fff',
cursor: 'pointer'
};
});
export default MenuItem;
import styled from 'styled-components';
var Wrapper = styled.div({
position: 'relative'
});
export default Wrapper;
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import React, { useRef, useState, useEffect } from 'react';
import useOnClickOutside from 'use-onclickoutside';
import DropdownItem from './DropdownItem';
var Dropdown = function Dropdown(_ref) {
var components = _ref.components,
getOptionLabel = _ref.getOptionLabel,
formatOptionLabel = _ref.formatOptionLabel,
options = _ref.options,
onSelect = _ref.onSelect,
closeMenu = _ref.closeMenu;
var rootRef = useRef(null);
useOnClickOutside(rootRef, closeMenu);
var isInitRef = useRef(true);
var _useState = useState(0),
_useState2 = _slicedToArray(_useState, 2),
highlightedIndex = _useState2[0],
setHighlightedIndex = _useState2[1];
var highlightedIndexRef = useRef(highlightedIndex);
highlightedIndexRef.current = highlightedIndex;
useEffect(function () {
if (isInitRef.current) {
isInitRef.current = false;
} else {
setHighlightedIndex(0);
}
var onKeyDown = function onKeyDown(event) {
switch (event.key) {
case 'ArrowDown':
setHighlightedIndex(function (prevIndex) {
if (prevIndex === options.length - 1) {
return 0;
}
return prevIndex + 1;
});
break;
case 'ArrowUp':
setHighlightedIndex(function (prevIndex) {
if (prevIndex === 0) {
return options.length - 1;
}
return prevIndex - 1;
});
break;
case 'Enter':
{
var option = options[highlightedIndexRef.current];
onSelect(getOptionLabel(option), option);
break;
}
default:
break;
}
};
document.addEventListener('keydown', onKeyDown);
return function () {
document.removeEventListener('keydown', onKeyDown);
};
}, [options, getOptionLabel, onSelect]);
var Menu = components.Menu;
return /*#__PURE__*/React.createElement(Menu, {
ref: rootRef
}, options.map(function (option, index) {
return /*#__PURE__*/React.createElement(DropdownItem, {
components: components,
getOptionLabel: getOptionLabel,
formatOptionLabel: formatOptionLabel,
option: option,
isHighlighted: highlightedIndex === index,
onSelect: onSelect,
setHighlightedIndex: setHighlightedIndex,
index: index,
key: index
});
}));
};
export default Dropdown;
import React, { useCallback } from 'react';
var DropdownItem = function DropdownItem(_ref) {
var components = _ref.components,
getOptionLabel = _ref.getOptionLabel,
formatOptionLabel = _ref.formatOptionLabel,
isHighlighted = _ref.isHighlighted,
option = _ref.option,
onSelect = _ref.onSelect,
setHighlightedIndex = _ref.setHighlightedIndex,
index = _ref.index;
var onClick = useCallback(function () {
onSelect(getOptionLabel(option), option);
}, [onSelect, option, getOptionLabel]);
var onMouseEnter = useCallback(function () {
setHighlightedIndex(index);
}, [setHighlightedIndex, index]);
var MenuItem = components.MenuItem;
return /*#__PURE__*/React.createElement(MenuItem, {
$isHighlighted: isHighlighted,
onMouseEnter: onMouseEnter,
onClick: onClick
}, formatOptionLabel(option));
};
export default DropdownItem;
export { default } from './Autocomplete';
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));
var _react = _interopRequireWildcard(require("react"));
var _components = _interopRequireDefault(require("./components"));
var _Dropdown = _interopRequireDefault(require("./Dropdown"));
var Autocomplete = function Autocomplete(_ref) {
var loadOptions = _ref.loadOptions,
value = _ref.value,
onChangeProp = _ref.onChange,
onSelectProp = _ref.onSelect,
disabled = _ref.disabled,
hasError = _ref.hasError,
hasWarning = _ref.hasWarning,
inputProps = _ref.inputProps,
labelKey = _ref.labelKey,
getOptionLabelProp = _ref.getOptionLabel,
formatOptionLabelProp = _ref.formatOptionLabel,
componentsProp = _ref.components;
var components = (0, _react.useMemo)(function () {
return (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, _components["default"]), componentsProp);
}, [componentsProp]);
var valueRef = (0, _react.useRef)(value);
valueRef.current = value;
var _useState = (0, _react.useState)([]),
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
options = _useState2[0],
setOptions = _useState2[1];
var _useState3 = (0, _react.useState)(false),
_useState4 = (0, _slicedToArray2["default"])(_useState3, 2),
isOpen = _useState4[0],
setIsOpen = _useState4[1];
var getOptionLabel = (0, _react.useCallback)(function (option) {
if (getOptionLabelProp) {
return getOptionLabelProp(option);
}
return option[labelKey];
}, [getOptionLabelProp, labelKey]);
var formatOptionLabel = (0, _react.useCallback)(function (option) {
if (formatOptionLabelProp) {
return formatOptionLabelProp(option);
}
return getOptionLabel(option);
}, [formatOptionLabelProp, getOptionLabel]);
var onInputFocus = (0, _react.useCallback)(function () {
setIsOpen(true);
}, []);
var closeMenu = (0, _react.useCallback)(function () {
setIsOpen(false);
}, []);
var onSelect = (0, _react.useCallback)(function (label, option) {
setIsOpen(false);
onSelectProp(label, option);
}, [onSelectProp]);
var onChange = (0, _react.useCallback)(function (event) {
onChangeProp(event);
if (!isOpen) {
setIsOpen(true);
}
}, [onChangeProp, isOpen]);
(0, _react.useEffect)(function () {
(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
var response;
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return loadOptions(value);
case 2:
response = _context.sent;
if (valueRef.current === value) {
setOptions(response.options);
}
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}))(); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
var Wrapper = components.Wrapper,
Input = components.Input;
return /*#__PURE__*/_react["default"].createElement(Wrapper, null, /*#__PURE__*/_react["default"].createElement(Input, (0, _extends2["default"])({}, inputProps, {
disabled: disabled,
$hasError: hasError,
$hasWarning: hasWarning,
value: value,
onChange: onChange,
onFocus: onInputFocus
})), isOpen && options.length > 0 && /*#__PURE__*/_react["default"].createElement(_Dropdown["default"], {
components: components,
getOptionLabel: getOptionLabel,
formatOptionLabel: formatOptionLabel,
options: options,
onSelect: onSelect,
closeMenu: closeMenu
}));
};
Autocomplete.defaultProps = {
onChange: function onChange() {},
onSelect: function onSelect() {},
disabled: false,
hasError: false,
hasWarning: false,
inputProps: {},
labelKey: 'label',
getOptionLabel: null,
formatOptionLabel: null,
components: {}
};
var _default = Autocomplete;
exports["default"] = _default;
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _Wrapper = _interopRequireDefault(require("./Wrapper"));
var _Input = _interopRequireDefault(require("./Input"));
var _Menu = _interopRequireDefault(require("./Menu"));
var _MenuItem = _interopRequireDefault(require("./MenuItem"));
var components = {
Wrapper: _Wrapper["default"],
Input: _Input["default"],
Menu: _Menu["default"],
MenuItem: _MenuItem["default"]
};
var _default = components;
exports["default"] = _default;
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _styledComponents = _interopRequireDefault(require("styled-components"));
var Input = _styledComponents["default"].input(function (_ref) {
var $hasError = _ref.$hasError,
$hasWarning = _ref.$hasWarning;
var res = {
width: '100%',
boxSizing: 'border-box'
};
if ($hasError) {
res.borderColor = '#eea505';
res.backgroundColor = '#fdf6e6';
} else if ($hasWarning) {
res.borderColor = '#d64c4c';
res.backgroundColor = '#fdf6f6';
}
return res;
});
var _default = Input;
exports["default"] = _default;
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _styledComponents = _interopRequireDefault(require("styled-components"));
var Menu = _styledComponents["default"].div({
boxSizing: 'border-box',
position: 'absolute',
top: '100%',
width: '100%',
zIndex: 1,
marginTop: 4,
maxHeight: 200,
overflowY: 'auto',
borderRadius: 4,
backgroundColor: '#fff',
border: '1px solid #d9e1e8',
boxShadow: '0 1px 0 rgba(0, 0, 0, 0.06)'
});
var _default = Menu;
exports["default"] = _default;
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _styledComponents = _interopRequireDefault(require("styled-components"));
var MenuItem = _styledComponents["default"].div(function (_ref) {
var $isHighlighted = _ref.$isHighlighted;
return {
display: 'block',
boxSizing: 'border-box',
padding: 10,
color: '#666',
backgroundColor: $isHighlighted ? '#f9f9f9' : '#fff',
cursor: 'pointer'
};
});
var _default = MenuItem;
exports["default"] = _default;
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _styledComponents = _interopRequireDefault(require("styled-components"));
var Wrapper = _styledComponents["default"].div({
position: 'relative'
});
var _default = Wrapper;
exports["default"] = _default;
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _react = _interopRequireWildcard(require("react"));
var _useOnclickoutside = _interopRequireDefault(require("use-onclickoutside"));
var _DropdownItem = _interopRequireDefault(require("./DropdownItem"));
var Dropdown = function Dropdown(_ref) {
var components = _ref.components,
getOptionLabel = _ref.getOptionLabel,
formatOptionLabel = _ref.formatOptionLabel,
options = _ref.options,
onSelect = _ref.onSelect,
closeMenu = _ref.closeMenu;
var rootRef = (0, _react.useRef)(null);
(0, _useOnclickoutside["default"])(rootRef, closeMenu);
var isInitRef = (0, _react.useRef)(true);
var _useState = (0, _react.useState)(0),
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
highlightedIndex = _useState2[0],
setHighlightedIndex = _useState2[1];
var highlightedIndexRef = (0, _react.useRef)(highlightedIndex);
highlightedIndexRef.current = highlightedIndex;
(0, _react.useEffect)(function () {
if (isInitRef.current) {
isInitRef.current = false;
} else {
setHighlightedIndex(0);
}
var onKeyDown = function onKeyDown(event) {
switch (event.key) {
case 'ArrowDown':
setHighlightedIndex(function (prevIndex) {
if (prevIndex === options.length - 1) {
return 0;
}
return prevIndex + 1;
});
break;
case 'ArrowUp':
setHighlightedIndex(function (prevIndex) {
if (prevIndex === 0) {
return options.length - 1;
}
return prevIndex - 1;
});
break;
case 'Enter':
{
var option = options[highlightedIndexRef.current];
onSelect(getOptionLabel(option), option);
break;
}
default:
break;
}
};
document.addEventListener('keydown', onKeyDown);
return function () {
document.removeEventListener('keydown', onKeyDown);
};
}, [options, getOptionLabel, onSelect]);
var Menu = components.Menu;
return /*#__PURE__*/_react["default"].createElement(Menu, {
ref: rootRef
}, options.map(function (option, index) {
return /*#__PURE__*/_react["default"].createElement(_DropdownItem["default"], {
components: components,
getOptionLabel: getOptionLabel,
formatOptionLabel: formatOptionLabel,
option: option,
isHighlighted: highlightedIndex === index,
onSelect: onSelect,
setHighlightedIndex: setHighlightedIndex,
index: index,
key: index
});
}));
};
var _default = Dropdown;
exports["default"] = _default;
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _react = _interopRequireWildcard(require("react"));
var DropdownItem = function DropdownItem(_ref) {
var components = _ref.components,
getOptionLabel = _ref.getOptionLabel,
formatOptionLabel = _ref.formatOptionLabel,
isHighlighted = _ref.isHighlighted,
option = _ref.option,
onSelect = _ref.onSelect,
setHighlightedIndex = _ref.setHighlightedIndex,
index = _ref.index;
var onClick = (0, _react.useCallback)(function () {
onSelect(getOptionLabel(option), option);
}, [onSelect, option, getOptionLabel]);
var onMouseEnter = (0, _react.useCallback)(function () {
setHighlightedIndex(index);
}, [setHighlightedIndex, index]);
var MenuItem = components.MenuItem;
return /*#__PURE__*/_react["default"].createElement(MenuItem, {
$isHighlighted: isHighlighted,
onMouseEnter: onMouseEnter,
onClick: onClick
}, formatOptionLabel(option));
};
var _default = DropdownItem;
exports["default"] = _default;
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function get() {
return _Autocomplete["default"];
}
});
var _Autocomplete = _interopRequireDefault(require("./Autocomplete"));
"use strict";
import type { FC, HTMLProps } from 'react';
import type { Components, FormatOptionLabel, GetOptionLabel, LoadOptions, OnChange, OnSelect } from './types';
export declare type AutocompleteProps = {
/**
* Функция загрузки опций
* @param {String} search - текущее значение поля ввода
* @returns {Object[]} response.options - опции
*/
loadOptions: LoadOptions;
/**
* Значение элемента input
*/
value: string;
/**
* Обработчик изменения значения поля при ручном вводе
*/
onChange?: OnChange;
/**
* Обработчик изменения значения поля при выборе из меню
* @param {String} value - текст выбранной опции
* @param {Object} option - выбранная опция
*/
onSelect?: OnSelect;
/**
* Выключено ли поле
*/
disabled?: boolean;
/**
* Есть ли у поля ошибка
*/
hasError?: boolean;
/**
* Есть ли у поля предупреждение
*/
hasWarning?: boolean;
/**
* Дополнительные props элемента input
*/
inputProps?: Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange' | 'disabled'>;
/**
* Ключ, по которому хранится текст опции
*/
labelKey?: string;
/**
* Функция получения текста опции, который будет подставлен при выборе
*/
getOptionLabel?: GetOptionLabel;
/**
* Функция отображения опции
*/
formatOptionLabel?: FormatOptionLabel;
/**
* Переиспользуемые компоненты
*/
components?: Partial<Components>;
};
declare const Autocomplete: FC<AutocompleteProps>;
export default Autocomplete;
import type { Components } from '../types';
declare const components: Components;
export default components;
import type { InputComponent } from '../types';
declare const Input: InputComponent;
export default Input;
import type { MenuComponent } from '../types';
declare const Menu: MenuComponent;
export default Menu;
import type { MenuItemComponent } from '../types';
declare const MenuItem: MenuItemComponent;
export default MenuItem;
import type { WrapperComponent } from '../types';
declare const Wrapper: WrapperComponent;
export default Wrapper;
import type { FC } from 'react';
import type { Components, FormatOptionLabel, GetOptionLabel, Option, OnSelect } from './types';
export declare type DropdownProps = {
components: Components;
getOptionLabel: GetOptionLabel;
formatOptionLabel: FormatOptionLabel;
options: Option[];
onSelect: OnSelect;
closeMenu: () => void;
};
declare const Dropdown: FC<DropdownProps>;
export default Dropdown;
import type { FC } from 'react';
import type { Components, FormatOptionLabel, GetOptionLabel, Option, OnSelect } from './types';
export declare type DropdownItemProps = {
components: Components;
getOptionLabel: GetOptionLabel;
formatOptionLabel: FormatOptionLabel;
option: Option;
isHighlighted: boolean;
onSelect: OnSelect;
setHighlightedIndex: (index: number) => void;
index: number;
};
declare const DropdownItem: FC<DropdownItemProps>;
export default DropdownItem;
export { default } from './Autocomplete';
export type { AutocompleteProps, } from './Autocomplete';
export type { WrapperComponent, InputComponentProps, InputComponent, MenuComponent, MenuItemProps, MenuItemComponent, Components, Option, LoadOptionsResponse, LoadOptions, GetOptionLabel, FormatOptionLabel, OnSelect, } from './types';
import type { ComponentType, HTMLProps, ReactNode, Ref, SyntheticEvent } from 'react';
export declare type WrapperComponent = ComponentType;
export declare type InputComponentProps = HTMLProps<HTMLInputElement> & {
$hasError: boolean;
$hasWarning: boolean;
};
export declare type InputComponent = ComponentType<InputComponentProps>;
export declare type MenuComponent = ComponentType<{
ref: Ref<HTMLElement>;
}>;
export declare type MenuItemProps = HTMLProps<HTMLDivElement> & {
$isHighlighted: boolean;
};
export declare type MenuItemComponent = ComponentType<MenuItemProps>;
export declare type Components = {
Wrapper: WrapperComponent;
Input: InputComponent;
Menu: MenuComponent;
MenuItem: MenuItemComponent;
};
export declare type Option = {
[key: string]: any;
};
export declare type LoadOptionsResponse = {
options: Option[];
};
export declare type LoadOptions = (inputValue: string) => LoadOptionsResponse | Promise<LoadOptionsResponse>;
export declare type GetOptionLabel = (option: Option) => string;
export declare type FormatOptionLabel = (Option: Option) => ReactNode;
export declare type OnChange = (event: SyntheticEvent<HTMLInputElement>) => void;
export declare type OnSelect = (value: string, option: Option) => void;