chore: Simpler autofocus of function call (#39946)

## Description

Improves the autofocus logic as per
https://github.com/appsmithorg/appsmith/pull/39916#discussion_r2014507020



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- When adding a new function field, it now automatically receives focus,
making the configuration process smoother and more intuitive.
- The transition for adding fields has been streamlined to improve user
interaction without distractions from unnecessary scrolling behavior.
- New state management for tracking the most recently added function
field enhances user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Hetu Nandu 2025-03-27 12:28:30 +05:30 committed by GitHub
parent afad67b434
commit 3f696facef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 22 additions and 40 deletions

View File

@ -1,5 +1,5 @@
import { Button, Text } from "@appsmith/ads";
import { default as React, useCallback, useRef, useEffect } from "react";
import React, { useCallback, useState } from "react";
import type { FieldArrayFieldsProps } from "redux-form";
import styled from "styled-components";
import { v4 as uuid } from "uuid";
@ -29,17 +29,19 @@ export const FunctionCallingConfigForm = ({
fields,
formName,
}: FunctionCallingConfigFormProps) => {
const latestFieldRef = useRef<HTMLDivElement>(null);
const previousFieldsLength = useRef(fields.length);
const [newlyAddedId, setNewlyAddedId] = useState<string | null>(null);
const handleAddFunctionButtonClick = useCallback(() => {
const id = uuid();
fields.push({
id: uuid(),
id,
description: "",
entityId: "",
isApprovalRequired: false,
entityType: "Query",
});
setNewlyAddedId(id);
}, [fields]);
const handleRemoveToolButtonClick = useCallback(
@ -49,38 +51,6 @@ export const FunctionCallingConfigForm = ({
[fields],
);
useEffect(
function handleAddFunction() {
// Only scroll and focus if a new field was added (length increased)
if (
fields.length > previousFieldsLength.current &&
latestFieldRef.current
) {
// Scroll the new field into view
latestFieldRef.current.scrollIntoView({
behavior: "smooth",
block: "center",
});
// Focus the menu button in the latest field
// The menu button is rendered by the Menu component from @appsmith/ads
const menuButton = latestFieldRef.current.querySelector(
"button.rc-select-selector",
);
if (menuButton) {
// Use setTimeout to ensure the button is fully rendered
setTimeout(() => {
(menuButton as HTMLButtonElement).focus();
}, 100);
}
}
previousFieldsLength.current = fields.length;
},
[fields.length],
);
return (
<>
<Header>
@ -108,14 +78,21 @@ export const FunctionCallingConfigForm = ({
) : (
<ConfigItems>
{fields.map((field, index) => {
const isLastField = index === fields.length - 1;
const fieldValue = fields.get(index);
const isNewlyAdded = fieldValue.id === newlyAddedId;
// Reset the newly added ID after rendering to ensure focus only happens once
if (isNewlyAdded) {
setTimeout(() => setNewlyAddedId(null), 100);
}
return (
<div key={field} ref={isLastField ? latestFieldRef : undefined}>
<div key={field}>
<FunctionCallingConfigToolField
fieldPath={field}
formName={formName}
index={index}
isLastAdded={isNewlyAdded}
onRemove={handleRemoveToolButtonClick}
/>
</div>

View File

@ -12,6 +12,7 @@ interface FunctionCallingConfigToolFieldProps {
formName: string;
index: number;
onRemove: (index: number) => void;
isLastAdded?: boolean;
}
const ConfigItemRoot = styled.div`
@ -39,6 +40,7 @@ const ConfigItemSelectWrapper = styled.div`
export const FunctionCallingConfigToolField = ({
index,
isLastAdded,
onRemove,
...props
}: FunctionCallingConfigToolFieldProps) => {
@ -100,6 +102,7 @@ export const FunctionCallingConfigToolField = ({
<ConfigItemRow>
<ConfigItemSelectWrapper>
<FunctionCallingMenuField
autoFocus={isLastAdded}
name={`${props.fieldPath}.entityId`}
onValueChange={handleFunctionChange}
/>

View File

@ -1,3 +1,4 @@
import React, { useCallback, useMemo } from "react";
import {
Button,
Menu,
@ -12,7 +13,6 @@ import {
MenuSubTrigger,
MenuSubContent,
} from "@appsmith/ads";
import React, { useCallback, useMemo } from "react";
import { Field, type WrappedFieldProps, type BaseFieldProps } from "redux-form";
import type {
FunctionCallingEntityType,
@ -35,6 +35,7 @@ interface FunctionCallingMenuFieldProps {
value: string,
entityType: FunctionCallingEntityType,
) => void;
autoFocus?: boolean;
}
interface FieldRenderProps
@ -82,7 +83,7 @@ const getSelectedValueInfo = (
};
const FunctionCallingMenuFieldRender = (props: FieldRenderProps) => {
const { children, disabled, input, onValueChange } = props;
const { autoFocus, children, disabled, input, onValueChange } = props;
const options = useSelector(selectEntityOptions);
const dispatch = useDispatch();
@ -154,6 +155,7 @@ const FunctionCallingMenuFieldRender = (props: FieldRenderProps) => {
<Menu>
<MenuTrigger>
<Button
autoFocus={autoFocus}
className="rc-select-selector"
disabled={disabled}
endIcon="arrow-down-s-line"