Browse Source

Enhance Scenario Filters (#1573)

* Enhance Scenario Filters
resolves #1511

* sort imports

* unused import

* FilterDesc impact on reducer test

* FilterDesc change

* raise when a lambda
supports function module

* support also a single filter

* doc

* fab's comment

* rename

* rename

* rename

* rename

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 10 months ago
parent
commit
320fbf40c8

+ 64 - 88
frontend/taipy-gui/package-lock.json

@@ -2403,9 +2403,9 @@
       }
     },
     "node_modules/@shikijs/core": {
-      "version": "1.11.0",
-      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.11.0.tgz",
-      "integrity": "sha512-VbEhDAhT/2ozO0TPr5/ZQBO/NWLqtk4ZiBf6NplYpF38mKjNfMMied5fNEfIfYfN+cdKvhDB4VMcKvG/g9c3zg==",
+      "version": "1.11.1",
+      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.11.1.tgz",
+      "integrity": "sha512-Qsn8h15SWgv5TDRoDmiHNzdQO2BxDe86Yq6vIHf5T0cCvmfmccJKIzHtep8bQO9HMBZYCtCBzaXdd1MnxZBPSg==",
       "dev": true,
       "dependencies": {
         "@types/hast": "^3.0.4"
@@ -2549,9 +2549,9 @@
       }
     },
     "node_modules/@testing-library/jest-dom": {
-      "version": "6.4.6",
-      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz",
-      "integrity": "sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w==",
+      "version": "6.4.8",
+      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz",
+      "integrity": "sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==",
       "dev": true,
       "dependencies": {
         "@adobe/css-tools": "^4.4.0",
@@ -2567,30 +2567,6 @@
         "node": ">=14",
         "npm": ">=6",
         "yarn": ">=1"
-      },
-      "peerDependencies": {
-        "@jest/globals": ">= 28",
-        "@types/bun": "latest",
-        "@types/jest": ">= 28",
-        "jest": ">= 28",
-        "vitest": ">= 0.32"
-      },
-      "peerDependenciesMeta": {
-        "@jest/globals": {
-          "optional": true
-        },
-        "@types/bun": {
-          "optional": true
-        },
-        "@types/jest": {
-          "optional": true
-        },
-        "jest": {
-          "optional": true
-        },
-        "vitest": {
-          "optional": true
-        }
       }
     },
     "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": {
@@ -2843,9 +2819,9 @@
       }
     },
     "node_modules/@types/eslint": {
-      "version": "8.56.10",
-      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
-      "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
+      "version": "8.56.11",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz",
+      "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==",
       "dev": true,
       "dependencies": {
         "@types/estree": "*",
@@ -3166,16 +3142,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz",
-      "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz",
+      "integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "7.16.1",
-        "@typescript-eslint/type-utils": "7.16.1",
-        "@typescript-eslint/utils": "7.16.1",
-        "@typescript-eslint/visitor-keys": "7.16.1",
+        "@typescript-eslint/scope-manager": "7.17.0",
+        "@typescript-eslint/type-utils": "7.17.0",
+        "@typescript-eslint/utils": "7.17.0",
+        "@typescript-eslint/visitor-keys": "7.17.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -3199,15 +3175,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz",
-      "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz",
+      "integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "7.16.1",
-        "@typescript-eslint/types": "7.16.1",
-        "@typescript-eslint/typescript-estree": "7.16.1",
-        "@typescript-eslint/visitor-keys": "7.16.1",
+        "@typescript-eslint/scope-manager": "7.17.0",
+        "@typescript-eslint/types": "7.17.0",
+        "@typescript-eslint/typescript-estree": "7.17.0",
+        "@typescript-eslint/visitor-keys": "7.17.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -3227,13 +3203,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz",
-      "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz",
+      "integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.16.1",
-        "@typescript-eslint/visitor-keys": "7.16.1"
+        "@typescript-eslint/types": "7.17.0",
+        "@typescript-eslint/visitor-keys": "7.17.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -3244,13 +3220,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz",
-      "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz",
+      "integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "7.16.1",
-        "@typescript-eslint/utils": "7.16.1",
+        "@typescript-eslint/typescript-estree": "7.17.0",
+        "@typescript-eslint/utils": "7.17.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -3271,9 +3247,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz",
-      "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz",
+      "integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -3284,13 +3260,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz",
-      "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz",
+      "integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.16.1",
-        "@typescript-eslint/visitor-keys": "7.16.1",
+        "@typescript-eslint/types": "7.17.0",
+        "@typescript-eslint/visitor-keys": "7.17.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -3312,15 +3288,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz",
-      "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz",
+      "integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "7.16.1",
-        "@typescript-eslint/types": "7.16.1",
-        "@typescript-eslint/typescript-estree": "7.16.1"
+        "@typescript-eslint/scope-manager": "7.17.0",
+        "@typescript-eslint/types": "7.17.0",
+        "@typescript-eslint/typescript-estree": "7.17.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -3334,12 +3310,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz",
-      "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz",
+      "integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.16.1",
+        "@typescript-eslint/types": "7.17.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -6017,9 +5993,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.832",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.832.tgz",
-      "integrity": "sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA=="
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz",
+      "integrity": "sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA=="
     },
     "node_modules/element-size": {
       "version": "1.1.1",
@@ -14140,12 +14116,12 @@
       }
     },
     "node_modules/shiki": {
-      "version": "1.11.0",
-      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.11.0.tgz",
-      "integrity": "sha512-NqH/O1zRHvnuk/WfSL6b7+DtI7/kkMMSQGlZhm9DyzSU+SoIHhaw/fBZMr+zp9R8KjdIzkk3JKSC6hORuGDyng==",
+      "version": "1.11.1",
+      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.11.1.tgz",
+      "integrity": "sha512-VHD3Q0EBXaaa245jqayBe5zQyMQUdXBFjmGr9MpDaDpAKRMYn7Ff00DM5MLk26UyKjnml3yQ0O2HNX7PtYVNFQ==",
       "dev": true,
       "dependencies": {
-        "@shikijs/core": "1.11.0",
+        "@shikijs/core": "1.11.1",
         "@types/hast": "^3.0.4"
       }
     },
@@ -15355,9 +15331,9 @@
       }
     },
     "node_modules/typedoc-plugin-markdown": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.1.tgz",
