UNPKG

3.83 kBPlain TextView Raw
1import * as React from "react";
2import { createComponent } from "reakit-system/createComponent";
3import { createHook } from "reakit-system/createHook";
4import { useLiveRef } from "reakit-utils/useLiveRef";
5import { BoxOptions, BoxHTMLProps, useBox } from "../Box/Box";
6import {
7 CompositeItemOptions,
8 CompositeItemHTMLProps,
9 useCompositeItem,
10} from "../Composite/CompositeItem";
11import { unstable_ComboboxStateReturn } from "./ComboboxState";
12import { COMBOBOX_ITEM_KEYS } from "./__keys";
13import { getItemId } from "./__utils/getItemId";
14import { Item } from "./__utils/types";
15
16export const unstable_useComboboxItem = createHook<
17 unstable_ComboboxItemOptions,
18 unstable_ComboboxItemHTMLProps
19>({
20 name: "ComboboxItem",
21 compose: useBox,
22 keys: COMBOBOX_ITEM_KEYS,
23
24 propsAreEqual(prev, next) {
25 if (prev.value !== next.value) return false;
26 if (!prev.value || !next.value || !prev.baseId || !next.baseId) {
27 return useCompositeItem.unstable_propsAreEqual(prev, next);
28 }
29 const {
30 currentValue: prevCurrentValue,
31 inputValue: prevInputValue,
32 // @ts-ignore
33 matches: prevMatches,
34 ...prevProps
35 } = prev;
36 const {
37 currentValue: nextCurrentValue,
38 inputValue: nextInputValue,
39 // @ts-ignore
40 matches: nextMatches,
41 ...nextProps
42 } = next;
43 if (prevCurrentValue !== nextCurrentValue) {
44 if (next.value === prevCurrentValue || next.value === nextCurrentValue) {
45 return false;
46 }
47 }
48 const prevId = getItemId(prev.baseId, prev.value, prev.id);
49 const nextId = getItemId(next.baseId, next.value, prev.id);
50 return useCompositeItem.unstable_propsAreEqual(
51 { ...prevProps, id: prevId },
52 { ...nextProps, id: nextId }
53 );
54 },
55
56 useOptions(options) {
57 const trulyDisabled = options.disabled && !options.focusable;
58 const value = trulyDisabled ? undefined : options.value;
59
60 const registerItem = React.useCallback(
61 (item: Item) => {
62 if (options.visible) {
63 options.registerItem?.({ ...item, value });
64 }
65 },
66 [options.registerItem, options.visible, value]
67 );
68
69 if (options.id || !options.baseId || !options.value) {
70 return { ...options, registerItem };
71 }
72
73 const id = getItemId(options.baseId, options.value, options.id);
74 return { ...options, registerItem, id };
75 },
76
77 useProps(options, { onClick: htmlOnClick, ...htmlProps }) {
78 const onClickRef = useLiveRef(htmlOnClick);
79
80 const onClick = React.useCallback(
81 (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
82 onClickRef.current?.(event);
83 if (event.defaultPrevented) return;
84 if (!options.value) return;
85 options.hide?.();
86 options.setInputValue?.(options.value);
87 },
88 [options.hide, options.setInputValue, options.value]
89 );
90
91 return {
92 children: options.value,
93 onClick,
94 tabIndex: -1,
95 ...htmlProps,
96 };
97 },
98});
99
100export const unstable_ComboboxItem = createComponent({
101 as: "span",
102 memo: true,
103 useHook: unstable_useComboboxItem,
104});
105
106export type unstable_ComboboxItemOptions = BoxOptions &
107 CompositeItemOptions &
108 Pick<
109 Partial<unstable_ComboboxStateReturn>,
110 "currentValue" | "inputValue" | "hide" | "visible"
111 > &
112 Pick<unstable_ComboboxStateReturn, "setInputValue" | "registerItem"> & {
113 /**
114 * Item's value that will be used to fill input value and filter `matches`
115 * based on the input value. You can omit this for items that perform
116 * actions other than filling a form. For example, items may open a dialog.
117 */
118 value?: string;
119 };
120
121export type unstable_ComboboxItemHTMLProps = BoxHTMLProps &
122 CompositeItemHTMLProps;
123
124export type unstable_ComboboxItemProps = unstable_ComboboxItemOptions &
125 unstable_ComboboxItemHTMLProps;