Forráskód Böngészése

Input control validation on focus out event (#2036)

* Added action_on_blur property to 'input' and 'number'
Karthik Prakash 5 hónapja
szülő
commit
5a6ff906f9

+ 73 - 50
frontend/taipy-gui/src/components/Taipy/Input.tsx

@@ -31,9 +31,9 @@ const getActionKeys = (keys?: string): string[] => {
     const ak = (
         keys
             ? keys
-                  .split(";")
-                  .map((v) => v.trim().toLowerCase())
-                  .filter((v) => AUTHORIZED_KEYS.some((k) => k.toLowerCase() === v))
+                .split(";")
+                .map((v) => v.trim().toLowerCase())
+                .filter((v) => AUTHORIZED_KEYS.some((k) => k.toLowerCase() === v))
             : []
     ).map((v) => AUTHORIZED_KEYS.find((k) => k.toLowerCase() == v) as string);
     return ak.length > 0 ? ak : [AUTHORIZED_KEYS[0]];
@@ -63,6 +63,7 @@ const Input = (props: TaipyInputProps) => {
         onAction,
         onChange,
         multiline = false,
+        actionOnBlur = false,
         linesShown = 5,
     } = props;
 
@@ -85,9 +86,9 @@ const Input = (props: TaipyInputProps) => {
         () =>
             props.width
                 ? {
-                      ...numberSx,
-                      maxWidth: getCssSize(props.width),
-                  }
+                    ...numberSx,
+                    maxWidth: getCssSize(props.width),
+                }
                 : numberSx,
         [props.width]
     );
@@ -138,6 +139,27 @@ const Input = (props: TaipyInputProps) => {
         [changeDelay, dispatch, updateVarName, module, onChange, propagate]
     );
 
+    const handleBlur = useCallback(
+        (evt: React.FocusEvent<HTMLInputElement>) => {
+            const val = (type === "number")
+                ? Number(evt.currentTarget.querySelector("input")?.value)
+                : (multiline
+                    ? evt.currentTarget.querySelector("textarea")?.value
+                    : evt.currentTarget.querySelector("input")?.value)
+                ;
+            if (delayCall.current > 0) {
+                if (changeDelay > 0) {
+                    clearTimeout(delayCall.current);
+                    delayCall.current = -1;
+                }
+                dispatch(createSendUpdateAction(updateVarName, val, module, onChange, propagate));
+            }
+            onAction && dispatch(createSendActionNameAction(id, module, onAction, "Tab", updateVarName, val));
+            evt.preventDefault();
+        },
+        [dispatch, type, updateVarName, module, onChange, propagate, changeDelay, id, multiline, onAction]
+    );
+
     const handleAction = useCallback(
         (evt: KeyboardEvent<HTMLDivElement>) => {
             if (evt.shiftKey && type === "number") {
@@ -265,51 +287,51 @@ const Input = (props: TaipyInputProps) => {
         () =>
             type == "number"
                 ? {
-                      htmlInput: {
-                          step: step ? step : 1,
-                          min: min,
-                          max: max,
-                      },
-                      input: {
-                          endAdornment: (
-                              <div style={verticalDivStyle}>
-                                  <IconButton
-                                      aria-label="Increment value"
-                                      size="small"
-                                      onMouseDown={handleUpStepperMouseDown}
-                                      disabled={!active}
-                                  >
-                                      <ArrowDropUpIcon fontSize="inherit" />
-                                  </IconButton>
-                                  <IconButton
-                                      aria-label="Decrement value"
-                                      size="small"
-                                      onMouseDown={handleDownStepperMouseDown}
-                                      disabled={!active}
-                                  >
-                                      <ArrowDropDownIcon fontSize="inherit" />
-                                  </IconButton>
-                              </div>
-                          ),
-                      },
-                  }
+                    htmlInput: {
+                        step: step ? step : 1,
+                        min: min,
+                        max: max,
+                    },
+                    input: {
+                        endAdornment: (
+                            <div style={verticalDivStyle}>
+                                <IconButton
+                                    aria-label="Increment value"
+                                    size="small"
+                                    onMouseDown={handleUpStepperMouseDown}
+                                    disabled={!active}
+                                >
+                                    <ArrowDropUpIcon fontSize="inherit" />
+                                </IconButton>
+                                <IconButton
+                                    aria-label="Decrement value"
+                                    size="small"
+                                    onMouseDown={handleDownStepperMouseDown}
+                                    disabled={!active}
+                                >
+                                    <ArrowDropDownIcon fontSize="inherit" />
+                                </IconButton>
+                            </div>
+                        ),
+                    },
+                }
                 : type == "password"
-                ? {
-                      htmlInput: { autoComplete: "current-password" },
-                      input: {
-                          endAdornment: (
-                              <IconButton
-                                  aria-label="toggle password visibility"
-                                  onClick={handleClickShowPassword}
-                                  onMouseDown={handleMouseDownPassword}
-                                  edge="end"
-                              >
-                                  {showPassword ? <VisibilityOff /> : <Visibility />}
-                              </IconButton>
-                          ),
-                      },
-                  }
-                : undefined,
+                    ? {
+                        htmlInput: { autoComplete: "current-password" },
+                        input: {
+                            endAdornment: (
+                                <IconButton
+                                    aria-label="toggle password visibility"
+                                    onClick={handleClickShowPassword}
+                                    onMouseDown={handleMouseDownPassword}
+                                    edge="end"
+                                >
+                                    {showPassword ? <VisibilityOff /> : <Visibility />}
+                                </IconButton>
+                            ),
+                        },
+                    }
+                    : undefined,
         [
             active,
             type,
@@ -344,6 +366,7 @@ const Input = (props: TaipyInputProps) => {
                     slotProps={inputProps}
                     label={props.label}
                     onChange={handleInput}
+                    onBlur={actionOnBlur ? handleBlur : undefined}
                     disabled={!active}
                     onKeyDown={handleAction}
                     multiline={multiline}

+ 1 - 0
frontend/taipy-gui/src/components/Taipy/utils.ts

@@ -62,6 +62,7 @@ export interface TaipyInputProps extends TaipyActiveProps, TaipyChangeProps, Tai
     changeDelay?: number;
     onAction?: string;
     actionKeys?: string;
+    actionOnBlur?: boolean;
     multiline?: boolean;
     linesShown?: number;
     width?: string | number;

+ 2 - 0
taipy/gui/_renderers/factory.py

@@ -326,6 +326,7 @@ class _Factory:
                 ("action_keys",),
                 ("label",),
                 ("change_delay", PropertyType.number, gui._get_config("change_delay", None)),
+                ("action_on_blur", PropertyType.boolean, False),
                 ("multiline", PropertyType.boolean, False),
                 ("lines_shown", PropertyType.number, 5),
                 ("width", PropertyType.string_or_number),
@@ -434,6 +435,7 @@ class _Factory:
                 ("on_action", PropertyType.function),
                 ("label",),
                 ("change_delay", PropertyType.number, gui._get_config("change_delay", None)),
+                ("action_on_blur", PropertyType.boolean, False),
                 ("width", PropertyType.string_or_number),
             ]
         ),

+ 3 - 3
taipy/gui/gui.py

@@ -1155,9 +1155,9 @@ class Gui:
 
         Arguments:
             converter: A function that converts a value with an unsupported data type (the only
-              parameter to the function) into data with a supported data type (the returned value
-              from the function).</br>
-              If set to `None`, it removes any existing converter.
+                parameter to the function) into data with a supported data type (the returned value
+                from the function).</br>
+                If set to `None`, it removes any existing converter.
         """
         Gui.__unsupported_data_converter = converter
 

+ 68 - 56
taipy/gui/viselements.json

@@ -23,7 +23,7 @@
                     {
                         "name": "mode",
                         "type": "str",
-                        "doc": "Define the way the text is processed:\n<ul><li>&quot;raw&quot;: synonym for setting the <i>raw</i> property to True</li><li>&quot;pre&quot;: keeps spaces and new lines</li><li>&quot;markdown&quot; or &quot;md&quot;: basic support for Markdown</li><li>&quot;latex&quot;: LaTe&chi; support</li>"
+                        "doc": "Define the way the text is processed:\n<ul><li>&quot;raw&quot;: synonym for setting the <i>raw</i> property to True</li><li>&quot;pre&quot;: keeps spaces and new lines</li><li>&quot;markdown&quot; or &quot;md&quot;: basic support for Markdown</li><li>&quot;latex&quot;: LaTe&chi; support</li></ul>"
                     },
                     {
                         "name": "format",
@@ -34,7 +34,7 @@
                         "name": "width",
                         "type": "Union[str,int]",
                         "default_value": "None",
-                        "doc": "The width of the element."
+                        "doc": "The width of the text element, in CSS units."
                     }
                 ]
             }
@@ -114,31 +114,37 @@
                         "name": "password",
                         "type": "bool",
                         "default_value": "False",
-                        "doc": "If True, the text is obscured: all input characters are displayed as an asterisk ('*')."
+                        "doc": "If True, the text is obscured, and all characters are displayed as asterisks ('*').<br/>This can be useful for sensitive information such as passwords."
                     },
                     {
                         "name": "label",
                         "type": "str",
                         "default_value": "None",
-                        "doc": "The label associated with the input."
+                        "doc": "The label associated with the input field.<br/>This provides context to the user and improves accessibility."
                     },
                     {
                         "name": "multiline",
                         "type": "bool",
                         "default_value": "False",
-                        "doc": "If True, the text is presented as a multi line input."
+                        "doc": "If True, the input is rendered as a multi-line text area<br/>The default behavior is a single-line input."
                     },
                     {
                         "name": "lines_shown",
                         "type": "int",
                         "default_value": "5",
-                        "doc": "The number of lines shown in the input control, when multiline is True."
+                        "doc": "The number of lines displayed in the input control when multiline is True."
                     },
                     {
                         "name": "type",
                         "type": "str",
                         "default_value": "\"text\"",
-                        "doc": "The type of generated input HTML element, as defined in [HTML input types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types).<br/>This value forces certain values to be entered and can be set to \"text\", \"tel\", \"email\", \"url\"..., among other choices."
+                        "doc": "The type of input element, as per <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types\">HTML input types</a>.<br/>This property enforces specific input formats where applicable. Supported values include \"text\", \"tel\", \"email\", \"url\", etc."
+                    },
+                    {
+                        "name": "action_on_blur",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "If True, the <code>on_action</code> callback is triggered when the input control looses keyboard focus (e.g., when the user presses the Tab key). When this happens, the key name for the event (set in the <i>args</i> property of the <i>payload</i> parameter to the callback function) is set to \"Tab\"."
                     }
                 ]
             }
@@ -162,29 +168,35 @@
                         "name": "label",
                         "type": "str",
                         "default_value": "None",
-                        "doc": "The label associated with the input."
+                        "doc": "The label associated with the number field.<br/>This provides context to the user and improves accessibility."
                     },
                     {
                         "name": "step",
                         "type": "dynamic(Union[int,float])",
                         "default_value": "1",
-                        "doc": "The amount by which the value is incremented or decremented when the user clicks one of the arrow buttons."
+                        "doc": "The increment or decrement applied to the value when the user clicks the arrow buttons."
                     },
                     {
                         "name": "step_multiplier",
                         "type": "dynamic(Union[int,float])",
                         "default_value": "10",
-                        "doc": "A factor that multiplies <i>step</i> when the user presses the Shift key while clicking one of the arrow buttons."
+                        "doc": "The factor by which the step value is multiplied when the user holds the Shift key while clicking the arrow buttons."
                     },
                     {
                         "name": "min",
                         "type": "dynamic(Union[int,float])",
-                        "doc": "The minimum value to accept for this input."
+                        "doc": "The minimum acceptable value.<br/>Values below this threshold are invalid."
                     },
                     {
                         "name": "max",
                         "type": "dynamic(Union[int,float])",
-                        "doc": "The maximum value to accept for this input."
+                        "doc": "The maximum acceptable value.<br/>Values above this threshold are invalid."
+                    },
+                    {
+                        "name": "action_on_blur",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "If True, the <code>on_action</code> callback is triggered when the number control looses keyboard focus (e.g., when the user presses the Tab key). When this happens, the key name for the event (set in the <i>args</i> property of the <i>payload</i> parameter to the callback function) is set to \"Tab\"."
                     }
                 ]
             }
@@ -236,30 +248,30 @@
                         "name": "continuous",
                         "type": "bool",
                         "default_value": "True",
-                        "doc": "If set to False, the control emits an <tt>on_change</tt> notification only when the mouse button is released, otherwise notifications are emitted during the cursor movements.<br/>If <i>lov</i> is defined, the default value is False."
+                        "doc": "If set to False, the control emits an <code>on_change</code> notification only when the mouse button is released, otherwise notifications are emitted during the cursor movements.<br/>If <i>lov</i> is defined, the default value is False."
                     },
                     {
                         "name": "change_delay",
                         "type": "int",
                         "default_value": "<i>App config</i>",
-                        "doc": "Minimum time between triggering two <tt>on_change</tt> callbacks.<br/>The default value is defined at the application configuration level by the <strong>change_delay</strong> configuration option. if None or 0, there's no delay."
+                        "doc": "Minimum time between triggering two <code>on_change</code> callbacks.<br/>The default value is defined at the application configuration level by the <strong>change_delay</strong> configuration option. if None or 0, there's no delay."
                     },
                     {
                         "name": "width",
                         "type": "str",
                         "default_value": "\"300px\"",
-                        "doc": "The width of this slider, in CSS units."
+                        "doc": "The width of the slider, in CSS units."
                     },
                     {
                         "name": "height",
                         "type": "str",
-                        "doc": "The height of this slider, in CSS units.<br/>It defaults to the value of <i>width</i> when using the vertical orientation."
+                        "doc": "The height of the slider, in CSS units.<br/>It defaults to the value of <i>width</i> when using the vertical orientation."
                     },
                     {
                         "name": "orientation",
                         "type": "str",
                         "default_value": "\"horizontal\"",
-                        "doc": "The orientation of this slider.<br/>Valid values are \"horizontal\" or \"vertical\"."
+                        "doc": "The orientation of the slider.<br/>Valid values are \"horizontal\" or \"vertical\"."
                     }
                 ]
             }
@@ -588,7 +600,7 @@
                     {
                         "name": "title",
                         "type": "str",
-                        "doc": "The title of this chart control."
+                        "doc": "The title of the chart control."
                     },
                     {
                         "name": "render",
@@ -659,7 +671,7 @@
                     {
                         "name": "selected_marker",
                         "type": "indexed(dict[str, Any])",
-                        "doc": "The type of markers used for selected points in the indicated trace.<br/>See <a href=\"https://plotly.com/javascript/reference/scatter/#scatter-selected-marker\">selected marker for more details."
+                        "doc": "The type of markers used for selected points in the indicated trace.<br/>See <a href=\"https://plotly.com/javascript/reference/scatter/#scatter-selected-marker\">selected marker for more details.</a>"
                     },
                     {
                         "name": "layout",
@@ -700,12 +712,12 @@
                         "name": "width",
                         "type": "Union[str,int,float]",
                         "default_value": "\"100%\"",
-                        "doc": "The width of this chart, in CSS units."
+                        "doc": "The width of the chart, in CSS units."
                     },
                     {
                         "name": "height",
                         "type": "Union[str,int,float]",
-                        "doc": "The height of this chart, in CSS units."
+                        "doc": "The height of the chart, in CSS units."
                     },
                     {
                         "name": "template",
@@ -895,13 +907,13 @@
                         "name": "width",
                         "type": "str",
                         "default_value": "\"100%\"",
-                        "doc": "The width of this table control, in CSS units."
+                        "doc": "The width of the table control, in CSS units."
                     },
                     {
                         "name": "height",
                         "type": "str",
                         "default_value": "\"80vh\"",
-                        "doc": "The height of this table control, in CSS units."
+                        "doc": "The height of the table control, in CSS units."
                     },
                     {
                         "name": "filter",
@@ -943,7 +955,7 @@
                         "name": "on_edit",
                         "type": "Union[bool, Callable]",
                         "default_value": "<i>default implementation</i>",
-                        "doc": "A function or the name of a function triggered when an edited cell is validated.<br/>This function is invoked with the following parameters:<ul><li><i>state</i> (<code>State^</code>): the state instance.</li><li><i>var_name</i> (str): the name of the tabular data variable.</li><li><i>payload</i> (dict): a dictionary containing details about the callback invocation, with the following keys:<ul><li><i>index</i> (int): the row index.</li><li><i>col</i> (str): the column name.</li><li><i>value</i> (Any): the new cell value, cast to the column's data type.</li><li><i>user_value</i> (str): the new cell value, as entered by the user.</li><li><i>tz</i> (str): the timezone, if the column type is <tt>date</tt>.</li></ul></li></ul>If this property is set to False, the table does not provide the cell editing functionality.<br/>If this property is not set, the table will use the default implementation for editing cells.",
+                        "doc": "A function or the name of a function triggered when an edited cell is validated.<br/>This function is invoked with the following parameters:<ul><li><i>state</i> (<code>State^</code>): the state instance.</li><li><i>var_name</i> (str): the name of the tabular data variable.</li><li><i>payload</i> (dict): a dictionary containing details about the callback invocation, with the following keys:<ul><li><i>index</i> (int): the row index.</li><li><i>col</i> (str): the column name.</li><li><i>value</i> (Any): the new cell value, cast to the column's data type.</li><li><i>user_value</i> (str): the new cell value, as entered by the user.</li><li><i>tz</i> (str): the timezone, if the column type is <code>date</code>.</li></ul></li></ul>If this property is set to False, the table does not provide the cell editing functionality.<br/>If this property is not set, the table will use the default implementation for editing cells.",
                         "signature": [
                             [
                                 "state",
@@ -1085,7 +1097,7 @@
                     {
                         "name": "mode",
                         "type": "str",
-                        "doc": "Define the way the selector is displayed:\n<ul><li>&quot;radio&quot;: as a list of radio buttons</li><li>&quot;check&quot;: as a list of check boxes</li><li>any other value: a plain list."
+                        "doc": "Define the way the selector is displayed:\n<ul><li>&quot;radio&quot;: as a list of radio buttons</li><li>&quot;check&quot;: as a list of check boxes</li><li>any other value: a plain list.</ul>"
                     },
                     {
                         "name": "dropdown",
@@ -1109,12 +1121,12 @@
                         "name": "width",
                         "type": "Union[str,int]",
                         "default_value": "\"360px\"",
-                        "doc": "The width of this selector, in CSS units."
+                        "doc": "The width of the selector, in CSS units."
                     },
                     {
                         "name": "height",
                         "type": "Union[str,int]",
-                        "doc": "The height of this selector, in CSS units."
+                        "doc": "The height of the selector, in CSS units."
                     }
                 ]
             }
@@ -1308,12 +1320,12 @@
                         "name": "width",
                         "type": "Union[str,int,float]",
                         "default_value": "\"300px\"",
-                        "doc": "The width of this image control, in CSS units."
+                        "doc": "The width of the image control, in CSS units."
                     },
                     {
                         "name": "height",
                         "type": "Union[str,int,float]",
-                        "doc": "The height of this image control, in CSS units."
+                        "doc": "The height of the image control, in CSS units."
                     }
                 ]
             }
@@ -1341,13 +1353,13 @@
                         "name": "min",
                         "type": "Union[int,float]",
                         "default_value": "0",
-                        "doc": "The minimum value of this metric control's gauge."
+                        "doc": "The minimum value of the metric control's gauge."
                     },
                     {
                         "name": "max",
                         "type": "Union[int,float]",
                         "default_value": "100",
-                        "doc": "The maximum value of this metric control's gauge."
+                        "doc": "The maximum value of the metric control's gauge."
                     },
                     {
                         "name": "delta",
@@ -1480,7 +1492,7 @@
                         "name": "width",
                         "type": "Union[str,int]",
                         "default_value": "None",
-                        "doc": "The width of this progress indicator, in CSS units."
+                        "doc": "The width of the progress indicator, in CSS units."
                     }
                 ]
             }
@@ -1525,7 +1537,7 @@
                         "name": "orientation",
                         "type": "str",
                         "default_value": "\"horizontal\"",
-                        "doc": "The orientation of this slider."
+                        "doc": "The orientation of the indicator."
                     },
                     {
                         "name": "width",
@@ -1596,7 +1608,7 @@
                     {
                         "name": "adapter",
                         "type": "Union[str, Callable]",
-                        "default_value": "<tt>lambda x: str(x)</tt>",
+                        "default_value": "<code>lambda x: str(x)</code>",
                         "doc": "A function or the name of the function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:Union[str,Icon])</i>.<br/>The default value is a function that returns the string representation of the <i>lov</i> element."
                     },
                     {
@@ -1801,7 +1813,7 @@
                     {
                         "name": "height",
                         "type": "Union[str,int,float]",
-                        "doc": "The maximum height of this chat control, in CSS units."
+                        "doc": "The maximum height of the chat control, in CSS units."
                     },
                     {
                         "name": "show_sender",
@@ -1858,7 +1870,7 @@
                     {
                         "name": "row_height",
                         "type": "str",
-                        "doc": "The height of each row of this tree, in CSS units."
+                        "doc": "The height of each row of the tree, in CSS units."
                     },
                     {
                         "name": "mode",
@@ -1901,7 +1913,7 @@
                     {
                         "name": "height",
                         "type": "dynamic(str)",
-                        "doc": "The height, in CSS units, of this block."
+                        "doc": "The height of the part, in CSS units."
                     },
                     {
                         "name": "content",
@@ -1924,7 +1936,7 @@
                         "name": "title",
                         "default_property": true,
                         "type": "dynamic(str)",
-                        "doc": "Title of this block element."
+                        "doc": "Title of the expandable block."
                     },
                     {
                         "name": "expanded",
@@ -1974,22 +1986,22 @@
                         "name": "close_label",
                         "type": "str",
                         "default_value": "\"Close\"",
-                        "doc": "The tooltip of the top-right close icon button. In the <tt>on_action</tt> callback, <i>args</i> will be set to -1."
+                        "doc": "The tooltip of the top-right close icon button. In the <code>on_action</code> callback, <i>args</i> will be set to -1."
                     },
                     {
                         "name": "labels",
                         "type": "Union[str,list[str]]",
-                        "doc": "A list of labels to show in a row of buttons at the bottom of the dialog. The index of the button in the list is reported as args in the <tt>on_action</tt> callback (that index is -1 for the <i>close</i> icon)."
+                        "doc": "A list of labels to show in a row of buttons at the bottom of the dialog. The index of the button in the list is reported as args in the <code>on_action</code> callback (that index is -1 for the <i>close</i> icon)."
                     },
                     {
                         "name": "width",
                         "type": "Union[str,int,float]",
-                        "doc": "The width of this dialog, in CSS units."
+                        "doc": "The width of the dialog, in CSS units."
                     },
                     {
                         "name": "height",
                         "type": "Union[str,int,float]",
-                        "doc": "The height of this dialog, in CSS units."
+                        "doc": "The height of the dialog, in CSS units."
                     }
                 ]
             }
@@ -2071,13 +2083,13 @@
                         "name": "width",
                         "type": "str",
                         "default_value": "\"30vw\"",
-                        "doc": "Width, in CSS units, of this pane.<br/>This is used only if <i>anchor</i> is \"left\" or \"right\"."
+                        "doc": "Width of the pane, in CSS units.<br/>This is used only if <i>anchor</i> is \"left\" or \"right\"."
                     },
                     {
                         "name": "height",
                         "type": "str",
                         "default_value": "\"30vh\"",
-                        "doc": "Height, in CSS units, of this pane.<br/>This is used only if <i>anchor</i> is \"top\" or \"bottom\"."
+                        "doc": "Height of this pane, in CSS units.<br/>This is used only if <i>anchor</i> is \"top\" or \"bottom\"."
                     },
                     {
                         "name": "show_button",
@@ -2098,7 +2110,7 @@
                         "name": "active",
                         "type": "dynamic(bool)",
                         "default_value": "True",
-                        "doc": "Indicates if this component is active.<br/>An inactive component allows no user interaction."
+                        "doc": "Indicates if this element is active.<br/>If False, the element is disabled, and user interaction is not allowed."
                     }
                 ]
             }
@@ -2124,7 +2136,7 @@
                     {
                         "name": "adapter",
                         "type": "Union[str, Callable]",
-                        "default_value": "<tt>lambda x: str(x)</tt>",
+                        "default_value": "<code>lambda x: str(x)</code>",
                         "doc": "A function or the name of the function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:Union[str,Icon])</i>.<br/>The default value is a function that returns the string representation of the <i>lov</i> element."
                     },
                     {
@@ -2149,7 +2161,7 @@
                     {
                         "name": "on_change",
                         "type": "Union[str, Callable]",
-                        "doc": "A function or the name of a function that is triggered when the value is updated.<br/>This function is invoked with the following parameters:<ul>\n<li>state (<code>State^</code>): the state instance.</li><li>var_name (str): the variable name.</li><li>value (Any): the new value.</li></ul>",
+                        "doc": "A function or the name of a function that is triggered when the value changes.<br/>The callback function receives the following parameters:<ul>\n<li>state (<code>State^</code>): the state instance.</li><li>var_name (str): the bound variable name.</li><li>value (Any): the updated value.</li></ul>",
                         "signature": [
                             [
                                 "state",
@@ -2197,7 +2209,7 @@
                         "name": "propagate",
                         "type": "bool",
                         "default_value": "<i>App config</i>",
-                        "doc": "Allows the control's main value to be automatically propagated.<br/>The default value is defined at the application configuration level by the <strong>propagate</strong> configuration option.<br/>If True, any change to the control's value is immediately reflected in the bound application variable."
+                        "doc": "Determines whether the control's value is automatically reflected in the bound application variable.<br/>The default value is defined at the application configuration level by the <strong>propagate</strong> configuration option.<br/>If True, any change to the control's value is immediately reflected in the variable."
                     }
                 ]
             }
@@ -2210,12 +2222,12 @@
                         "name": "change_delay",
                         "type": "int",
                         "default_value": "<i>App config</i>",
-                        "doc": "Minimum interval between two consecutive calls to the <tt>on_change</tt> callback.<br/>The default value is defined at the application configuration level by the <strong>change_delay</strong> configuration option.<br/>if None, the delay is set to 300 ms.<br/>If set to -1, the input change is triggered only when the user presses the Enter key."
+                        "doc": "The minimum interval (in milliseconds) between two consecutive calls to the <code>on_change</code> callback.<br/>The default value is defined at the application configuration level by the <strong>change_delay</strong> configuration option.<br/>if None, the delay is set to 300 ms.<br/>If set to -1, the callback is triggered only when the user presses the Enter key."
                     },
                     {
                         "name": "on_action",
                         "type": "Union[str, Callable]",
-                        "doc": "A function or the name of a function that is triggered when a specific key is pressed.<br/>This function is invoked with the following parameters:<ul>\n<li>state (<code>State^</code>): the state instance.</li><li>id (str): the identifier of the control if it has one.</li><li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li><li>args (list):\n<ul><li>key name</li><li>variable name</li><li>current value</li></ul></li></ul></li></ul>",
+                        "doc": "A function or the name of a function that is triggered when a specific key is pressed.<br/>The callback function is invoked with the following parameters:<ul>\n<li>state (<code>State^</code>): the state instance.</li><li>id (str): the identifier of the control if it has one.</li><li>payload (dict): the callback details<br/>\nThis dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li><li>args (list):\n<ul><li>The key name pressed.</li><li>The variable name.</li><li>The current value of the variable.</li></ul></li></ul></li></ul>",
                         "signature": [
                             [
                                 "state",
@@ -2235,13 +2247,13 @@
                         "name": "action_keys",
                         "type": "str",
                         "default_value": "\"Enter\"",
-                        "doc": "Semicolon (';')-separated list of supported key names.<br/>Authorized values are Enter, Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12."
+                        "doc": "A semicolon-separated list of keys that can trigger the <code>on_action</code> callback.<br/>Authorized values are Enter, Escape, and function keys F1 to F12."
                     },
                     {
                         "name": "width",
                         "type": "Union[str,int]",
                         "default_value": "None",
-                        "doc": "The width of the element."
+                        "doc": "The width of the element, in CSS units."
                     }
                 ]
             }
@@ -2253,22 +2265,22 @@
                     {
                         "name": "id",
                         "type": "str",
-                        "doc": "The identifier that is assigned to the rendered HTML component."
+                        "doc": "The identifier assigned to the rendered HTML component.<br/>This can be used in callbacks or to target the element for styling."
                     },
                     {
                         "name": "properties",
                         "type": "dict[str, Any]",
-                        "doc": "Bound to a dictionary that contains additional properties for this element."
+                        "doc": "A dictionary of additional properties that can be set to the element."
                     },
                     {
                         "name": "class_name",
                         "type": "dynamic(str)",
-                        "doc": "The list of CSS class names that are associated with the generated HTML Element.<br/>These class names are added to the default <code>taipy-[element_type]</code> class name."
+                        "doc": "A space-separated list of CSS class names to be applied to the generated HTML element.<br/>These classes are added to the default <code>taipy-[element_type]</code> class."
                     },
                     {
                         "name": "hover_text",
                         "type": "dynamic(str)",
-                        "doc": "The information that is displayed when the user hovers over this element."
+                        "doc": "The text that is displayed when the user hovers over the element."
                     }
                 ]
             }

+ 1 - 1
taipy/gui_core/viselements.json

@@ -208,7 +208,7 @@
                     {
                         "name": "on_submission_change",
                         "type": "Union[str, Callable]",
-                        "doc": "A function or the name of a function that is triggered when a submission status is changed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>submission (Submission): the submission entity containing submission information.</li>\n<li>details (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>submission_status (str): the new status of the submission (possible values are: \"SUBMITTED\", \"COMPLETED\", \"CANCELED\", \"FAILED\", \"BLOCKED\", \"WAITING\", or \"RUNNING\").</li>\n<li>job: the Job (if any) that is at the origin of the submission status change.</li>\n<li>submittable_entity (Submittable): the entity (usually a Scenario) that was submitted.</li>\n</ul>",
+                        "doc": "A function or the name of a function that is triggered when a submission status is changed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>submission (Submission): the submission entity containing submission information.</li>\n<li>details (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>submission_status (str): the new status of the submission (possible values are: \"SUBMITTED\", \"COMPLETED\", \"CANCELED\", \"FAILED\", \"BLOCKED\", \"WAITING\", or \"RUNNING\").</li>\n<li>job: the Job (if any) that is at the origin of the submission status change.</li>\n<li>submittable_entity (Submittable): the entity (usually a Scenario) that was submitted.</li></ul></ul>",
                         "signature": [
                             [
                                 "state",

+ 83 - 0
tests/gui/e2e/with_action/test_input.py

@@ -0,0 +1,83 @@
+# Copyright 2021-2024 Avaiga Private Limited
+#
+# Licensed 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 inspect
+import logging
+from importlib import util
+
+import pytest
+
+if util.find_spec("playwright"):
+    from playwright._impl._page import Page
+
+from taipy.gui import Gui
+
+
+@pytest.mark.teste2e
+def test_input_action(page: "Page", gui: Gui, helpers):
+    page_md = """
+<|{input1_value}|input|on_action=input_action|id=input1|>
+<|{input2_value}|input|on_action=input_action|id=input2|action_on_blur|>
+<|X|button|id=button1|on_action=button_action|>
+<|{input1_action_tracker}|id=input1_tracker|>
+<|{input2_action_tracker}|id=input2_tracker|>
+<|{button_action_tracker}|id=button_tracker|>
+"""
+    input1_value = "init"  # noqa: F841
+    input2_value = "init"  # noqa: F841
+    input1_action_tracker = 0  # noqa: F841
+    input2_action_tracker = 0  # noqa: F841
+    button_action_tracker = 0  # noqa: F841
+
+    def input_action(state, id):
+        if id == "input1":
+            state.input1_action_tracker = state.input1_action_tracker + 1
+        elif id == "input2":
+            state.input2_action_tracker = state.input2_action_tracker + 1
+
+    def button_action(state, id):
+        state.button_action_tracker = state.button_action_tracker + 1
+
+    gui._set_frame(inspect.currentframe())
+    gui.add_page(name="test", page=page_md)
+    helpers.run_e2e(gui)
+    page.goto("./test")
+    page.expect_websocket()
+    page.wait_for_selector("#input1_tracker")
+    assert page.query_selector("#input1").input_value() == "init", "Wrong initial value"
+    page.click("#button1")
+    try:
+        page.wait_for_function("document.querySelector('#button_tracker').innerText !== '0'")
+    except Exception as e:
+        logging.getLogger().debug(f"Function evaluation timeout.\n{e}")
+    assert page.query_selector("#button_tracker").inner_text() == "1"
+    page.click("#input1")
+    page.fill("#input1", "step2")
+    page.click("#button1")
+    try:
+        page.wait_for_function("document.querySelector('#button_tracker').innerText !== '1'")
+    except Exception as e:
+        logging.getLogger().debug(f"Function evaluation timeout.\n{e}")
+    assert page.query_selector("#button_tracker").inner_text() == "2", "Button action should have been invoked"
+    assert (
+        page.query_selector("#input1_tracker").inner_text() == "0"
+    ), "Action should not have been invoked (no action_on_blur)"
+    page.click("#input2")
+    page.fill("#input2", "step2")
+    page.click("#button1")
+    try:
+        page.wait_for_function("document.querySelector('#button_tracker').innerText !== '2'")
+    except Exception as e:
+        logging.getLogger().debug(f"Function evaluation timeout.\n{e}")
+    assert page.query_selector("#button_tracker").inner_text() == "3", "Button action should have been invoked"
+    assert (
+        page.query_selector("#input2_tracker").inner_text() == "1"
+    ), "Action should have been invoked (action_on_blur)"