@gravity-ui/aikit 2.22.1 → 2.22.2-beta.702b45e371aa44880e4fb2b41d062ead4c26a19c.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/components/molecules/PromptInputBody/PromptInputBody.js +31 -1
- package/build/cjs/components/molecules/PromptInputBody/PromptInputBody.js.map +1 -1
- package/build/cjs/package.json +1 -1
- package/build/docs/components/molecules/PromptInputBody.md +1 -0
- package/build/esm/components/molecules/PromptInputBody/PromptInputBody.js +33 -3
- package/build/esm/components/molecules/PromptInputBody/PromptInputBody.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/package.json +1 -1
|
@@ -30,6 +30,26 @@ exports.PromptInputBody = (0, react_1.forwardRef)((props, ref) => {
|
|
|
30
30
|
const resolvedSize = (0, useMobileControlSize_1.useMobileControlSize)(size, 'l', 'xl');
|
|
31
31
|
const hasHandledInitialFocusRef = (0, react_1.useRef)(false);
|
|
32
32
|
const pendingInitialPointerIdRef = (0, react_1.useRef)(null);
|
|
33
|
+
const controlRef = (0, react_1.useRef)(null);
|
|
34
|
+
const isFocusedRef = (0, react_1.useRef)(false);
|
|
35
|
+
// `autoFocus` does nothing for a textarea that is already disabled on mount, which is what
|
|
36
|
+
// happens when the input is remounted while an answer is still loading. Remember the
|
|
37
|
+
// request and honour it as soon as the textarea is enabled.
|
|
38
|
+
const awaitsAutoFocusRef = (0, react_1.useRef)(autoFocus && disabledInput);
|
|
39
|
+
const setControlRef = (0, uikit_1.useForkRef)(ref, controlRef);
|
|
40
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
41
|
+
if (disabledInput || !(isFocusedRef.current || awaitsAutoFocusRef.current)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
awaitsAutoFocusRef.current = false;
|
|
45
|
+
const textarea = controlRef.current;
|
|
46
|
+
const activeElement = textarea === null || textarea === void 0 ? void 0 : textarea.ownerDocument.activeElement;
|
|
47
|
+
// Do not pull the focus back if it has moved to another element in the meantime.
|
|
48
|
+
if (!textarea || (activeElement && activeElement !== textarea.ownerDocument.body)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
textarea.focus();
|
|
52
|
+
}, [disabledInput]);
|
|
33
53
|
const handlePointerDown = (event) => {
|
|
34
54
|
pendingInitialPointerIdRef.current =
|
|
35
55
|
!hasHandledInitialFocusRef.current && event.isPrimary && event.button === 0
|
|
@@ -49,6 +69,7 @@ exports.PromptInputBody = (0, react_1.forwardRef)((props, ref) => {
|
|
|
49
69
|
}
|
|
50
70
|
};
|
|
51
71
|
const handleFocus = (event) => {
|
|
72
|
+
isFocusedRef.current = true;
|
|
52
73
|
if (hasHandledInitialFocusRef.current) {
|
|
53
74
|
return;
|
|
54
75
|
}
|
|
@@ -62,12 +83,21 @@ exports.PromptInputBody = (0, react_1.forwardRef)((props, ref) => {
|
|
|
62
83
|
scheduleCaretAtEndIfCollapsed(textarea);
|
|
63
84
|
}
|
|
64
85
|
};
|
|
86
|
+
const handleBlur = (event) => {
|
|
87
|
+
// The browser blurs a focused element as soon as it becomes disabled. The input is
|
|
88
|
+
// only blocked while the answer is loading, so keep it marked as focused and take the
|
|
89
|
+
// focus back once it is enabled again instead of leaving the user on `document.body`.
|
|
90
|
+
if (event.currentTarget.disabled) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
isFocusedRef.current = false;
|
|
94
|
+
};
|
|
65
95
|
// If custom content is provided, render it
|
|
66
96
|
if (children) {
|
|
67
97
|
return ((0, jsx_runtime_1.jsx)("div", { className: b(null, className), "data-qa": qa, children: children }));
|
|
68
98
|
}
|
|
69
99
|
// Render default textarea
|
|
70
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: b(null, className), "data-qa": qa, children: (0, jsx_runtime_1.jsx)(uikit_1.TextArea, { controlRef:
|
|
100
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: b(null, className), "data-qa": qa, children: (0, jsx_runtime_1.jsx)(uikit_1.TextArea, { controlRef: setControlRef, size: resolvedSize, value: value, placeholder: placeholder, minRows: minRows, maxRows: maxRows, autoFocus: autoFocus, disabled: disabledInput, onUpdate: onChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: onKeyDown, view: "clear", className: b('textarea'), controlProps: {
|
|
71
101
|
className: b('textarea-control', inputClassName),
|
|
72
102
|
maxLength,
|
|
73
103
|
onPointerDown: handlePointerDown,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptInputBody.js","sourceRoot":"../../../../../src","sources":["components/molecules/PromptInputBody/PromptInputBody.tsx"],"names":[],"mappings":"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","sourcesContent":["import {ReactNode, forwardRef, useRef} from 'react';\n\nimport type {TextAreaProps} from '@gravity-ui/uikit';\nimport {TextArea} from '@gravity-ui/uikit';\n\nimport {useMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {block} from '../../../utils/cn';\n\nimport './PromptInputBody.scss';\n\nconst b = block('prompt-input-body');\n\nconst scheduleCaretAtEndIfCollapsed = (textarea: HTMLTextAreaElement) => {\n requestAnimationFrame(() => {\n if (\n textarea.isConnected &&\n textarea.ownerDocument.activeElement === textarea &&\n textarea.selectionStart === textarea.selectionEnd\n ) {\n const caretPosition = textarea.value.length;\n textarea.setSelectionRange(caretPosition, caretPosition);\n }\n });\n};\n\n/**\n * Props for the PromptInputBody component\n */\nexport type PromptInputBodyProps = {\n /** Value of the textarea */\n value?: string;\n /** Placeholder text */\n placeholder?: string;\n /** Maximum length of input */\n maxLength?: number;\n /** Size of the textarea. Defaults to `l`, or `xl` in mobile mode */\n size?: TextAreaProps['size'];\n /** Minimum number of rows */\n minRows?: number;\n /** Maximum number of rows */\n maxRows?: number;\n /** Auto focus on mount */\n autoFocus?: boolean;\n /** Disabled state for input */\n disabledInput?: boolean;\n /** Change handler */\n onChange?: (value: string) => void;\n /** Key down handler */\n onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n /** Custom content to replace the default textarea */\n children?: ReactNode;\n /** Additional CSS class */\n className?: string;\n /** Additional CSS class for input element */\n inputClassName?: string;\n /** QA/test identifier */\n qa?: string;\n};\n\n/**\n * PromptInputBody component displays the main input area\n * with textarea or custom content\n *\n * @param props - Component props\n * @returns React component\n */\nexport const PromptInputBody = forwardRef<HTMLTextAreaElement, PromptInputBodyProps>(\n (props, ref) => {\n const {\n value,\n placeholder,\n maxLength,\n size,\n minRows = 1,\n maxRows = 15,\n autoFocus = false,\n disabledInput = false,\n onChange,\n onKeyDown,\n children,\n className,\n inputClassName,\n qa,\n } = props;\n\n const resolvedSize = useMobileControlSize(size, 'l', 'xl');\n const hasHandledInitialFocusRef = useRef(false);\n const pendingInitialPointerIdRef = useRef<number | null>(null);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n pendingInitialPointerIdRef.current =\n !hasHandledInitialFocusRef.current && event.isPrimary && event.button === 0\n ? event.pointerId\n : null;\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n if (pendingInitialPointerIdRef.current !== event.pointerId) {\n return;\n }\n\n pendingInitialPointerIdRef.current = null;\n scheduleCaretAtEndIfCollapsed(event.currentTarget);\n };\n\n const handlePointerCancel = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n if (pendingInitialPointerIdRef.current === event.pointerId) {\n pendingInitialPointerIdRef.current = null;\n }\n };\n\n const handleFocus = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n if (hasHandledInitialFocusRef.current) {\n return;\n }\n\n hasHandledInitialFocusRef.current = true;\n\n const textarea = event.currentTarget;\n if (!textarea.value) {\n pendingInitialPointerIdRef.current = null;\n return;\n }\n\n if (pendingInitialPointerIdRef.current === null) {\n scheduleCaretAtEndIfCollapsed(textarea);\n }\n };\n\n // If custom content is provided, render it\n if (children) {\n return (\n <div className={b(null, className)} data-qa={qa}>\n {children}\n </div>\n );\n }\n\n // Render default textarea\n return (\n <div className={b(null, className)} data-qa={qa}>\n <TextArea\n controlRef={ref}\n size={resolvedSize}\n value={value}\n placeholder={placeholder}\n minRows={minRows}\n maxRows={maxRows}\n autoFocus={autoFocus}\n disabled={disabledInput}\n onUpdate={onChange}\n onFocus={handleFocus}\n onKeyDown={onKeyDown}\n view=\"clear\"\n className={b('textarea')}\n controlProps={{\n className: b('textarea-control', inputClassName),\n maxLength,\n onPointerDown: handlePointerDown,\n onPointerUp: handlePointerUp,\n onPointerCancel: handlePointerCancel,\n }}\n />\n </div>\n );\n },\n);\n\nPromptInputBody.displayName = 'PromptInputBody';\n"]}
|
|
1
|
+
{"version":3,"file":"PromptInputBody.js","sourceRoot":"../../../../../src","sources":["components/molecules/PromptInputBody/PromptInputBody.tsx"],"names":[],"mappings":"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","sourcesContent":["import {ReactNode, forwardRef, useLayoutEffect, useRef} from 'react';\n\nimport type {TextAreaProps} from '@gravity-ui/uikit';\nimport {TextArea, useForkRef} from '@gravity-ui/uikit';\n\nimport {useMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {block} from '../../../utils/cn';\n\nimport './PromptInputBody.scss';\n\nconst b = block('prompt-input-body');\n\nconst scheduleCaretAtEndIfCollapsed = (textarea: HTMLTextAreaElement) => {\n requestAnimationFrame(() => {\n if (\n textarea.isConnected &&\n textarea.ownerDocument.activeElement === textarea &&\n textarea.selectionStart === textarea.selectionEnd\n ) {\n const caretPosition = textarea.value.length;\n textarea.setSelectionRange(caretPosition, caretPosition);\n }\n });\n};\n\n/**\n * Props for the PromptInputBody component\n */\nexport type PromptInputBodyProps = {\n /** Value of the textarea */\n value?: string;\n /** Placeholder text */\n placeholder?: string;\n /** Maximum length of input */\n maxLength?: number;\n /** Size of the textarea. Defaults to `l`, or `xl` in mobile mode */\n size?: TextAreaProps['size'];\n /** Minimum number of rows */\n minRows?: number;\n /** Maximum number of rows */\n maxRows?: number;\n /** Auto focus on mount */\n autoFocus?: boolean;\n /** Disabled state for input */\n disabledInput?: boolean;\n /** Change handler */\n onChange?: (value: string) => void;\n /** Key down handler */\n onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n /** Custom content to replace the default textarea */\n children?: ReactNode;\n /** Additional CSS class */\n className?: string;\n /** Additional CSS class for input element */\n inputClassName?: string;\n /** QA/test identifier */\n qa?: string;\n};\n\n/**\n * PromptInputBody component displays the main input area\n * with textarea or custom content\n *\n * @param props - Component props\n * @returns React component\n */\nexport const PromptInputBody = forwardRef<HTMLTextAreaElement, PromptInputBodyProps>(\n (props, ref) => {\n const {\n value,\n placeholder,\n maxLength,\n size,\n minRows = 1,\n maxRows = 15,\n autoFocus = false,\n disabledInput = false,\n onChange,\n onKeyDown,\n children,\n className,\n inputClassName,\n qa,\n } = props;\n\n const resolvedSize = useMobileControlSize(size, 'l', 'xl');\n const hasHandledInitialFocusRef = useRef(false);\n const pendingInitialPointerIdRef = useRef<number | null>(null);\n const controlRef = useRef<HTMLTextAreaElement | null>(null);\n const isFocusedRef = useRef(false);\n // `autoFocus` does nothing for a textarea that is already disabled on mount, which is what\n // happens when the input is remounted while an answer is still loading. Remember the\n // request and honour it as soon as the textarea is enabled.\n const awaitsAutoFocusRef = useRef(autoFocus && disabledInput);\n const setControlRef = useForkRef(ref, controlRef);\n\n useLayoutEffect(() => {\n if (disabledInput || !(isFocusedRef.current || awaitsAutoFocusRef.current)) {\n return;\n }\n\n awaitsAutoFocusRef.current = false;\n\n const textarea = controlRef.current;\n const activeElement = textarea?.ownerDocument.activeElement;\n\n // Do not pull the focus back if it has moved to another element in the meantime.\n if (!textarea || (activeElement && activeElement !== textarea.ownerDocument.body)) {\n return;\n }\n\n textarea.focus();\n }, [disabledInput]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n pendingInitialPointerIdRef.current =\n !hasHandledInitialFocusRef.current && event.isPrimary && event.button === 0\n ? event.pointerId\n : null;\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n if (pendingInitialPointerIdRef.current !== event.pointerId) {\n return;\n }\n\n pendingInitialPointerIdRef.current = null;\n scheduleCaretAtEndIfCollapsed(event.currentTarget);\n };\n\n const handlePointerCancel = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n if (pendingInitialPointerIdRef.current === event.pointerId) {\n pendingInitialPointerIdRef.current = null;\n }\n };\n\n const handleFocus = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n isFocusedRef.current = true;\n\n if (hasHandledInitialFocusRef.current) {\n return;\n }\n\n hasHandledInitialFocusRef.current = true;\n\n const textarea = event.currentTarget;\n if (!textarea.value) {\n pendingInitialPointerIdRef.current = null;\n return;\n }\n\n if (pendingInitialPointerIdRef.current === null) {\n scheduleCaretAtEndIfCollapsed(textarea);\n }\n };\n\n const handleBlur = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n // The browser blurs a focused element as soon as it becomes disabled. The input is\n // only blocked while the answer is loading, so keep it marked as focused and take the\n // focus back once it is enabled again instead of leaving the user on `document.body`.\n if (event.currentTarget.disabled) {\n return;\n }\n\n isFocusedRef.current = false;\n };\n\n // If custom content is provided, render it\n if (children) {\n return (\n <div className={b(null, className)} data-qa={qa}>\n {children}\n </div>\n );\n }\n\n // Render default textarea\n return (\n <div className={b(null, className)} data-qa={qa}>\n <TextArea\n controlRef={setControlRef}\n size={resolvedSize}\n value={value}\n placeholder={placeholder}\n minRows={minRows}\n maxRows={maxRows}\n autoFocus={autoFocus}\n disabled={disabledInput}\n onUpdate={onChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={onKeyDown}\n view=\"clear\"\n className={b('textarea')}\n controlProps={{\n className: b('textarea-control', inputClassName),\n maxLength,\n onPointerDown: handlePointerDown,\n onPointerUp: handlePointerUp,\n onPointerCancel: handlePointerCancel,\n }}\n />\n </div>\n );\n },\n);\n\nPromptInputBody.displayName = 'PromptInputBody';\n"]}
|
package/build/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.22.
|
|
1
|
+
{"version":"2.22.2-beta.702b45e371aa44880e4fb2b41d062ead4c26a19c.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
|
@@ -9,6 +9,7 @@ A body component for prompt input that displays a textarea with auto-growing cap
|
|
|
9
9
|
- **Maximum Length**: Optional character limit
|
|
10
10
|
- **Custom Content**: Replace default textarea with any custom React content
|
|
11
11
|
- **Keyboard Support**: Full keyboard event handling
|
|
12
|
+
- **Focus Preservation**: A textarea that was focused when `disabledInput` turned on gets the focus back once it is enabled again, and a textarea mounted with `autoFocus` while already disabled takes the focus as soon as it is enabled — in both cases only while nothing else holds the focus
|
|
12
13
|
|
|
13
14
|
## Usage
|
|
14
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useRef } from 'react';
|
|
3
|
-
import { TextArea } from '@gravity-ui/uikit';
|
|
2
|
+
import { forwardRef, useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import { TextArea, useForkRef } from '@gravity-ui/uikit';
|
|
4
4
|
import { useMobileControlSize } from "../../../hooks/useMobileControlSize.js";
|
|
5
5
|
import { block } from "../../../utils/cn.js";
|
|
6
6
|
import "./PromptInputBody.css";
|
|
@@ -27,6 +27,26 @@ export const PromptInputBody = forwardRef((props, ref) => {
|
|
|
27
27
|
const resolvedSize = useMobileControlSize(size, 'l', 'xl');
|
|
28
28
|
const hasHandledInitialFocusRef = useRef(false);
|
|
29
29
|
const pendingInitialPointerIdRef = useRef(null);
|
|
30
|
+
const controlRef = useRef(null);
|
|
31
|
+
const isFocusedRef = useRef(false);
|
|
32
|
+
// `autoFocus` does nothing for a textarea that is already disabled on mount, which is what
|
|
33
|
+
// happens when the input is remounted while an answer is still loading. Remember the
|
|
34
|
+
// request and honour it as soon as the textarea is enabled.
|
|
35
|
+
const awaitsAutoFocusRef = useRef(autoFocus && disabledInput);
|
|
36
|
+
const setControlRef = useForkRef(ref, controlRef);
|
|
37
|
+
useLayoutEffect(() => {
|
|
38
|
+
if (disabledInput || !(isFocusedRef.current || awaitsAutoFocusRef.current)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
awaitsAutoFocusRef.current = false;
|
|
42
|
+
const textarea = controlRef.current;
|
|
43
|
+
const activeElement = textarea === null || textarea === void 0 ? void 0 : textarea.ownerDocument.activeElement;
|
|
44
|
+
// Do not pull the focus back if it has moved to another element in the meantime.
|
|
45
|
+
if (!textarea || (activeElement && activeElement !== textarea.ownerDocument.body)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
textarea.focus();
|
|
49
|
+
}, [disabledInput]);
|
|
30
50
|
const handlePointerDown = (event) => {
|
|
31
51
|
pendingInitialPointerIdRef.current =
|
|
32
52
|
!hasHandledInitialFocusRef.current && event.isPrimary && event.button === 0
|
|
@@ -46,6 +66,7 @@ export const PromptInputBody = forwardRef((props, ref) => {
|
|
|
46
66
|
}
|
|
47
67
|
};
|
|
48
68
|
const handleFocus = (event) => {
|
|
69
|
+
isFocusedRef.current = true;
|
|
49
70
|
if (hasHandledInitialFocusRef.current) {
|
|
50
71
|
return;
|
|
51
72
|
}
|
|
@@ -59,12 +80,21 @@ export const PromptInputBody = forwardRef((props, ref) => {
|
|
|
59
80
|
scheduleCaretAtEndIfCollapsed(textarea);
|
|
60
81
|
}
|
|
61
82
|
};
|
|
83
|
+
const handleBlur = (event) => {
|
|
84
|
+
// The browser blurs a focused element as soon as it becomes disabled. The input is
|
|
85
|
+
// only blocked while the answer is loading, so keep it marked as focused and take the
|
|
86
|
+
// focus back once it is enabled again instead of leaving the user on `document.body`.
|
|
87
|
+
if (event.currentTarget.disabled) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
isFocusedRef.current = false;
|
|
91
|
+
};
|
|
62
92
|
// If custom content is provided, render it
|
|
63
93
|
if (children) {
|
|
64
94
|
return (_jsx("div", { className: b(null, className), "data-qa": qa, children: children }));
|
|
65
95
|
}
|
|
66
96
|
// Render default textarea
|
|
67
|
-
return (_jsx("div", { className: b(null, className), "data-qa": qa, children: _jsx(TextArea, { controlRef:
|
|
97
|
+
return (_jsx("div", { className: b(null, className), "data-qa": qa, children: _jsx(TextArea, { controlRef: setControlRef, size: resolvedSize, value: value, placeholder: placeholder, minRows: minRows, maxRows: maxRows, autoFocus: autoFocus, disabled: disabledInput, onUpdate: onChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: onKeyDown, view: "clear", className: b('textarea'), controlProps: {
|
|
68
98
|
className: b('textarea-control', inputClassName),
|
|
69
99
|
maxLength,
|
|
70
100
|
onPointerDown: handlePointerDown,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptInputBody.js","sourceRoot":"../../../../../src","sources":["components/molecules/PromptInputBody/PromptInputBody.tsx"],"names":[],"mappings":"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","sourcesContent":["import {ReactNode, forwardRef, useRef} from 'react';\n\nimport type {TextAreaProps} from '@gravity-ui/uikit';\nimport {TextArea} from '@gravity-ui/uikit';\n\nimport {useMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {block} from '../../../utils/cn';\n\nimport './PromptInputBody.scss';\n\nconst b = block('prompt-input-body');\n\nconst scheduleCaretAtEndIfCollapsed = (textarea: HTMLTextAreaElement) => {\n requestAnimationFrame(() => {\n if (\n textarea.isConnected &&\n textarea.ownerDocument.activeElement === textarea &&\n textarea.selectionStart === textarea.selectionEnd\n ) {\n const caretPosition = textarea.value.length;\n textarea.setSelectionRange(caretPosition, caretPosition);\n }\n });\n};\n\n/**\n * Props for the PromptInputBody component\n */\nexport type PromptInputBodyProps = {\n /** Value of the textarea */\n value?: string;\n /** Placeholder text */\n placeholder?: string;\n /** Maximum length of input */\n maxLength?: number;\n /** Size of the textarea. Defaults to `l`, or `xl` in mobile mode */\n size?: TextAreaProps['size'];\n /** Minimum number of rows */\n minRows?: number;\n /** Maximum number of rows */\n maxRows?: number;\n /** Auto focus on mount */\n autoFocus?: boolean;\n /** Disabled state for input */\n disabledInput?: boolean;\n /** Change handler */\n onChange?: (value: string) => void;\n /** Key down handler */\n onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n /** Custom content to replace the default textarea */\n children?: ReactNode;\n /** Additional CSS class */\n className?: string;\n /** Additional CSS class for input element */\n inputClassName?: string;\n /** QA/test identifier */\n qa?: string;\n};\n\n/**\n * PromptInputBody component displays the main input area\n * with textarea or custom content\n *\n * @param props - Component props\n * @returns React component\n */\nexport const PromptInputBody = forwardRef<HTMLTextAreaElement, PromptInputBodyProps>(\n (props, ref) => {\n const {\n value,\n placeholder,\n maxLength,\n size,\n minRows = 1,\n maxRows = 15,\n autoFocus = false,\n disabledInput = false,\n onChange,\n onKeyDown,\n children,\n className,\n inputClassName,\n qa,\n } = props;\n\n const resolvedSize = useMobileControlSize(size, 'l', 'xl');\n const hasHandledInitialFocusRef = useRef(false);\n const pendingInitialPointerIdRef = useRef<number | null>(null);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n pendingInitialPointerIdRef.current =\n !hasHandledInitialFocusRef.current && event.isPrimary && event.button === 0\n ? event.pointerId\n : null;\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n if (pendingInitialPointerIdRef.current !== event.pointerId) {\n return;\n }\n\n pendingInitialPointerIdRef.current = null;\n scheduleCaretAtEndIfCollapsed(event.currentTarget);\n };\n\n const handlePointerCancel = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n if (pendingInitialPointerIdRef.current === event.pointerId) {\n pendingInitialPointerIdRef.current = null;\n }\n };\n\n const handleFocus = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n if (hasHandledInitialFocusRef.current) {\n return;\n }\n\n hasHandledInitialFocusRef.current = true;\n\n const textarea = event.currentTarget;\n if (!textarea.value) {\n pendingInitialPointerIdRef.current = null;\n return;\n }\n\n if (pendingInitialPointerIdRef.current === null) {\n scheduleCaretAtEndIfCollapsed(textarea);\n }\n };\n\n // If custom content is provided, render it\n if (children) {\n return (\n <div className={b(null, className)} data-qa={qa}>\n {children}\n </div>\n );\n }\n\n // Render default textarea\n return (\n <div className={b(null, className)} data-qa={qa}>\n <TextArea\n controlRef={ref}\n size={resolvedSize}\n value={value}\n placeholder={placeholder}\n minRows={minRows}\n maxRows={maxRows}\n autoFocus={autoFocus}\n disabled={disabledInput}\n onUpdate={onChange}\n onFocus={handleFocus}\n onKeyDown={onKeyDown}\n view=\"clear\"\n className={b('textarea')}\n controlProps={{\n className: b('textarea-control', inputClassName),\n maxLength,\n onPointerDown: handlePointerDown,\n onPointerUp: handlePointerUp,\n onPointerCancel: handlePointerCancel,\n }}\n />\n </div>\n );\n },\n);\n\nPromptInputBody.displayName = 'PromptInputBody';\n"]}
|
|
1
|
+
{"version":3,"file":"PromptInputBody.js","sourceRoot":"../../../../../src","sources":["components/molecules/PromptInputBody/PromptInputBody.tsx"],"names":[],"mappings":"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","sourcesContent":["import {ReactNode, forwardRef, useLayoutEffect, useRef} from 'react';\n\nimport type {TextAreaProps} from '@gravity-ui/uikit';\nimport {TextArea, useForkRef} from '@gravity-ui/uikit';\n\nimport {useMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {block} from '../../../utils/cn';\n\nimport './PromptInputBody.scss';\n\nconst b = block('prompt-input-body');\n\nconst scheduleCaretAtEndIfCollapsed = (textarea: HTMLTextAreaElement) => {\n requestAnimationFrame(() => {\n if (\n textarea.isConnected &&\n textarea.ownerDocument.activeElement === textarea &&\n textarea.selectionStart === textarea.selectionEnd\n ) {\n const caretPosition = textarea.value.length;\n textarea.setSelectionRange(caretPosition, caretPosition);\n }\n });\n};\n\n/**\n * Props for the PromptInputBody component\n */\nexport type PromptInputBodyProps = {\n /** Value of the textarea */\n value?: string;\n /** Placeholder text */\n placeholder?: string;\n /** Maximum length of input */\n maxLength?: number;\n /** Size of the textarea. Defaults to `l`, or `xl` in mobile mode */\n size?: TextAreaProps['size'];\n /** Minimum number of rows */\n minRows?: number;\n /** Maximum number of rows */\n maxRows?: number;\n /** Auto focus on mount */\n autoFocus?: boolean;\n /** Disabled state for input */\n disabledInput?: boolean;\n /** Change handler */\n onChange?: (value: string) => void;\n /** Key down handler */\n onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n /** Custom content to replace the default textarea */\n children?: ReactNode;\n /** Additional CSS class */\n className?: string;\n /** Additional CSS class for input element */\n inputClassName?: string;\n /** QA/test identifier */\n qa?: string;\n};\n\n/**\n * PromptInputBody component displays the main input area\n * with textarea or custom content\n *\n * @param props - Component props\n * @returns React component\n */\nexport const PromptInputBody = forwardRef<HTMLTextAreaElement, PromptInputBodyProps>(\n (props, ref) => {\n const {\n value,\n placeholder,\n maxLength,\n size,\n minRows = 1,\n maxRows = 15,\n autoFocus = false,\n disabledInput = false,\n onChange,\n onKeyDown,\n children,\n className,\n inputClassName,\n qa,\n } = props;\n\n const resolvedSize = useMobileControlSize(size, 'l', 'xl');\n const hasHandledInitialFocusRef = useRef(false);\n const pendingInitialPointerIdRef = useRef<number | null>(null);\n const controlRef = useRef<HTMLTextAreaElement | null>(null);\n const isFocusedRef = useRef(false);\n // `autoFocus` does nothing for a textarea that is already disabled on mount, which is what\n // happens when the input is remounted while an answer is still loading. Remember the\n // request and honour it as soon as the textarea is enabled.\n const awaitsAutoFocusRef = useRef(autoFocus && disabledInput);\n const setControlRef = useForkRef(ref, controlRef);\n\n useLayoutEffect(() => {\n if (disabledInput || !(isFocusedRef.current || awaitsAutoFocusRef.current)) {\n return;\n }\n\n awaitsAutoFocusRef.current = false;\n\n const textarea = controlRef.current;\n const activeElement = textarea?.ownerDocument.activeElement;\n\n // Do not pull the focus back if it has moved to another element in the meantime.\n if (!textarea || (activeElement && activeElement !== textarea.ownerDocument.body)) {\n return;\n }\n\n textarea.focus();\n }, [disabledInput]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n pendingInitialPointerIdRef.current =\n !hasHandledInitialFocusRef.current && event.isPrimary && event.button === 0\n ? event.pointerId\n : null;\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n if (pendingInitialPointerIdRef.current !== event.pointerId) {\n return;\n }\n\n pendingInitialPointerIdRef.current = null;\n scheduleCaretAtEndIfCollapsed(event.currentTarget);\n };\n\n const handlePointerCancel = (event: React.PointerEvent<HTMLTextAreaElement>) => {\n if (pendingInitialPointerIdRef.current === event.pointerId) {\n pendingInitialPointerIdRef.current = null;\n }\n };\n\n const handleFocus = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n isFocusedRef.current = true;\n\n if (hasHandledInitialFocusRef.current) {\n return;\n }\n\n hasHandledInitialFocusRef.current = true;\n\n const textarea = event.currentTarget;\n if (!textarea.value) {\n pendingInitialPointerIdRef.current = null;\n return;\n }\n\n if (pendingInitialPointerIdRef.current === null) {\n scheduleCaretAtEndIfCollapsed(textarea);\n }\n };\n\n const handleBlur = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n // The browser blurs a focused element as soon as it becomes disabled. The input is\n // only blocked while the answer is loading, so keep it marked as focused and take the\n // focus back once it is enabled again instead of leaving the user on `document.body`.\n if (event.currentTarget.disabled) {\n return;\n }\n\n isFocusedRef.current = false;\n };\n\n // If custom content is provided, render it\n if (children) {\n return (\n <div className={b(null, className)} data-qa={qa}>\n {children}\n </div>\n );\n }\n\n // Render default textarea\n return (\n <div className={b(null, className)} data-qa={qa}>\n <TextArea\n controlRef={setControlRef}\n size={resolvedSize}\n value={value}\n placeholder={placeholder}\n minRows={minRows}\n maxRows={maxRows}\n autoFocus={autoFocus}\n disabled={disabledInput}\n onUpdate={onChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={onKeyDown}\n view=\"clear\"\n className={b('textarea')}\n controlProps={{\n className: b('textarea-control', inputClassName),\n maxLength,\n onPointerDown: handlePointerDown,\n onPointerUp: handlePointerUp,\n onPointerCancel: handlePointerCancel,\n }}\n />\n </div>\n );\n },\n);\n\nPromptInputBody.displayName = 'PromptInputBody';\n"]}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.22.
|
|
1
|
+
{"version":"2.22.2-beta.702b45e371aa44880e4fb2b41d062ead4c26a19c.0","type":"module","sideEffects":["*.css","*.scss"]}
|
package/package.json
CHANGED