-      "integrity": "sha512-7hQt/1WaW/VI4+x3sxwcCGsEylP1E1GvF6OTTELK5sfTEp6AeK+83jkCOgZGp1pI2DiOammMYQMnxxOny9TKsQ==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.2.tgz",
+      "integrity": "sha512-4Amnhjiw4L9aN5yBn6Ryh5WZr+uW41e6IU3EuQCNcVWgHQC+tlNIbbQMKVYAb33ES7yaM01dAXGS4BdJtQi7mA==",
       "dev": true,
       "engines": {
         "node": ">= 18"
@@ -15379,9 +15355,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.5.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
-      "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+      "version": "5.5.4",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+      "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
       "dev": true,
       "bin": {
         "tsc": "bin/tsc",

+ 1 - 0
frontend/taipy-gui/packaging/taipy-gui.d.ts

@@ -121,6 +121,7 @@ export interface FilterDesc {
     col: string;
     action: string;
     value: string | number | boolean | Date;
+    type: string;
 }
 export interface TableFilterProps {
     columns: Record<string, ColumnDesc>;

+ 17 - 5
frontend/taipy-gui/src/components/Taipy/TableFilter.spec.tsx

@@ -12,7 +12,7 @@
  */
 
 import React from "react";
-import { getByTitle, render } from "@testing-library/react";
+import { render } from "@testing-library/react";
 import "@testing-library/jest-dom";
 import userEvent from "@testing-library/user-event";
 
@@ -140,7 +140,7 @@ describe("Table Filter Component", () => {
     });
     it("behaves on date column", async () => {
         const { getByTestId, getAllByTestId, findByRole, getByText, getByPlaceholderText } = render(
-                <TableFilter columns={tableColumns} colsOrder={colsOrder} onValidate={jest.fn()} filteredCount={0} />
+            <TableFilter columns={tableColumns} colsOrder={colsOrder} onValidate={jest.fn()} filteredCount={0} />
         );
         const elt = getByTestId("FilterListIcon");
         await userEvent.click(elt);
@@ -155,7 +155,7 @@ describe("Table Filter Component", () => {
         const validate = getByTestId("CheckIcon").parentElement;
         expect(validate).toBeDisabled();
         const input = getByPlaceholderText("YYYY/MM/DD");
-        await userEvent.type(input, "{ArrowLeft}{ArrowLeft}{ArrowLeft}2020/11/11", {delay: 1});
+        await userEvent.type(input, "{ArrowLeft}{ArrowLeft}{ArrowLeft}2020/11/11", { delay: 1 });
         expect(validate).not.toBeDisabled();
     });
     it("adds a row on validation", async () => {
@@ -212,7 +212,13 @@ describe("Table Filter Component", () => {
     it("reset filters", async () => {
         const onValidate = jest.fn();
         const { getAllByTestId, getByTestId } = render(
-            <TableFilter columns={tableColumns} colsOrder={colsOrder} onValidate={onValidate} appliedFilters={[{col: "StringCol", action: "==", value: ""}]} filteredCount={0} />
+            <TableFilter
+                columns={tableColumns}
+                colsOrder={colsOrder}
+                onValidate={onValidate}
+                appliedFilters={[{ col: "StringCol", action: "==", value: "", type: "" }]}
+                filteredCount={0}
+            />
         );
         const elt = getByTestId("FilterListIcon");
         await userEvent.click(elt);
@@ -225,7 +231,13 @@ describe("Table Filter Component", () => {
     });
     it("ignores unapplicable filters", async () => {
         const { getAllByTestId, getByTestId } = render(
-            <TableFilter columns={tableColumns} colsOrder={colsOrder} onValidate={jest.fn()} appliedFilters={[{col: "unknown col", action: "==", value: ""}]} filteredCount={0} />
+            <TableFilter
+                columns={tableColumns}
+                colsOrder={colsOrder}
+                onValidate={jest.fn()}
+                appliedFilters={[{ col: "unknown col", action: "==", value: "", type: "" }]}
+                filteredCount={0}
+            />
         );
         const elt = getByTestId("FilterListIcon");
         await userEvent.click(elt);

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

@@ -38,6 +38,7 @@ export interface FilterDesc {
     col: string;
     action: string;
     value: string | number | boolean | Date;
+    type: string;
 }
 
 interface TableFilterProps {
@@ -118,6 +119,7 @@ const getFilterDesc = (columns: Record<string, ColumnDesc>, colId?: string, act?
                             ? getDateTime(val)
                             : val
                         : val,
+                type: colType,
             } as FilterDesc;
         } catch (e) {
             console.info("could not parse value ", val, e);

+ 22 - 21
frontend/taipy-gui/src/context/taipyReducers.spec.ts

@@ -64,7 +64,7 @@ const sendWsMessageSpy = jest.spyOn(wsUtils, "sendWsMessage");
 describe("reducer", () => {
     it("store socket connected", async () => {
         expect(
-            taipyReducer({ ...INITIAL_STATE }, { type: "SOCKET_CONNECTED" } as TaipyBaseAction).isSocketConnected,
+            taipyReducer({ ...INITIAL_STATE }, { type: "SOCKET_CONNECTED" } as TaipyBaseAction).isSocketConnected
         ).toBeDefined();
     });
     it("returns update", async () => {
@@ -73,7 +73,7 @@ describe("reducer", () => {
                 type: "UPDATE",
                 name: "name",
                 payload: { value: "value" },
-            } as TaipyBaseAction).data.name,
+            } as TaipyBaseAction).data.name
         ).toBeDefined();
     });
     it("store locations", async () => {
@@ -81,7 +81,7 @@ describe("reducer", () => {
             taipyReducer({ ...INITIAL_STATE }, {
                 type: "SET_LOCATIONS",
                 payload: { value: { loc: "loc" } },
-            } as TaipyBaseAction).locations,
+            } as TaipyBaseAction).locations
         ).toBeDefined();
     });
     it("set alert", async () => {
@@ -91,7 +91,7 @@ describe("reducer", () => {
                 atype: "i",
                 message: "message",
                 system: "system",
-            } as TaipyBaseAction).alerts,
+            } as TaipyBaseAction).alerts
         ).toHaveLength(1);
     });
     it("set show block", async () => {
@@ -100,7 +100,7 @@ describe("reducer", () => {
                 type: "SET_BLOCK",
                 action: "action",
                 message: "message",
-            } as TaipyBaseAction).block,
+            } as TaipyBaseAction).block
         ).toBeDefined();
     });
     it("set hide block", async () => {
@@ -110,7 +110,7 @@ describe("reducer", () => {
                 action: "action",
                 message: "message",
                 close: true,
-            } as TaipyBaseAction).block,
+            } as TaipyBaseAction).block
         ).toBeUndefined();
     });
     it("set navigate", async () => {
@@ -119,7 +119,7 @@ describe("reducer", () => {
                 type: "NAVIGATE",
                 to: "navigateTo",
                 tab: "_blank",
-            } as TaipyBaseAction).navigateTo,
+            } as TaipyBaseAction).navigateTo
         ).toBeDefined();
     });
     it("set client id", async () => {
@@ -130,7 +130,7 @@ describe("reducer", () => {
             taipyReducer({ ...INITIAL_STATE }, {
                 type: "ACKNOWLEDGEMENT",
                 id: "id",
-            } as TaipyBaseAction),
+            } as TaipyBaseAction)
         ).toEqual(INITIAL_STATE);
     });
     it("remove Acknowledgement", async () => {
@@ -138,7 +138,7 @@ describe("reducer", () => {
             taipyReducer({ ...INITIAL_STATE, ackList: ["ack"] }, {
                 type: "ACKNOWLEDGEMENT",
                 id: "ack",
-            } as TaipyBaseAction),
+            } as TaipyBaseAction)
         ).toEqual(INITIAL_STATE);
     });
     it("set Theme", async () => {
@@ -146,7 +146,7 @@ describe("reducer", () => {
             taipyReducer({ ...INITIAL_STATE }, {
                 type: "SET_THEME",
                 payload: { value: "dark" },
-            } as TaipyBaseAction).theme,
+            } as TaipyBaseAction).theme
         ).toBeDefined();
     });
     it("set TimeZone", async () => {
@@ -154,7 +154,7 @@ describe("reducer", () => {
             taipyReducer({ ...INITIAL_STATE }, {
                 type: "SET_TIMEZONE",
                 payload: { timeZone: "tz" },
-            } as TaipyBaseAction).timeZone,
+            } as TaipyBaseAction).timeZone
         ).toBeDefined();
     });
     it("set default TimeZone", async () => {
@@ -162,12 +162,12 @@ describe("reducer", () => {
             taipyReducer({ ...INITIAL_STATE }, {
                 type: "SET_TIMEZONE",
                 payload: {},
-            } as TaipyBaseAction).timeZone,
+            } as TaipyBaseAction).timeZone
         ).toBeDefined();
     });
     it("set Menu", async () => {
         expect(
-            taipyReducer({ ...INITIAL_STATE }, { type: "SET_MENU", menu: {} } as TaipyBaseAction).menu,
+            taipyReducer({ ...INITIAL_STATE }, { type: "SET_MENU", menu: {} } as TaipyBaseAction).menu
         ).toBeDefined();
     });
     it("sets download", async () => {
@@ -175,12 +175,12 @@ describe("reducer", () => {
             taipyReducer({ ...INITIAL_STATE }, {
                 type: "DOWNLOAD_FILE",
                 content: {},
-            } as TaipyBaseAction).download,
+            } as TaipyBaseAction).download
         ).toBeDefined();
     });
     it("resets download", async () => {
         expect(
-            taipyReducer({ ...INITIAL_STATE }, { type: "DOWNLOAD_FILE" } as TaipyBaseAction).download,
+            taipyReducer({ ...INITIAL_STATE }, { type: "DOWNLOAD_FILE" } as TaipyBaseAction).download
         ).toBeUndefined();
     });
     it("sets partial", async () => {
@@ -189,7 +189,7 @@ describe("reducer", () => {
                 type: "PARTIAL",
                 name: "partial",
                 create: true,
-            } as TaipyBaseAction).data.partial,
+            } as TaipyBaseAction).data.partial
         ).toBeDefined();
     });
     it("resets partial", async () => {
@@ -197,7 +197,7 @@ describe("reducer", () => {
             taipyReducer({ ...INITIAL_STATE, data: { partial: true } }, {
                 type: "PARTIAL",
                 name: "partial",
-            } as TaipyBaseAction).data.partial,
+            } as TaipyBaseAction).data.partial
         ).toBeUndefined();
     });
     it("creates an alert action", () => {
@@ -333,6 +333,7 @@ describe("createRequestInfiniteTableUpdateAction function", () => {
                 value: "testValue",
                 col: "yourColValue",
                 action: "yourActionValue",
+                type: "yourTypeValue",
             },
         ];
         const action = createRequestInfiniteTableUpdateAction(
@@ -354,7 +355,7 @@ describe("createRequestInfiniteTableUpdateAction function", () => {
             compare,
             compareDatas,
             stateContext,
-            reverse,
+            reverse
         );
         expect(action.type).toEqual(Types.RequestDataUpdate);
         expect(action.name).toEqual(name);
@@ -396,7 +397,7 @@ describe("createRequestTableUpdateAction function", () => {
         const tooltips = { tooltipKey: "tooltipValue" };
         const handleNan = true;
         const filters = [
-            { field: "testField", operator: "testOperator", value: "testValue", col: "testCol", action: "testAction" },
+            { field: "testField", operator: "testOperator", value: "testValue", col: "testCol", action: "testAction", type: "type" },
         ];
         const compare = "testCompare";
         const compareDatas = "testCompareDatas";
@@ -419,7 +420,7 @@ describe("createRequestTableUpdateAction function", () => {
             filters,
             compare,
             compareDatas,
-            stateContext,
+            stateContext
         );
         expect(action.type).toEqual(Types.RequestDataUpdate);
         expect(action.name).toEqual(name);
@@ -1058,7 +1059,7 @@ describe("initializeWebSocket function", () => {
                 "mockId",
                 undefined,
                 false,
-                expect.any(Function),
+                expect.any(Function)
             );
         }
     });

+ 179 - 173
frontend/taipy/package-lock.json

@@ -41,7 +41,6 @@
       }
     },
     "../../taipy/gui/webapp": {
-      "name": "taipy-gui",
       "version": "4.0.0"
     },
     "node_modules/@babel/code-frame": {
@@ -235,15 +234,15 @@
       }
     },
     "node_modules/@emotion/babel-plugin": {
-      "version": "11.11.0",
-      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
-      "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
+      "version": "11.12.0",
+      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz",
+      "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==",
       "dependencies": {
         "@babel/helper-module-imports": "^7.16.7",
         "@babel/runtime": "^7.18.3",
-        "@emotion/hash": "^0.9.1",
-        "@emotion/memoize": "^0.8.1",
-        "@emotion/serialize": "^1.1.2",
+        "@emotion/hash": "^0.9.2",
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/serialize": "^1.2.0",
         "babel-plugin-macros": "^3.1.0",
         "convert-source-map": "^1.5.0",
         "escape-string-regexp": "^4.0.0",
@@ -253,47 +252,47 @@
       }
     },
     "node_modules/@emotion/cache": {
-      "version": "11.11.0",
-      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
-      "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
-      "dependencies": {
-        "@emotion/memoize": "^0.8.1",
-        "@emotion/sheet": "^1.2.2",
-        "@emotion/utils": "^1.2.1",
-        "@emotion/weak-memoize": "^0.3.1",
+      "version": "11.13.0",
+      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.0.tgz",
+      "integrity": "sha512-hPV345J/tH0Cwk2wnU/3PBzORQ9HeX+kQSbwI+jslzpRCHE6fSGTohswksA/Ensr8znPzwfzKZCmAM9Lmlhp7g==",
+      "dependencies": {
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/sheet": "^1.4.0",
+        "@emotion/utils": "^1.4.0",
+        "@emotion/weak-memoize": "^0.4.0",
         "stylis": "4.2.0"
       }
     },
     "node_modules/@emotion/hash": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
-      "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
+      "version": "0.9.2",
+      "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+      "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
     },
     "node_modules/@emotion/is-prop-valid": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
-      "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz",
+      "integrity": "sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==",
       "dependencies": {
-        "@emotion/memoize": "^0.8.1"
+        "@emotion/memoize": "^0.9.0"
       }
     },
     "node_modules/@emotion/memoize": {
-      "version": "0.8.1",
-      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
-      "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+      "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
     },
     "node_modules/@emotion/react": {
-      "version": "11.11.4",
-      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz",
-      "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==",
+      "version": "11.13.0",
+      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz",
+      "integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==",
       "dependencies": {
         "@babel/runtime": "^7.18.3",
-        "@emotion/babel-plugin": "^11.11.0",
-        "@emotion/cache": "^11.11.0",
-        "@emotion/serialize": "^1.1.3",
-        "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
-        "@emotion/utils": "^1.2.1",
-        "@emotion/weak-memoize": "^0.3.1",
+        "@emotion/babel-plugin": "^11.12.0",
+        "@emotion/cache": "^11.13.0",
+        "@emotion/serialize": "^1.3.0",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+        "@emotion/utils": "^1.4.0",
+        "@emotion/weak-memoize": "^0.4.0",
         "hoist-non-react-statics": "^3.3.1"
       },
       "peerDependencies": {
@@ -306,33 +305,33 @@
       }
     },
     "node_modules/@emotion/serialize": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz",
-      "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz",
+      "integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==",
       "dependencies": {
-        "@emotion/hash": "^0.9.1",
-        "@emotion/memoize": "^0.8.1",
-        "@emotion/unitless": "^0.8.1",
-        "@emotion/utils": "^1.2.1",
+        "@emotion/hash": "^0.9.2",
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/unitless": "^0.9.0",
+        "@emotion/utils": "^1.4.0",
         "csstype": "^3.0.2"
       }
     },
     "node_modules/@emotion/sheet": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
