Repository: apache/airflow
Pull request: Connect Trigger UI Conf with the flexible form
This PR is the continuation of [#45270](https://github.com/apache/airflow/pull/45270) by @jscheffl 

Future Work (Not decided, might need discussion):

1. Introduce a reset button 
2. Have a asterisk on the accordion item which contains a required field inside.
3. If we face error when Trigger button when clicked, related field should turn in error mode.

closes: #22408 #44858

<!-- Please keep an empty line above the dashes. -->
---
**^ Add meaningful description above**
Read the **[Pull Request Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)** for more information.
In case of fundamental code changes, an Airflow Improvement Proposal ([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals)) is needed.
In case of a new dependency, check compliance with the [ASF 3rd Party License Policy](https://www.apache.org/legal/resolved.html#category-x).
In case of backwards incompatible changes please leave a note in a newsfragment file, named `{pr_number}.significant.rst` or `{issue_number}.significant.rst`, in [newsfragments](https://github.com/apache/airflow/tree/main/newsfragments).


Problem statement:
Streamline trigger dag UX
Problem:

Triggering a dag run is a relatively simple process conceptually yet a user is run through multiple different redirects and views. This results in a clunky experience that is slower, causes one to lose context and even end up on a page where they didn't even start on.

Solution:

Handle the API calls within the client and have everything happen within a modal on the user's current page. The modal can handle confirmation events too.
This means that we should copy the logic from the Task Instance modal in `dag.html` and `dag.js`, create a new Trigger Dag modal, and update the relevant parts of `views.py`

Hints from issue discussion:
I can work on this if this is still relevant 
Assigned you @maxnathaniel. I think checking it should be the first thing to do and if you find it's fixed, making a comment about it would be useful as well, so that we can close the ticket :).
@maxnathaniel We should be merging [this](https://github.com/apache/airflow/pull/27063) PR soon, so you could branch off of that and get started. I think the next useful step would be to convert the trigger UI from a separate page to a modal. Like the actions in the graph and gantt views.
@bbovenzi ok great! Will take a look at that 
@bbovenzi  apologies, have been busy with work. Will continue where I had left off with this task this week and aim to push out something by end of week.

Commit message: accordion css

Patch to review:
diff --git a/airflow/ui/package.json b/airflow/ui/package.json
index 4c2a71d3302b7..6cec2a49e3ccc 100644
--- a/airflow/ui/package.json
+++ b/airflow/ui/package.json
@@ -45,7 +45,8 @@
     "react-syntax-highlighter": "^15.5.6",
     "remark-gfm": "^4.0.0",
     "use-debounce": "^10.0.3",
-    "usehooks-ts": "^3.1.0"
+    "usehooks-ts": "^3.1.0",
+    "zustand": "^5.0.3"
   },
   "devDependencies": {
     "@7nohe/openapi-react-query-codegen": "^1.6.0",
diff --git a/airflow/ui/pnpm-lock.yaml b/airflow/ui/pnpm-lock.yaml
index 54f8983d08125..21f05b5046569 100644
--- a/airflow/ui/pnpm-lock.yaml
+++ b/airflow/ui/pnpm-lock.yaml
@@ -98,6 +98,9 @@ importers:
       usehooks-ts:
         specifier: ^3.1.0
         version: 3.1.0(react@18.3.1)
+      zustand:
+        specifier: ^5.0.3
+        version: 5.0.3(@types/react@18.3.5)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1))
     devDependencies:
       '@7nohe/openapi-react-query-codegen':
         specifier: ^1.6.0
@@ -3888,6 +3891,24 @@ packages:
       react:
         optional: true
 
+  zustand@5.0.3:
+    resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
+    engines: {node: '>=12.20.0'}
+    peerDependencies:
+      '@types/react': '>=18.0.0'
+      immer: '>=9.0.6'
+      react: '>=18.0.0'
+      use-sync-external-store: '>=1.2.0'
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      immer:
+        optional: true
+      react:
+        optional: true
+      use-sync-external-store:
+        optional: true
+
   zwitch@2.0.4:
     resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
 
@@ -8714,4 +8735,10 @@ snapshots:
       '@types/react': 18.3.5
       react: 18.3.1
 
+  zustand@5.0.3(@types/react@18.3.5)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)):
+    optionalDependencies:
+      '@types/react': 18.3.5
+      react: 18.3.1
+      use-sync-external-store: 1.2.2(react@18.3.1)
+
   zwitch@2.0.4: {}
diff --git a/airflow/ui/src/components/FlexibleForm/FieldAdvancedArray.tsx b/airflow/ui/src/components/FlexibleForm/FieldAdvancedArray.tsx
index ac995c8ca1332..92b332a324b81 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldAdvancedArray.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldAdvancedArray.tsx
@@ -16,37 +16,66 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { Text } from "@chakra-ui/react";
 import { json } from "@codemirror/lang-json";
 import { githubLight, githubDark } from "@uiw/codemirror-themes-all";
 import CodeMirror from "@uiw/react-codemirror";
+import { useState } from "react";
 
 import { useColorMode } from "src/context/colorMode";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
