UNPKG

3.83 kBPlain TextView Raw
1import * as React from "react";
2import { createComponent } from "reakit-system/createComponent";
3import { createHook } from "reakit-system/createHook";
4import { isTextField } from "reakit-utils/isTextField";
5import { getDocument } from "reakit-utils/getDocument";
6import { isSelfTarget } from "reakit-utils/isSelfTarget";
7import { useLiveRef } from "reakit-utils/useLiveRef";
8import { useRole, RoleOptions, RoleHTMLProps } from "../Role/Role";
9import { CompositeStateReturn } from "./CompositeState";
10import { setTextFieldValue } from "./__utils/setTextFieldValue";
11import { COMPOSITE_ITEM_WIDGET_KEYS } from "./__keys";
12
13export type unstable_CompositeItemWidgetOptions = RoleOptions &
14 Pick<Partial<CompositeStateReturn>, "wrap"> &
15 Pick<
16 CompositeStateReturn,
17 "unstable_hasActiveWidget" | "unstable_setHasActiveWidget" | "currentId"
18 >;
19
20export type unstable_CompositeItemWidgetHTMLProps = RoleHTMLProps;
21
22export type unstable_CompositeItemWidgetProps = unstable_CompositeItemWidgetOptions &
23 unstable_CompositeItemWidgetHTMLProps;
24
25function focusCurrentItem(widget: Element, currentId?: string | null) {
26 if (currentId) {
27 getDocument(widget).getElementById(currentId)?.focus();
28 }
29}
30
31function getTextFieldValue(element: HTMLElement) {
32 return (element as HTMLInputElement).value;
33}
34
35export const unstable_useCompositeItemWidget = createHook<
36 unstable_CompositeItemWidgetOptions,
37 unstable_CompositeItemWidgetHTMLProps
38>({
39 name: "CompositeItemWidget",
40 compose: useRole,
41 keys: COMPOSITE_ITEM_WIDGET_KEYS,
42
43 useProps(
44 options,
45 {
46 onFocus: htmlOnFocus,
47 onBlur: htmlOnBlur,
48 onKeyDown: htmlOnKeyDown,
49 ...htmlProps
50 }
51 ) {
52 const initialValue = React.useRef("");
53 const onFocusRef = useLiveRef(htmlOnFocus);
54 const onBlurRef = useLiveRef(htmlOnBlur);
55 const onKeyDownRef = useLiveRef(htmlOnKeyDown);
56
57 const onFocus = React.useCallback(
58 (event: React.FocusEvent<HTMLElement>) => {
59 onFocusRef.current?.(event);
60 options.unstable_setHasActiveWidget?.(true);
61 if (isTextField(event.currentTarget)) {
62 initialValue.current = getTextFieldValue(event.currentTarget);
63 }
64 },
65 [options.unstable_setHasActiveWidget]
66 );
67
68 const onBlur = React.useCallback(
69 (event: React.FocusEvent) => {
70 onBlurRef.current?.(event);
71 options.unstable_setHasActiveWidget?.(false);
72 },
73 [options.unstable_setHasActiveWidget]
74 );
75
76 const onKeyDown = React.useCallback(
77 (event: React.KeyboardEvent<HTMLElement>) => {
78 onKeyDownRef.current?.(event);
79 if (event.defaultPrevented) return;
80 if (!isSelfTarget(event)) return;
81 if (event.nativeEvent.isComposing) return;
82 const element = event.currentTarget;
83 if (event.key === "Enter") {
84 if (isTextField(element)) {
85 const isMultilineTextField = element.tagName === "TEXTAREA";
86 // Make sure we can create new lines using Shift+Enter
87 if (isMultilineTextField && event.shiftKey) return;
88 // Make sure it'll not trigger a click on the parent button
89 event.preventDefault();
90 focusCurrentItem(element, options.currentId);
91 }
92 } else if (event.key === "Escape") {
93 focusCurrentItem(element, options.currentId);
94 if (isTextField(element)) {
95 setTextFieldValue(element, initialValue.current);
96 }
97 }
98 },
99 [options.currentId]
100 );
101
102 return {
103 tabIndex: options.unstable_hasActiveWidget ? 0 : -1,
104 "data-composite-item-widget": true,
105 onFocus,
106 onBlur,
107 onKeyDown,
108 ...htmlProps,
109 };
110 },
111});
112
113export const unstable_CompositeItemWidget = createComponent({
114 as: "div",
115 useHook: unstable_useCompositeItemWidget,
116});