-      "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+      "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="
     },
     "node_modules/@emotion/styled": {
-      "version": "11.11.5",
-      "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz",
-      "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==",
+      "version": "11.13.0",
+      "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz",
+      "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==",
       "dependencies": {
         "@babel/runtime": "^7.18.3",
-        "@emotion/babel-plugin": "^11.11.0",
-        "@emotion/is-prop-valid": "^1.2.2",
-        "@emotion/serialize": "^1.1.4",
-        "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
-        "@emotion/utils": "^1.2.1"
+        "@emotion/babel-plugin": "^11.12.0",
+        "@emotion/is-prop-valid": "^1.3.0",
+        "@emotion/serialize": "^1.3.0",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+        "@emotion/utils": "^1.4.0"
       },
       "peerDependencies": {
         "@emotion/react": "^11.0.0-rc.0",
@@ -345,27 +344,27 @@
       }
     },
     "node_modules/@emotion/unitless": {
-      "version": "0.8.1",
-      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
-      "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz",
+      "integrity": "sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ=="
     },
     "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
-      "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
+      "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==",
       "peerDependencies": {
         "react": ">=16.8.0"
       }
     },
     "node_modules/@emotion/utils": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
-      "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg=="
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz",
+      "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ=="
     },
     "node_modules/@emotion/weak-memoize": {
-      "version": "0.3.1",
-      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
-      "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+      "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
     },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