-export const FieldAdvancedArray = ({ name, param }: FlexibleFormElementProps) => {
+export const FieldAdvancedArray = ({ name }: FlexibleFormElementProps) => {
   const { colorMode } = useColorMode();
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const [error, setError] = useState<unknown>(undefined);
+
+  const handleChange = (value: string) => {
+    setError(undefined);
+    try {
+      const parsedValue = JSON.parse(value) as JSON;
+
+      if (!Array.isArray(parsedValue)) {
+        throw new TypeError("Value must be an array");
+      }
+
+      if (paramsDict[name]) {
+        paramsDict[name].value = parsedValue;
+      }
+
+      setParamsDict(paramsDict);
+    } catch (_error) {
+      setError(_error);
+    }
+  };
 
   return (
-    <CodeMirror
-      basicSetup={{
-        autocompletion: true,
-        bracketMatching: true,
-        foldGutter: true,
-        lineNumbers: true,
-      }}
-      extensions={[json()]}
-      height="200px"
-      id={`element_${name}`}
-      style={{
-        border: "1px solid var(--chakra-colors-border)",
-        borderRadius: "8px",
-        outline: "none",
-        padding: "2px",
-        width: "100%",
-      }}
-      theme={colorMode === "dark" ? githubDark : githubLight}
-      value={JSON.stringify(param.value ?? [], undefined, 2)}
-    />
+    <>
+      <CodeMirror
+        basicSetup={{
+          autocompletion: true,
+          bracketMatching: true,
+          foldGutter: true,
+          lineNumbers: true,
+        }}
+        extensions={[json()]}
+        height="200px"
+        id={`element_${name}`}
+        onChange={handleChange}
+        style={{
+          border: "1px solid var(--chakra-colors-border)",
+          borderRadius: "8px",
+          outline: "none",
+          padding: "2px",
+          width: "100%",
+        }}
+        theme={colorMode === "dark" ? githubDark : githubLight}
+        value={JSON.stringify(param.value ?? [], undefined, 2)}
+      />
+      {Boolean(error) ? <Text color="red">{String(error)}</Text> : undefined}
+    </>
   );
 };
diff --git a/airflow/ui/src/components/FlexibleForm/FieldBool.tsx b/airflow/ui/src/components/FlexibleForm/FieldBool.tsx
index 609e7ade5a1c9..a92bf833d880e 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldBool.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldBool.tsx
@@ -17,13 +17,27 @@
  * under the License.
  */
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 import { Switch } from "../ui";
 
-export const FieldBool = ({ name, param }: FlexibleFormElementProps) => (
-  <Switch
-    colorPalette="blue"
-    defaultChecked={Boolean(param.value)}
-    id={`element_${name}`}
-    name={`element_${name}`}
-  />
-);
+export const FieldBool = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const onCheck = (value: boolean) => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = value;
+    }
+
+    setParamsDict(paramsDict);
+  };
+
+  return (
+    <Switch
+      checked={Boolean(param.value)}
+      colorPalette="blue"
+      id={`element_${name}`}
+      name={`element_${name}`}
+      onCheckedChange={(event) => onCheck(event.checked)}
+    />
+  );
+};
diff --git a/airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx b/airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx
index f109363988a6b..309a04bd1b376 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx
@@ -19,13 +19,29 @@
 import { Input, type InputProps } from "@chakra-ui/react";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
