@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.
@@ -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: ref, size: resolvedSize, value: value, placeholder: placeholder, minRows: minRows, maxRows: maxRows, autoFocus: autoFocus, disabled: disabledInput, onUpdate: onChange, onFocus: handleFocus, onKeyDown: onKeyDown, view: "clear", className: b('textarea'), controlProps: {
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"]}
@@ -1 +1 @@
1
- {"version":"2.22.1","type":"commonjs","sideEffects":["*.css","*.scss"]}
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: ref, size: resolvedSize, value: value, placeholder: placeholder, minRows: minRows, maxRows: maxRows, autoFocus: autoFocus, disabled: disabledInput, onUpdate: onChange, onFocus: handleFocus, onKeyDown: onKeyDown, view: "clear", className: b('textarea'), controlProps: {
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"]}
@@ -1 +1 @@
1
- {"version":"2.22.1","type":"module","sideEffects":["*.css","*.scss"]}
1
+ {"version":"2.22.2-beta.702b45e371aa44880e4fb2b41d062ead4c26a19c.0","type":"module","sideEffects":["*.css","*.scss"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/aikit",
3
- "version": "2.22.1",
3
+ "version": "2.22.2-beta.702b45e371aa44880e4fb2b41d062ead4c26a19c.0",
4
4
  "description": "Gravity UI base kit for building ai assistant chats",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",