@@ -461,20 +460,20 @@
       }
     },
     "node_modules/@floating-ui/core": {
-      "version": "1.6.4",
-      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz",
-      "integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==",
+      "version": "1.6.5",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz",
+      "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==",
       "dependencies": {
-        "@floating-ui/utils": "^0.2.4"
+        "@floating-ui/utils": "^0.2.5"
       }
     },
     "node_modules/@floating-ui/dom": {
-      "version": "1.6.7",
-      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz",
-      "integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==",
+      "version": "1.6.8",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz",
+      "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==",
       "dependencies": {
         "@floating-ui/core": "^1.6.0",
-        "@floating-ui/utils": "^0.2.4"
+        "@floating-ui/utils": "^0.2.5"
       }
     },
     "node_modules/@floating-ui/react-dom": {
@@ -490,9 +489,9 @@
       }
     },
     "node_modules/@floating-ui/utils": {
-      "version": "0.2.4",
-      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
-      "integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA=="
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz",
+      "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ=="
     },
     "node_modules/@humanwhocodes/config-array": {
       "version": "0.11.14",
@@ -990,14 +989,14 @@
       }
     },
     "node_modules/@mui/x-date-pickers": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.10.0.tgz",
-      "integrity": "sha512-mfJuKOdrrdlH5FskXl0aypRmZuVctNRwn5Xw0aMgE3n1ORCpzDSGCXd5El1/PdH3/3olT+vPFmxXKMQju5UMow==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.11.0.tgz",
+      "integrity": "sha512-+zPWs1dwe7J1nZ2iFhTgCae31BLMYMQ2VtQfHxx21Dh6gbBRy/U7YJZg1LdhfQyE093S3e4A5uMZ6PUWdne7iA==",
       "dependencies": {
-        "@babel/runtime": "^7.24.7",
+        "@babel/runtime": "^7.24.8",
         "@mui/base": "^5.0.0-beta.40",
-        "@mui/system": "^5.16.0",
-        "@mui/utils": "^5.16.0",
+        "@mui/system": "^5.16.2",
+        "@mui/utils": "^5.16.2",
         "@types/react-transition-group": "^4.4.10",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
@@ -1054,15 +1053,35 @@
         }
       }
     },
