Jelajahi Sumber

add width (#1724)

* add width
resolves #1720

* unused

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 8 bulan lalu
induk
melakukan
f5ecc2c0c8
34 mengubah file dengan 627 tambahan dan 257 penghapusan
  1. 2 2
      frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx
  2. 25 8
      frontend/taipy-gui/src/components/Taipy/Button.spec.tsx
  3. 13 3
      frontend/taipy-gui/src/components/Taipy/Button.tsx
  4. 1 1
      frontend/taipy-gui/src/components/Taipy/Chat.tsx
  5. 21 3
      frontend/taipy-gui/src/components/Taipy/DateRange.spec.tsx
  6. 30 12
      frontend/taipy-gui/src/components/Taipy/DateRange.tsx
  7. 28 2
      frontend/taipy-gui/src/components/Taipy/DateSelector.spec.tsx
  8. 11 5
      frontend/taipy-gui/src/components/Taipy/DateSelector.tsx
  9. 28 14
      frontend/taipy-gui/src/components/Taipy/Field.spec.tsx
  10. 19 4
      frontend/taipy-gui/src/components/Taipy/Field.tsx
  11. 44 19
      frontend/taipy-gui/src/components/Taipy/FileDownload.spec.tsx
  12. 12 2
      frontend/taipy-gui/src/components/Taipy/FileDownload.tsx
  13. 19 9
      frontend/taipy-gui/src/components/Taipy/FileSelector.spec.tsx
  14. 7 4
      frontend/taipy-gui/src/components/Taipy/FileSelector.tsx
  15. 2 3
      frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx
  16. 69 31
      frontend/taipy-gui/src/components/Taipy/ThemeToggle.spec.tsx
  17. 17 6
      frontend/taipy-gui/src/components/Taipy/ThemeToggle.tsx
  18. 37 8
      frontend/taipy-gui/src/components/Taipy/Toggle.spec.tsx
  19. 22 9
      frontend/taipy-gui/src/components/Taipy/Toggle.tsx
  20. 4 6
      frontend/taipy-gui/src/components/Taipy/utils.ts
  21. 4 2
      frontend/taipy-gui/src/utils/ErrorBoundary.tsx
  22. 35 30
      frontend/taipy/src/CoreSelector.tsx
  23. 20 46
      frontend/taipy/src/DataNodeViewer.tsx
  24. 7 0
      taipy/gui/_renderers/factory.py
  25. 65 23
      taipy/gui/viselements.json
  26. 5 5
      taipy/gui_core/viselements.json
  27. 8 0
      tests/gui/control/test_button.py
  28. 13 0
      tests/gui/control/test_date.py
  29. 15 0
      tests/gui/control/test_date_range.py
  30. 12 0
      tests/gui/control/test_file_download.py
  31. 13 0
      tests/gui/control/test_file_selector.py
  32. 6 0
      tests/gui/control/test_number.py
  33. 7 0
      tests/gui/control/test_text.py
  34. 6 0
      tests/gui/control/test_toggle.py

+ 2 - 2
frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx

@@ -247,7 +247,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                 setOrder(isAsc ? "desc" : "asc");
                 setOrder(isAsc ? "desc" : "asc");
                 setOrderBy(col);
                 setOrderBy(col);
                 setRows([]);
                 setRows([]);
-                setTimeout(() => infiniteLoaderRef.current?.resetloadMoreItemsCache(true), 1); // So that the state can be changed
+                Promise.resolve().then(() => infiniteLoaderRef.current?.resetloadMoreItemsCache(true)); // So that the state can be changed
             }
             }
         },
         },
         [orderBy, order]
         [orderBy, order]
@@ -256,7 +256,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
     useEffect(() => {
     useEffect(() => {
         if (refresh) {
         if (refresh) {
             setRows([]);
             setRows([]);
-            setTimeout(() => infiniteLoaderRef.current?.resetloadMoreItemsCache(true), 1); // So that the state can be changed
+            Promise.resolve().then(() => infiniteLoaderRef.current?.resetloadMoreItemsCache(true)); // So that the state can be changed
         }
         }
     }, [refresh]);
     }, [refresh]);
 
 

+ 25 - 8
frontend/taipy-gui/src/components/Taipy/Button.spec.tsx

@@ -32,17 +32,28 @@ describe("Button Component", () => {
         expect(elt).toHaveClass("taipy-button");
         expect(elt).toHaveClass("taipy-button");
     });
     });
     it("displays the default value", async () => {
     it("displays the default value", async () => {
-        const { getByText } = render(
-            <Button defaultLabel="titi" label={undefined as unknown as string}  />
-        );
+        const { getByText } = render(<Button defaultLabel="titi" label={undefined as unknown as string} />);
         getByText("titi");
         getByText("titi");
     });
     });
     it("displays an image", async () => {
     it("displays an image", async () => {
         const { getByAltText } = render(
         const { getByAltText } = render(
-            <Button defaultLabel={JSON.stringify({path: "/image/fred.png", text: "fred"})} label={undefined as unknown as string} />
+            <Button
+                defaultLabel={JSON.stringify({ path: "/image/fred.png", text: "fred" })}
+                label={undefined as unknown as string}
+            />
         );
         );
         const img = getByAltText("fred");
         const img = getByAltText("fred");
-        expect(img.tagName).toBe("IMG")
+        expect(img.tagName).toBe("IMG");
+    });
+    it("displays with width=70%", async () => {
+        const { getByText } = render(<Button label="toto" width="70%" />);
+        const element = getByText("toto");
+        expect(element).toHaveStyle("width: 70%");
+    });
+    it("displays with width=500", async () => {
+        const { getByText } = render(<Button label="toto" width={500} />);
+        const element = getByText("toto");
+        expect(element).toHaveStyle("width: 500px");
     });
     });
     it("is disabled", async () => {
     it("is disabled", async () => {
         const { getByText } = render(<Button label="val" active={false} />);
         const { getByText } = render(<Button label="val" active={false} />);
@@ -62,11 +73,17 @@ describe("Button Component", () => {
     it("dispatch a well formed message", async () => {
     it("dispatch a well formed message", async () => {
         const dispatch = jest.fn();
         const dispatch = jest.fn();
         const state: TaipyState = INITIAL_STATE;
         const state: TaipyState = INITIAL_STATE;
-        const { getByText } = render(<TaipyContext.Provider value={{ state, dispatch }}>
+        const { getByText } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
                 <Button label="Button" onAction="on_action" />
                 <Button label="Button" onAction="on_action" />
-            </TaipyContext.Provider>);
+            </TaipyContext.Provider>
+        );
         const elt = getByText("Button");
         const elt = getByText("Button");
         await userEvent.click(elt);
         await userEvent.click(elt);
-        expect(dispatch).toHaveBeenCalledWith({"name": "", "payload": {args: [], action: "on_action"}, "type": "SEND_ACTION_ACTION"});
+        expect(dispatch).toHaveBeenCalledWith({
+            name: "",
+            payload: { args: [], action: "on_action" },
+            type: "SEND_ACTION_ACTION",
+        });
     });
     });
 });
 });

+ 13 - 3
frontend/taipy-gui/src/components/Taipy/Button.tsx

@@ -11,13 +11,13 @@
  * specific language governing permissions and limitations under the License.
  * specific language governing permissions and limitations under the License.
  */
  */
 
 
-import React, { useState, useEffect, useCallback } from "react";
+import React, { useState, useEffect, useCallback, useMemo } from "react";
 import CardHeader from "@mui/material/CardHeader";
 import CardHeader from "@mui/material/CardHeader";
 import MuiButton from "@mui/material/Button";
 import MuiButton from "@mui/material/Button";
 import Tooltip from "@mui/material/Tooltip";
 import Tooltip from "@mui/material/Tooltip";
 
 
 import { createSendActionNameAction } from "../../context/taipyReducers";
 import { createSendActionNameAction } from "../../context/taipyReducers";
-import { getSuffixedClassNames, TaipyActiveProps } from "./utils";
+import { getCssSize, getSuffixedClassNames, TaipyActiveProps } from "./utils";
 import { useClassNames, useDispatch, useDynamicProperty, useModule } from "../../utils/hooks";
 import { useClassNames, useDispatch, useDynamicProperty, useModule } from "../../utils/hooks";
 import { stringIcon, Icon, IconAvatar } from "../../utils/icon";
 import { stringIcon, Icon, IconAvatar } from "../../utils/icon";
 
 
@@ -25,6 +25,7 @@ interface ButtonProps extends TaipyActiveProps {
     onAction?: string;
     onAction?: string;
     label: string;
     label: string;
     defaultLabel?: string;
     defaultLabel?: string;
+    width?: string | number;
 }
 }
 
 
 const cardSx = { p: 0 };
 const cardSx = { p: 0 };
