Ver Fonte

support filter on selector dropdown (#1338)

* support filter on selector dropdown
update date-fns to 3.x
resolves #428

* Fab's comment

Co-authored-by: Fabien Lelaquais <86590727+FabienLelaquais@users.noreply.github.com>

* Fab's comments

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Co-authored-by: Fabien Lelaquais <86590727+FabienLelaquais@users.noreply.github.com>
Fred Lefévère-Laoide há 1 ano atrás
pai
commit
cff32624e1

+ 37 - 43
frontend/taipy-gui/package-lock.json

@@ -16,8 +16,8 @@
         "@mui/x-tree-view": "^7.0.0",
         "apache-arrow": "^14.0.2",
         "axios": "^1.2.0",
-        "date-fns": "^2.30.0",
-        "date-fns-tz": "^2.0.0",
+        "date-fns": "^3.6.0",
+        "date-fns-tz": "^3.1.3",
         "lodash": "^4.17.21",
         "notistack": "^3.0.0",
         "plotly.js": "^2.6.0",
@@ -1640,18 +1640,18 @@
       }
     },
     "node_modules/@mui/core-downloads-tracker": {
-      "version": "5.15.18",
-      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.18.tgz",
-      "integrity": "sha512-/9pVk+Al8qxAjwFUADv4BRZgMpZM4m5E+2Q/20qhVPuIJWqKp4Ie4tGExac6zu93rgPTYVQGgu+1vjiT0E+cEw==",
+      "version": "5.15.19",
+      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.19.tgz",
+      "integrity": "sha512-tCHSi/Tomez9ERynFhZRvFO6n9ATyrPs+2N80DMDzp6xDVirbBjEwhPcE+x7Lj+nwYw0SqFkOxyvMP0irnm55w==",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/mui-org"
       }
     },
     "node_modules/@mui/icons-material": {
-      "version": "5.15.18",
-      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.18.tgz",
-      "integrity": "sha512-jGhyw02TSLM0NgW+MDQRLLRUD/K4eN9rlK2pTBTL1OtzyZmQ8nB060zK1wA0b7cVrIiG+zyrRmNAvGWXwm2N9Q==",
+      "version": "5.15.19",
+      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.19.tgz",
+      "integrity": "sha512-RsEiRxA5azN9b8gI7JRqekkgvxQUlitoBOtZglflb8cUDyP12/cP4gRwhb44Ea1/zwwGGjAj66ZJpGHhKfibNA==",
       "dependencies": {
         "@babel/runtime": "^7.23.9"
       },
@@ -1674,13 +1674,13 @@
       }
     },
     "node_modules/@mui/material": {
-      "version": "5.15.18",
-      "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.18.tgz",
-      "integrity": "sha512-n+/dsiqux74fFfcRUJjok+ieNQ7+BEk6/OwX9cLcLvriZrZb+/7Y8+Fd2HlUUbn5N0CDurgAHm0VH1DqyJ9HAw==",
+      "version": "5.15.19",
+      "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.19.tgz",
+      "integrity": "sha512-lp5xQBbcRuxNtjpWU0BWZgIrv2XLUz4RJ0RqFXBdESIsKoGCQZ6P3wwU5ZPuj5TjssNiKv9AlM+vHopRxZhvVQ==",
       "dependencies": {
         "@babel/runtime": "^7.23.9",
         "@mui/base": "5.0.0-beta.40",
-        "@mui/core-downloads-tracker": "^5.15.18",
+        "@mui/core-downloads-tracker": "^5.15.19",
         "@mui/system": "^5.15.15",
         "@mui/types": "^7.2.14",
         "@mui/utils": "^5.15.14",
@@ -1983,9 +1983,9 @@
       }
     },
     "node_modules/@plotly/d3": {
-      "version": "3.8.1",
-      "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.1.tgz",
-      "integrity": "sha512-x49ThEu1FRA00kTso4Jdfyf2byaCPLBGmLjAYQz5OzaPyLUhHesX3/Nfv2OHEhynhdy2UB39DLXq6thYe2L2kg=="
+      "version": "3.8.2",
+      "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.2.tgz",
+      "integrity": "sha512-wvsNmh1GYjyJfyEBPKJLTMzgf2c2bEbSIL50lmqVUi+o1NHaLPi1Lb4v7VxXXJn043BhNyrxUrWI85Q+zmjOVA=="
     },
     "node_modules/@plotly/d3-sankey": {
       "version": "0.7.2",
@@ -3909,9 +3909,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001624",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001624.tgz",
-      "integrity": "sha512-0dWnQG87UevOCPYaOR49CBcLBwoZLpws+k6W37nLjWUhumP1Isusj0p2u+3KhjNloRWK9OKMgjBBzPujQHw4nA==",
+      "version": "1.0.30001625",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz",
+      "integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==",
       "funding": [
         {
           "type": "opencollective",
@@ -4019,9 +4019,9 @@
       }
     },
     "node_modules/chrome-trace-event": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
-      "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
+      "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
       "dev": true,
       "engines": {
         "node": ">=6.0"
@@ -4915,26 +4915,20 @@
       }
     },
     "node_modules/date-fns": {
-      "version": "2.30.0",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
-      "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
-      "dependencies": {
-        "@babel/runtime": "^7.21.0"
-      },
-      "engines": {
-        "node": ">=0.11"
-      },
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+      "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
       "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/date-fns"
+        "type": "github",
+        "url": "https://github.com/sponsors/kossnocorp"
       }
     },
     "node_modules/date-fns-tz": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.1.tgz",