-export const FieldDateTime = ({ name, param, ...rest }: FlexibleFormElementProps & InputProps) => (
-  <Input
-    defaultValue={typeof param.value === "string" ? param.value : undefined}
-    id={`element_${name}`}
-    name={`element_${name}`}
-    size="sm"
-    type={rest.type}
-  />
-);
+export const FieldDateTime = ({ name, ...rest }: FlexibleFormElementProps & InputProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const handleChange = (value: string) => {
+    const date = new Date(value);
+
+    if (paramsDict[name]) {
+      paramsDict[name].value = date.toISOString();
+    }
+
+    setParamsDict(paramsDict);
+  };
+
+  return (
+    <Input
+      id={`element_${name}`}
+      name={`element_${name}`}
+      onChange={(event) => handleChange(event.target.value)}
+      size="sm"
+      type={rest.type}
+      value={typeof param.value === "string" ? param.value.slice(0, 16) : undefined}
+    />
+  );
+};
diff --git a/airflow/ui/src/components/FlexibleForm/FieldDropdown.tsx b/airflow/ui/src/components/FlexibleForm/FieldDropdown.tsx
index bbea023168b45..ebaadb4a9d886 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldDropdown.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldDropdown.tsx
@@ -22,6 +22,7 @@ import { useRef } from "react";
 import { Select } from "src/components/ui";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
 const labelLookup = (key: string, valuesDisplay: Record<string, string> | undefined): string => {
   if (valuesDisplay && typeof valuesDisplay === "object") {
@@ -32,7 +33,10 @@ const labelLookup = (key: string, valuesDisplay: Record<string, string> | undefi
 };
 const enumTypes = ["string", "number", "integer"];
 
-export const FieldDropdown = ({ name, param }: FlexibleFormElementProps) => {
+export const FieldDropdown = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+
   const selectOptions = createListCollection({
     items:
       param.schema.enum?.map((value) => ({
@@ -40,16 +44,26 @@ export const FieldDropdown = ({ name, param }: FlexibleFormElementProps) => {
         value,
       })) ?? [],
   });
+
   const contentRef = useRef<HTMLDivElement>(null);
 
+  const handleChange = (value: Array<string>) => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = value;
+    }
+
+    setParamsDict(paramsDict);
+  };
+
   return (
     <Select.Root
       collection={selectOptions}
-      defaultValue={enumTypes.includes(typeof param.value) ? [String(param.value)] : undefined}
       id={`element_${name}`}
       name={`element_${name}`}
+      onValueChange={(event) => handleChange(event.value)}
       ref={contentRef}
       size="sm"
+      value={enumTypes.includes(typeof param.value) ? [String(param.value)] : undefined}
     >
       <Select.Trigger>
         <Select.ValueText placeholder="Select Value" />
diff --git a/airflow/ui/src/components/FlexibleForm/FieldMultiSelect.tsx b/airflow/ui/src/components/FlexibleForm/FieldMultiSelect.tsx
index 02ae473324cfe..606a66aa9530b 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldMultiSelect.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldMultiSelect.tsx
@@ -17,9 +17,10 @@
  * under the License.
  */
 import { Select as ReactSelect } from "chakra-react-select";
-import { useState } from "react";
+import { useEffect, useState } from "react";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
 const labelLookup = (key: string, valuesDisplay: Record<string, string> | undefined): string => {
   if (valuesDisplay && typeof valuesDisplay === "object") {
@@ -29,7 +30,9 @@ const labelLookup = (key: string, valuesDisplay: Record<string, string> | undefi
   return key;
 };
 
-export const FieldMultiSelect = ({ name, param }: FlexibleFormElementProps) => {
+export const FieldMultiSelect = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
   const [selectedOptions, setSelectedOptions] = useState(
     Array.isArray(param.value)
       ? (param.value as Array<string>).map((value) => ({
@@ -39,6 +42,13 @@ export const FieldMultiSelect = ({ name, param }: FlexibleFormElementProps) => {
       : [],
   );
 
+  useEffect(() => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = selectedOptions.map((option) => option.value);
+    }
+    setParamsDict(paramsDict);
+  }, [selectedOptions, paramsDict, setParamsDict, name]);
+
   return (
     <ReactSelect
       aria-label="Select one or multiple values"
diff --git a/airflow/ui/src/components/FlexibleForm/FieldMultilineText.tsx b/airflow/ui/src/components/FlexibleForm/FieldMultilineText.tsx
index 70ee631cd3c99..45880130129f7 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldMultilineText.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldMultilineText.tsx
@@ -19,13 +19,27 @@
 import { Textarea } from "@chakra-ui/react";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
-export const FieldMultilineText = ({ name, param }: FlexibleFormElementProps) => (
-  <Textarea
-    defaultValue={String(param.value ?? "")}
-    id={`element_${name}`}
-    name={`element_${name}`}
-    rows={6}
-    size="sm"
-  />
-);
+export const FieldMultilineText = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const handleChange = (value: string) => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = value;
+    }
+
+    setParamsDict(paramsDict);
+  };
+
+  return (
+    <Textarea
+      id={`element_${name}`}
+      name={`element_${name}`}
+      onChange={(event) => handleChange(event.target.value)}
+      rows={6}
+      size="sm"
+      value={String(param.value ?? "")}
+    />
+  );
+};
diff --git a/airflow/ui/src/components/FlexibleForm/FieldNumber.tsx b/airflow/ui/src/components/FlexibleForm/FieldNumber.tsx
index 710b884c77658..1bd169454bb35 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldNumber.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldNumber.tsx
@@ -17,18 +17,32 @@
  * under the License.
  */
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 import { NumberInputField, NumberInputRoot } from "../ui/NumberInput";
 
-export const FieldNumber = ({ name, param }: FlexibleFormElementProps) => (
-  <NumberInputRoot
-    allowMouseWheel
-    defaultValue={String(param.value ?? "")}
-    id={`element_${name}`}
-    max={param.schema.maximum ?? undefined}
-    min={param.schema.minimum ?? undefined}
-    name={`element_${name}`}
-    size="sm"
-  >
-    <NumberInputField />
-  </NumberInputRoot>
-);
+export const FieldNumber = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const handleChange = (value: string) => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = Number(value);
+    }
+
+    setParamsDict(paramsDict);
+  };
+
+  return (
+    <NumberInputRoot
+      allowMouseWheel
+      id={`element_${name}`}
+      max={param.schema.maximum ?? undefined}
+      min={param.schema.minimum ?? undefined}
+      name={`element_${name}`}
+      onValueChange={(event) => handleChange(event.value)}
+      size="sm"
+      value={String(param.value ?? "")}
+    >
+      <NumberInputField />
+    </NumberInputRoot>
+  );
+};
diff --git a/airflow/ui/src/components/FlexibleForm/FieldObject.tsx b/airflow/ui/src/components/FlexibleForm/FieldObject.tsx
index e019eb6607f41..67e9631cd6e8d 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldObject.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldObject.tsx
@@ -16,37 +16,63 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { Text } from "@chakra-ui/react";
 import { json } from "@codemirror/lang-json";
 import { githubLight, githubDark } from "@uiw/codemirror-themes-all";
 import CodeMirror from "@uiw/react-codemirror";
+import { useState } from "react";
 
 import { useColorMode } from "src/context/colorMode";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
-export const FieldObject = ({ name, param }: FlexibleFormElementProps) => {
+export const FieldObject = ({ name }: FlexibleFormElementProps) => {
   const { colorMode } = useColorMode();
 
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const [error, setError] = useState<unknown>(undefined);
+
+  const handleChange = (value: string) => {
+    setError(undefined);
+    try {
+      const parsedValue = JSON.parse(value) as JSON;
+
+      if (paramsDict[name]) {
+        paramsDict[name].value = parsedValue;
+      }
+
+      setParamsDict(paramsDict);
+    } catch (_error) {
+      setError(_error);
+    }
+  };
+
   return (
-    <CodeMirror
-      basicSetup={{
-        autocompletion: true,
-        bracketMatching: true,
-        foldGutter: true,
-        lineNumbers: true,
-      }}
-      extensions={[json()]}
-      height="200px"
-      id={`element_${name}`}
-      style={{
-        border: "1px solid var(--chakra-colors-border)",
-        borderRadius: "8px",
-        outline: "none",
-        padding: "2px",
-        width: "100%",
-      }}
-      theme={colorMode === "dark" ? githubDark : githubLight}
-      value={JSON.stringify(param.value ?? {}, undefined, 2)}
-    />
+    <>
+      <CodeMirror
+        basicSetup={{
+          autocompletion: true,
+          bracketMatching: true,
+          foldGutter: true,
+          lineNumbers: true,
+        }}
+        extensions={[json()]}
+        height="200px"
+        id={`element_${name}`}
+        onChange={handleChange}
+        style={{
+          border: "1px solid var(--chakra-colors-border)",
+          borderRadius: "8px",
+          outline: "none",
+          padding: "2px",
+          width: "100%",
+        }}
+        theme={colorMode === "dark" ? githubDark : githubLight}
+        value={JSON.stringify(param.value ?? {}, undefined, 2)}
+      />
+      {Boolean(error) ? <Text color="red">{String(error)}</Text> : undefined}
+    </>
   );
 };
diff --git a/airflow/ui/src/components/FlexibleForm/FieldRow.tsx b/airflow/ui/src/components/FlexibleForm/FieldRow.tsx
index 583c8c62b5412..eb4de973a3a79 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldRow.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldRow.tsx
@@ -23,6 +23,7 @@ import remarkGfm from "remark-gfm";
 import type { ParamSpec } from "src/queries/useDagParams";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 import { FieldSelector } from "./FieldSelector";
 
 const isRequired = (param: ParamSpec) =>
@@ -32,18 +33,25 @@ const isRequired = (param: ParamSpec) =>
   Boolean(param.schema.type) && (!Array.isArray(param.schema.type) || !param.schema.type.includes("null"));
 
 /** Render a normal form row with a field that is auto-selected */
-export const FieldRow = ({ name, param }: FlexibleFormElementProps) => (
-  <Field.Root orientation="horizontal" required={isRequired(param)}>
-    <Stack>
-      <Field.Label fontSize="md">
-        {param.schema.title ?? name} <Field.RequiredIndicator />
-      </Field.Label>
-    </Stack>
-    <Stack css={{ "flex-basis": "70%" }}>
-      <FieldSelector name={name} param={param} />
-      <Field.HelperText>
-        {param.description ?? <Markdown remarkPlugins={[remarkGfm]}>{param.schema.description_md}</Markdown>}
-      </Field.HelperText>
-    </Stack>
-  </Field.Root>
-);
+export const FieldRow = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+
+  return (
+    <Field.Root orientation="horizontal" required={isRequired(param)}>
+      <Stack>
+        <Field.Label fontSize="md">
+          {param.schema.title ?? name} <Field.RequiredIndicator />
+        </Field.Label>
+      </Stack>
+      <Stack css={{ "flex-basis": "70%" }}>
+        <FieldSelector name={name} />
+        <Field.HelperText>
+          {param.description ?? (
+            <Markdown remarkPlugins={[remarkGfm]}>{param.schema.description_md}</Markdown>
+          )}
+        </Field.HelperText>
+      </Stack>
+    </Field.Root>
+  );
+};
diff --git a/airflow/ui/src/components/FlexibleForm/FieldSelector.tsx b/airflow/ui/src/components/FlexibleForm/FieldSelector.tsx
index ddedb75728b80..39812a45e0dbd 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldSelector.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldSelector.tsx
@@ -19,6 +19,7 @@
 import type { ParamSchema, ParamSpec } from "src/queries/useDagParams";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 import { FieldAdvancedArray } from "./FieldAdvancedArray";
 import { FieldBool } from "./FieldBool";
 import { FieldDateTime } from "./FieldDateTime";
@@ -86,33 +87,35 @@ const isFieldStringArray = (fieldType: string, fieldSchema: ParamSchema) =>
 const isFieldTime = (fieldType: string, fieldSchema: ParamSchema) =>
   fieldType === "string" && fieldSchema.format === "time";
 
-export const FieldSelector = ({ name, param }: FlexibleFormElementProps) => {
+export const FieldSelector = ({ name }: FlexibleFormElementProps) => {
   // FUTURE: Add support for other types as described in AIP-68 via Plugins
+  const { paramsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
   const fieldType = inferType(param);
 
   if (isFieldBool(fieldType)) {
-    return <FieldBool name={name} param={param} />;
+    return <FieldBool name={name} />;
   } else if (isFieldDateTime(fieldType, param.schema)) {
-    return <FieldDateTime name={name} param={param} type="datetime-local" />;
+    return <FieldDateTime name={name} type="datetime-local" />;
   } else if (isFieldDate(fieldType, param.schema)) {
-    return <FieldDateTime name={name} param={param} type="date" />;
+    return <FieldDateTime name={name} type="date" />;
   } else if (isFieldTime(fieldType, param.schema)) {
-    return <FieldDateTime name={name} param={param} type="time" />;
+    return <FieldDateTime name={name} type="time" />;
   } else if (isFieldDropdown(fieldType, param.schema)) {
-    return <FieldDropdown name={name} param={param} />;
+    return <FieldDropdown name={name} />;
   } else if (isFieldMultiSelect(fieldType, param.schema)) {
-    return <FieldMultiSelect name={name} param={param} />;
+    return <FieldMultiSelect name={name} />;
   } else if (isFieldStringArray(fieldType, param.schema)) {
-    return <FieldStringArray name={name} param={param} />;
+    return <FieldStringArray name={name} />;
   } else if (isFieldAdvancedArray(fieldType, param.schema)) {
-    return <FieldAdvancedArray name={name} param={param} />;
+    return <FieldAdvancedArray name={name} />;
   } else if (isFieldObject(fieldType)) {
-    return <FieldObject name={name} param={param} />;
+    return <FieldObject name={name} />;
   } else if (isFieldNumber(fieldType)) {
-    return <FieldNumber name={name} param={param} />;
+    return <FieldNumber name={name} />;
   } else if (isFieldMultilineText(fieldType, param.schema)) {
-    return <FieldMultilineText name={name} param={param} />;
+    return <FieldMultilineText name={name} />;
   } else {
-    return <FieldString name={name} param={param} />;
+    return <FieldString name={name} />;
   }
 };
diff --git a/airflow/ui/src/components/FlexibleForm/FieldString.tsx b/airflow/ui/src/components/FlexibleForm/FieldString.tsx
index a3901801621a5..51ae6e27d0802 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldString.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldString.tsx
@@ -19,27 +19,43 @@
 import { Input } from "@chakra-ui/react";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
-export const FieldString = ({ name, param }: FlexibleFormElementProps) => (
-  <>
-    <Input
-      defaultValue={String(param.value ?? "")}
-      id={`element_${name}`}
-      list={param.schema.examples ? `list_${name}` : undefined}
-      maxLength={param.schema.maxLength ?? undefined}
-      minLength={param.schema.minLength ?? undefined}
-      name={`element_${name}`}
-      placeholder={param.schema.examples ? "Start typing to see options." : undefined}
-      size="sm"
-    />
-    {param.schema.examples ? (
-      <datalist id={`list_${name}`}>
-        {param.schema.examples.map((example) => (
-          <option key={example} value={example}>
-            {example}
-          </option>
-        ))}
-      </datalist>
-    ) : undefined}
-  </>
-);
+export const FieldString = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const handleChange = (value: string) => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = value;
+    }
+
+    setParamsDict(paramsDict);
+  };
+
+  return (
+    <>
+      <Input
+        id={`element_${name}`}
+        list={param.schema.examples ? `list_${name}` : undefined}
+        maxLength={param.schema.maxLength ?? undefined}
+        minLength={param.schema.minLength ?? undefined}
+        name={`element_${name}`}
+        onChange={(event) => {
+          handleChange(event.target.value);
+        }}
+        placeholder={param.schema.examples ? "Start typing to see options." : undefined}
+        size="sm"
+        value={String(param.value ?? "")}
+      />
+      {param.schema.examples ? (
+        <datalist id={`list_${name}`}>
+          {param.schema.examples.map((example) => (
+            <option key={example} value={example}>
+              {example}
+            </option>
+          ))}
+        </datalist>
+      ) : undefined}
+    </>
+  );
+};
diff --git a/airflow/ui/src/components/FlexibleForm/FieldStringArray.tsx b/airflow/ui/src/components/FlexibleForm/FieldStringArray.tsx
index 494cd2e59fd12..c3e25c278de7c 100644
--- a/airflow/ui/src/components/FlexibleForm/FieldStringArray.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FieldStringArray.tsx
@@ -19,15 +19,32 @@
 import { Textarea } from "@chakra-ui/react";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
-export const FieldStringArray = ({ name, param }: FlexibleFormElementProps) => (
-  <Textarea
-    defaultValue={
-      Array.isArray(param.value) ? (param.value as Array<string>).join("\n") : String(param.value ?? "")
+export const FieldStringArray = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const handleChange = (newValue: string) => {
+    const newValueArray = newValue.split("\n");
+
+    if (paramsDict[name]) {
+      paramsDict[name].value = newValueArray;
     }
-    id={`element_${name}`}
-    name={`element_${name}`}
-    rows={6}
-    size="sm"
-  />
-);
+
+    setParamsDict(paramsDict);
+  };
+
+  const value = Array.isArray(param.value) ? param.value.join("\n") : "";
+
+  return (
+    <Textarea
+      defaultValue={value}
+      id={`element_${name}`}
+      name={`element_${name}`}
+      onBlur={(event) => handleChange(event.target.value)}
+      placeholder="Enter each string on a new line"
+      rows={6}
+      size="sm"
+    />
+  );
+};
diff --git a/airflow/ui/src/components/FlexibleForm/FlexibleForm.tsx b/airflow/ui/src/components/FlexibleForm/FlexibleForm.tsx
index 17070037b6acc..997e8ba999311 100644
--- a/airflow/ui/src/components/FlexibleForm/FlexibleForm.tsx
+++ b/airflow/ui/src/components/FlexibleForm/FlexibleForm.tsx
@@ -16,13 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Stack, StackSeparator } from "@chakra-ui/react";
+import { Box, Stack, StackSeparator } from "@chakra-ui/react";
 
-import { flexibleFormDefaultSection, type FlexibleFormProps } from ".";
-import { Accordion, Alert } from "../ui";
+import { flexibleFormDefaultSection } from ".";
+import { useParamStore } from "../TriggerDag/useParamStore";
+import { Accordion } from "../ui";
 import { Row } from "./Row";
 
-export const FlexibleForm = ({ params }: FlexibleFormProps) => {
+export const FlexibleForm = () => {
+  const { paramsDict: params } = useParamStore();
   const processedSections = new Map();
 
   return Object.entries(params).some(([, param]) => typeof param.schema.section !== "string")
@@ -37,22 +39,20 @@ export const FlexibleForm = ({ params }: FlexibleFormProps) => {
           return (
             <Accordion.Item key={currentSection} value={currentSection}>
               <Accordion.ItemTrigger cursor="button">{currentSection}</Accordion.ItemTrigger>
-              <Accordion.ItemContent>
-                <Stack separator={<StackSeparator />}>
-                  <Alert
-                    status="warning"
-                    title="Population of changes in trigger form fields is not implemented yet. Please stay tuned for upcoming updates... and change the run conf in the 'Advanced Options' conf section below meanwhile."
-                  />
-                  {Object.entries(params)
-                    .filter(
-                      ([, param]) =>
-                        param.schema.section === currentSection ||
-                        (currentSection === flexibleFormDefaultSection && !Boolean(param.schema.section)),
-                    )
-                    .map(([name, param]) => (
-                      <Row key={name} name={name} param={param} />
-                    ))}
-                </Stack>
+              <Accordion.ItemContent paddingTop={0}>
+                <Box p={5}>
+                  <Stack separator={<StackSeparator />}>
+                    {Object.entries(params)
+                      .filter(
+                        ([, param]) =>
+                          param.schema.section === currentSection ||
+                          (currentSection === flexibleFormDefaultSection && !Boolean(param.schema.section)),
+                      )
+                      .map(([name]) => (
+                        <Row key={name} name={name} />
+                      ))}
+                  </Stack>
+                </Box>
               </Accordion.ItemContent>
             </Accordion.Item>
           );
diff --git a/airflow/ui/src/components/FlexibleForm/HiddenInput.tsx b/airflow/ui/src/components/FlexibleForm/HiddenInput.tsx
index 718a679b30f12..48749e6a3dd34 100644
--- a/airflow/ui/src/components/FlexibleForm/HiddenInput.tsx
+++ b/airflow/ui/src/components/FlexibleForm/HiddenInput.tsx
@@ -19,10 +19,21 @@
 import { VisuallyHidden } from "@chakra-ui/react";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
 /** Render a "const" field where user can not change data as hidden */
-export const HiddenInput = ({ name, param }: FlexibleFormElementProps) => (
-  <VisuallyHidden asChild>
-    <input id={`element_${name}`} name={`element_${name}`} type="hidden" value={String(param.value ?? "")} />
-  </VisuallyHidden>
-);
+export const HiddenInput = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+
+  return (
+    <VisuallyHidden asChild>
+      <input
+        id={`element_${name}`}
+        name={`element_${name}`}
+        type="hidden"
+        value={String(param.value ?? "")}
+      />
+    </VisuallyHidden>
+  );
+};
diff --git a/airflow/ui/src/components/FlexibleForm/Row.tsx b/airflow/ui/src/components/FlexibleForm/Row.tsx
index 615d9400455e6..48596377cc3c2 100644
--- a/airflow/ui/src/components/FlexibleForm/Row.tsx
+++ b/airflow/ui/src/components/FlexibleForm/Row.tsx
@@ -19,11 +19,16 @@
 import type { ParamSchema } from "src/queries/useDagParams";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 import { FieldRow } from "./FieldRow";
 import { HiddenInput } from "./HiddenInput";
 
 const isHidden = (fieldSchema: ParamSchema) => Boolean(fieldSchema.const);
 
 /** Generates a form row */
-export const Row = ({ name, param }: FlexibleFormElementProps) =>
-  isHidden(param.schema) ? <HiddenInput name={name} param={param} /> : <FieldRow name={name} param={param} />;
+export const Row = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+
+  return isHidden(param.schema) ? <HiddenInput name={name} /> : <FieldRow name={name} />;
+};
diff --git a/airflow/ui/src/components/FlexibleForm/index.tsx b/airflow/ui/src/components/FlexibleForm/index.tsx
index 8c8d6818a9012..7698a8c5f6ab6 100644
--- a/airflow/ui/src/components/FlexibleForm/index.tsx
+++ b/airflow/ui/src/components/FlexibleForm/index.tsx
@@ -16,15 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import type { DagParamsSpec, ParamSpec } from "src/queries/useDagParams";
-
-export type FlexibleFormProps = {
-  readonly params: DagParamsSpec;
-};
 
 export type FlexibleFormElementProps = {
   readonly name: string;
-  readonly param: ParamSpec;
 };
 
 export const flexibleFormDefaultSection = "Run Parameters";
diff --git a/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx b/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
index 6048a27f5a021..662c291a5da68 100644
--- a/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
+++ b/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
@@ -29,8 +29,9 @@ import { useDagParams } from "src/queries/useDagParams";
 import { useTrigger } from "src/queries/useTrigger";
 
 import { ErrorAlert } from "../ErrorAlert";
-import { FlexibleForm, flexibleFormDefaultSection } from "../FlexibleForm";
+import { FlexibleForm } from "../FlexibleForm";
 import { Accordion } from "../ui";
+import { useParamStore } from "./useParamStore";
 
 type TriggerDAGFormProps = {
   readonly dagId: string;
@@ -48,22 +49,24 @@ export type DagRunTriggerParams = {
 
 const TriggerDAGForm = ({ dagId, onClose, open }: TriggerDAGFormProps) => {
   const [errors, setErrors] = useState<{ conf?: string; date?: unknown }>({});
-  const { initialConf, paramsDict } = useDagParams(dagId, open);
+  const initialParamsDict = useDagParams(dagId, open);
   const {
     dateValidationError,
     error: errorTrigger,
     isPending,
     triggerDagRun,
   } = useTrigger({ onSuccessConfirm: onClose });
-  const conf = initialConf;
+  const { conf, paramsDict, setConf, setParamsDict } = useParamStore();
 
-  const {
-    control,
-    formState: { isDirty },
-    handleSubmit,
-    reset,
-    watch,
-  } = useForm<DagRunTriggerParams>({
+  useEffect(() => {
+    setParamsDict({});
+  }, [open, setParamsDict]);
+
+  if (Object.keys(initialParamsDict.paramsDict).length > 0 && Object.keys(paramsDict).length === 0) {
+    setParamsDict(initialParamsDict.paramsDict);
+  }
+
+  const { control, handleSubmit, reset, watch } = useForm<DagRunTriggerParams>({
     defaultValues: {
       conf,
       dagRunId: "",
@@ -89,17 +92,6 @@ const TriggerDAGForm = ({ dagId, onClose, open }: TriggerDAGFormProps) => {
   const dataIntervalStart = watch("dataIntervalStart");
   const dataIntervalEnd = watch("dataIntervalEnd");
 
-  const handleReset = () => {
-    setErrors({ conf: undefined, date: undefined });
-    reset({
-      conf,
-      dagRunId: "",
-      dataIntervalEnd: "",
-      dataIntervalStart: "",
-      note: "",
-    });
-  };
-
   const onSubmit = (data: DagRunTriggerParams) => {
     triggerDagRun(dagId, data);
   };
@@ -110,7 +102,13 @@ const TriggerDAGForm = ({ dagId, onClose, open }: TriggerDAGFormProps) => {
 
       setErrors((prev) => ({ ...prev, conf: undefined }));
 
-      return JSON.stringify(parsedJson, undefined, 2);
+      const formattedJson = JSON.stringify(parsedJson, undefined, 2);
+
+      if (formattedJson !== conf) {
+        setConf(formattedJson); // Update only if the value is different
+      }
+
+      return formattedJson;
     } catch (error) {
       const errorMessage = error instanceof Error ? error.message : "Unknown error occurred.";
 
@@ -131,15 +129,8 @@ const TriggerDAGForm = ({ dagId, onClose, open }: TriggerDAGFormProps) => {
 
   return (
     <>
-      <Accordion.Root
-        collapsible
-        defaultValue={[flexibleFormDefaultSection]}
-        mb={4}
-        mt={4}
-        size="lg"
-        variant="enclosed"
-      >
-        <FlexibleForm params={paramsDict} />
+      <Accordion.Root collapsible mb={4} mt={4} size="lg" variant="enclosed">
+        <FlexibleForm />
         <Accordion.Item key="advancedOptions" value="advancedOptions">
           <Accordion.ItemTrigger cursor="button">Advanced Options</Accordion.ItemTrigger>
           <Accordion.ItemContent>
@@ -245,11 +236,6 @@ const TriggerDAGForm = ({ dagId, onClose, open }: TriggerDAGFormProps) => {
       <ErrorAlert error={errors.date ?? errorTrigger} />
       <Box as="footer" display="flex" justifyContent="flex-end" mt={4}>
         <HStack w="full">
-          {isDirty ? (
-            <Button onClick={handleReset} variant="outline">
-              Reset
-            </Button>
-          ) : undefined}
           <Spacer />
           <Button
             colorPalette="blue"
diff --git a/airflow/ui/src/components/TriggerDag/useParamStore.ts b/airflow/ui/src/components/TriggerDag/useParamStore.ts
new file mode 100644
index 0000000000000..17e240ef2cf88
--- /dev/null
+++ b/airflow/ui/src/components/TriggerDag/useParamStore.ts
@@ -0,0 +1,95 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { create } from "zustand";
+
+import type { DagParamsSpec, ParamSpec } from "src/queries/useDagParams";
+
+export const paramPlaceholder: ParamSpec = {
+  description: undefined,
+  schema: {
+    const: undefined,
+    description_md: undefined,
+    enum: undefined,
+    examples: undefined,
+    format: undefined,
+    items: undefined,
+    maximum: undefined,
+    maxLength: undefined,
+    minimum: undefined,
+    minLength: undefined,
+    section: undefined,
+    title: undefined,
+    type: undefined,
+    values_display: undefined,
+  },
+  value: "",
+};
+
+type FormStore = {
+  conf: string;
+  paramsDict: DagParamsSpec;
+  setConf: (confString: string) => void;
+  setParamsDict: (newParamsDict: DagParamsSpec) => void;
+};
+
+export const useParamStore = create<FormStore>((set) => ({
+  conf: "{}",
+  paramsDict: {},
+
+  setConf: (confString: string) =>
+    set((state) => {
+      if (state.conf === confString) {
+        return {};
+      }
+
+      const parsedConf = JSON.parse(confString) as JSON;
+
+      const updatedParamsDict: DagParamsSpec = Object.fromEntries(
+        Object.entries(parsedConf).map(([key, value]) => {
+          const existingParam = state.paramsDict[key];
+
+          return [
+            key,
+            {
+              description: existingParam?.description,
+              schema: existingParam?.schema ?? paramPlaceholder.schema,
+              value: value as unknown,
+            },
+          ];
+        }),
+      );
+
+      return { conf: confString, paramsDict: updatedParamsDict };
+    }),
+
+  setParamsDict: (newParamsDict: DagParamsSpec) =>
+    set((state) => {
+      const newConf = JSON.stringify(
+        Object.fromEntries(Object.entries(newParamsDict).map(([key, { value }]) => [key, value])),
+        undefined,
+        2,
+      );
+
+      if (state.conf === newConf && JSON.stringify(state.paramsDict) === JSON.stringify(newParamsDict)) {
+        return {};
+      }
+
+      return { conf: newConf, paramsDict: newParamsDict };
+    }),
+}));
diff --git a/airflow/ui/src/queries/useDagParams.ts b/airflow/ui/src/queries/useDagParams.ts
index 2711099ac7351..91b221cad76ff 100644
--- a/airflow/ui/src/queries/useDagParams.ts
+++ b/airflow/ui/src/queries/useDagParams.ts
@@ -64,13 +64,7 @@ export const useDagParams = (dagId: string, open: boolean) => {
     });
   }
 
-  const transformedParams = data?.params
-    ? Object.fromEntries(Object.entries(data.params).map(([key, param]) => [key, param.value]))
-    : {};
-
-  const initialConf = JSON.stringify(transformedParams, undefined, 2);
-
   const paramsDict: DagParamsSpec = data?.params ?? ({} as DagParamsSpec);
 
-  return { initialConf, paramsDict };
+  return { paramsDict };
 };


Code review comments:
diff_hunk: @@ -32,24 +33,37 @@ const labelLookup = (key: string, valuesDisplay: Record<string, string> | undefi
 };
 const enumTypes = ["string", "number", "integer"];
 
-export const FieldDropdown = ({ name, param }: FlexibleFormElementProps) => {
+export const FieldDropdown = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+
   const selectOptions = createListCollection({
     items:
       param.schema.enum?.map((value) => ({
         label: labelLookup(value, param.schema.values_display),
         value,
       })) ?? [],
   });
+
   const contentRef = useRef<HTMLDivElement>(null);
 
+  const handleChange = (value: Array<string>) => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = value;
path: airflow/ui/src/components/FlexibleForm/FieldDropdown.tsx
text: @user1:
In the case of dropdown we know it will be a single value only. Need to pick first element from list?
```suggestion
      paramsDict[name].value = value[0];
```

diff_hunk: @@ -19,13 +19,29 @@
 import { Input, type InputProps } from "@chakra-ui/react";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
-export const FieldDateTime = ({ name, param, ...rest }: FlexibleFormElementProps & InputProps) => (
-  <Input
-    defaultValue={typeof param.value === "string" ? param.value : undefined}
-    id={`element_${name}`}
-    name={`element_${name}`}
-    size="sm"
-    type={rest.type}
-  />
-);
+export const FieldDateTime = ({ name, ...rest }: FlexibleFormElementProps & InputProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const handleChange = (value: string) => {
+    const date = new Date(value);
path: airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx
text: @user1:
Can this go wrong if the user has a special locale or enters "garbage" into the input field? Should there be a try/catch just to be safe?

@author:
User will not be able to add garbage value from the UI.

diff_hunk: @@ -19,27 +19,43 @@
 import { Input } from "@chakra-ui/react";
 
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 
-export const FieldString = ({ name, param }: FlexibleFormElementProps) => (
-  <>
-    <Input
-      defaultValue={String(param.value ?? "")}
-      id={`element_${name}`}
-      list={param.schema.examples ? `list_${name}` : undefined}
-      maxLength={param.schema.maxLength ?? undefined}
-      minLength={param.schema.minLength ?? undefined}
-      name={`element_${name}`}
-      placeholder={param.schema.examples ? "Start typing to see options." : undefined}
-      size="sm"
-    />
-    {param.schema.examples ? (
-      <datalist id={`list_${name}`}>
-        {param.schema.examples.map((example) => (
-          <option key={example} value={example}>
-            {example}
-          </option>
-        ))}
-      </datalist>
-    ) : undefined}
-  </>
-);
+export const FieldString = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const handleChange = (value: string) => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = value;
path: airflow/ui/src/components/FlexibleForm/FieldString.tsx
text: @user1:
If previous implementation an empty string always was set to `null`. Would propose to keep it like this.

diff_hunk: @@ -17,18 +17,32 @@
  * under the License.
  */
 import type { FlexibleFormElementProps } from ".";
+import { paramPlaceholder, useParamStore } from "../TriggerDag/useParamStore";
 import { NumberInputField, NumberInputRoot } from "../ui/NumberInput";
 
-export const FieldNumber = ({ name, param }: FlexibleFormElementProps) => (
-  <NumberInputRoot
-    allowMouseWheel
-    defaultValue={String(param.value ?? "")}
-    id={`element_${name}`}
-    max={param.schema.maximum ?? undefined}
-    min={param.schema.minimum ?? undefined}
-    name={`element_${name}`}
-    size="sm"
-  >
-    <NumberInputField />
-  </NumberInputRoot>
-);
+export const FieldNumber = ({ name }: FlexibleFormElementProps) => {
+  const { paramsDict, setParamsDict } = useParamStore();
+  const param = paramsDict[name] ?? paramPlaceholder;
+  const handleChange = (value: string) => {
+    if (paramsDict[name]) {
+      paramsDict[name].value = Number(value);
path: airflow/ui/src/components/FlexibleForm/FieldNumber.tsx
text: @user1:
Can we be sure the input field will always provide a valid number?

@author:
this will handled via NumberInputRoot, non-integers are not allowed

@user1:
Just re-checked. Also how it is in the legacy Trigger form: If the number is optional, then you can remove the value, is rendered as `null` in JSON. In the new it is not possible to clear the field:
![image](https://github.com/user-attachments/assets/14215bc9-c8ad-404c-837b-b12a9e78a1e7)