+    "node_modules/@mui/x-internals": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.11.0.tgz",
+      "integrity": "sha512-GqCYylKiB4cLH9tK4JweJlT2JvPjnpXjS3TEIqtHB4BcSsezhdRrMGzHOO5zCJqkasqTirJh2t6X16Qw1llr4Q==",
+      "dependencies": {
+        "@babel/runtime": "^7.24.8",
+        "@mui/utils": "^5.16.2"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "react": "^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/@mui/x-tree-view": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.10.0.tgz",
-      "integrity": "sha512-9OCAIb0wS5uuEDyjcSwSturrB4RUXBfE0UO/xpKjrMvRzCaAvxbCf2aFILP8uH9NyynYZkIGYfGnlqdAPy2OLg==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.11.0.tgz",
+      "integrity": "sha512-/nk3hhTW5c4Uk2MIcIujC6w5/e5m8RbfWY0YTfRdHApmcFjeEZDX7O5pky5DojhaALopDuNebr9PlE8QYloaiw==",
       "dependencies": {
-        "@babel/runtime": "^7.24.7",
+        "@babel/runtime": "^7.24.8",
         "@mui/base": "^5.0.0-beta.40",
-        "@mui/system": "^5.16.0",
-        "@mui/utils": "^5.16.0",
+        "@mui/system": "^5.16.2",
+        "@mui/utils": "^5.16.2",
+        "@mui/x-internals": "7.11.0",
         "@types/react-transition-group": "^4.4.10",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
@@ -1208,9 +1227,9 @@
       "dev": true
     },
     "node_modules/@types/eslint": {
-      "version": "8.56.10",
-      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
-      "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
+      "version": "8.56.11",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz",
+      "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==",
       "dev": true,
       "dependencies": {
         "@types/estree": "*",
@@ -1273,9 +1292,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.14.10",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
-      "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==",
+      "version": "20.14.11",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
+      "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
       "dev": true,
       "dependencies": {
         "undici-types": "~5.26.4"
@@ -1324,16 +1343,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz",
-      "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz",
+      "integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "7.16.1",
-        "@typescript-eslint/type-utils": "7.16.1",
-        "@typescript-eslint/utils": "7.16.1",
-        "@typescript-eslint/visitor-keys": "7.16.1",
+        "@typescript-eslint/scope-manager": "7.17.0",
+        "@typescript-eslint/type-utils": "7.17.0",
+        "@typescript-eslint/utils": "7.17.0",
+        "@typescript-eslint/visitor-keys": "7.17.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -1357,15 +1376,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz",
-      "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz",
+      "integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "7.16.1",
-        "@typescript-eslint/types": "7.16.1",
-        "@typescript-eslint/typescript-estree": "7.16.1",
-        "@typescript-eslint/visitor-keys": "7.16.1",
+        "@typescript-eslint/scope-manager": "7.17.0",
+        "@typescript-eslint/types": "7.17.0",
+        "@typescript-eslint/typescript-estree": "7.17.0",
+        "@typescript-eslint/visitor-keys": "7.17.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1385,13 +1404,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz",
-      "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz",
+      "integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.16.1",
-        "@typescript-eslint/visitor-keys": "7.16.1"
+        "@typescript-eslint/types": "7.17.0",
+        "@typescript-eslint/visitor-keys": "7.17.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -1402,13 +1421,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz",
-      "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz",
+      "integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "7.16.1",
-        "@typescript-eslint/utils": "7.16.1",
+        "@typescript-eslint/typescript-estree": "7.17.0",
+        "@typescript-eslint/utils": "7.17.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -1429,9 +1448,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz",
-      "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz",
+      "integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -1442,13 +1461,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz",
-      "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz",
+      "integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.16.1",
-        "@typescript-eslint/visitor-keys": "7.16.1",
+        "@typescript-eslint/types": "7.17.0",
+        "@typescript-eslint/visitor-keys": "7.17.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -1470,15 +1489,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz",
-      "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz",
+      "integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "7.16.1",
-        "@typescript-eslint/types": "7.16.1",
-        "@typescript-eslint/typescript-estree": "7.16.1"
+        "@typescript-eslint/scope-manager": "7.17.0",
+        "@typescript-eslint/types": "7.17.0",
+        "@typescript-eslint/typescript-estree": "7.17.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -1492,12 +1511,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.16.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz",
-      "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz",
+      "integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.16.1",
+        "@typescript-eslint/types": "7.17.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -1937,18 +1956,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/array.prototype.toreversed": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz",
-      "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1",
-        "es-shim-unscopables": "^1.0.0"
-      }
-    },
     "node_modules/array.prototype.tosorted": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
@@ -2109,9 +2116,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001642",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz",
-      "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==",
+      "version": "1.0.30001643",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
+      "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
       "dev": true,
       "funding": [
         {
@@ -2450,9 +2457,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.828",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.828.tgz",
-      "integrity": "sha512-QOIJiWpQJDHAVO4P58pwb133Cwee0nbvy/MV1CwzZVGpkH1RX33N3vsaWRCpR6bF63AAq366neZrRTu7Qlsbbw==",
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz",
+      "integrity": "sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {
@@ -2728,15 +2735,14 @@
       }
     },
     "node_modules/eslint-plugin-react": {
-      "version": "7.34.4",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.4.tgz",
-      "integrity": "sha512-Np+jo9bUwJNxCsT12pXtrGhJgT3T44T1sHhn1Ssr42XFn8TES0267wPGo5nNrMHi8qkyimDAX2BUmkf9pSaVzA==",
+      "version": "7.35.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz",
+      "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==",
       "dev": true,
       "dependencies": {
         "array-includes": "^3.1.8",
         "array.prototype.findlast": "^1.2.5",
         "array.prototype.flatmap": "^1.3.2",
-        "array.prototype.toreversed": "^1.1.2",
         "array.prototype.tosorted": "^1.1.4",
         "doctrine": "^2.1.0",
         "es-iterator-helpers": "^1.0.19",
@@ -2757,7 +2763,7 @@
         "node": ">=4"
       },
       "peerDependencies": {
-        "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
+        "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
       }
     },
     "node_modules/eslint-plugin-react-hooks": {
@@ -3581,9 +3587,9 @@
       }
     },
     "node_modules/import-local": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
-      "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+      "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
       "dev": true,
       "dependencies": {
         "pkg-dir": "^4.2.0",
@@ -3725,9 +3731,9 @@
       }
     },
     "node_modules/is-core-module": {
-      "version": "2.14.0",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz",
-      "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==",
+      "version": "2.15.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
+      "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
       "dependencies": {
         "hasown": "^2.0.2"
       },
@@ -4414,9 +4420,9 @@
       "dev": true
     },
     "node_modules/node-releases": {
-      "version": "2.0.14",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
-      "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+      "version": "2.0.18",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
+      "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
       "dev": true
     },
     "node_modules/normalize-path": {
@@ -5154,9 +5160,9 @@
       "dev": true
     },
     "node_modules/semver": {
-      "version": "7.6.2",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
-      "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
       "dev": true,
       "bin": {
         "semver": "bin/semver.js"
@@ -5795,9 +5801,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.5.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
-      "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+      "version": "5.5.4",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+      "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
       "dev": true,
       "bin": {
         "tsc": "bin/tsc",

+ 6 - 6
frontend/taipy/src/CoreSelector.tsx

@@ -509,10 +509,10 @@ const CoreSelector = (props: CoreSelectorProps) => {
     // filters
     const colFilters = useMemo(() => {
         try {
-            const res = props.filter ? (JSON.parse(props.filter) as Array<[string, string, string[]]>) : undefined;
+            const res = props.filter ? (JSON.parse(props.filter) as Array<[string, string, string, string[]]>) : undefined;
             return Array.isArray(res)
-                ? res.reduce((pv, [name, coltype, lov], idx) => {
-                      pv[name] = { dfid: name, type: coltype, index: idx, filter: true, lov: lov, freeLov: !!lov };
+                ? res.reduce((pv, [name, id, coltype, lov], idx) => {
+                      pv[name] = { dfid: id, title: name, type: coltype, index: idx, filter: true, lov: lov, freeLov: !!lov };
                       return pv;
                   }, {} as Record<string, ColumnDesc>)
                 : undefined;
@@ -554,10 +554,10 @@ const CoreSelector = (props: CoreSelectorProps) => {
     // sort
     const colSorts = useMemo(() => {
         try {
-            const res = props.sort ? (JSON.parse(props.sort) as Array<[string]>) : undefined;
+            const res = props.sort ? (JSON.parse(props.sort) as Array<[string, string]>) : undefined;
             return Array.isArray(res)
-                ? res.reduce((pv, [name], idx) => {
-                      pv[name] = { dfid: name, type: "str", index: idx };
+                ? res.reduce((pv, [name, id], idx) => {
+                      pv[name] = { dfid: id, title: name, type: "str", index: idx };
                       return pv;
                   }, {} as Record<string, ColumnDesc>)
                 : undefined;

+ 22 - 5
frontend/taipy/src/ScenarioViewer.tsx

@@ -366,7 +366,13 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
             }
         }
         setValid(!!sc);
-        setScenario((oldSc) => (oldSc === sc ? oldSc : sc ? (deepEqual(oldSc, sc) ? oldSc : sc) : invalidScenario));
+        setScenario((oldSc) => {
+            if (oldSc === sc || (sc && deepEqual(oldSc, sc))) {
+                return oldSc;
+            }
+            setSubmissionStatus(-1);
+            return sc || invalidScenario;
+        });
     }, [props.scenario, props.defaultScenario]);
 
     const [
@@ -397,7 +403,18 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
     const onDeleteScenario = useCallback(() => {
         setDeleteDialogOpen(false);
         if (valid) {
-            dispatch(createSendActionNameAction(id, module, { action: props.onDelete, error_id: getUpdateVar(updateScVars, "error_id") }, undefined, undefined, true, true, { id: scId }));
+            dispatch(
+                createSendActionNameAction(
+                    id,
+                    module,
+                    { action: props.onDelete, error_id: getUpdateVar(updateScVars, "error_id") },
+                    undefined,
+                    undefined,
+                    true,
+                    true,
+                    { id: scId }
+                )
+            );
         }
     }, [valid, props.onDelete, scId, id, dispatch, module, updateScVars]);
 
@@ -424,6 +441,9 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
         [expandable]
     );
 
+    // Submission status
+    const [submissionStatus, setSubmissionStatus] = useState(-1);
+
     // submits
     const submitSequence = useCallback(
         (label: string) => {
@@ -577,9 +597,6 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
 
     const addSequenceHandler = useCallback(() => setSequences((seq) => [...seq, ["", [], "", true]]), []);
 
-    // Submission status
-    const [submissionStatus, setSubmissionStatus] = useState(-1);
-
     // on scenario change
     useEffect(() => {
         showTags && setTags(scTags);

+ 1 - 0
taipy/gui_core/__init__.py

@@ -9,4 +9,5 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 
+from ._adapters import CustomScenarioFilter, DataNodeFilter, DataNodeScenarioFilter, ScenarioFilter
 from ._init import *

+ 153 - 106
taipy/gui_core/_adapters.py

@@ -246,14 +246,18 @@ _operators: t.Dict[str, t.Callable] = {
 
 
 def _filter_value(base_val: t.Any, operator: t.Callable, val: t.Any, adapt: t.Optional[t.Callable] = None):
-    if isinstance(base_val, (datetime, date)):
-        base_val = base_val.isoformat()
-    val = adapt(base_val, val) if adapt else val
-    if isinstance(base_val, str) and isinstance(val, str):
-        base_val = base_val.lower()
-        val = val.lower()
+    if base_val is None:
+        base_val = "" if isinstance(val, str) else 0
+    else:
+        if isinstance(base_val, (datetime, date)):
+            base_val = base_val.isoformat()
+        val = adapt(base_val, val) if adapt else val
+        if isinstance(base_val, str) and isinstance(val, str):
+            base_val = base_val.lower()
+            val = val.lower()
     return operator(base_val, val)
 
+
 def _adapt_type(base_val, val):
     # try casting the filter to the value
     if isinstance(val, str) and not isinstance(base_val, str):
@@ -267,6 +271,7 @@ def _adapt_type(base_val, val):
                 pass
     return val
 
+
 def _filter_iterable(list_val: Iterable, operator: t.Callable, val: t.Any):
     if operator is contains:
         types = {type(v) for v in list_val}
@@ -285,17 +290,19 @@ def _invoke_action(
 ) -> bool:
     if ent is None:
         return False
-    if not (col_fn or col).isidentifier():
-        _warn(f'Error filtering with "{col_fn or col}": not a valid Python identifier.')
-        return True
     try:
         if col_type == "any":
             # when a property is not found, return True only if action is not equals
             if not is_dn and not hasattr(ent, "properties") or not ent.properties.get(col_fn or col):
                 return action == "!="
         if op := _operators.get(action):
-            cur_val = attrgetter(col_fn or col)(ent)
-            cur_val = cur_val() if col_fn else cur_val
+            if callable(col):
+                cur_val = col(ent)
+            else:
+                cur_val = attrgetter(col_fn or col)(ent)
+                cur_val = cur_val() if col_fn else cur_val
+            if isinstance(cur_val, DataNode):
+                cur_val = cur_val.read()
             if not isinstance(cur_val, str) and isinstance(cur_val, Iterable):
                 return _filter_iterable(cur_val, op, val)
             return _filter_value(cur_val, op, val, _adapt_type)
@@ -341,26 +348,76 @@ def _get_entity_property(col: str, *types: t.Type):
     return sort_key
 
 
-def _get_datanode_property(attr: str):
-    if (parts := attr.split(".")) and len(parts) > 1:
-        return parts[1]
-    return None
+@dataclass
+class _Filter(_DoNotUpdate):
+    label: str
+    property_type: t.Optional[t.Type]
 
+    def get_property(self):
+        return self.label
+
+    def get_type(self):
+        if self.property_type is bool:
+            return "boolean"
+        elif self.property_type is int or self.property_type is float:
+            return "number"
+        elif self.property_type is datetime or self.property_type is date:
+            return "date"
+        elif self.property_type is str:
+            return "str"
+        return "any"
+
+
+@dataclass
+class ScenarioFilter(_Filter):
+    property_id: str
+
+    def get_property(self):
+        return self.property_id
 
-class _GuiCoreProperties(ABC):
-    @staticmethod
-    @abstractmethod
-    def get_type(attr: str):
-        raise NotImplementedError
+
+@dataclass
+class DataNodeScenarioFilter(_Filter):
+    datanode_config_id: str
+    property_id: str
+
+    def get_property(self):
+        return f"{self.datanode_config_id}.{self.property_id}"
+
+
+_CUSTOM_PREFIX = "fn:"
+
+
+@dataclass
+class CustomScenarioFilter(_Filter):
+    filter_function: t.Callable[[Scenario], t.Any]
+
+    def __post_init__(self):
+        if self.filter_function.__name__ == "<lambda>":
+            raise TypeError("ScenarioCustomFilter does not support lambda functions.")
+        mod = self.filter_function.__module__
+        self.module = mod if isinstance(mod, str) else mod.__name__
+
+    def get_property(self):
+        return f"{_CUSTOM_PREFIX}{self.module}:{self.filter_function.__name__}"
 
     @staticmethod
-    @abstractmethod
-    def get_col_name(attr: str):
-        raise NotImplementedError
+    def _get_custom(col: str) -> t.Optional[t.List[str]]:
+        return col[len(_CUSTOM_PREFIX) :].split(":") if col.startswith(_CUSTOM_PREFIX) else None
+
+
+@dataclass
+class DataNodeFilter(_Filter):
+    property_id: str
 
+    def get_property(self):
+        return self.property_id
+
+
+class _GuiCoreProperties(ABC):
     @staticmethod
     @abstractmethod
-    def get_default_list():
+    def get_default_list() -> t.List[_Filter]:
         raise NotImplementedError
 
     @staticmethod
@@ -380,18 +437,32 @@ class _GuiCoreProperties(ABC):
                 return None
         if isinstance(data, str):
             data = data.split(";")
+        if isinstance(data, _Filter):
+            data = (data,)
         if isinstance(data, (list, tuple)):
-            flist = []
+            flist: t.List[_Filter] = []  # type: ignore[annotation-unchecked]
             for f in data:
-                if f == "*":
-                    flist.extend(self.get_default_list())
-                else:
+                if isinstance(f, str):
+                    f = f.strip()
+                    if f == "*":
+                        flist.extend(p.filter for p in self.get_default_list())
+                    elif f:
+                        flist.append(
+                            next((p.filter for p in self.get_default_list() if p.get_property() == f), _Filter(f))
+                        )
+                elif isinstance(f, _Filter):
                     flist.append(f)
             return json.dumps(
                 [
-                    (attr, self.get_type(attr), self.get_enums().get(attr)) if self.full_desc() else (attr,)
+                    (
+                        attr.label,
+                        attr.get_property(),
+                        attr.get_type(),
+                        self.get_enums().get(attr.get_property()),
+                    )
+                    if self.full_desc()
+                    else (attr.label, attr.get_property())
                     for attr in flist
-                    if attr
                 ]
             )
         return None
@@ -399,63 +470,57 @@ class _GuiCoreProperties(ABC):
 
 @dataclass(frozen=True)
 class _GuiCorePropDesc:
-    attr: str
-    type: str
+    filter: _Filter
     extended: bool = False
     for_sort: bool = False
 
 
-_EMPTY_PROP_DESC = _GuiCorePropDesc("", "any")
-
-
 class _GuiCoreScenarioProperties(_GuiCoreProperties):
-    _SC_PROPS: t.Dict[str, _GuiCorePropDesc] = {
-        "Config id": _GuiCorePropDesc("config_id", "string", for_sort=True),
-        "Label": _GuiCorePropDesc("get_simple_label()", "string", for_sort=True),
-        "Creation date": _GuiCorePropDesc("creation_date", "date", for_sort=True),
-        "Cycle label": _GuiCorePropDesc("cycle.name", "string", extended=True),
-        "Cycle start": _GuiCorePropDesc("cycle.start_date", "date", extended=True),
-        "Cycle end": _GuiCorePropDesc("cycle.end_date", "date", extended=True),
-        "Primary": _GuiCorePropDesc("is_primary", "boolean", extended=True),
-        "Tags": _GuiCorePropDesc("tags", "string"),
-    }
-    __DN_PROPS = {
-        "Up to date": _GuiCorePropDesc("is_up_to_date", "boolean"),
-        "Valid": _GuiCorePropDesc("is_valid", "boolean"),
-        "Last edit date": _GuiCorePropDesc("last_edit_date", "date"),
-    }
+    _SC_PROPS: t.List[_GuiCorePropDesc] = [
+        _GuiCorePropDesc(ScenarioFilter("Config id", str, "config_id"), for_sort=True),
+        _GuiCorePropDesc(ScenarioFilter("Label", str, "get_simple_label()"), for_sort=True),
+        _GuiCorePropDesc(ScenarioFilter("Creation date", datetime, "creation_date"), for_sort=True),
+        _GuiCorePropDesc(ScenarioFilter("Cycle label", str, "cycle.name"), extended=True),
+        _GuiCorePropDesc(ScenarioFilter("Cycle start", datetime, "cycle.start_date"), extended=True),
+        _GuiCorePropDesc(ScenarioFilter("Cycle end", datetime, "cycle.end_date"), extended=True),
+        _GuiCorePropDesc(ScenarioFilter("Primary", bool, "is_primary"), extended=True),
+        _GuiCorePropDesc(ScenarioFilter("Tags", str, "tags")),
+    ]
     __ENUMS = None
     __SC_CYCLE = None
 
     @staticmethod
-    def get_type(attr: str):
-        if prop := _get_datanode_property(attr):
-            return _GuiCoreScenarioProperties.__DN_PROPS.get(prop, _EMPTY_PROP_DESC).type
-        return _GuiCoreScenarioProperties._SC_PROPS.get(attr, _EMPTY_PROP_DESC).type
-
-    @staticmethod
-    def get_col_name(attr: str):
-        if prop := _get_datanode_property(attr):
-            return (
-                attr.split(".")[0]
-                + f".{_GuiCoreScenarioProperties.__DN_PROPS.get(prop, _EMPTY_PROP_DESC).attr or prop}"
+    def is_datanode_property(attr: str):
+        if "." not in attr:
+            return False
+        return (
+            next(
+                (
+                    p
+                    for p in _GuiCoreScenarioProperties._SC_PROPS
+                    if t.cast(ScenarioFilter, p.filter).property_id == attr
+                ),
+                None,
             )
-        return _GuiCoreScenarioProperties._SC_PROPS.get(attr, _EMPTY_PROP_DESC).attr or attr
+            is None
+        )
 
     def get_enums(self):
+        if not self.full_desc():
+            return {}
         if _GuiCoreScenarioProperties.__ENUMS is None:
             _GuiCoreScenarioProperties.__ENUMS = {
                 k: v
                 for k, v in {
-                    "Config id": [c for c in Config.scenarios.keys() if c != "default"],
-                    "Tags": list(
+                    "config_id": [c for c in Config.scenarios.keys() if c != "default"],
+                    "tags": list(
                         {t for s in Config.scenarios.values() for t in s.properties.get("authorized_tags", [])}
                     ),
                 }.items()
                 if len(v)
             }
 
-        return _GuiCoreScenarioProperties.__ENUMS if self.full_desc() else {}
+        return _GuiCoreScenarioProperties.__ENUMS
 
     @staticmethod
     def has_cycle():
@@ -467,10 +532,8 @@ class _GuiCoreScenarioProperties(_GuiCoreProperties):
 
 
 class _GuiCoreScenarioFilter(_GuiCoreScenarioProperties, _TaipyBase):
-    DEFAULT = list(_GuiCoreScenarioProperties._SC_PROPS.keys())
-    DEFAULT_NO_CYCLE = [
-        p[0] for p in filter(lambda prop: not prop[1].extended, _GuiCoreScenarioProperties._SC_PROPS.items())
-    ]
+    DEFAULT = _GuiCoreScenarioProperties._SC_PROPS
+    DEFAULT_NO_CYCLE = list(filter(lambda prop: not prop.extended, _GuiCoreScenarioProperties._SC_PROPS))
 
     @staticmethod
     def full_desc():
@@ -490,13 +553,10 @@ class _GuiCoreScenarioFilter(_GuiCoreScenarioProperties, _TaipyBase):
 
 
 class _GuiCoreScenarioSort(_GuiCoreScenarioProperties, _TaipyBase):
-    DEFAULT = [p[0] for p in filter(lambda prop: prop[1].for_sort, _GuiCoreScenarioProperties._SC_PROPS.items())]
-    DEFAULT_NO_CYCLE = [
-        p[0]
-        for p in filter(
-            lambda prop: prop[1].for_sort and not prop[1].extended, _GuiCoreScenarioProperties._SC_PROPS.items()
-        )
-    ]
+    DEFAULT = list(filter(lambda prop: prop.for_sort, _GuiCoreScenarioProperties._SC_PROPS))
+    DEFAULT_NO_CYCLE = list(
+        filter(lambda prop: prop.for_sort and not prop.extended, _GuiCoreScenarioProperties._SC_PROPS)
+    )
 
     @staticmethod
     def full_desc():
@@ -516,27 +576,19 @@ class _GuiCoreScenarioSort(_GuiCoreScenarioProperties, _TaipyBase):
 
 
 class _GuiCoreDatanodeProperties(_GuiCoreProperties):
-    _DN_PROPS: t.Dict[str, _GuiCorePropDesc] = {
-        "Config id": _GuiCorePropDesc("config_id", "string", for_sort=True),
-        "Label": _GuiCorePropDesc("get_simple_label()", "string", for_sort=True),
-        "Up to date": _GuiCorePropDesc("is_up_to_date", "boolean"),
-        "Last edit date": _GuiCorePropDesc("last_edit_date", "date", for_sort=True),
-        "Input": _GuiCorePropDesc("is_input", "boolean"),
-        "Output": _GuiCorePropDesc("is_output", "boolean"),
-        "Intermediate": _GuiCorePropDesc("is_intermediate", "boolean"),
-        "Expiration date": _GuiCorePropDesc("expiration_date", "date", extended=True, for_sort=True),
-        "Expired": _GuiCorePropDesc("is_expired", "boolean", extended=True),
-    }
+    _DN_PROPS: t.List[_GuiCorePropDesc] = [
+        _GuiCorePropDesc(DataNodeFilter("Config id", str, "config_id"), for_sort=True),
+        _GuiCorePropDesc(DataNodeFilter("Label", str, "get_simple_label()"), for_sort=True),
+        _GuiCorePropDesc(DataNodeFilter("Up to date", bool, "is_up_to_date")),
+        _GuiCorePropDesc(DataNodeFilter("Last edit date", datetime, "last_edit_date"), for_sort=True),
+        _GuiCorePropDesc(DataNodeFilter("Input", bool, "is_input")),
+        _GuiCorePropDesc(DataNodeFilter("Output", bool, "is_output")),
+        _GuiCorePropDesc(DataNodeFilter("Intermediate", bool, "is_intermediate")),
+        _GuiCorePropDesc(DataNodeFilter("Expiration date", datetime, "expiration_date"), extended=True, for_sort=True),
+        _GuiCorePropDesc(DataNodeFilter("Expired", bool, "is_expired"), extended=True),
+    ]
     __DN_VALIDITY = None
 
-    @staticmethod
-    def get_type(attr: str):
-        return _GuiCoreDatanodeProperties._DN_PROPS.get(attr, _EMPTY_PROP_DESC).type
-
-    @staticmethod
-    def get_col_name(attr: str):
-        return _GuiCoreDatanodeProperties._DN_PROPS.get(attr, _EMPTY_PROP_DESC).attr or attr
-
     @staticmethod
     def has_validity():
         if _GuiCoreDatanodeProperties.__DN_VALIDITY is None:
@@ -547,10 +599,8 @@ class _GuiCoreDatanodeProperties(_GuiCoreProperties):
 
 
 class _GuiCoreDatanodeFilter(_GuiCoreDatanodeProperties, _TaipyBase):
-    DEFAULT = list(_GuiCoreDatanodeProperties._DN_PROPS.keys())
-    DEFAULT_NO_VALIDITY = [
-        p[0] for p in filter(lambda prop: not prop[1].extended, _GuiCoreDatanodeProperties._DN_PROPS.items())
-    ]
+    DEFAULT = _GuiCoreDatanodeProperties._DN_PROPS
+    DEFAULT_NO_VALIDITY = list(filter(lambda prop: not prop.extended, _GuiCoreDatanodeProperties._DN_PROPS))
 
     @staticmethod
     def full_desc():
@@ -570,13 +620,10 @@ class _GuiCoreDatanodeFilter(_GuiCoreDatanodeProperties, _TaipyBase):
 
 
 class _GuiCoreDatanodeSort(_GuiCoreDatanodeProperties, _TaipyBase):
-    DEFAULT = [p[0] for p in filter(lambda prop: prop[1].for_sort, _GuiCoreDatanodeProperties._DN_PROPS.items())]
-    DEFAULT_NO_VALIDITY = [
-        p[0]
-        for p in filter(
-            lambda prop: prop[1].for_sort and not prop[1].extended, _GuiCoreDatanodeProperties._DN_PROPS.items()
-        )
-    ]
+    DEFAULT = list(filter(lambda prop: prop.for_sort, _GuiCoreDatanodeProperties._DN_PROPS))
+    DEFAULT_NO_VALIDITY = list(
+        filter(lambda prop: prop.for_sort and not prop.extended, _GuiCoreDatanodeProperties._DN_PROPS)
+    )
 
     @staticmethod
     def full_desc():

+ 29 - 12
taipy/gui_core/_context.py

@@ -12,7 +12,6 @@
 import json
 import typing as t
 from collections import defaultdict
-from datetime import datetime
 from numbers import Number
 from threading import Lock
 
@@ -62,11 +61,10 @@ from taipy.gui._warnings import _warn
 from taipy.gui.gui import _DoNotUpdate
 
 from ._adapters import (
+    CustomScenarioFilter,
     _EntityType,
-    _get_datanode_property,
     _get_entity_property,
     _GuiCoreDatanodeAdapter,
-    _GuiCoreDatanodeProperties,
     _GuiCoreScenarioProperties,
     _invoke_action,
 )
@@ -286,7 +284,6 @@ class _GuiCoreContext(CoreEventConsumerBase):
             sorted_list = entities
             for sd in reversed(sorts):
                 col = sd.get("col", "")
-                col = _GuiCoreScenarioProperties.get_col_name(col)
                 order = sd.get("order", True)
                 sorted_list = sorted(sorted_list, key=_get_entity_property(col, Scenario, Cycle), reverse=not order)
         else:
@@ -304,12 +301,24 @@ class _GuiCoreContext(CoreEventConsumerBase):
         filtered_list = list(entities)
         for fd in filters:
             col = fd.get("col", "")
-            is_datanode_prop = _get_datanode_property(col) is not None
-            col_type = _GuiCoreScenarioProperties.get_type(col)
-            col = _GuiCoreScenarioProperties.get_col_name(col)
+            is_datanode_prop = _GuiCoreScenarioProperties.is_datanode_property(col)
+            col_type = fd.get("type", "no type")
             col_fn = cp[0] if (cp := col.split("(")) and len(cp) > 1 else None
             val = fd.get("value")
             action = fd.get("action", "")
+            customs = CustomScenarioFilter._get_custom(col)
+            if customs:
+                with self.gui._set_locals_context(customs[0] or None):
+                    fn = self.gui._get_user_function(customs[1])
+                    if callable(fn):
+                        col = fn
+            if (
+                isinstance(col, str)
+                and next(filter(lambda s: not s.isidentifier(), (col_fn or col).split(".")), False) is True
+            ):
+                _warn(f'Error filtering with "{col_fn or col}": not a valid Python identifier.')
+                continue
+
             # level 1 filtering
             filtered_list = [
                 e
@@ -606,13 +615,22 @@ class _GuiCoreContext(CoreEventConsumerBase):
         filtered_list = list(entities)
         for fd in filters:
             col = fd.get("col", "")
-            col_type = _GuiCoreDatanodeProperties.get_type(col)
-            col = _GuiCoreDatanodeProperties.get_col_name(col)
+            col_type = fd.get("type", "no type")
             col_fn = cp[0] if (cp := col.split("(")) and len(cp) > 1 else None
             val = fd.get("value")
             action = fd.get("action", "")
-            if isinstance(val, str) and col_type == "date":
-                val = datetime.fromisoformat(val[:-1])
+            customs = CustomScenarioFilter._get_custom(col)
+            if customs:
+                with self.gui._set_locals_context(customs[0] or None):
+                    fn = self.gui._get_user_function(customs[1])
+                    if callable(fn):
+                        col = fn
+            if (
+                isinstance(col, str)
+                and next(filter(lambda s: not s.isidentifier(), (col_fn or col).split(".")), False) is True
+            ):
+                _warn(f'Error filtering with "{col_fn or col}": not a valid Python identifier.')
+                continue
             # level 1 filtering
             filtered_list = [
                 e
@@ -642,7 +660,6 @@ class _GuiCoreContext(CoreEventConsumerBase):
             sorted_list = entities
             for sd in reversed(sorts):
                 col = sd.get("col", "")
-                col = _GuiCoreDatanodeProperties.get_col_name(col)
                 order = sd.get("order", True)
                 sorted_list = sorted(sorted_list, key=_get_entity_property(col, DataNode), reverse=not order)
         else:

+ 8 - 8
taipy/gui_core/viselements.json

@@ -100,8 +100,8 @@
                     },
                     {
                         "name": "filter",
-                        "type": "bool|str|list[str]",
-                        "default_value": "\"Config id;Label;Creation date;Cycle label;Cycle start;Cycle end;Primary;Tags\"",
+                        "type": "bool|str|ScenarioFilter|list[str|ScenarioFilter]",
+                        "default_value": "\"*\"",
                         "doc": "TODO: a list of <code>Scenario^</code> attributes to filter on. If False, do not allow filter."
                     },
                     {
@@ -112,8 +112,8 @@
                     },
                     {
                         "name": "sort",
-                        "type": "bool|str|list[str]",
-                        "default_value": "\"Config id;Label;Creation date\"",
+                        "type": "bool|str|ScenarioFilter|list[str|ScenarioFilter]",
+                        "default_value": "\"*\"",
                         "doc": "TODO: a list of <code>Scenario^</code> attributes to sort on. If False, do not allow sort."
                     }
                 ]
@@ -355,8 +355,8 @@
                     },
                     {
                         "name": "filter",
-                        "type": "bool|str|list[str]",
-                        "default_value": "\"Config id;Label;Up to date;Last edit date;Input;Output;Intermediate;Expiration date;Expired\"",
+                        "type": "bool|str|DataNodeFilter|list[str|DataNodeFilter]",
+                        "default_value": "\"*\"",
                         "doc": "TODO: a list of <code>DataNode^</code> attributes to filter on. If False, do not allow filter."
                     },
                     {
@@ -367,8 +367,8 @@
                     },
                     {
                         "name": "sort",
-                        "type": "bool|str|list[str]",
-                        "default_value": "\"Config id;Label;Last edit date;Expiration date\"",
+                        "type": "bool|str|DataNodeFilter|list[str|DataNodeFilter]",
+                        "default_value": "\"*\"",
                         "doc": "TODO: a list of <code>DataNode^</code> attributes to sort on. If False, do not allow sort."
                     }
                 ]