-      "integrity": "sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==",
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz",
+      "integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==",
       "peerDependencies": {
-        "date-fns": "2.x"
+        "date-fns": "^3.0.0"
       }
     },
     "node_modules/debug": {
@@ -5316,9 +5310,9 @@
       "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.783",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz",
-      "integrity": "sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ=="
+      "version": "1.4.786",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.786.tgz",
+      "integrity": "sha512-i/A2UB0sxYViMN0M2zIotQFRIOt1jLuVXudACHBDiJ5gGuAUzf/crZxwlBTdA0O52Hy4CNtTzS7AKRAacs/08Q=="
     },
     "node_modules/element-size": {
       "version": "1.1.1",
@@ -10983,11 +10977,11 @@
       }
     },
     "node_modules/plotly.js": {
-      "version": "2.32.0",
-      "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.32.0.tgz",
-      "integrity": "sha512-QBYyfVFs1XdoXQBq/f7SoiqQD/BEyDA5WwvN1NwY4ZTrTX6GmJ5jE5ydlt1I4K8i5W6H1atgti31jcSYD6StKA==",
+      "version": "2.33.0",
+      "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.33.0.tgz",
+      "integrity": "sha512-pzuf6hSUCaSYmEag2b2DngkHdYMn+U/QMSC/UJOLIS8yd2UwIG1iGUmOR7pqZIS87oKx/+cMoG8aknGytgJKig==",
       "dependencies": {
-        "@plotly/d3": "3.8.1",
+        "@plotly/d3": "3.8.2",
         "@plotly/d3-sankey": "0.7.2",
         "@plotly/d3-sankey-circular": "0.33.1",
         "@plotly/mapbox-gl": "1.13.4",

+ 2 - 2
frontend/taipy-gui/package.json

@@ -11,8 +11,8 @@
     "@mui/x-tree-view": "^7.0.0",
     "apache-arrow": "^14.0.2",
     "axios": "^1.2.0",
-    "date-fns": "^2.30.0",
-    "date-fns-tz": "^2.0.0",
+    "date-fns": "^3.6.0",
+    "date-fns-tz": "^3.1.3",
     "lodash": "^4.17.21",
     "notistack": "^3.0.0",
     "plotly.js": "^2.6.0",

+ 1 - 1
frontend/taipy-gui/src/components/Router.tsx

@@ -18,7 +18,7 @@ import CircularProgress from "@mui/material/CircularProgress";
 import CssBaseline from "@mui/material/CssBaseline";
 import { ThemeProvider } from "@mui/system";
 import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
-import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
+import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
 import { SnackbarProvider } from "notistack";
 import { HelmetProvider } from "react-helmet-async";
 import { BrowserRouter, Route, Routes } from "react-router-dom";

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

@@ -17,7 +17,7 @@ import userEvent from "@testing-library/user-event";
 import "@testing-library/jest-dom";
 
 import { LocalizationProvider } from "@mui/x-date-pickers";
-import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
+import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
 
 import DateRange from "./DateRange";
 import { TaipyContext } from "../../context/taipyContext";

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

@@ -17,7 +17,7 @@ import userEvent from "@testing-library/user-event";
 import "@testing-library/jest-dom";
 
 import { LocalizationProvider } from "@mui/x-date-pickers";
-import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
+import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
 
 import DateSelector from "./DateSelector";
 import { TaipyContext } from "../../context/taipyContext";

+ 64 - 6
frontend/taipy-gui/src/components/Taipy/Selector.spec.tsx

@@ -177,8 +177,10 @@ describe("Selector Component", () => {
             const { getByTestId } = render(<Selector lov={lov} dropdown={true} />);
             getByTestId("ArrowDropDownIcon");
         });
-        it("displays as an simple input with default value", async () => {
-            const { getByText, getByTestId, queryAllByTestId } = render(<Selector lov={lov} defaultValue="id1" dropdown={true} />);
+        it("displays as a simple input with default value", async () => {
+            const { getByText, getByTestId, queryAllByTestId } = render(
+                <Selector lov={lov} defaultValue="id1" dropdown={true} />
+            );
             getByText("Item 1");
             expect(queryAllByTestId("CancelIcon")).toHaveLength(0);
             getByTestId("ArrowDropDownIcon");
@@ -202,10 +204,10 @@ describe("Selector Component", () => {
             const elt = getByText("Item 1");
             expect(elt.parentElement).not.toHaveClass("Mui-disabled");
         });
-            it("opens a dropdown on click", async () => {
+        it("opens a dropdown on click", async () => {
             const { getByText, getByRole, queryAllByRole } = render(<Selector lov={lov} dropdown={true} />);
             const butElt = getByRole("combobox");
-            expect(butElt).toBeInTheDocument()
+            expect(butElt).toBeInTheDocument();
             await userEvent.click(butElt);
             getByRole("listbox");
             const elt = getByText("Item 2");
@@ -214,6 +216,58 @@ describe("Selector Component", () => {
         });
     });
 
+    describe("Selector Component with dropdown + filter", () => {
+        //dropdown
+        it("displays as an empty control with arrow", async () => {
+            const { getByTestId } = render(<Selector lov={lov} dropdown={true} filter={true} />);
+            getByTestId("ArrowDropDownIcon");
+        });
+        it("displays as a simple input with default value", async () => {
+            const { getByRole, getByTestId, queryAllByTestId } = render(
+                <Selector lov={lov} defaultValue="id1" dropdown={true} filter={true} />
+            );
+            expect(getByRole("combobox")).toHaveValue("Item 1");
+            expect(queryAllByTestId("CancelIcon")).toHaveLength(0);
+            getByTestId("ArrowDropDownIcon");
+        });
+        it("displays a delete icon when multiple", async () => {
+            const { getByTestId } = render(
+                <Selector lov={lov} defaultValue="id1" dropdown={true} multiple={true} filter={true} />
+            );
+            getByTestId("CancelIcon");
+        });
+        it("is disabled", async () => {
+            const { getByRole } = render(
+                <Selector lov={lov} defaultValue="id1" active={false} dropdown={true} filter={true} />
+            );
+            const elt = getByRole("combobox");
+            expect(elt.parentElement).toHaveClass("Mui-disabled");
+        });
+        it("is enabled by default", async () => {
+            const { getByRole } = render(<Selector lov={lov} defaultValue="id1" dropdown={true} filter={true} />);
+            const elt = getByRole("combobox");
+            expect(elt.parentElement).not.toHaveClass("Mui-disabled");
+        });
+        it("is enabled by active", async () => {
+            const { getByRole } = render(
+                <Selector defaultValue="id1" lov={lov} active={true} dropdown={true} filter={true} />
+            );
+            const elt = getByRole("combobox");
+            expect(elt.parentElement).not.toHaveClass("Mui-disabled");
+        });
+        it("opens a dropdown on click", async () => {
+            const { getByText, getByRole, queryAllByRole } = render(
+                <Selector lov={lov} dropdown={true} filter={true} />
+            );
+            const butElt = getByRole("combobox");
+            expect(butElt).toBeInTheDocument();
+            await userEvent.click(butElt);
+            getByRole("listbox");
+            const elt = getByText("Item 2");
+            await userEvent.click(elt);
+            expect(queryAllByRole("listbox")).toHaveLength(0);
+        });
+    });
     describe("Selector Component radio mode", () => {
         //dropdown
         it("displays a list of unselected radios", async () => {
@@ -228,7 +282,9 @@ describe("Selector Component", () => {
             expect(elt.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
         });
         it("selects on click", async () => {
-            const { getByText, getByRole, queryAllByRole } = render(<Selector lov={lov} defaultValue="id1"  mode="radio" />);
+            const { getByText, getByRole, queryAllByRole } = render(
+                <Selector lov={lov} defaultValue="id1" mode="radio" />
+            );
             const elt = getByText("Item 2");
             expect(elt.parentElement?.querySelector("span.Mui-checked")).toBeNull();
             await userEvent.click(elt);
@@ -250,7 +306,9 @@ describe("Selector Component", () => {
             expect(elt.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
         });
         it("selects on click", async () => {
-            const { getByText, getByRole, queryAllByRole } = render(<Selector lov={lov} defaultValue="id1"  mode="check" />);
+            const { getByText, getByRole, queryAllByRole } = render(
+                <Selector lov={lov} defaultValue="id1" mode="check" />
+            );
             const elt1 = getByText("Item 1");
             expect(elt1.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
             const elt2 = getByText("Item 2");

+ 149 - 64
frontend/taipy-gui/src/components/Taipy/Selector.tsx

@@ -11,7 +11,18 @@
  * specific language governing permissions and limitations under the License.
  */
 
-import React, { useState, useCallback, useEffect, useMemo, CSSProperties, MouseEvent, ChangeEvent } from "react";
+import React, {
+    useState,
+    useCallback,
+    useEffect,
+    useMemo,
+    CSSProperties,
+    MouseEvent,
+    ChangeEvent,
+    SyntheticEvent,
+    HTMLAttributes,
+} from "react";
+import Autocomplete from "@mui/material/Autocomplete";
 import Avatar from "@mui/material/Avatar";
 import Box from "@mui/material/Box";
 import Checkbox from "@mui/material/Checkbox";
@@ -33,6 +44,7 @@ import Tooltip from "@mui/material/Tooltip";
 import Radio from "@mui/material/Radio";
 import RadioGroup from "@mui/material/RadioGroup";
 import Select, { SelectChangeEvent } from "@mui/material/Select";
+import TextField from "@mui/material/TextField";
 import { Theme, useTheme } from "@mui/material";
 
 import { doNotPropagateEvent, getSuffixedClassNames, getUpdateVar } from "./utils";
@@ -46,6 +58,7 @@ import {
     useModule,
 } from "../../utils/hooks";
 import { Icon } from "../../utils/icon";
+import { LovItem } from "../../utils/lov";
 
 const MultipleItem = ({ value, clickHandler, selectedValue, item, disabled }: ItemProps) => (
     <ListItemButton onClick={clickHandler} data-id={value} dense disabled={disabled}>
@@ -82,6 +95,26 @@ const getStyles = (id: string, ids: readonly string[], theme: Theme) => ({
     fontWeight: ids.indexOf(id) === -1 ? theme.typography.fontWeightRegular : theme.typography.fontWeightMedium,
 });
 
+const getOptionLabel = (option: LovItem) => (typeof option.item === "string" ? option.item : option.item?.text) || "";
+const getOptionKey = (option: LovItem) => "" + option.id;
+const isOptionEqualToValue = (option: LovItem, value: LovItem) => option.id == value.id;
+const renderOption = (props: HTMLAttributes<HTMLLIElement>, option: LovItem) => (
+    <li {...props}>{typeof option.item === "string" ? option.item : <LovImage item={option.item} />}</li>
+);
+
+const getLovItemsFromStr = (value: string | string[] | undefined, lovList: LovItem[], multiple: boolean) => {
+    const val = multiple
+        ? Array.isArray(value)
+            ? value
+            : [value]
+        : Array.isArray(value) && value.length
+        ? value[0]
+        : value;
+    return Array.isArray(val)
+        ? (val.map((v) => lovList.find((item) => item.id == "" + v)).filter((i) => i) as LovItem[])
+        : (val && lovList.find((item) => item.id == "" + val)) || null;
+};
+
 const renderBoxSx = {
     display: "flex",
     flexWrap: "wrap",
@@ -137,16 +170,17 @@ const Selector = (props: SelTreeProps) => {
         if (height !== undefined) {
             sx = { ...sx, maxHeight: height };
         }
-        if (width !== undefined) {
-            // sx = { ...sx, maxWidth: width };
-        }
         return sx;
-    }, [height, width]);
-    const controlSx = useMemo(() => ({ my: 1, mx: 0, width: width, display: "flex" }), [width]);
+    }, [height]);
+    const controlSx = useMemo(
+        () => ({ my: 1, mx: 0, maxWidth: width, display: "flex", "& .MuiFormControl-root": { maxWidth: "unset" } }),
+        [width]
+    );
 
     useEffect(() => {
         if (value !== undefined && value !== null) {
             setSelectedValue(Array.isArray(value) ? value.map((v) => "" + v) : ["" + value]);
+            setAutoValue(getLovItemsFromStr(value, lovList, multiple));
         } else if (defaultValue) {
             let parsedValue;
             try {
@@ -155,8 +189,9 @@ const Selector = (props: SelTreeProps) => {
                 parsedValue = defaultValue;
             }
             setSelectedValue(Array.isArray(parsedValue) ? parsedValue : [parsedValue]);
+            setAutoValue(getLovItemsFromStr(parsedValue, lovList, multiple));
         }
-    }, [defaultValue, value]);
+    }, [defaultValue, value, lovList, multiple]);
 
     const selectHandler = useCallback(
         (key: string) => {
@@ -238,12 +273,31 @@ const Selector = (props: SelTreeProps) => {
         [dispatch, updateVarName, propagate, updateVars, valueById, props.onChange, module]
     );
 
+    const [autoValue, setAutoValue] = useState<LovItem | LovItem[] | null>(() => (multiple ? [] : null));
+    const handleAutoChange = useCallback(
+        (e: SyntheticEvent, sel: LovItem | LovItem[] | null) => {
+            setAutoValue(sel);
+            setSelectedValue(Array.isArray(sel) ? sel.map((item) => item.id) : sel ? [sel.id] : []);
+            dispatch(
+                createSendUpdateAction(
+                    updateVarName,
+                    Array.isArray(sel) ? sel.map((item) => item.id) : sel?.id,
+                    module,
+                    props.onChange,
+                    propagate,
+                    valueById ? undefined : getUpdateVar(updateVars, "lov")
+                )
+            );
+        },
+        [dispatch, updateVarName, propagate, updateVars, valueById, props.onChange, module]
+    );
+
     const handleDelete = useCallback(
         (e: React.SyntheticEvent) => {
             const id = e.currentTarget?.parentElement?.getAttribute("data-id");
             id &&
-                setSelectedValue((vals) => {
-                    const keys = vals.filter((valId) => valId !== id);
+                setSelectedValue((oldKeys) => {
+                    const keys = oldKeys.filter((valId) => valId !== id);
                     dispatch(
                         createSendUpdateAction(
                             updateVarName,
@@ -263,20 +317,12 @@ const Selector = (props: SelTreeProps) => {
     const handleInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => setSearchValue(e.target.value), []);
 
     const dropdownValue = ((dropdown || isRadio) &&
-        (multiple ? selectedValue : selectedValue.length > 0 ? selectedValue[0] : "")) as string[];
+        (multiple ? selectedValue : selectedValue.length ? selectedValue[0] : "")) as string[];
 
-    return (
+    return isRadio || isCheck ? (
         <FormControl sx={controlSx} className={className}>
-            {props.label ? (
-                isRadio || isCheck ? (
-                    <FormLabel>{props.label}</FormLabel>
-                ) : (
-                    <InputLabel disableAnimation className={!dropdown ? "static-label" : undefined}>
-                        {props.label}
-                    </InputLabel>
-                )
-            ) : null}
-            <Tooltip title={hover || ""} placement={dropdown ? "top" : undefined}>
+            {props.label ? <FormLabel>{props.label}</FormLabel> : null}
+            <Tooltip title={hover || ""}>
                 {isRadio ? (
                     <RadioGroup
                         value={dropdownValue}
@@ -296,7 +342,7 @@ const Selector = (props: SelTreeProps) => {
                             />
                         ))}
                     </RadioGroup>
-                ) : isCheck ? (
+                ) : (
                     <FormGroup className={getSuffixedClassNames(className, "-check-group")}>
                         {lovList.map((item) => (
                             <FormControlLabel
@@ -316,7 +362,32 @@ const Selector = (props: SelTreeProps) => {
                             ></FormControlLabel>
                         ))}
                     </FormGroup>
-                ) : dropdown ? (
+                )}
+            </Tooltip>
+        </FormControl>
+    ) : dropdown ? (
+        filter ? (
+            <Tooltip title={hover || ""} placement="top">
+                <Autocomplete
+                    id={id}
+                    disabled={!active}
+                    multiple={multiple}
+                    options={lovList}
+                    value={autoValue}
+                    onChange={handleAutoChange}
+                    getOptionLabel={getOptionLabel}
+                    getOptionKey={getOptionKey}
+                    isOptionEqualToValue={isOptionEqualToValue}
+                    sx={controlSx}
+                    className={className}
+                    renderInput={(params) => <TextField {...params} label={props.label} margin="dense" />}
+                    renderOption={renderOption}
+                />
+            </Tooltip>
+        ) : (
+            <FormControl sx={controlSx} className={className}>
+                {props.label ? <InputLabel disableAnimation>{props.label}</InputLabel> : null}
+                <Tooltip title={hover || ""} placement="top">
                     <Select
                         id={id}
                         multiple={multiple}
@@ -364,51 +435,65 @@ const Selector = (props: SelTreeProps) => {
                         MenuProps={getMenuProps(height)}
                     >
                         {lovList.map((item) => (
-                            <MenuItem key={item.id} value={item.id} style={getStyles(item.id, selectedValue, theme)} disabled={item.id === null}>
+                            <MenuItem
+                                key={item.id}
+                                value={item.id}
+                                style={getStyles(item.id, selectedValue, theme)}
+                                disabled={item.id === null}
+                            >
                                 {typeof item.item === "string" ? item.item : <LovImage item={item.item as Icon} />}
                             </MenuItem>
                         ))}
                     </Select>
-                ) : (
-                    <Paper sx={paperSx}>
-                        {filter && (
-                            <Box>
-                                <OutlinedInput
-                                    margin="dense"
-                                    placeholder="Search field"
-                                    value={searchValue}
-                                    onChange={handleInput}
-                                    disabled={!active}
-                                />
-                            </Box>
-                        )}
-                        <List sx={listSx} id={id}>
-                            {lovList
-                                .filter((elt) => showItem(elt, searchValue))
-                                .map((elt) =>
-                                    multiple ? (
-                                        <MultipleItem
-                                            key={elt.id}
-                                            value={elt.id}
-                                            item={elt.item}
-                                            selectedValue={selectedValue}
-                                            clickHandler={clickHandler}
-                                            disabled={!active}
-                                        />
-                                    ) : (
-                                        <SingleItem
-                                            key={elt.id}
-                                            value={elt.id}
-                                            item={elt.item}
-                                            selectedValue={selectedValue}
-                                            clickHandler={clickHandler}
-                                            disabled={!active}
-                                        />
-                                    )
-                                )}
-                        </List>
-                    </Paper>
-                )}
+                </Tooltip>
+            </FormControl>
+        )
+    ) : (
+        <FormControl sx={controlSx} className={className}>
+            {props.label ? (
+                <InputLabel disableAnimation className="static-label">
+                    {props.label}
+                </InputLabel>
+            ) : null}
+            <Tooltip title={hover || ""}>
+                <Paper sx={paperSx}>
+                    {filter && (
+                        <Box>
+                            <OutlinedInput
+                                margin="dense"
+                                placeholder="Search field"
+                                value={searchValue}
+                                onChange={handleInput}
+                                disabled={!active}
+                            />
+                        </Box>
+                    )}
+                    <List sx={listSx} id={id}>
+                        {lovList
+                            .filter((elt) => showItem(elt, searchValue))
+                            .map((elt) =>
+                                multiple ? (
+                                    <MultipleItem
+                                        key={elt.id}
+                                        value={elt.id}
+                                        item={elt.item}
+                                        selectedValue={selectedValue}
+                                        clickHandler={clickHandler}
+                                        disabled={!active}
+                                    />
+                                ) : (
+                                    <SingleItem
+                                        key={elt.id}
+                                        value={elt.id}
+                                        item={elt.item}
+                                        selectedValue={selectedValue}
+                                        clickHandler={clickHandler}
+                                        disabled={!active}
+                                    />
+                                )
+                            )}
+                    </List>
+                </Paper>
             </Tooltip>
         </FormControl>
     );

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

@@ -28,7 +28,7 @@ import Select, { SelectChangeEvent } from "@mui/material/Select";
 import TextField from "@mui/material/TextField";
 import Tooltip from "@mui/material/Tooltip";
 import { DateField, LocalizationProvider } from "@mui/x-date-pickers";
-import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
+import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
 
 import { ColumnDesc, defaultDateFormat, getsortByIndex, iconInRowSx } from "./tableUtils";
 import { getDateTime, getTypeFromDf } from "../../utils";

+ 2 - 2
frontend/taipy-gui/src/utils/index.ts

@@ -11,7 +11,7 @@
  * specific language governing permissions and limitations under the License.
  */
 
-import { utcToZonedTime, getTimezoneOffset, formatInTimeZone } from "date-fns-tz";
+import { toZonedTime, getTimezoneOffset, formatInTimeZone } from "date-fns-tz";
 import { format } from "date-fns";
 import { sprintf } from "sprintf-js";
 import { FormatConfig } from "../context/taipyReducers";
@@ -92,7 +92,7 @@ export const getDateTime = (value: string | null | undefined, tz?: string, withT
         return null;
     }
     try {
-        return tz && tz !== "Etc/Unknown" && withTime ? utcToZonedTime(value, tz) : new Date(value);
+        return tz && tz !== "Etc/Unknown" && withTime ? toZonedTime(value, tz) : new Date(value);
     } catch (e) {
         return null;
     }

+ 28 - 34
frontend/taipy/package-lock.json

@@ -16,7 +16,7 @@
         "@mui/x-date-pickers": "^7.0.0",
         "@mui/x-tree-view": "^7.0.0",
         "@projectstorm/react-diagrams": "^7.0.2",
-        "date-fns": "^2.29.3",
+        "date-fns": "^3.6.0",
         "fast-deep-equal": "^3.1.3",
         "formik": "^2.2.9",
         "react": "^18.2.0",
@@ -644,18 +644,18 @@
       }
     },
     "node_modules/@mui/core-downloads-tracker": {
-      "version": "5.15.18",
-      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.18.tgz",
-      "integrity": "sha512-/9pVk+Al8qxAjwFUADv4BRZgMpZM4m5E+2Q/20qhVPuIJWqKp4Ie4tGExac6zu93rgPTYVQGgu+1vjiT0E+cEw==",
+      "version": "5.15.19",
+      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.19.tgz",
+      "integrity": "sha512-tCHSi/Tomez9ERynFhZRvFO6n9ATyrPs+2N80DMDzp6xDVirbBjEwhPcE+x7Lj+nwYw0SqFkOxyvMP0irnm55w==",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/mui-org"
       }
     },
     "node_modules/@mui/icons-material": {
-      "version": "5.15.18",
-      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.18.tgz",
-      "integrity": "sha512-jGhyw02TSLM0NgW+MDQRLLRUD/K4eN9rlK2pTBTL1OtzyZmQ8nB060zK1wA0b7cVrIiG+zyrRmNAvGWXwm2N9Q==",
+      "version": "5.15.19",
+      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.19.tgz",
+      "integrity": "sha512-RsEiRxA5azN9b8gI7JRqekkgvxQUlitoBOtZglflb8cUDyP12/cP4gRwhb44Ea1/zwwGGjAj66ZJpGHhKfibNA==",
       "dependencies": {
         "@babel/runtime": "^7.23.9"
       },
@@ -678,13 +678,13 @@
       }
     },
     "node_modules/@mui/material": {
-      "version": "5.15.18",
-      "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.18.tgz",
-      "integrity": "sha512-n+/dsiqux74fFfcRUJjok+ieNQ7+BEk6/OwX9cLcLvriZrZb+/7Y8+Fd2HlUUbn5N0CDurgAHm0VH1DqyJ9HAw==",
+      "version": "5.15.19",
+      "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.19.tgz",
+      "integrity": "sha512-lp5xQBbcRuxNtjpWU0BWZgIrv2XLUz4RJ0RqFXBdESIsKoGCQZ6P3wwU5ZPuj5TjssNiKv9AlM+vHopRxZhvVQ==",
       "dependencies": {
         "@babel/runtime": "^7.23.9",
         "@mui/base": "5.0.0-beta.40",
-        "@mui/core-downloads-tracker": "^5.15.18",
+        "@mui/core-downloads-tracker": "^5.15.19",
         "@mui/system": "^5.15.15",
         "@mui/types": "^7.2.14",
         "@mui/utils": "^5.15.14",
@@ -1141,9 +1141,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.12.12",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
-      "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
+      "version": "20.12.13",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.13.tgz",
+      "integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==",
       "dev": true,
       "dependencies": {
         "undici-types": "~5.26.4"
@@ -1978,9 +1978,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001624",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001624.tgz",
-      "integrity": "sha512-0dWnQG87UevOCPYaOR49CBcLBwoZLpws+k6W37nLjWUhumP1Isusj0p2u+3KhjNloRWK9OKMgjBBzPujQHw4nA==",
+      "version": "1.0.30001625",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz",
+      "integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==",
       "dev": true,
       "funding": [
         {
@@ -2020,9 +2020,9 @@
       "dev": true
     },
     "node_modules/chrome-trace-event": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
-      "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
+      "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
       "dev": true,
       "engines": {
         "node": ">=6.0"
@@ -2201,18 +2201,12 @@
       }
     },
     "node_modules/date-fns": {
-      "version": "2.30.0",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
-      "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
-      "dependencies": {
-        "@babel/runtime": "^7.21.0"
-      },
-      "engines": {
-        "node": ">=0.11"
-      },
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+      "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
       "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/date-fns"
+        "type": "github",
+        "url": "https://github.com/sponsors/kossnocorp"
       }
     },
     "node_modules/debug": {
@@ -2326,9 +2320,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.783",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz",
-      "integrity": "sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==",
+      "version": "1.4.786",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.786.tgz",
+      "integrity": "sha512-i/A2UB0sxYViMN0M2zIotQFRIOt1jLuVXudACHBDiJ5gGuAUzf/crZxwlBTdA0O52Hy4CNtTzS7AKRAacs/08Q==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {

+ 1 - 1
frontend/taipy/package.json

@@ -26,7 +26,7 @@
     "@mui/x-date-pickers": "^7.0.0",
     "@mui/x-tree-view": "^7.0.0",
     "@projectstorm/react-diagrams": "^7.0.2",
-    "date-fns": "^2.29.3",
+    "date-fns": "^3.6.0",
     "fast-deep-equal": "^3.1.3",
     "formik": "^2.2.9",
     "react": "^18.2.0",

+ 1 - 1
frontend/taipy/src/DataNodeViewer.tsx

@@ -49,7 +49,7 @@ import LockOutlined from "@mui/icons-material/LockOutlined";
 import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
 import { BaseDateTimePickerSlotProps } from "@mui/x-date-pickers/DateTimePicker/shared";
 import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
-import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
+import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
 import { format } from "date-fns";
 import deepEqual from "fast-deep-equal/es6";
 

+ 1 - 1
frontend/taipy/src/ScenarioSelector.tsx

@@ -32,7 +32,7 @@ import TextField from "@mui/material/TextField";
 import Typography from "@mui/material/Typography";
 import { Close, DeleteOutline, Add, EditOutlined } from "@mui/icons-material";
 import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers";
-import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
+import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
 import { useFormik } from "formik";
 
 import {

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

@@ -827,6 +827,8 @@ class _Builder:
                             value = float(value)
                     if isinstance(value, (int, float)):
                         return self.__set_react_attribute(_to_camel_case(var_name), value)
+                if isinstance(value, (datetime, date, time)):
+                    value = _date_to_string(value)
                 self.set_attribute(_to_camel_case(var_name), value)
         return self
 

+ 19 - 19
taipy/gui/viselements.json

@@ -1030,10 +1030,21 @@
                 ],
                 "properties": [
                     {
-                        "name": "filter",
+                        "name": "label",
+                        "type": "str",
+                        "default_value": "None",
+                        "doc": "The label associated with the selector when in dropdown mode."
+                    },
+                    {
+                        "name": "mode",
+                        "type": "str",
+                        "doc": "Define the way the selector is displayed:<ul><li>&quot;radio&quot;: list of radio buttons</li><li>&quot;check&quot;: list of check buttons</li><li>any other value: selector as usual."
+                    },
+                    {
+                        "name": "dropdown",
                         "type": "bool",
                         "default_value": "False",
-                        "doc": "If True, this control is combined with a filter input area."
+                        "doc": "If True, the list of items is shown in a dropdown menu.<br/><br/>You cannot use the filter in that situation."
                     },
                     {
                         "name": "multiple",
@@ -1041,6 +1052,12 @@
                         "default_value": "False",
                         "doc": "If True, the user can select multiple items."
                     },
+                    {
+                        "name": "filter",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "If True, this control is combined with a filter input area."
+                    },
                     {
                         "name": "width",
                         "type": "str|int",
@@ -1051,23 +1068,6 @@
                         "name": "height",
                         "type": "str|int",
                         "doc": "The height, in CSS units, of this element."
-                    },
-                    {
-                        "name": "dropdown",
-                        "type": "bool",
-                        "default_value": "False",
-                        "doc": "If True, the list of items is shown in a dropdown menu.<br/><br/>You cannot use the filter in that situation."
-                    },
-                    {
-                        "name": "label",
-                        "type": "str",
-                        "default_value": "None",
-                        "doc": "The label associated with the selector when in dropdown mode."
-                    },
-                    {
-                        "name": "mode",
-                        "type": "str",
-                        "doc": "Define the way the selector is displayed:<ul><li>&quot;radio&quot;: list of radio buttons</li><li>&quot;check&quot;: list of check buttons</li><li>any other value: selector as usual."
                     }
                 ]
             }