@@ -39,6 +40,8 @@ const Button = (props: ButtonProps) => {
     const active = useDynamicProperty(props.active, props.defaultActive, true);
     const active = useDynamicProperty(props.active, props.defaultActive, true);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
 
 
+    const buttonSx = useMemo(() => (props.width ? { width: getCssSize(props.width) } : undefined), [props.width]);
+
     const handleClick = useCallback(() => {
     const handleClick = useCallback(() => {
         dispatch(createSendActionNameAction(id, module, onAction));
         dispatch(createSendActionNameAction(id, module, onAction));
     }, [id, onAction, dispatch, module]);
     }, [id, onAction, dispatch, module]);
@@ -61,7 +64,14 @@ const Button = (props: ButtonProps) => {
 
 
     return (
     return (
         <Tooltip title={hover || ""}>
         <Tooltip title={hover || ""}>
-            <MuiButton id={id} variant="outlined" className={className} onClick={handleClick} disabled={!active}>
+            <MuiButton
+                id={id}
+                variant="outlined"
+                className={className}
+                onClick={handleClick}
+                disabled={!active}
+                sx={buttonSx}
+            >
                 {typeof value === "string" ? (
                 {typeof value === "string" ? (
                     value
                     value
                 ) : (value as Icon).text ? (
                 ) : (value as Icon).text ? (

+ 1 - 1
frontend/taipy-gui/src/components/Taipy/Chat.tsx

@@ -329,7 +329,7 @@ const Chat = (props: ChatProps) => {
 
 
     useEffect(() => {
     useEffect(() => {
         if (refresh) {
         if (refresh) {
-            setTimeout(() => loadMoreItems(0), 1); // So that the state can be changed
+            Promise.resolve().then(() => loadMoreItems(0)); // So that the state can be changed
         }
         }
     }, [refresh, loadMoreItems]);
     }, [refresh, loadMoreItems]);
 
 

+ 21 - 3
frontend/taipy-gui/src/components/Taipy/DateRange.spec.tsx

@@ -156,6 +156,24 @@ describe("DateRange Component", () => {
         const endInput = getByLabelText("end") as HTMLInputElement;
         const endInput = getByLabelText("end") as HTMLInputElement;
         expect(endInput.value).toBe("01/31/2001");
         expect(endInput.value).toBe("01/31/2001");
     });
     });
+    it("displays with width=70%", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <DateRange dates={curDates} width="70%" />
+            </LocalizationProvider>
+        );
+        const elt = document.querySelector(".MuiStack-root");
+        expect(elt).toHaveStyle("width: 70%");
+    });
+    it("displays with width=500", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <DateRange dates={curDates} width={500} />
+            </LocalizationProvider>
+        );
+        const elt = document.querySelector(".MuiStack-root");
+        expect(elt).toHaveStyle("width: 500px");
+    });
     it("is disabled", async () => {
     it("is disabled", async () => {
         render(
         render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>
             <LocalizationProvider dateAdapter={AdapterDateFns}>
@@ -256,7 +274,7 @@ describe("DateRange with time Component", () => {
         render(
         render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>
             <LocalizationProvider dateAdapter={AdapterDateFns}>
                 <DateRange
                 <DateRange
-                    defaultDates="[&quot;2001-01-01T00:00:01.001Z&quot;,&quot;2001-01-31T00:00:01.001Z&quot;]"
+                    defaultDates='["2001-01-01T00:00:01.001Z","2001-01-31T00:00:01.001Z"]'
                     withTime={true}
                     withTime={true}
                     dates={undefined as unknown as string[]}
                     dates={undefined as unknown as string[]}
                     className="tp-dt"
                     className="tp-dt"
@@ -274,7 +292,7 @@ describe("DateRange with time Component", () => {
         render(
         render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>
             <LocalizationProvider dateAdapter={AdapterDateFns}>
                 <DateRange
                 <DateRange
-                    defaultDates="[&quot;2001-01-01T00:10:01.001Z&quot;,&quot;2001-01-31T00:11:01.001Z&quot;]"
+                    defaultDates='["2001-01-01T00:10:01.001Z","2001-01-31T00:11:01.001Z"]'
                     withTime={true}
                     withTime={true}
                     dates={undefined as unknown as string[]}
                     dates={undefined as unknown as string[]}
                     className="tp-dt"
                     className="tp-dt"
@@ -293,7 +311,7 @@ describe("DateRange with time Component", () => {
         const { getByLabelText } = render(
         const { getByLabelText } = render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>
             <LocalizationProvider dateAdapter={AdapterDateFns}>
                 <DateRange
                 <DateRange
-                    defaultDates="[&quot;2001-01-01T00:00:01.001Z&quot;,&quot;2001-01-31T00:00:01.001Z&quot;]"
+                    defaultDates='["2001-01-01T00:00:01.001Z","2001-01-31T00:00:01.001Z"]'
                     dates={undefined as unknown as string[]}
                     dates={undefined as unknown as string[]}
                     withTime={true}
                     withTime={true}
                     className="taipy-date-range"
                     className="taipy-date-range"

+ 30 - 12
frontend/taipy-gui/src/components/Taipy/DateRange.tsx

@@ -11,9 +11,10 @@
  * specific language governing permissions and limitations under the License.
  * specific language governing permissions and limitations under the License.
  */
  */
 
 
-import React, { useState, useEffect, useCallback } from "react";
-import Box from "@mui/material/Box";
+import React, { useState, useEffect, useCallback, useMemo } from "react";
+import Stack from "@mui/material/Stack";
 import Tooltip from "@mui/material/Tooltip";
 import Tooltip from "@mui/material/Tooltip";
+import Typography from "@mui/material/Typography";
 import { DatePicker, DatePickerProps } from "@mui/x-date-pickers/DatePicker";
 import { DatePicker, DatePickerProps } from "@mui/x-date-pickers/DatePicker";
 import { BaseDateTimePickerSlotProps } from "@mui/x-date-pickers/DateTimePicker/shared";
 import { BaseDateTimePickerSlotProps } from "@mui/x-date-pickers/DateTimePicker/shared";
 import { DateTimePicker, DateTimePickerProps } from "@mui/x-date-pickers/DateTimePicker";
 import { DateTimePicker, DateTimePickerProps } from "@mui/x-date-pickers/DateTimePicker";
@@ -21,7 +22,7 @@ import { isValid } from "date-fns";
 import { ErrorBoundary } from "react-error-boundary";
 import { ErrorBoundary } from "react-error-boundary";
 
 
 import { createSendUpdateAction } from "../../context/taipyReducers";
 import { createSendUpdateAction } from "../../context/taipyReducers";
-import { getSuffixedClassNames, TaipyActiveProps, TaipyChangeProps, DateProps, getProps } from "./utils";
+import { getCssSize, getSuffixedClassNames, TaipyActiveProps, TaipyChangeProps, DateProps, getProps } from "./utils";
 import { dateToString, getDateTime, getTimeZonedDate } from "../../utils";
 import { dateToString, getDateTime, getTimeZonedDate } from "../../utils";
 import { useClassNames, useDispatch, useDynamicProperty, useFormatConfig, useModule } from "../../utils/hooks";
 import { useClassNames, useDispatch, useDynamicProperty, useFormatConfig, useModule } from "../../utils/hooks";
 import Field from "./Field";
 import Field from "./Field";
@@ -36,9 +37,10 @@ interface DateRangeProps extends TaipyActiveProps, TaipyChangeProps {
     editable?: boolean;
     editable?: boolean;
     labelStart?: string;
     labelStart?: string;
     labelEnd?: string;
     labelEnd?: string;
+    separator?: string;
+    width?: string | number;
 }
 }
 
 
-const boxSx = { display: "inline-flex", alignItems: "center", gap: "0.5em" };
 const textFieldProps = { textField: { margin: "dense" } } as BaseDateTimePickerSlotProps<Date>;
 const textFieldProps = { textField: { margin: "dense" } } as BaseDateTimePickerSlotProps<Date>;
 
 
 const getRangeDateTime = (
 const getRangeDateTime = (
@@ -61,7 +63,7 @@ const getRangeDateTime = (
 };
 };
 
 
 const DateRange = (props: DateRangeProps) => {
 const DateRange = (props: DateRangeProps) => {
-    const { updateVarName, withTime = false, id, propagate = true } = props;
+    const { updateVarName, withTime = false, id, propagate = true, separator = "-" } = props;
     const dispatch = useDispatch();
     const dispatch = useDispatch();
     const formatConfig = useFormatConfig();
     const formatConfig = useFormatConfig();
     const tz = formatConfig.timeZone;
     const tz = formatConfig.timeZone;
@@ -75,6 +77,8 @@ const DateRange = (props: DateRangeProps) => {
     const editable = useDynamicProperty(props.editable, props.defaultEditable, true);
     const editable = useDynamicProperty(props.editable, props.defaultEditable, true);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
 
 
+    const dateSx = useMemo(() => (props.width ? { maxWidth: "100%" } : undefined), [props.width]);
+
     const handleChange = useCallback(
     const handleChange = useCallback(
         (v: Date | null, start: boolean) => {
         (v: Date | null, start: boolean) => {
             setValue((dates) => {
             setValue((dates) => {
@@ -124,7 +128,15 @@ const DateRange = (props: DateRangeProps) => {
     return (
     return (
         <ErrorBoundary FallbackComponent={ErrorFallback}>
         <ErrorBoundary FallbackComponent={ErrorFallback}>
             <Tooltip title={hover || ""}>
             <Tooltip title={hover || ""}>
-                <Box id={id} className={className} sx={boxSx}>
+                <Stack
+                    id={id}
+                    className={className}
+                    gap={0.5}
+                    direction="row"
+                    display="inline-flex"
+                    alignItems="center"
+                    width={props.width ? getCssSize(props.width) : undefined}
+                >
                     {editable ? (
                     {editable ? (
                         withTime ? (
                         withTime ? (
                             <>
                             <>
@@ -141,8 +153,9 @@ const DateRange = (props: DateRangeProps) => {
                                     slotProps={textFieldProps}
                                     slotProps={textFieldProps}
                                     label={props.labelStart}
                                     label={props.labelStart}
                                     format={props.format}
                                     format={props.format}
+                                    sx={dateSx}
                                 />
                                 />
-                                -
+                                <Typography>{separator}</Typography>
                                 <DateTimePicker
                                 <DateTimePicker
                                     {...(endProps as DateTimePickerProps<Date>)}
                                     {...(endProps as DateTimePickerProps<Date>)}
                                     value={value[1]}
                                     value={value[1]}
@@ -156,6 +169,7 @@ const DateRange = (props: DateRangeProps) => {
                                     slotProps={textFieldProps}
                                     slotProps={textFieldProps}
                                     label={props.labelEnd}
                                     label={props.labelEnd}
                                     format={props.format}
                                     format={props.format}
+                                    sx={dateSx}
                                 />
                                 />
                             </>
                             </>
                         ) : (
                         ) : (
@@ -173,8 +187,9 @@ const DateRange = (props: DateRangeProps) => {
                                     slotProps={textFieldProps}
                                     slotProps={textFieldProps}
                                     label={props.labelStart}
                                     label={props.labelStart}
                                     format={props.format}
                                     format={props.format}
+                                    sx={dateSx}
                                 />
                                 />
-                                -
+                                <Typography>{separator}</Typography>
                                 <DatePicker
                                 <DatePicker
                                     {...(endProps as DatePickerProps<Date>)}
                                     {...(endProps as DatePickerProps<Date>)}
                                     value={value[1]}
                                     value={value[1]}
@@ -188,6 +203,7 @@ const DateRange = (props: DateRangeProps) => {
                                     slotProps={textFieldProps}
                                     slotProps={textFieldProps}
                                     label={props.labelEnd}
                                     label={props.labelEnd}
                                     format={props.format}
                                     format={props.format}
+                                    sx={dateSx}
                                 />
                                 />
                             </>
                             </>
                         )
                         )
@@ -195,22 +211,24 @@ const DateRange = (props: DateRangeProps) => {
                         <>
                         <>
                             <Field
                             <Field
                                 dataType="datetime"
                                 dataType="datetime"
-                                value={props.dates[0]}
+                                value={value[0] && isValid(value[0]) ? value[0].toISOString() : ""}
                                 format={props.format}
                                 format={props.format}
                                 id={id && id + "-field"}
                                 id={id && id + "-field"}
                                 className={getSuffixedClassNames(className, "-text")}
                                 className={getSuffixedClassNames(className, "-text")}
+                                width={props.width && "100%"}
                             />
                             />
-                            -
+                            <Typography>{separator}</Typography>
                             <Field
                             <Field
                                 dataType="datetime"
                                 dataType="datetime"
-                                value={props.dates[1]}
+                                value={value[1] && isValid(value[1]) ? value[1].toISOString() : ""}
                                 format={props.format}
                                 format={props.format}
                                 id={id && id + "-field"}
                                 id={id && id + "-field"}
                                 className={getSuffixedClassNames(className, "-text")}
                                 className={getSuffixedClassNames(className, "-text")}
+                                width={props.width && "100%"}
                             />
                             />
                         </>
                         </>
                     )}
                     )}
-                </Box>
+                </Stack>
             </Tooltip>
             </Tooltip>
         </ErrorBoundary>
         </ErrorBoundary>
     );
     );

+ 28 - 2
frontend/taipy-gui/src/components/Taipy/DateSelector.spec.tsx

@@ -99,7 +99,11 @@ describe("DateSelector Component", () => {
     it("displays the default value with format", async () => {
     it("displays the default value with format", async () => {
         render(
         render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>
             <LocalizationProvider dateAdapter={AdapterDateFns}>
-                <DateSelector defaultDate="2011-01-01T00:00:01.001Z" date={undefined as unknown as string} format="yy-MM-dd" />
+                <DateSelector
+                    defaultDate="2011-01-01T00:00:01.001Z"
+                    date={undefined as unknown as string}
+                    format="yy-MM-dd"
+                />
             </LocalizationProvider>
             </LocalizationProvider>
         );
         );
         const input = document.querySelector("input");
         const input = document.querySelector("input");
@@ -120,6 +124,24 @@ describe("DateSelector Component", () => {
         const input = getByLabelText("a label") as HTMLInputElement;
         const input = getByLabelText("a label") as HTMLInputElement;
         expect(input.value).toBe("01/01/2001");
         expect(input.value).toBe("01/01/2001");
     });
     });
+    it("displays with width=70%", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <DateSelector date={curDateStr} width="70%" />
+            </LocalizationProvider>
+        );
+        const elt = document.querySelector(".MuiFormControl-root");
+        expect(elt).toHaveStyle("max-width: 70%");
+    });
+    it("displays with width=500", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <DateSelector date={curDateStr} width={500} />
+            </LocalizationProvider>
+        );
+        const elt = document.querySelector(".MuiFormControl-root");
+        expect(elt).toHaveStyle("max-width: 500px");
+    });
     it("is disabled", async () => {
     it("is disabled", async () => {
         render(
         render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>
             <LocalizationProvider dateAdapter={AdapterDateFns}>
@@ -212,7 +234,11 @@ describe("DateSelector with time Component", () => {
     it("displays the default value with format", async () => {
     it("displays the default value with format", async () => {
         render(
         render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>
             <LocalizationProvider dateAdapter={AdapterDateFns}>
-                <DateSelector defaultDate="2011-01-01T00:10:01.001Z" date={undefined as unknown as string} format="yy-MM-dd mm" />
+                <DateSelector
+                    defaultDate="2011-01-01T00:10:01.001Z"
+                    date={undefined as unknown as string}
+                    format="yy-MM-dd mm"
+                />
             </LocalizationProvider>
             </LocalizationProvider>
         );
         );
         const input = document.querySelector("input");
         const input = document.querySelector("input");

+ 11 - 5
frontend/taipy-gui/src/components/Taipy/DateSelector.tsx

@@ -11,7 +11,7 @@
  * specific language governing permissions and limitations under the License.
  * specific language governing permissions and limitations under the License.
  */
  */
 
 
-import React, { useState, useEffect, useCallback } from "react";
+import React, { useState, useEffect, useCallback, useMemo } from "react";
 import Box from "@mui/material/Box";
 import Box from "@mui/material/Box";
 import Tooltip from "@mui/material/Tooltip";
 import Tooltip from "@mui/material/Tooltip";
 import { DatePicker, DatePickerProps } from "@mui/x-date-pickers/DatePicker";
 import { DatePicker, DatePickerProps } from "@mui/x-date-pickers/DatePicker";
@@ -21,7 +21,7 @@ import { isValid } from "date-fns";
 import { ErrorBoundary } from "react-error-boundary";
 import { ErrorBoundary } from "react-error-boundary";
 
 
 import { createSendUpdateAction } from "../../context/taipyReducers";
 import { createSendUpdateAction } from "../../context/taipyReducers";
-import { getSuffixedClassNames, TaipyActiveProps, TaipyChangeProps, DateProps, getProps } from "./utils";
+import { getSuffixedClassNames, TaipyActiveProps, TaipyChangeProps, DateProps, getProps, getCssSize } from "./utils";
 import { dateToString, getDateTime, getTimeZonedDate } from "../../utils";
 import { dateToString, getDateTime, getTimeZonedDate } from "../../utils";
 import { useClassNames, useDispatch, useDynamicProperty, useFormatConfig, useModule } from "../../utils/hooks";
 import { useClassNames, useDispatch, useDynamicProperty, useFormatConfig, useModule } from "../../utils/hooks";
 import Field from "./Field";
 import Field from "./Field";
@@ -39,6 +39,7 @@ interface DateSelectorProps extends TaipyActiveProps, TaipyChangeProps {
     defaultEditable?: boolean;
     defaultEditable?: boolean;
     editable?: boolean;
     editable?: boolean;
     label?: string;
     label?: string;
+    width?: string | number;
 }
 }
 
 
 const boxSx = { display: "inline-block" };
 const boxSx = { display: "inline-block" };
@@ -61,6 +62,8 @@ const DateSelector = (props: DateSelectorProps) => {
     const min = useDynamicProperty(props.min, props.defaultMin, undefined);
     const min = useDynamicProperty(props.min, props.defaultMin, undefined);
     const max = useDynamicProperty(props.max, props.defaultMax, undefined);
     const max = useDynamicProperty(props.max, props.defaultMax, undefined);
 
 
+    const dateSx = useMemo(() => (props.width ? { maxWidth: getCssSize(props.width) } : undefined), [props.width]);
+
     const handleChange = useCallback(
     const handleChange = useCallback(
         (v: Date | null) => {
         (v: Date | null) => {
             setValue(v);
             setValue(v);
@@ -72,12 +75,12 @@ const DateSelector = (props: DateSelectorProps) => {
                         dateToString(newDate, withTime),
                         dateToString(newDate, withTime),
                         module,
                         module,
                         props.onChange,
                         props.onChange,
-                        propagate,
-                    ),
+                        propagate
+                    )
                 );
                 );
             }
             }
         },
         },
-        [updateVarName, dispatch, withTime, propagate, tz, props.onChange, module],
+        [updateVarName, dispatch, withTime, propagate, tz, props.onChange, module]
     );
     );
 
 
     // Run every time props.value get updated
     // Run every time props.value get updated
@@ -115,6 +118,7 @@ const DateSelector = (props: DateSelectorProps) => {
                                 slotProps={textFieldProps}
                                 slotProps={textFieldProps}
                                 label={props.label}
                                 label={props.label}
                                 format={props.format}
                                 format={props.format}
+                                sx={dateSx}
                             />
                             />
                         ) : (
                         ) : (
                             <DatePicker
                             <DatePicker
@@ -127,6 +131,7 @@ const DateSelector = (props: DateSelectorProps) => {
                                 slotProps={textFieldProps}
                                 slotProps={textFieldProps}
                                 label={props.label}
                                 label={props.label}
                                 format={props.format}
                                 format={props.format}
+                                sx={dateSx}
                             />
                             />
                         )
                         )
                     ) : (
                     ) : (
@@ -137,6 +142,7 @@ const DateSelector = (props: DateSelectorProps) => {
                             format={props.format}
                             format={props.format}
                             id={id && id + "-field"}
                             id={id && id + "-field"}
                             className={getSuffixedClassNames(className, "-text")}
                             className={getSuffixedClassNames(className, "-text")}
+                            width={props.width}
                         />
                         />
                     )}
                     )}
                 </Box>
                 </Box>

+ 28 - 14
frontend/taipy-gui/src/components/Taipy/Field.spec.tsx

@@ -12,38 +12,52 @@
  */
  */
 
 
 import React from "react";
 import React from "react";
-import {render} from "@testing-library/react";
+import { render } from "@testing-library/react";
 import "@testing-library/jest-dom";
 import "@testing-library/jest-dom";
 
 
-import Field from './Field';
+import Field from "./Field";
 
 
 describe("Field Component", () => {
 describe("Field Component", () => {
     it("renders", async () => {
     it("renders", async () => {
-        const {getByText} = render(<Field value="toto" />);
+        const { getByText } = render(<Field value="toto" />);
         const elt = getByText("toto");
         const elt = getByText("toto");
         expect(elt.tagName).toBe("SPAN");
         expect(elt.tagName).toBe("SPAN");
-    })
+    });
     it("displays the right info for string", async () => {
     it("displays the right info for string", async () => {
-        const {getByText} = render(<Field value="toto" defaultValue="titi" className="taipy-field" />);
+        const { getByText } = render(<Field value="toto" defaultValue="titi" className="taipy-field" />);
         const elt = getByText("toto");
         const elt = getByText("toto");
         expect(elt).toHaveClass("taipy-field");
         expect(elt).toHaveClass("taipy-field");
-    })
+    });
     it("displays the default value", async () => {
     it("displays the default value", async () => {
-        const {getByText} = render(<Field defaultValue="titi" value={undefined as unknown as string} />);
+        const { getByText } = render(<Field defaultValue="titi" value={undefined as unknown as string} />);
         getByText("titi");
         getByText("titi");
-    })
+    });
     it("displays a date with format", async () => {
     it("displays a date with format", async () => {
         const myDate = new Date();
         const myDate = new Date();
         myDate.setMonth(1, 1);
         myDate.setMonth(1, 1);
-        const {getByText} = render(<Field defaultValue="titi" value={myDate.toISOString()} dataType="datetime" format="MM/dd" /> );
+        const { getByText } = render(
+            <Field defaultValue="titi" value={myDate.toISOString()} dataType="datetime" format="MM/dd" />
+        );
         getByText("02/01");
         getByText("02/01");
-    })
+    });
     it("displays a int with format", async () => {
     it("displays a int with format", async () => {
-        const {getByText} = render(<Field defaultValue="titi" value={12} dataType="int" format="%.2f" /> );
+        const { getByText } = render(<Field defaultValue="titi" value={12} dataType="int" format="%.2f" />);
         getByText("12.00");
         getByText("12.00");
-    })
+    });
     it("displays a float with format", async () => {
     it("displays a float with format", async () => {
-        const {getByText} = render(<Field defaultValue="titi" value={12.1} dataType="float" format="float is %.0f" /> );
+        const { getByText } = render(
+            <Field defaultValue="titi" value={12.1} dataType="float" format="float is %.0f" />
+        );
         getByText("float is 12");
         getByText("float is 12");
-    })
+    });
+    it("displays with width=70%", async () => {
+        const { getByText } = render(<Field value="titi" width="70%" />);
+        const elt = getByText("titi");
+        expect(elt).toHaveStyle("width: 70%");
+    });
+    it("displays with width=500", async () => {
+        const { getByText } = render(<Field value="titi" width={500} />);
+        const elt = getByText("titi");
+        expect(elt).toHaveStyle("width: 500px");
+    });
 });
 });

+ 19 - 4
frontend/taipy-gui/src/components/Taipy/Field.tsx

@@ -17,7 +17,7 @@ import Tooltip from "@mui/material/Tooltip";
 
 
 import { formatWSValue } from "../../utils";
 import { formatWSValue } from "../../utils";
 import { useClassNames, useDynamicProperty, useFormatConfig } from "../../utils/hooks";
 import { useClassNames, useDynamicProperty, useFormatConfig } from "../../utils/hooks";
-import { TaipyBaseProps, TaipyHoverProps } from "./utils";
+import { TaipyBaseProps, TaipyHoverProps, getCssSize } from "./utils";
 
 
 interface TaipyFieldProps extends TaipyBaseProps, TaipyHoverProps {
 interface TaipyFieldProps extends TaipyBaseProps, TaipyHoverProps {
     dataType?: string;
     dataType?: string;
@@ -26,6 +26,7 @@ interface TaipyFieldProps extends TaipyBaseProps, TaipyHoverProps {
     format?: string;
     format?: string;
     raw?: boolean;
     raw?: boolean;
     mode?: string;
     mode?: string;
+    width?: string | number;
 }
 }
 
 
 const unsetWeightSx = { fontWeight: "unset" };
 const unsetWeightSx = { fontWeight: "unset" };
@@ -41,6 +42,18 @@ const Field = (props: TaipyFieldProps) => {
 
 
     const mode = typeof props.mode === "string" ? props.mode.toLowerCase() : undefined;
     const mode = typeof props.mode === "string" ? props.mode.toLowerCase() : undefined;
 
 
+    const style = useMemo(
+        () => ({ overflow: "auto", width: props.width ? getCssSize(props.width) : undefined }),
+        [props.width]
+    );
+    const typoSx = useMemo(
+        () =>
+            props.width
+                ? { ...unsetWeightSx, overflow: "auto", width: getCssSize(props.width), display: "inline-block" }
+                : unsetWeightSx,
+        [props.width]
+    );
+
     const value = useMemo(() => {
     const value = useMemo(() => {
         return formatWSValue(
         return formatWSValue(
             props.value !== undefined ? props.value : defaultValue || "",
             props.value !== undefined ? props.value : defaultValue || "",
@@ -53,15 +66,17 @@ const Field = (props: TaipyFieldProps) => {
     return (
     return (
         <Tooltip title={hover || ""}>
         <Tooltip title={hover || ""}>
             {mode == "pre" ? (
             {mode == "pre" ? (
-                <pre className={className} id={id}>{value}</pre>
+                <pre className={className} id={id} style={style}>
+                    {value}
+                </pre>
             ) : mode == "markdown" || mode == "md" ? (
             ) : mode == "markdown" || mode == "md" ? (
                 <Markdown className={className}>{value}</Markdown>
                 <Markdown className={className}>{value}</Markdown>
             ) : raw || mode == "raw" ? (
             ) : raw || mode == "raw" ? (
-                <span className={className} id={id}>
+                <span className={className} id={id} style={style}>
                     {value}
                     {value}
                 </span>
                 </span>
             ) : (
             ) : (
-                <Typography className={className} id={id} component="span" sx={unsetWeightSx}>
+                <Typography className={className} id={id} component="span" sx={typoSx}>
                     {value}
                     {value}
                 </Typography>
                 </Typography>
             )}
             )}

+ 44 - 19
frontend/taipy-gui/src/components/Taipy/FileDownload.spec.tsx

@@ -15,7 +15,7 @@ import React from "react";
 import { render, waitFor } from "@testing-library/react";
 import { render, waitFor } from "@testing-library/react";
 import "@testing-library/jest-dom";
 import "@testing-library/jest-dom";
 import userEvent from "@testing-library/user-event";
 import userEvent from "@testing-library/user-event";
-import { newServer } from 'mock-xmlhttprequest';
+import { newServer } from "mock-xmlhttprequest";
 
 
 import FileDownload from "./FileDownload";
 import FileDownload from "./FileDownload";
 import { TaipyContext } from "../../context/taipyContext";
 import { TaipyContext } from "../../context/taipyContext";
@@ -33,7 +33,9 @@ describe("FileDownload Component", () => {
         expect(elt.parentElement).toHaveClass("taipy-file-download");
         expect(elt.parentElement).toHaveClass("taipy-file-download");
     });
     });
     it("displays the default content", async () => {
     it("displays the default content", async () => {
-        const { getByRole } = render(<FileDownload defaultContent="/url/toto.png" content={undefined as unknown as string} />);
+        const { getByRole } = render(
+            <FileDownload defaultContent="/url/toto.png" content={undefined as unknown as string} />
+        );
         const elt = getByRole("button");
         const elt = getByRole("button");
         const aElt = elt.parentElement?.querySelector("a");
         const aElt = elt.parentElement?.querySelector("a");
         expect(aElt).toBeEmptyDOMElement();
         expect(aElt).toBeEmptyDOMElement();
@@ -45,6 +47,16 @@ describe("FileDownload Component", () => {
         );
         );
         getByText("titi");
         getByText("titi");
     });
     });
+    it("displays with width=70%", async () => {
+        const { getByRole } = render(<FileDownload defaultContent="/url/toto.png" width="70%" />);
+        const elt = getByRole("button");
+        expect(elt).toHaveStyle("width: 70%");
+    });
+    it("displays with width=500", async () => {
+        const { getByRole } = render(<FileDownload defaultContent="/url/toto.png" width={500} />);
+        const elt = getByRole("button");
+        expect(elt).toHaveStyle("width: 500px");
+    });
     it("is disabled", async () => {
     it("is disabled", async () => {
         const { getByRole } = render(<FileDownload defaultContent="/url/toto.png" active={false} />);
         const { getByRole } = render(<FileDownload defaultContent="/url/toto.png" active={false} />);
         const elt = getByRole("button");
         const elt = getByRole("button");
@@ -70,35 +82,48 @@ describe("FileDownload Component", () => {
         );
         );
         const elt = getByText("label");
         const elt = getByText("label");
         await userEvent.click(elt);
         await userEvent.click(elt);
-        await waitFor(() => expect(dispatch).toHaveBeenCalledWith({
-            name: "anId",
-            payload: { args: ["from.png", ""], action: "on_action" },
-            type: "SEND_ACTION_ACTION",
-        }));
+        await waitFor(() =>
+            expect(dispatch).toHaveBeenCalledWith({
+                name: "anId",
+                payload: { args: ["from.png", ""], action: "on_action" },
+                type: "SEND_ACTION_ACTION",
+            })
+        );
     });
     });
     it("dispatch a well formed message when content is not empty", async () => {
     it("dispatch a well formed message when content is not empty", async () => {
         const server = newServer({
         const server = newServer({
-            get: ['/some/link/to.png?bypass=', {
-              // status: 200 is the default
-              //headers: { 'Content-Type': 'application/json' },
-              body: '{ "message": "Success!" }',
-            }],
-          });
+            get: [
+                "/some/link/to.png?bypass=",
+                {
+                    // status: 200 is the default
+                    //headers: { 'Content-Type': 'application/json' },
+                    body: '{ "message": "Success!" }',
+                },
+            ],
+        });
         server.install();
         server.install();
         const dispatch = jest.fn();
         const dispatch = jest.fn();
         const state: TaipyState = INITIAL_STATE;
         const state: TaipyState = INITIAL_STATE;
         const { getByText } = render(
         const { getByText } = render(
             <TaipyContext.Provider value={{ state, dispatch }}>
             <TaipyContext.Provider value={{ state, dispatch }}>
-                <FileDownload defaultContent="/some/link/to.png" onAction="on_action" id="anId" name="from.png" label="label" />
+                <FileDownload
+                    defaultContent="/some/link/to.png"
+                    onAction="on_action"
+                    id="anId"
+                    name="from.png"
+                    label="label"
+                />
             </TaipyContext.Provider>
             </TaipyContext.Provider>
         );
         );
         const elt = getByText("label");
         const elt = getByText("label");
         await userEvent.click(elt);
         await userEvent.click(elt);
-        await waitFor(() => expect(dispatch).toHaveBeenCalledWith({
-            name: "anId",
-            payload: { args: ["from.png", "/some/link/to.png?bypass="], action: "on_action" },
-            type: "SEND_ACTION_ACTION",
-        }));
+        await waitFor(() =>
+            expect(dispatch).toHaveBeenCalledWith({
+                name: "anId",
+                payload: { args: ["from.png", "/some/link/to.png?bypass="], action: "on_action" },
+                type: "SEND_ACTION_ACTION",
+            })
+        );
         server.remove();
         server.remove();
     });
     });
 });
 });

+ 12 - 2
frontend/taipy-gui/src/components/Taipy/FileDownload.tsx

@@ -18,7 +18,7 @@ import Tooltip from "@mui/material/Tooltip";
 import FileDownloadIco from "@mui/icons-material/FileDownload";
 import FileDownloadIco from "@mui/icons-material/FileDownload";
 
 
 import { useClassNames, useDispatch, useDynamicProperty, useModule } from "../../utils/hooks";
 import { useClassNames, useDispatch, useDynamicProperty, useModule } from "../../utils/hooks";
-import { noDisplayStyle, TaipyActiveProps } from "./utils";
+import { getCssSize, noDisplayStyle, TaipyActiveProps } from "./utils";
 import { createSendActionNameAction } from "../../context/taipyReducers";
 import { createSendActionNameAction } from "../../context/taipyReducers";
 import { runXHR } from "../../utils/downloads";
 import { runXHR } from "../../utils/downloads";
 
 
@@ -33,6 +33,7 @@ interface FileDownloadProps extends TaipyActiveProps {
     defaultRender?: boolean;
     defaultRender?: boolean;
     bypassPreview?: boolean;
     bypassPreview?: boolean;
     onAction?: string;
     onAction?: string;
+    width?: string | number;
 }
 }
 
 
 const FileDownload = (props: FileDownloadProps) => {
 const FileDownload = (props: FileDownloadProps) => {
@@ -47,6 +48,8 @@ const FileDownload = (props: FileDownloadProps) => {
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const linkId = useMemo(() => (id || `tp-${Date.now()}-${Math.random()}`) + "-download-file", [id]);
     const linkId = useMemo(() => (id || `tp-${Date.now()}-${Math.random()}`) + "-download-file", [id]);
 
 
+    const buttonSx = useMemo(() => (props.width ? { width: getCssSize(props.width) } : undefined), [props.width]);
+
     const [url, download] = useMemo(() => {
     const [url, download] = useMemo(() => {
         const url = props.content || props.defaultContent || "";
         const url = props.content || props.defaultContent || "";
         if (!url || url.startsWith("data:")) {
         if (!url || url.startsWith("data:")) {
@@ -97,7 +100,14 @@ const FileDownload = (props: FileDownloadProps) => {
             <a style={noDisplayStyle} id={linkId} download={download} {...aProps} ref={aRef} />
             <a style={noDisplayStyle} id={linkId} download={download} {...aProps} ref={aRef} />
             {auto ? null : (
             {auto ? null : (
                 <Tooltip title={hover || ""}>
                 <Tooltip title={hover || ""}>
-                    <Button id={id} variant="outlined" aria-label="download" disabled={!active} onClick={clickHandler}>
+                    <Button
+                        id={id}
+                        variant="outlined"
+                        aria-label="download"
+                        disabled={!active}
+                        onClick={clickHandler}
+                        sx={buttonSx}
+                    >
                         <FileDownloadIco /> {label || defaultLabel}
                         <FileDownloadIco /> {label || defaultLabel}
                     </Button>
                     </Button>
                 </Tooltip>
                 </Tooltip>

+ 19 - 9
frontend/taipy-gui/src/components/Taipy/FileSelector.spec.tsx

@@ -40,6 +40,16 @@ describe("FileSelector Component", () => {
         const { getByText } = render(<FileSelector defaultLabel="titi" label={undefined as unknown as string} />);
         const { getByText } = render(<FileSelector defaultLabel="titi" label={undefined as unknown as string} />);
         getByText("titi");
         getByText("titi");
     });
     });
+    it("displays with width=70%", async () => {
+        const { getByText } = render(<FileSelector label="toto" width="70%" />);
+        const elt = getByText("toto");
+        expect(elt).toHaveStyle("width: 70%");
+    });
+    it("displays with width=500", async () => {
+        const { getByText } = render(<FileSelector label="toto" width={500} />);
+        const elt = getByText("toto");
+        expect(elt).toHaveStyle("width: 500px");
+    });
     it("is disabled", async () => {
     it("is disabled", async () => {
         const { getByText } = render(<FileSelector label="val" active={false} />);
         const { getByText } = render(<FileSelector label="val" active={false} />);
         const elt = getByText("val");
         const elt = getByText("val");
@@ -64,7 +74,7 @@ describe("FileSelector Component", () => {
         const { getByText } = render(
         const { getByText } = render(
             <TaipyContext.Provider value={{ state, dispatch }}>
             <TaipyContext.Provider value={{ state, dispatch }}>
                 <FileSelector label="FileSelector" onAction="on_action" />
                 <FileSelector label="FileSelector" onAction="on_action" />
-            </TaipyContext.Provider>,
+            </TaipyContext.Provider>
         );
         );
         const elt = getByText("FileSelector");
         const elt = getByText("FileSelector");
         const inputElt = elt.parentElement?.parentElement?.querySelector("input");
         const inputElt = elt.parentElement?.parentElement?.querySelector("input");
@@ -93,7 +103,7 @@ describe("FileSelector Component", () => {
     it("displays a dropped custom message", async () => {
     it("displays a dropped custom message", async () => {
         const file = new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" });
         const file = new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" });
         const { getByRole, getByText } = render(
         const { getByRole, getByText } = render(
-            <FileSelector label="FileSelectorDrop" dropMessage="drop here those files" />,
+            <FileSelector label="FileSelectorDrop" dropMessage="drop here those files" />
         );
         );
         const elt = getByRole("button");
         const elt = getByRole("button");
         const inputElt = elt.parentElement?.parentElement?.querySelector("input");
         const inputElt = elt.parentElement?.parentElement?.querySelector("input");
@@ -164,7 +174,7 @@ describe("FileSelector Component", () => {
         const { getByLabelText } = render(
         const { getByLabelText } = render(
             <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
             <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
                 <FileSelector label="FileSelector" notify={true} />
                 <FileSelector label="FileSelector" notify={true} />
-            </TaipyContext.Provider>,
+            </TaipyContext.Provider>
         );
         );
 
 
         // Simulate file upload
         // Simulate file upload
@@ -183,7 +193,7 @@ describe("FileSelector Component", () => {
                 duration: 3000,
                 duration: 3000,
                 message: "mocked response",
                 message: "mocked response",
                 system: false,
                 system: false,
-            }),
+            })
         );
         );
     });
     });
 
 
@@ -200,7 +210,7 @@ describe("FileSelector Component", () => {
         const { getByLabelText } = render(
         const { getByLabelText } = render(
             <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
             <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
                 <FileSelector label="FileSelector" notify={true} />
                 <FileSelector label="FileSelector" notify={true} />
-            </TaipyContext.Provider>,
+            </TaipyContext.Provider>
         );
         );
 
 
         // Simulate file upload
         // Simulate file upload
@@ -219,7 +229,7 @@ describe("FileSelector Component", () => {
                 duration: 3000,
                 duration: 3000,
                 message: "Upload failed",
                 message: "Upload failed",
                 system: false,
                 system: false,
-            }),
+            })
         );
         );
     });
     });
 
 
@@ -231,7 +241,7 @@ describe("FileSelector Component", () => {
         const { getByLabelText, queryByRole } = render(
         const { getByLabelText, queryByRole } = render(
             <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
             <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
                 <FileSelector label="FileSelector" notify={true} onAction="testAction" />
                 <FileSelector label="FileSelector" notify={true} onAction="testAction" />
-            </TaipyContext.Provider>,
+            </TaipyContext.Provider>
         );
         );
 
 
         // Simulate file upload
         // Simulate file upload
@@ -254,7 +264,7 @@ describe("FileSelector Component", () => {
                 type: "SEND_ACTION_ACTION",
                 type: "SEND_ACTION_ACTION",
                 name: "",
                 name: "",
                 payload: { args: [], action: "testAction" },
                 payload: { args: [], action: "testAction" },
-            }),
+            })
         );
         );
     });
     });
 
 
@@ -263,7 +273,7 @@ describe("FileSelector Component", () => {
         const { getByLabelText } = render(
         const { getByLabelText } = render(
             <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
             <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
                 <FileSelector label="FileSelector" notify={true} />
                 <FileSelector label="FileSelector" notify={true} />
-            </TaipyContext.Provider>,
+            </TaipyContext.Provider>
         );
         );
 
 
         // Simulate file upload without providing a file
         // Simulate file upload without providing a file

+ 7 - 4
frontend/taipy-gui/src/components/Taipy/FileSelector.tsx

@@ -20,7 +20,7 @@ import UploadFile from "@mui/icons-material/UploadFile";
 import { TaipyContext } from "../../context/taipyContext";
 import { TaipyContext } from "../../context/taipyContext";
 import { createAlertAction, createSendActionNameAction } from "../../context/taipyReducers";
 import { createAlertAction, createSendActionNameAction } from "../../context/taipyReducers";
 import { useClassNames, useDynamicProperty, useModule } from "../../utils/hooks";
 import { useClassNames, useDynamicProperty, useModule } from "../../utils/hooks";
-import { noDisplayStyle, TaipyActiveProps } from "./utils";
+import { getCssSize, noDisplayStyle, TaipyActiveProps } from "./utils";
 import { uploadFile } from "../../workers/fileupload";
 import { uploadFile } from "../../workers/fileupload";
 
 
 interface FileSelectorProps extends TaipyActiveProps {
 interface FileSelectorProps extends TaipyActiveProps {
@@ -31,6 +31,7 @@ interface FileSelectorProps extends TaipyActiveProps {
     extensions?: string;
     extensions?: string;
     dropMessage?: string;
     dropMessage?: string;
     notify?: boolean;
     notify?: boolean;
+    width?: string | number;
 }
 }
 
 
 const handleDragOver = (evt: DragEvent) => {
 const handleDragOver = (evt: DragEvent) => {
@@ -66,6 +67,8 @@ const FileSelector = (props: FileSelectorProps) => {
     const active = useDynamicProperty(props.active, props.defaultActive, true);
     const active = useDynamicProperty(props.active, props.defaultActive, true);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
 
 
+    useEffect(() => setDropSx((sx) => (props.width ? { ...sx, width: getCssSize(props.width) } : sx)), [props.width]);
+
     const handleFiles = useCallback(
     const handleFiles = useCallback(
         (files: FileList | undefined | null, evt: Event | ChangeEvent) => {
         (files: FileList | undefined | null, evt: Event | ChangeEvent) => {
             evt.stopPropagation();
             evt.stopPropagation();
@@ -102,7 +105,7 @@ const FileSelector = (props: FileSelectorProps) => {
     const handleDrop = useCallback(
     const handleDrop = useCallback(
         (e: DragEvent) => {
         (e: DragEvent) => {
             setDropLabel("");
             setDropLabel("");
-            setDropSx(defaultSx);
+            setDropSx((sx) => ({ ...sx, ...defaultSx }));
             handleFiles(e.dataTransfer?.files, e);
             handleFiles(e.dataTransfer?.files, e);
         },
         },
         [handleFiles]
         [handleFiles]
@@ -110,7 +113,7 @@ const FileSelector = (props: FileSelectorProps) => {
 
 
     const handleDragLeave = useCallback(() => {
     const handleDragLeave = useCallback(() => {
         setDropLabel("");
         setDropLabel("");
-        setDropSx(defaultSx);
+        setDropSx((sx) => ({ ...sx, ...defaultSx }));
     }, []);
     }, []);
 
 
     const handleDragOverWithLabel = useCallback(
     const handleDragOverWithLabel = useCallback(
@@ -118,7 +121,7 @@ const FileSelector = (props: FileSelectorProps) => {
             console.log(evt);
             console.log(evt);
             const target = evt.currentTarget as HTMLElement;
             const target = evt.currentTarget as HTMLElement;
             setDropSx((sx) =>
             setDropSx((sx) =>
-                sx.minWidth === defaultSx.minWidth && target ? { minWidth: target.clientWidth + "px" } : sx
+                sx.minWidth === defaultSx.minWidth && target ? { ...sx, minWidth: target.clientWidth + "px" } : sx
             );
             );
             setDropLabel(dropMessage);
             setDropLabel(dropMessage);
             handleDragOver(evt);
             handleDragOver(evt);

+ 2 - 3
frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx

@@ -563,11 +563,10 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                                 {rows.map((row, index) => {
                                 {rows.map((row, index) => {
                                     const sel = selected.indexOf(index + startIndex);
                                     const sel = selected.indexOf(index + startIndex);
                                     if (sel == 0) {
                                     if (sel == 0) {
-                                        setTimeout(
+                                        Promise.resolve().then(
                                             () =>
                                             () =>
                                                 selectedRowRef.current?.scrollIntoView &&
                                                 selectedRowRef.current?.scrollIntoView &&
-                                                selectedRowRef.current.scrollIntoView({ block: "center" }),
-                                            1
+                                                selectedRowRef.current.scrollIntoView({ block: "center" })
                                         );
                                         );
                                     }
                                     }
                                     return (
                                     return (

+ 69 - 31
frontend/taipy-gui/src/components/Taipy/ThemeToggle.spec.tsx

@@ -12,11 +12,11 @@
  */
  */
 
 
 import React from "react";
 import React from "react";
-import {render} from "@testing-library/react";
+import { render } from "@testing-library/react";
 import "@testing-library/jest-dom";
 import "@testing-library/jest-dom";
 import userEvent from "@testing-library/user-event";
 import userEvent from "@testing-library/user-event";
 
 
-import ThemeToggle from './ThemeToggle';
+import ThemeToggle from "./ThemeToggle";
 import { INITIAL_STATE, TaipyState } from "../../context/taipyReducers";
 import { INITIAL_STATE, TaipyState } from "../../context/taipyReducers";
 import { TaipyContext } from "../../context/taipyContext";
 import { TaipyContext } from "../../context/taipyContext";
 
 
@@ -31,65 +31,103 @@ beforeEach(() => {
 
 
 describe("ThemeToggle Component", () => {
 describe("ThemeToggle Component", () => {
     it("renders", async () => {
     it("renders", async () => {
-        const { getByText, getByTestId, getByTitle } = render(<TaipyContext.Provider value={{ state, dispatch }}>
-            <ThemeToggle />
-        </TaipyContext.Provider>);
+        const { getByText, getByTestId, getByTitle } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle />
+            </TaipyContext.Provider>
+        );
         expect(getByTestId("Brightness3Icon")).toBeInTheDocument();
         expect(getByTestId("Brightness3Icon")).toBeInTheDocument();
         expect(getByTestId("WbSunnyIcon")).toBeInTheDocument();
         expect(getByTestId("WbSunnyIcon")).toBeInTheDocument();
         expect(getByTitle("Light")).toBeInTheDocument();
         expect(getByTitle("Light")).toBeInTheDocument();
         expect(getByTitle("Dark")).toBeInTheDocument();
         expect(getByTitle("Dark")).toBeInTheDocument();
         const label = getByText("Mode");
         const label = getByText("Mode");
         expect(label.tagName).toBe("P");
         expect(label.tagName).toBe("P");
-    })
+    });
     it("uses the class", async () => {
     it("uses the class", async () => {
-        const {getByText} = render(<TaipyContext.Provider value={{ state, dispatch }}>
-            <ThemeToggle className="taipy-toggle" />
-        </TaipyContext.Provider>);
+        const { getByText } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle className="taipy-toggle" />
+            </TaipyContext.Provider>
+        );
         const elt = getByText("Mode");
         const elt = getByText("Mode");
         expect(elt.parentElement).toHaveClass("taipy-toggle");
         expect(elt.parentElement).toHaveClass("taipy-toggle");
-    })
+    });
     it("shows Light theme selected at start", async () => {
     it("shows Light theme selected at start", async () => {
-        const {getByTitle} = render(<TaipyContext.Provider value={{ state, dispatch }}>
-            <ThemeToggle />
-        </TaipyContext.Provider>);
+        const { getByTitle } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle />
+            </TaipyContext.Provider>
+        );
         expect(getByTitle("Dark")).not.toHaveClass("Mui-selected");
         expect(getByTitle("Dark")).not.toHaveClass("Mui-selected");
         expect(getByTitle("Light")).toHaveClass("Mui-selected");
         expect(getByTitle("Light")).toHaveClass("Mui-selected");
     });
     });
     it("shows Dark theme selected at start", async () => {
     it("shows Dark theme selected at start", async () => {
         state.theme.palette.mode = "dark";
         state.theme.palette.mode = "dark";
-        const {getByTitle} = render(<TaipyContext.Provider value={{ state, dispatch }}>
-            <ThemeToggle />
-        </TaipyContext.Provider>);
+        const { getByTitle } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle />
+            </TaipyContext.Provider>
+        );
         expect(getByTitle("Dark")).toHaveClass("Mui-selected");
         expect(getByTitle("Dark")).toHaveClass("Mui-selected");
         expect(getByTitle("Light")).not.toHaveClass("Mui-selected");
         expect(getByTitle("Light")).not.toHaveClass("Mui-selected");
     });
     });
+    it("displays with width=70%", async () => {
+        render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle width="70%" />
+            </TaipyContext.Provider>
+        );
+        const elt = document.querySelector(".MuiBox-root");
+        expect(elt).toHaveStyle("width: 70%");
+    });
+    it("displays with width=500", async () => {
+        render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle width={500} />
+            </TaipyContext.Provider>
+        );
+        const elt = document.querySelector(".MuiBox-root");
+        expect(elt).toHaveStyle("width: 500px");
+    });
     it("is disabled", async () => {
     it("is disabled", async () => {
-        const { getAllByRole } = render(<TaipyContext.Provider value={{ state, dispatch }}>
-            <ThemeToggle active={false} />
-        </TaipyContext.Provider>);
+        const { getAllByRole } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle active={false} />
+            </TaipyContext.Provider>
+        );
         const elts = getAllByRole("button");
         const elts = getAllByRole("button");
-        elts.forEach(elt => expect(elt).toBeDisabled());
+        elts.forEach((elt) => expect(elt).toBeDisabled());
     });
     });
     it("is enabled by default", async () => {
     it("is enabled by default", async () => {
-        const { getAllByRole } = render(<TaipyContext.Provider value={{ state, dispatch }}>
-            <ThemeToggle />
-        </TaipyContext.Provider>);
+        const { getAllByRole } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle />
+            </TaipyContext.Provider>
+        );
         const elts = getAllByRole("button");
         const elts = getAllByRole("button");
-        elts.forEach(elt => expect(elt).not.toBeDisabled());
+        elts.forEach((elt) => expect(elt).not.toBeDisabled());
     });
     });
     it("is enabled by active", async () => {
     it("is enabled by active", async () => {
-        const { getAllByRole } = render(<TaipyContext.Provider value={{ state, dispatch }}>
-            <ThemeToggle active={true}/>
-        </TaipyContext.Provider>);
+        const { getAllByRole } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
+                <ThemeToggle active={true} />
+            </TaipyContext.Provider>
+        );
         const elts = getAllByRole("button");
         const elts = getAllByRole("button");
-        elts.forEach(elt => expect(elt).not.toBeDisabled());
+        elts.forEach((elt) => expect(elt).not.toBeDisabled());
     });
     });
     it("dispatch a well formed message", async () => {
     it("dispatch a well formed message", async () => {
-        const { getByTitle } = render(<TaipyContext.Provider value={{ state, dispatch }}>
+        const { getByTitle } = render(
+            <TaipyContext.Provider value={{ state, dispatch }}>
                 <ThemeToggle />
                 <ThemeToggle />
-            </TaipyContext.Provider>);
+            </TaipyContext.Provider>
+        );
         const elt = getByTitle("Dark");
         const elt = getByTitle("Dark");
         await userEvent.click(elt);
         await userEvent.click(elt);
-        expect(dispatch).toHaveBeenCalledWith({name: "theme", payload: {value: "dark", "fromBackend": false}, "type": "SET_THEME"});
+        expect(dispatch).toHaveBeenCalledWith({
+            name: "theme",
+            payload: { value: "dark", fromBackend: false },
+            type: "SET_THEME",
+        });
     });
     });
 });
 });

+ 17 - 6
frontend/taipy-gui/src/components/Taipy/ThemeToggle.tsx

@@ -11,24 +11,25 @@
  * specific language governing permissions and limitations under the License.
  * specific language governing permissions and limitations under the License.
  */
  */
 
 
-import React, { CSSProperties, MouseEvent, useCallback, useContext, useEffect, useMemo } from "react";
+import React, { MouseEvent, useCallback, useContext, useEffect, useMemo } from "react";
 import Box from "@mui/material/Box";
 import Box from "@mui/material/Box";
 import Typography from "@mui/material/Typography";
 import Typography from "@mui/material/Typography";
-import { PaletteMode } from "@mui/material";
+import { PaletteMode, SxProps } from "@mui/material";
 import ToggleButton from "@mui/material/ToggleButton";
 import ToggleButton from "@mui/material/ToggleButton";
 import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
 import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
 import WbSunny from "@mui/icons-material/WbSunny";
 import WbSunny from "@mui/icons-material/WbSunny";
 import Brightness3 from "@mui/icons-material/Brightness3";
 import Brightness3 from "@mui/icons-material/Brightness3";
 
 
-import { TaipyActiveProps, emptyStyle } from "./utils";
+import { TaipyActiveProps, getCssSize } from "./utils";
 import { TaipyContext } from "../../context/taipyContext";
 import { TaipyContext } from "../../context/taipyContext";
 import { createThemeAction } from "../../context/taipyReducers";
 import { createThemeAction } from "../../context/taipyReducers";
 import { useClassNames } from "../../utils/hooks";
 import { useClassNames } from "../../utils/hooks";
 import { getLocalStorageValue } from "../../context/utils";
 import { getLocalStorageValue } from "../../context/utils";
 
 
 interface ThemeToggleProps extends TaipyActiveProps {
 interface ThemeToggleProps extends TaipyActiveProps {
-    style?: CSSProperties;
+    style?: SxProps;
     label?: string;
     label?: string;
+    width?: string | number;
 }
 }
 
 
 const boxSx = {
 const boxSx = {
@@ -41,7 +42,9 @@ const boxSx = {
     "& > *": {
     "& > *": {
         m: 1,
         m: 1,
     },
     },
-} as CSSProperties;
+} as SxProps;
+
+export const emptyStyle = {} as SxProps;
 
 
 const groupSx = { verticalAlign: "middle" };
 const groupSx = { verticalAlign: "middle" };
 
 
@@ -63,7 +66,14 @@ const ThemeToggle = (props: ThemeToggleProps) => {
         }
         }
     }, [state.theme.palette.mode, dispatch]);
     }, [state.theme.palette.mode, dispatch]);
 
 
-    const mainSx = useMemo(() => ({ ...boxSx, ...style }), [style]);
+    const mainSx = useMemo(
+        () =>
+            props.width
+                ? ({ ...boxSx, ...style, width: getCssSize(props.width) } as SxProps)
+                : ({ ...boxSx, ...style } as SxProps),
+        [style, props.width]
+    );
+
     return (
     return (
         <Box id={id} sx={mainSx} className={className}>
         <Box id={id} sx={mainSx} className={className}>
             <Typography>{label}</Typography>
             <Typography>{label}</Typography>
@@ -74,6 +84,7 @@ const ThemeToggle = (props: ThemeToggleProps) => {
                 aria-label="Theme mode"
                 aria-label="Theme mode"
                 disabled={!active}
                 disabled={!active}
                 sx={groupSx}
                 sx={groupSx}
+                fullWidth={!!props.width}
             >
             >
                 <ToggleButton value="light" aria-label="light" title="Light">
                 <ToggleButton value="light" aria-label="light" title="Light">
                     <WbSunny />
                     <WbSunny />

+ 37 - 8
frontend/taipy-gui/src/components/Taipy/Toggle.spec.tsx

@@ -82,6 +82,16 @@ describe("Toggle Component", () => {
         const elt2 = getByText("Item 2");
         const elt2 = getByText("Item 2");
         expect(elt2.parentElement).toHaveClass("Mui-selected");
         expect(elt2.parentElement).toHaveClass("Mui-selected");
     });
     });
+    it("displays with width=70%", async () => {
+        render(<Toggle lov={lov} width="70%" />);
+        const elt = document.querySelector(".MuiBox-root");
+        expect(elt).toHaveStyle("width: 70%");
+    });
+    it("displays with width=500", async () => {
+        render(<Toggle lov={lov} width={500} />);
+        const elt = document.querySelector(".MuiBox-root");
+        expect(elt).toHaveStyle("width: 500px");
+    });
     it("is disabled", async () => {
     it("is disabled", async () => {
         const { getAllByRole } = render(<Toggle lov={lov} active={false} />);
         const { getAllByRole } = render(<Toggle lov={lov} active={false} />);
         const elts = getAllByRole("button");
         const elts = getAllByRole("button");
@@ -151,32 +161,47 @@ describe("Toggle Component", () => {
             expect(elt.tagName).toBe("SPAN");
             expect(elt.tagName).toBe("SPAN");
         });
         });
         it("uses the class", async () => {
         it("uses the class", async () => {
-            const { getByText } = render(<Toggle isSwitch={true}  label="switch" className="taipy-toggle" />);
+            const { getByText } = render(<Toggle isSwitch={true} label="switch" className="taipy-toggle" />);
             const elt = getByText("switch");
             const elt = getByText("switch");
             expect(elt.parentElement).toHaveClass("taipy-toggle-switch");
             expect(elt.parentElement).toHaveClass("taipy-toggle-switch");
         });
         });
         it("shows a selection at start", async () => {
         it("shows a selection at start", async () => {
-            const { getByText } = render(<Toggle isSwitch={true} defaultValue={true as unknown as string} label="switch" />);
+            const { getByText } = render(
+                <Toggle isSwitch={true} defaultValue={true as unknown as string} label="switch" />
+            );
             const elt = getByText("switch");
             const elt = getByText("switch");
             expect(elt.parentElement?.querySelector(".MuiSwitch-switchBase")).toHaveClass("Mui-checked");
             expect(elt.parentElement?.querySelector(".MuiSwitch-switchBase")).toHaveClass("Mui-checked");
         });
         });
         it("shows a selection at start through value", async () => {
         it("shows a selection at start through value", async () => {
-            const { getByText } = render(<Toggle isSwitch={true} value={true as unknown as string} defaultValue={false as unknown as string} label="switch" />);
+            const { getByText } = render(
+                <Toggle
+                    isSwitch={true}
+                    value={true as unknown as string}
+                    defaultValue={false as unknown as string}
+                    label="switch"
+                />
+            );
             const elt = getByText("switch");
             const elt = getByText("switch");
             expect(elt.parentElement?.querySelector(".MuiSwitch-switchBase")).toHaveClass("Mui-checked");
             expect(elt.parentElement?.querySelector(".MuiSwitch-switchBase")).toHaveClass("Mui-checked");
         });
         });
         it("is disabled", async () => {
         it("is disabled", async () => {
-            const { getByText } = render(<Toggle isSwitch={true} defaultValue={false as unknown as string} label="switch" active={false} />);
+            const { getByText } = render(
+                <Toggle isSwitch={true} defaultValue={false as unknown as string} label="switch" active={false} />
+            );
             const elt = getByText("switch");
             const elt = getByText("switch");
             expect(elt.parentElement?.querySelector("input")).toBeDisabled();
             expect(elt.parentElement?.querySelector("input")).toBeDisabled();
         });
         });
         it("is enabled by default", async () => {
         it("is enabled by default", async () => {
-            const { getByText } = render(<Toggle isSwitch={true} defaultValue={false as unknown as string} label="switch" />);
+            const { getByText } = render(
+                <Toggle isSwitch={true} defaultValue={false as unknown as string} label="switch" />
+            );
             const elt = getByText("switch");
             const elt = getByText("switch");
             expect(elt.parentElement?.querySelector("input")).not.toBeDisabled();
             expect(elt.parentElement?.querySelector("input")).not.toBeDisabled();
         });
         });
         it("is enabled by active", async () => {
         it("is enabled by active", async () => {
-            const { getByText } = render(<Toggle isSwitch={true} defaultValue={false as unknown as string} label="switch" active={true} />);
+            const { getByText } = render(
+                <Toggle isSwitch={true} defaultValue={false as unknown as string} label="switch" active={true} />
+            );
             const elt = getByText("switch");
             const elt = getByText("switch");
             expect(elt.parentElement?.querySelector("input")).not.toBeDisabled();
             expect(elt.parentElement?.querySelector("input")).not.toBeDisabled();
         });
         });
@@ -185,7 +210,12 @@ describe("Toggle Component", () => {
             const state: TaipyState = INITIAL_STATE;
             const state: TaipyState = INITIAL_STATE;
             const { getByText } = render(
             const { getByText } = render(
                 <TaipyContext.Provider value={{ state, dispatch }}>
                 <TaipyContext.Provider value={{ state, dispatch }}>
-                    <Toggle isSwitch={true} updateVarName="varname" defaultValue={false as unknown as string} label="switch" />
+                    <Toggle
+                        isSwitch={true}
+                        updateVarName="varname"
+                        defaultValue={false as unknown as string}
+                        label="switch"
+                    />
                 </TaipyContext.Provider>
                 </TaipyContext.Provider>
             );
             );
             const elt = getByText("switch");
             const elt = getByText("switch");
@@ -197,6 +227,5 @@ describe("Toggle Component", () => {
                 type: "SEND_UPDATE_ACTION",
                 type: "SEND_UPDATE_ACTION",
             });
             });
         });
         });
-
     });
     });
 });
 });

+ 22 - 9
frontend/taipy-gui/src/components/Taipy/Toggle.tsx

@@ -11,7 +11,7 @@
  * specific language governing permissions and limitations under the License.
  * specific language governing permissions and limitations under the License.
  */
  */
 
 
-import React, { CSSProperties, MouseEvent, SyntheticEvent, useCallback, useEffect, useState } from "react";
+import React, { MouseEvent, SyntheticEvent, useCallback, useEffect, useMemo, useState } from "react";
 import Box from "@mui/material/Box";
 import Box from "@mui/material/Box";
 import Switch from "@mui/material/Switch";
 import Switch from "@mui/material/Switch";
 import Typography from "@mui/material/Typography";
 import Typography from "@mui/material/Typography";
@@ -20,22 +20,23 @@ import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
 import Tooltip from "@mui/material/Tooltip";
 import Tooltip from "@mui/material/Tooltip";
 
 
 import { createSendUpdateAction } from "../../context/taipyReducers";
 import { createSendUpdateAction } from "../../context/taipyReducers";
-import ThemeToggle from "./ThemeToggle";
+import ThemeToggle, { emptyStyle } from "./ThemeToggle";
 import { LovProps, useLovListMemo } from "./lovUtils";
 import { LovProps, useLovListMemo } from "./lovUtils";
 import { useClassNames, useDispatch, useDynamicProperty, useModule } from "../../utils/hooks";
 import { useClassNames, useDispatch, useDynamicProperty, useModule } from "../../utils/hooks";
-import { emptyStyle, getSuffixedClassNames, getUpdateVar } from "./utils";
+import { getCssSize, getSuffixedClassNames, getUpdateVar } from "./utils";
 import { Icon, IconAvatar } from "../../utils/icon";
 import { Icon, IconAvatar } from "../../utils/icon";
-import { FormControlLabel } from "@mui/material";
+import { FormControlLabel, SxProps } from "@mui/material";
 
 
-const groupSx = { verticalAlign: "middle" };
+const baseGroupSx = { verticalAlign: "middle" };
 
 
 interface ToggleProps extends LovProps<string> {
 interface ToggleProps extends LovProps<string> {
-    style?: CSSProperties;
+    style?: SxProps;
     label?: string;
     label?: string;
     unselectedValue?: string;
     unselectedValue?: string;
     allowUnselect?: boolean;
     allowUnselect?: boolean;
     mode?: string;
     mode?: string;
-    isSwitch? : boolean;
+    isSwitch?: boolean;
+    width?: string | number;
 }
 }
 
 
 const Toggle = (props: ToggleProps) => {
 const Toggle = (props: ToggleProps) => {
@@ -70,6 +71,11 @@ const Toggle = (props: ToggleProps) => {
 
 
     const lovList = useLovListMemo(lov, defaultLov);
     const lovList = useLovListMemo(lov, defaultLov);
 
 
+    const boxSx = useMemo(
+        () => (props.width ? ({ ...style, width: getCssSize(props.width) } as SxProps) : style),
+        [props.width, style]
+    );
+
     const changeValue = useCallback(
     const changeValue = useCallback(
         (evt: MouseEvent, val: string) => {
         (evt: MouseEvent, val: string) => {
             if (!props.allowUnselect && val === null) {
             if (!props.allowUnselect && val === null) {
@@ -112,7 +118,7 @@ const Toggle = (props: ToggleProps) => {
     return mode.toLowerCase() === "theme" ? (
     return mode.toLowerCase() === "theme" ? (
         <ThemeToggle {...props} />
         <ThemeToggle {...props} />
     ) : (
     ) : (
-        <Box id={id} sx={style} className={className}>
+        <Box id={id} sx={boxSx} className={className}>
             {label && !isSwitch ? <Typography>{label}</Typography> : null}
             {label && !isSwitch ? <Typography>{label}</Typography> : null}
             <Tooltip title={hover || ""}>
             <Tooltip title={hover || ""}>
                 {isSwitch ? (
                 {isSwitch ? (
@@ -125,7 +131,14 @@ const Toggle = (props: ToggleProps) => {
                         className={getSuffixedClassNames(className, "-switch")}
                         className={getSuffixedClassNames(className, "-switch")}
                     />
                     />
                 ) : (
                 ) : (
-                    <ToggleButtonGroup value={value} exclusive onChange={changeValue} disabled={!active} sx={groupSx}>
+                    <ToggleButtonGroup
+                        value={value}
+                        exclusive
+                        onChange={changeValue}
+                        disabled={!active}
+                        sx={baseGroupSx}
+                        fullWidth={!!props.width}
+                    >
                         {lovList &&
                         {lovList &&
                             lovList.map((v) => (
                             lovList.map((v) => (
                                 <ToggleButton value={v.id} key={v.id}>
                                 <ToggleButton value={v.id} key={v.id}>

+ 4 - 6
frontend/taipy-gui/src/components/Taipy/utils.ts

@@ -11,7 +11,7 @@
  * specific language governing permissions and limitations under the License.
  * specific language governing permissions and limitations under the License.
  */
  */
 
 
-import { CSSProperties, MouseEvent } from "react";
+import { MouseEvent } from "react";
 
 
 export interface TaipyActiveProps extends TaipyDynamicProps, TaipyHoverProps {
 export interface TaipyActiveProps extends TaipyDynamicProps, TaipyHoverProps {
     defaultActive?: boolean;
     defaultActive?: boolean;
@@ -128,8 +128,6 @@ export const getSuffixedClassNames = (names: string | undefined, suffix: string)
         .map((n) => n + suffix)
         .map((n) => n + suffix)
         .join(" ");
         .join(" ");
 
 
-export const emptyStyle = {} as CSSProperties;
-
 export const disableColor = <T>(color: T, disabled: boolean) => (disabled ? ("disabled" as T) : color);
 export const disableColor = <T>(color: T, disabled: boolean) => (disabled ? ("disabled" as T) : color);
 
 
 export const getProps = (p: DateProps, start: boolean, val: Date | null, withTime: boolean): DateProps => {
 export const getProps = (p: DateProps, start: boolean, val: Date | null, withTime: boolean): DateProps => {
@@ -141,10 +139,10 @@ export const getProps = (p: DateProps, start: boolean, val: Date | null, withTim
             ? "minDateTime"
             ? "minDateTime"
             : "maxDateTime"
             : "maxDateTime"
         : start
         : start
-            ? "minDate"
-            : "maxDate";
+        ? "minDate"
+        : "maxDate";
     if (p[propName] == val) {
     if (p[propName] == val) {
         return p;
         return p;
     }
     }
-    return {...p, [propName]: val};
+    return { ...p, [propName]: val };
 };
 };

+ 4 - 2
frontend/taipy-gui/src/utils/ErrorBoundary.tsx

@@ -23,8 +23,10 @@ interface ErrorFallBackProps {
 const ErrorFallback = (props: ErrorFallBackProps) => (
 const ErrorFallback = (props: ErrorFallBackProps) => (
     <Box sx={{ backgroundColor: "error.main" }}>
     <Box sx={{ backgroundColor: "error.main" }}>
         <Box>Something went wrong ...</Box>
         <Box>Something went wrong ...</Box>
-        <Box>{(props.error as Error).message}</Box>
-        <Button onClick={props.resetErrorBoundary}>Try again</Button>
+        <Box>{props.error.message}</Box>
+        <Button onClick={props.resetErrorBoundary} color="secondary">
+            Try again
+        </Button>
     </Box>
     </Box>
 );
 );
 
 

+ 35 - 30
frontend/taipy/src/CoreSelector.tsx

@@ -378,10 +378,9 @@ const CoreSelector = (props: CoreSelectorProps) => {
                 if (isSelectable) {
                 if (isSelectable) {
                     const lovVar = getUpdateVar(updateVars, lovPropertyName);
                     const lovVar = getUpdateVar(updateVars, lovPropertyName);
                     const val = nodeId;
                     const val = nodeId;
-                    setTimeout(
+                    Promise.resolve().then(
                         // to avoid set state while render react errors
                         // to avoid set state while render react errors
-                        () => dispatch(createSendUpdateAction(updateVarName, val, module, onChange, propagate, lovVar)),
-                        1
+                        () => dispatch(createSendUpdateAction(updateVarName, val, module, onChange, propagate, lovVar))
                     );
                     );
                     onSelect && onSelect(val);
                     onSelect && onSelect(val);
                 }
                 }
@@ -421,19 +420,17 @@ const CoreSelector = (props: CoreSelectorProps) => {
             setSelectedItems((old) => {
             setSelectedItems((old) => {
                 if (old.length) {
                 if (old.length) {
                     const lovVar = getUpdateVar(updateVars, lovPropertyName);
                     const lovVar = getUpdateVar(updateVars, lovPropertyName);
-                    setTimeout(
-                        () =>
-                            dispatch(
-                                createSendUpdateAction(
-                                    updateVarName,
-                                    multiple ? [] : "",
-                                    module,
-                                    onChange,
-                                    propagate,
-                                    lovVar
-                                )
-                            ),
-                        1
+                    Promise.resolve().then(() =>
+                        dispatch(
+                            createSendUpdateAction(
+                                updateVarName,
+                                multiple ? [] : "",
+                                module,
+                                onChange,
+                                propagate,
+                                lovVar
+                            )
+                        )
                     );
                     );
                     return [];
                     return [];
                 }
                 }
@@ -511,10 +508,20 @@ const CoreSelector = (props: CoreSelectorProps) => {
     // filters
     // filters
     const colFilters = useMemo(() => {
     const colFilters = useMemo(() => {
         try {
         try {
-            const res = props.filter ? (JSON.parse(props.filter) as Array<[string, string, string, string[]]>) : undefined;
+            const res = props.filter
+                ? (JSON.parse(props.filter) as Array<[string, string, string, string[]]>)
+                : undefined;
             return Array.isArray(res)
             return Array.isArray(res)
                 ? res.reduce((pv, [name, id, coltype, lov], idx) => {
                 ? res.reduce((pv, [name, id, coltype, lov], idx) => {
-                      pv[name] = { dfid: id, title: name, type: coltype, index: idx, filter: true, lov: lov, freeLov: !!lov };
+                      pv[name] = {
+                          dfid: id,
+                          title: name,
+                          type: coltype,
+                          index: idx,
+                          filter: true,
+                          lov: lov,
+                          freeLov: !!lov,
+                      };
                       return pv;
                       return pv;
                   }, {} as Record<string, ColumnDesc>)
                   }, {} as Record<string, ColumnDesc>)
                 : undefined;
                 : undefined;
@@ -532,18 +539,16 @@ const CoreSelector = (props: CoreSelectorProps) => {
                     localStoreSet(jsonFilters, id, lovPropertyName, "filter");
                     localStoreSet(jsonFilters, id, lovPropertyName, "filter");
                     const filterVar = getUpdateVar(updateCoreVars, "filter");
                     const filterVar = getUpdateVar(updateCoreVars, "filter");
                     const lovVar = getUpdateVarNames(updateVars, lovPropertyName);
                     const lovVar = getUpdateVarNames(updateVars, lovPropertyName);
-                    setTimeout(
-                        () =>
-                            dispatch(
-                                createRequestUpdateAction(
-                                    id,
-                                    module,
-                                    lovVar,
-                                    true,
-                                    filterVar ? { [filterVar]: filters } : undefined
-                                )
-                            ),
-                        1
+                    Promise.resolve().then(() =>
+                        dispatch(
+                            createRequestUpdateAction(
+                                id,
+                                module,
+                                lovVar,
+                                true,
+                                filterVar ? { [filterVar]: filters } : undefined
+                            )
+                        )
                     );
                     );
                     return filters;
                     return filters;
                 }
                 }

+ 20 - 46
frontend/taipy/src/DataNodeViewer.tsx

@@ -348,16 +348,14 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
             // clean lock on change
             // clean lock on change
             if (oldDn[DataNodeFullProps.id] && isNewDn && editLock.current) {
             if (oldDn[DataNodeFullProps.id] && isNewDn && editLock.current) {
                 const oldId = oldDn[DataNodeFullProps.id];
                 const oldId = oldDn[DataNodeFullProps.id];
-                setTimeout(
-                    () =>
-                        dispatch(
-                            createSendActionNameAction(id, module, props.onLock, {
-                                id: oldId,
-                                lock: false,
-                                error_id: getUpdateVar(updateDnVars, "error_id"),
-                            })
-                        ),
-                    1
+                Promise.resolve().then(() =>
+                    dispatch(
+                        createSendActionNameAction(id, module, props.onLock, {
+                            id: oldId,
+                            lock: false,
+                            error_id: getUpdateVar(updateDnVars, "error_id"),
+                        })
+                    )
                 );
                 );
             }
             }
             if (!dn || isNewDn) {
             if (!dn || isNewDn) {
@@ -371,18 +369,10 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                 if (req && !isNewDn && tabValue == TabValues.History) {
                 if (req && !isNewDn && tabValue == TabValues.History) {
                     const idVar = getUpdateVar(updateDnVars, "history_id");
                     const idVar = getUpdateVar(updateDnVars, "history_id");
                     const vars = getUpdateVarNames(updateVars, "history");
                     const vars = getUpdateVarNames(updateVars, "history");
-                    setTimeout(
-                        () =>
-                            dispatch(
-                                createRequestUpdateAction(
-                                    id,
-                                    module,
-                                    vars,
-                                    true,
-                                    idVar ? { [idVar]: newDnId } : undefined
-                                )
-                            ),
-                        1
+                    Promise.resolve().then(() =>
+                        dispatch(
+                            createRequestUpdateAction(id, module, vars, true, idVar ? { [idVar]: newDnId } : undefined)
+                        )
                     );
                     );
                     return true;
                     return true;
                 }
                 }
@@ -392,18 +382,10 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                 if (showData && tabValue == TabValues.Data && dn[DataNodeFullProps.data][DatanodeDataProps.tabular]) {
                 if (showData && tabValue == TabValues.Data && dn[DataNodeFullProps.data][DatanodeDataProps.tabular]) {
                     const idVar = getUpdateVar(updateDnVars, "data_id");
                     const idVar = getUpdateVar(updateDnVars, "data_id");
                     const vars = getUpdateVarNames(updateVars, "tabularData", "tabularColumns");
                     const vars = getUpdateVarNames(updateVars, "tabularData", "tabularColumns");
-                    setTimeout(
-                        () =>
-                            dispatch(
-                                createRequestUpdateAction(
-                                    id,
-                                    module,
-                                    vars,
-                                    true,
-                                    idVar ? { [idVar]: newDnId } : undefined
-                                )
-                            ),
-                        1
+                    Promise.resolve().then(() =>
+                        dispatch(
+                            createRequestUpdateAction(id, module, vars, true, idVar ? { [idVar]: newDnId } : undefined)
+                        )
                     );
                     );
                     return true;
                     return true;
                 }
                 }
@@ -413,18 +395,10 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                 if ((req || !showData) && tabValue == TabValues.Properties) {
                 if ((req || !showData) && tabValue == TabValues.Properties) {
                     const idVar = getUpdateVar(updateDnVars, "properties_id");
                     const idVar = getUpdateVar(updateDnVars, "properties_id");
                     const vars = getUpdateVarNames(updateVars, "properties");
                     const vars = getUpdateVarNames(updateVars, "properties");
-                    setTimeout(
-                        () =>
-                            dispatch(
-                                createRequestUpdateAction(
-                                    id,
-                                    module,
-                                    vars,
-                                    true,
-                                    idVar ? { [idVar]: newDnId } : undefined
-                                )
-                            ),
-                        1
+                    Promise.resolve().then(() =>
+                        dispatch(
+                            createRequestUpdateAction(id, module, vars, true, idVar ? { [idVar]: newDnId } : undefined)
+                        )
                     );
                     );
                     return true;
                     return true;
                 }
                 }

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

@@ -81,6 +81,7 @@ class _Factory:
                 ("on_action", PropertyType.function),
                 ("on_action", PropertyType.function),
                 ("active", PropertyType.dynamic_boolean, True),
                 ("active", PropertyType.dynamic_boolean, True),
                 ("hover_text", PropertyType.dynamic_string),
                 ("hover_text", PropertyType.dynamic_string),
+                ("width", PropertyType.string_or_number),
             ]
             ]
         ),
         ),
         "chat": lambda gui, control_type, attrs: _Builder(
         "chat": lambda gui, control_type, attrs: _Builder(
@@ -145,6 +146,7 @@ class _Factory:
                 ("label",),
                 ("label",),
                 ("on_change", PropertyType.function),
                 ("on_change", PropertyType.function),
                 ("format",),
                 ("format",),
+                ("width", PropertyType.string_or_number),
             ]
             ]
         )
         )
         ._set_propagate(),
         ._set_propagate(),
@@ -165,6 +167,7 @@ class _Factory:
                 ("label_end",),
                 ("label_end",),
                 ("on_change", PropertyType.function),
                 ("on_change", PropertyType.function),
                 ("format",),
                 ("format",),
+                ("width", PropertyType.string_or_number),
             ]
             ]
         )
         )
         ._set_propagate(),
         ._set_propagate(),
@@ -220,6 +223,7 @@ class _Factory:
                 ("bypass_preview", PropertyType.boolean, True),
                 ("bypass_preview", PropertyType.boolean, True),
                 ("name",),
                 ("name",),
                 ("hover_text", PropertyType.dynamic_string),
                 ("hover_text", PropertyType.dynamic_string),
+                ("width", PropertyType.string_or_number),
             ]
             ]
         ),
         ),
         "file_selector": lambda gui, control_type, attrs: _Builder(
         "file_selector": lambda gui, control_type, attrs: _Builder(
@@ -239,6 +243,7 @@ class _Factory:
                 ("drop_message",),
                 ("drop_message",),
                 ("hover_text", PropertyType.dynamic_string),
                 ("hover_text", PropertyType.dynamic_string),
                 ("notify", PropertyType.boolean, True),
                 ("notify", PropertyType.boolean, True),
+                ("width", PropertyType.string_or_number),
             ]
             ]
         ),
         ),
         "image": lambda gui, control_type, attrs: _Builder(
         "image": lambda gui, control_type, attrs: _Builder(
@@ -542,6 +547,7 @@ class _Factory:
                 ("hover_text", PropertyType.dynamic_string),
                 ("hover_text", PropertyType.dynamic_string),
                 ("raw", PropertyType.boolean, False),
                 ("raw", PropertyType.boolean, False),
                 ("mode",),
                 ("mode",),
+                ("width", PropertyType.string_or_number),
             ]
             ]
         ),
         ),
         "toggle": lambda gui, control_type, attrs: _Builder(
         "toggle": lambda gui, control_type, attrs: _Builder(
@@ -559,6 +565,7 @@ class _Factory:
                 ("on_change", PropertyType.function),
                 ("on_change", PropertyType.function),
                 ("mode",),
                 ("mode",),
                 ("lov", PropertyType.single_lov),
                 ("lov", PropertyType.single_lov),
+                ("width", PropertyType.string_or_number),
             ]
             ]
         )
         )
         ._set_kind()
         ._set_kind()

+ 65 - 23
taipy/gui/viselements.json

@@ -29,6 +29,12 @@
                         "name": "format",
                         "name": "format",
                         "type": "str",
                         "type": "str",
                         "doc": "The format to apply to the value.<br/>See below."
                         "doc": "The format to apply to the value.<br/>See below."
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the element."
                     }
                     }
                 ]
                 ]
             }
             }
@@ -50,7 +56,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when the button is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button it it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "doc": "The name of a function that is triggered when the button is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button it it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -66,6 +72,12 @@
                                 "dict"
                                 "dict"
                             ]
                             ]
                         ]
                         ]
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the button element."
                     }
                     }
                 ]
                 ]
             }
             }
@@ -110,12 +122,6 @@
                         "default_value": "5",
                         "default_value": "5",
                         "doc": "The number of lines shown in the input control, when multiline is True."
                         "doc": "The number of lines shown in the input control, when multiline is True."
                     },
                     },
-                    {
-                        "name": "width",
-                        "type": "Union[str,int]",
-                        "default_value": "None",
-                        "doc": "The width of the input element."
-                    },
                     {
                     {
                         "name": "type",
                         "name": "type",
                         "type": "str",
                         "type": "str",
@@ -284,6 +290,12 @@
                         "name": "label",
                         "name": "label",
                         "type": "str",
                         "type": "str",
                         "doc": "The label associated with the toggle."
                         "doc": "The label associated with the toggle."
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the element."
                     }
                     }
                 ]
                 ]
             }
             }
@@ -333,6 +345,12 @@
                         "name": "max",
                         "name": "max",
                         "type": "dynamic(datetime)",
                         "type": "dynamic(datetime)",
                         "doc": "The maximum date to accept for this input."
                         "doc": "The maximum date to accept for this input."
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the date element."
                     }
                     }
                 ]
                 ]
             }
             }
@@ -377,6 +395,12 @@
                         "name": "label_end",
                         "name": "label_end",
                         "type": "str",
                         "type": "str",
                         "doc": "The label associated with the second input."
                         "doc": "The label associated with the second input."
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the date_range element."
                     }
                     }
                 ]
                 ]
             }
             }
@@ -511,7 +535,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_range_change",
                         "name": "on_range_change",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The callback function that is invoked when the visible part of the x axis changes.<br/>The function receives three parameters:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the chart control if it has one.</li>\n<li>payload (dict[str, any]): the full details on this callback's invocation, as emitted by <a href=\"https://plotly.com/javascript/plotlyjs-events/#update-data\">Plotly</a>.</li>\n</ul>",
                         "doc": "The callback function that is invoked when the visible part of the x axis changes.<br/>The function receives three parameters:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the chart control if it has one.</li>\n<li>payload (dict[str, any]): the full details on this callback's invocation, as emitted by <a href=\"https://plotly.com/javascript/plotlyjs-events/#update-data\">Plotly</a>.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -808,7 +832,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_edit",
                         "name": "on_edit",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when a cell edition is validated.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n<li>col (str): the column name.</li>\n<li>value (any): the new cell value cast to the type of the column.</li>\n<li>user_value (str): the new cell value, as it was provided by the user.</li>\n<li>tz (str): the timezone if the column type is date.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot edit cells.",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when a cell edition is validated.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n<li>col (str): the column name.</li>\n<li>value (any): the new cell value cast to the type of the column.</li>\n<li>user_value (str): the new cell value, as it was provided by the user.</li>\n<li>tz (str): the timezone if the column type is date.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot edit cells.",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -827,7 +851,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_delete",
                         "name": "on_delete",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when a row is deleted.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot delete rows.",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when a row is deleted.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot delete rows.",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -846,7 +870,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_add",
                         "name": "on_add",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when the user requests a row to be added.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot add rows.",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when the user requests a row to be added.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot add rows.",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -865,7 +889,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when the user selects a row.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>index (int): the row index.</li>\n<li>col (str): the column name.</li>\n<li>reason (str): the origin of the action: \"click\", or \"button\" if the cell contains a Markdown link syntax.</li>\n<li>value (str): the *link value* indicated in the cell when using a Markdown link syntax (that is, <i>reason</i> is set to \"button\").</li></ul></li></ul>.",
                         "doc": "The name of a function that is triggered when the user selects a row.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>index (int): the row index.</li>\n<li>col (str): the column name.</li>\n<li>reason (str): the origin of the action: \"click\", or \"button\" if the cell contains a Markdown link syntax.</li>\n<li>value (str): the *link value* indicated in the cell when using a Markdown link syntax (that is, <i>reason</i> is set to \"button\").</li></ul></li></ul>.",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -906,7 +930,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_compare",
                         "name": "on_compare",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "A data comparison function that would return a structure that identifies the differences between the different data passed as name. The default implementation compares the default data with the data[1] value.",
                         "doc": "A data comparison function that would return a structure that identifies the differences between the different data passed as name. The default implementation compares the default data with the data[1] value.",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -998,7 +1022,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when the download is terminated (or on user action if <i>content</i> is None).<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has two keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: A list of two elements: <i>args[0]</i> reflects the <i>name</i> property and <i>args[1]</i> holds the file URL.</li>\n</ul>\n</li>\n</ul>",
                         "doc": "The name of a function that is triggered when the download is terminated (or on user action if <i>content</i> is None).<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has two keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: A list of two elements: <i>args[0]</i> reflects the <i>name</i> property and <i>args[1]</i> holds the file URL.</li>\n</ul>\n</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1037,6 +1061,12 @@
                         "name": "name",
                         "name": "name",
                         "type": "str",
                         "type": "str",
                         "doc": "A name proposition for the file to save, that the user can change."
                         "doc": "A name proposition for the file to save, that the user can change."
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the element."
                     }
                     }
                 ]
                 ]
             }
             }
@@ -1062,7 +1092,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of the function that will be triggered.<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "doc": "The name of the function that will be triggered.<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1102,6 +1132,12 @@
                         "type": "bool",
                         "type": "bool",
                         "default_value": "True",
                         "default_value": "True",
                         "doc": "If set to False, the user won't be notified of upload finish."
                         "doc": "If set to False, the user won't be notified of upload finish."
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the element."
                     }
                     }
                 ]
                 ]
             }
             }
@@ -1127,7 +1163,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when the user clicks on the image.<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "doc": "The name of a function that is triggered when the user clicks on the image.<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1417,7 +1453,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of the function that is triggered when a menu option is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<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>\n<li>args: List where the first element contains the id of the selected option.</li>\n</ul>\n</li>\n</ul>",
                         "doc": "The name of the function that is triggered when a menu option is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<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>\n<li>args: List where the first element contains the id of the selected option.</li>\n</ul>\n</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1492,7 +1528,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of the function that is triggered when the dialog button is pressed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<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>\n<li>args: a list with three elements:\n<ul><li>The first element is the username</li><li>The second element is the password</li><li>The third element is the current page name</li></ul></li></li>\n</ul>\n</li>\n</ul><br/>When the button is pressed, and if this property is not set, Taipy will try to find a callback function called <i>on_login()</i> and invoke it with the parameters listed above.",
                         "doc": "The name of the function that is triggered when the dialog button is pressed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<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>\n<li>args: a list with three elements:\n<ul><li>The first element is the username</li><li>The second element is the password</li><li>The third element is the current page name</li></ul></li></li>\n</ul>\n</li>\n</ul><br/>When the button is pressed, and if this property is not set, Taipy will try to find a callback function called <i>on_login()</i> and invoke it with the parameters listed above.",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1551,7 +1587,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when the user enters a new message.<br/>All the parameters of that function are optional:\n<ul>\n<li><i>state</i> (<code>State^</code>): the state instance.</li>\n<li><i>var_name</i> (str): the name of the variable bound to the <i>messages</i> property.</li>\n<li><i>payload</i> (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li><i>action</i>: the name of the action that triggered this callback.</li>\n<li><i>args</i> (list): A list composed of a reason (\"click\" or \"Enter\"), the variable name, message, the user identifier of the sender.</li></ul></li></ul>",
                         "doc": "The name of a function that is triggered when the user enters a new message.<br/>All the parameters of that function are optional:\n<ul>\n<li><i>state</i> (<code>State^</code>): the state instance.</li>\n<li><i>var_name</i> (str): the name of the variable bound to the <i>messages</i> property.</li>\n<li><i>payload</i> (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li><i>action</i>: the name of the action that triggered this callback.</li>\n<li><i>args</i> (list): A list composed of a reason (\"click\" or \"Enter\"), the variable name, message, the user identifier of the sender.</li></ul></li></ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1697,7 +1733,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "Name of a function triggered when a button is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the dialog if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: a list where the first element contains the index of the selected label.</li>\n</ul>\n</li>\n</ul>",
                         "doc": "Name of a function triggered when a button is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the dialog if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: a list where the first element contains the index of the selected label.</li>\n</ul>\n</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1786,7 +1822,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_close",
                         "name": "on_close",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when this pane is closed (if the user clicks outside of it or presses the Esc key).<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (optional[str]): the identifier of the close button if it has one.</li>\n</ul><br/>If this property is not set, no function is called when this pane is closed.",
                         "doc": "The name of a function that is triggered when this pane is closed (if the user clicks outside of it or presses the Esc key).<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (optional[str]): the identifier of the close button if it has one.</li>\n</ul><br/>If this property is not set, no function is called when this pane is closed.",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1886,7 +1922,7 @@
                 "properties": [
                 "properties": [
                     {
                     {
                         "name": "on_change",
                         "name": "on_change",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when the value is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (any): the new value.</li>\n</ul>",
                         "doc": "The name of a function that is triggered when the value is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (any): the new value.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1952,7 +1988,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "Name of a function that is triggered when a specific key is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the control if it has one.</li>\n<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>\n<li>args (list):\n<ul><li>key name</li><li>variable name</li><li>current value</li></ul>\n</li>\n</ul>\n</li>\n</ul>",
                         "doc": "Name of a function that is triggered when a specific key is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the control if it has one.</li>\n<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>\n<li>args (list):\n<ul><li>key name</li><li>variable name</li><li>current value</li></ul>\n</li>\n</ul>\n</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -1974,6 +2010,12 @@
                         "type": "str",
                         "type": "str",
                         "default_value": "\"Enter\"",
                         "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": "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."
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the element."
                     }
                     }
                 ]
                 ]
             }
             }

+ 5 - 5
taipy/gui_core/viselements.json

@@ -33,7 +33,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_change",
                         "name": "on_change",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when the value is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Scenario^</code>): the selected scenario.</li>\n</ul>",
                         "doc": "The name of a function that is triggered when the value is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Scenario^</code>): the selected scenario.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -64,7 +64,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_creation",
                         "name": "on_creation",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of the function that is triggered when a scenario is about to be created.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of this scenario selector.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>config (str): the name of the selected scenario configuration.</li>\n<li>date (datetime): the creation date for the new scenario.</li>\n<li>label (str): the user-specified label.</li>\n<li>properties (dic): a dictionary containing all the user-defined custom properties.</li>\n</ul>\n</li>\n<li>The callback function can return a scenario, a string containing an error message (a scenario will not be created), or None (then a new scenario is created with the user parameters).</li>\n</ul>",
                         "doc": "The name of the function that is triggered when a scenario is about to be created.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of this scenario selector.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>config (str): the name of the selected scenario configuration.</li>\n<li>date (datetime): the creation date for the new scenario.</li>\n<li>label (str): the user-specified label.</li>\n<li>properties (dic): a dictionary containing all the user-defined custom properties.</li>\n</ul>\n</li>\n<li>The callback function can return a scenario, a string containing an error message (a scenario will not be created), or None (then a new scenario is created with the user parameters).</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -265,7 +265,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_action",
                         "name": "on_action",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of the function that is triggered when a a node is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>entity (DataNode | Task): the entity (DataNode or Task) that was selected.</li>\n</ul>",
                         "doc": "The name of the function that is triggered when a a node is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>entity (DataNode | Task): the entity (DataNode or Task) that was selected.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -308,7 +308,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_change",
                         "name": "on_change",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when a data node is selected.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>DataNode^</code>): the selected data node.</li>\n</ul>",
                         "doc": "The name of a function that is triggered when a data node is selected.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>DataNode^</code>): the selected data node.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [
@@ -517,7 +517,7 @@
                     },
                     },
                     {
                     {
                         "name": "on_change",
                         "name": "on_change",
-                        "type": "Callback",
+                        "type": "Callable",
                         "doc": "The name of a function that is triggered when the selection is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Job^</code>): the selected job.</li>\n</ul>",
                         "doc": "The name of a function that is triggered when the selection is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Job^</code>): the selected job.</li>\n</ul>",
                         "signature": [
                         "signature": [
                             [
                             [

+ 8 - 0
tests/gui/control/test_button.py

@@ -27,6 +27,14 @@ def test_button_md_2(gui: Gui, test_client, helpers):
     helpers.test_control_md(gui, md_string, expected_list)
     helpers.test_control_md(gui, md_string, expected_list)
 
 
 
 
+def test_button_md_width(gui: Gui, test_client, helpers):
+    gui._bind_var_val("name", "World!")
+    gui._bind_var_val("btn_id", "button1")
+    md_string = "<|Hello {name}|button|id={btn_id}|width=70%|>"
+    expected_list = ["<Button", 'defaultLabel="Hello World!"', "label={tp_TpExPr_Hello_name_TPMDL_0_0", 'width="70%"']
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
 def test_button_html_1(gui: Gui, test_client, helpers):
 def test_button_html_1(gui: Gui, test_client, helpers):
     gui._bind_var_val("name", "World!")
     gui._bind_var_val("name", "World!")
     gui._bind_var_val("btn_id", "button1")
     gui._bind_var_val("btn_id", "button1")

+ 13 - 0
tests/gui/control/test_date.py

@@ -40,6 +40,19 @@ def test_date_md_2(gui: Gui, test_client, helpers):
     helpers.test_control_md(gui, md_string, expected_list)
     helpers.test_control_md(gui, md_string, expected_list)
 
 
 
 
+def test_date_md_width(gui: Gui, test_client, helpers):
+    gui._bind_var_val("date", datetime.strptime("15 Dec 2020", "%d %b %Y"))
+    md_string = "<|{date}|date|width=70%|>"
+    expected_list = [
+        "<DateSelector",
+        'defaultDate="2020-12-',
+        'updateVarName="_TpDt_tpec_TpExPr_date_TPMDL_0"',
+        'width="70%"',
+        "date={_TpDt_tpec_TpExPr_date_TPMDL_0}",
+    ]
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
 def test_date_html_1(gui: Gui, test_client, helpers):
 def test_date_html_1(gui: Gui, test_client, helpers):
     gui._bind_var_val("date", datetime.strptime("15 Dec 2020", "%d %b %Y"))
     gui._bind_var_val("date", datetime.strptime("15 Dec 2020", "%d %b %Y"))
     html_string = '<taipy:date date="{date}" />'
     html_string = '<taipy:date date="{date}" />'

+ 15 - 0
tests/gui/control/test_date_range.py

@@ -45,6 +45,21 @@ def test_date_range_md_2(gui: Gui, test_client, helpers):
     helpers.test_control_md(gui, md_string, expected_list)
     helpers.test_control_md(gui, md_string, expected_list)
 
 
 
 
+def test_date_range_md_width(gui: Gui, helpers):
+    gui._bind_var_val(
+        "dates", [datetime.strptime("15 Dec 2020", "%d %b %Y"), datetime.strptime("31 Dec 2020", "%d %b %Y")]
+    )
+    md_string = "<|{dates}|date_range|width=70%|>"
+    expected_list = [
+        "<DateRange",
+        'defaultDates="[&quot;2020-12-',
+        'updateVarName="_TpDr_tpec_TpExPr_dates_TPMDL_0"',
+        'width="70%"',
+        "dates={_TpDr_tpec_TpExPr_dates_TPMDL_0}",
+    ]
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
 def test_date_range_html_1(gui: Gui, test_client, helpers):
 def test_date_range_html_1(gui: Gui, test_client, helpers):
     gui._bind_var_val(
     gui._bind_var_val(
         "dates", [datetime.strptime("15 Dec 2020", "%d %b %Y"), datetime.strptime("31 Dec 2020", "%d %b %Y")]
         "dates", [datetime.strptime("15 Dec 2020", "%d %b %Y"), datetime.strptime("31 Dec 2020", "%d %b %Y")]

+ 12 - 0
tests/gui/control/test_file_download.py

@@ -64,6 +64,18 @@ def test_file_download_any_file_md(gui: Gui, test_client, helpers):
         helpers.test_control_md(gui, md_string, expected_list)
         helpers.test_control_md(gui, md_string, expected_list)
 
 
 
 
+def test_file_download_url_width_md(gui: Gui, test_client, helpers):
+    gui._bind_var_val("content", "some_url")
+    md_string = "<|{content}|file_download|width=70%|>"
+    expected_list = [
+        "<FileDownload",
+        "content={_TpC_tpec_TpExPr_content_TPMDL_0}",
+        'defaultContent="some_url"',
+        'width="70%"',
+    ]
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
 def test_file_download_url_html(gui: Gui, test_client, helpers):
 def test_file_download_url_html(gui: Gui, test_client, helpers):
     gui._bind_var_val("content", "some_url")
     gui._bind_var_val("content", "some_url")
     html_string = '<taipy:file_download content="{content}" />'
     html_string = '<taipy:file_download content="{content}" />'

+ 13 - 0
tests/gui/control/test_file_selector.py

@@ -24,6 +24,19 @@ def test_file_selector_md(gui: Gui, test_client, helpers):
     helpers.test_control_md(gui, md_string, expected_list)
     helpers.test_control_md(gui, md_string, expected_list)
 
 
 
 
+def test_file_selector_width_md(gui: Gui, test_client, helpers):
+    gui._bind_var_val("content", None)
+    md_string = "<|{content}|file_selector|label=label|on_action=action|width=70%|>"
+    expected_list = [
+        "<FileSelector",
+        'updateVarName="tpec_TpExPr_content_TPMDL_0"',
+        'label="label"',
+        'onAction="action"',
+        'width="70%"',
+    ]
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
 def test_file_selector_html(gui: Gui, test_client, helpers):
 def test_file_selector_html(gui: Gui, test_client, helpers):
     gui._bind_var_val("content", None)
     gui._bind_var_val("content", None)
     html_string = '<taipy:file_selector content="{content}" label="label" on_action="action" />'
     html_string = '<taipy:file_selector content="{content}" label="label" on_action="action" />'

+ 6 - 0
tests/gui/control/test_number.py

@@ -31,6 +31,12 @@ def test_number_md_2(gui: Gui, test_client, helpers):
     helpers.test_control_md(gui, md_string, expected_list)
     helpers.test_control_md(gui, md_string, expected_list)
 
 
 
 
+def test_number_md_width(gui: Gui, helpers):
+    md_string = "<|10|number|width=70%|>"
+    expected_list = ["<Input", 'value="10"', 'type="number"', 'width="70%"']
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
 def test_number_html_1(gui: Gui, test_client, helpers):
 def test_number_html_1(gui: Gui, test_client, helpers):
     gui._bind_var_val("x", 10)
     gui._bind_var_val("x", 10)
     html_string = '<taipy:number value="{x}" />'
     html_string = '<taipy:number value="{x}" />'

+ 7 - 0
tests/gui/control/test_text.py

@@ -19,6 +19,13 @@ def test_text_md_1(gui: Gui, test_client, helpers):
     helpers.test_control_md(gui, md_string, expected_list)
     helpers.test_control_md(gui, md_string, expected_list)
 
 
 
 
+def test_text_md_width(gui: Gui, test_client, helpers):
+    gui._bind_var_val("x", 10)
+    md_string = "<|{x}|width=70%|>"
+    expected_list = ["<Field", 'dataType="int"', 'defaultValue="10"', "value={tpec_TpExPr_x_TPMDL_0}", 'width="70%"']
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
 def test_text_html_1(gui: Gui, test_client, helpers):
 def test_text_html_1(gui: Gui, test_client, helpers):
     gui._bind_var_val("x", 10)
     gui._bind_var_val("x", 10)
     html_string = '<taipy:text value="{x}" />'
     html_string = '<taipy:text value="{x}" />'

+ 6 - 0
tests/gui/control/test_toggle.py

@@ -18,6 +18,12 @@ def test_toggle_md(gui: Gui, helpers):
     helpers.test_control_md(gui, md_string, expected_list)
     helpers.test_control_md(gui, md_string, expected_list)
 
 
 
 
+def test_toggle_width_md(gui: Gui, helpers):
+    md_string = "<|toggle|theme|width=70%|>"
+    expected_list = ["<Toggle", 'mode="theme"', 'unselectedValue=""', 'width="70%"']
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
 def test_toggle_allow_unselected_md(gui: Gui, helpers):
 def test_toggle_allow_unselected_md(gui: Gui, helpers):
     md_string = "<|toggle|lov=1;2|allow_unselect|>"
     md_string = "<|toggle|lov=1;2|allow_unselect|>"
     expected_list = ["<Toggle", 'unselectedValue=""', "allowUnselect={true}"]
     expected_list = ["<Toggle", 'unselectedValue=""', "allowUnselect={true}"]