瀏覽代碼

Better UI for Core selector elements (#2237)

* Minor rewording.
* Added customization for field name in Filter and Sort.
Fabien Lelaquais 6 月之前
父節點
當前提交
5ba8435a02

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

@@ -133,6 +133,8 @@ export interface FilterDesc {
     type: string;
 }
 export interface TableFilterProps {
+    fieldHeader?: string;
+    fieldHeaderTooltip?: string;
     columns: Record<string, ColumnDesc>;
     colsOrder?: Array<string>;
     onValidate: (data: Array<FilterDesc>) => void;
@@ -148,6 +150,8 @@ export interface SortDesc {
 }
 
 export interface TableSortProps {
+    fieldHeader?: string;
+    fieldHeaderTooltip?: string;
     columns: Record<string, ColumnDesc>;
     colsOrder?: Array<string>;
     onValidate: (data: Array<SortDesc>) => void;

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

@@ -68,7 +68,7 @@ describe("Table Filter Component", () => {
         const elt = getByTestId("FilterListIcon");
         await userEvent.click(elt);
         expect(getAllByText("Column")).toHaveLength(2);
-        expect(getAllByText("Action")).toHaveLength(2);
+        expect(getAllByText("Condition")).toHaveLength(2);
         expect(getAllByText("Empty String")).toHaveLength(2);
         const dropdownElements = getAllByTestId("ArrowDropDownIcon");
         expect(dropdownElements).toHaveLength(2);
@@ -105,7 +105,7 @@ describe("Table Filter Component", () => {
         await userEvent.click(getByText("NumberCol"));
         await userEvent.click(dropdownElements[1].parentElement?.firstElementChild || dropdownElements[1]);
         await findByRole("listbox");
-        await userEvent.click(getByText("less equals"));
+        await userEvent.click(getByText("is less than or equal to"));
         const validate = getByTestId("CheckIcon").parentElement;
         expect(validate).toBeDisabled();
         const labels = getAllByText("Number");
@@ -128,7 +128,7 @@ describe("Table Filter Component", () => {
         await userEvent.click(getByText("BoolCol"));
         await userEvent.click(dropdownElements[1].parentElement?.firstElementChild || dropdownElements[1]);
         await findByRole("listbox");
-        await userEvent.click(getByText("equals"));
+        await userEvent.click(getByText("is"));
         const validate = getByTestId("CheckIcon").parentElement;
         expect(validate).toBeDisabled();
         const dddElements = getAllByTestId("ArrowDropDownIcon");
@@ -152,7 +152,7 @@ describe("Table Filter Component", () => {
         await userEvent.click(getByText("DateCol"));
         await userEvent.click(dropdownElements[1].parentElement?.firstElementChild || dropdownElements[1]);
         await findByRole("listbox");
-        await userEvent.click(getByText("before equal"));
+        await userEvent.click(getByText("is on or before"));
         const validate = getByTestId("CheckIcon").parentElement;
         expect(validate).toBeDisabled();
         const input = getByPlaceholderText("YYYY/MM/DD");
@@ -263,7 +263,7 @@ describe("Table Filter Component - Case Insensitive Test", () => {
         await userEvent.click(getByText("StringCol"));
 
         // Check for the case-sensitive toggle and interact with it
-        const caseButton = getByRole("button", { name: /case insensitive/i });
+        const caseButton = getByRole("button", { name: /ignore case/i });
         expect(caseButton).toBeInTheDocument(); // Ensure the button is rendered
         await userEvent.click(caseButton); // change case sensitivity
     });

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

@@ -36,6 +36,8 @@ import { getSuffixedClassNames } from "./utils";
 import { MatchCase } from "../icons/MatchCase";
 
 interface TableFilterProps {
+    fieldHeader?: string;
+    fieldHeaderTooltip?: string;
     columns: Record<string, ColumnDesc>;
     colsOrder?: Array<string>;
     onValidate: (data: Array<FilterDesc>) => void;
@@ -46,6 +48,8 @@ interface TableFilterProps {
 
 interface FilterRowProps {
     idx: number;
+    fieldHeader?: string;
+    fieldHeaderTooltip?: string;
     filter?: FilterDesc;
     columns: Record<string, ColumnDesc>;
     colsOrder: Array<string>;
@@ -58,23 +62,23 @@ const anchorOrigin = {
 } as PopoverOrigin;
 
 const actionsByType = {
-    string: { "==": "equals", contains: "contains", "!=": "not equals" },
+    string: { "==": "is", contains: "contains", "!=": "is not" },
     number: {
-        "<": "less",
-        "<=": "less equals",
+        "<": "is less than",
+        "<=": "is less than or equal to",
         "==": "equals",
-        "!=": "not equals",
-        ">=": "greater equals",
-        ">": "greater",
+        "!=": "does not equal",
+        ">=": "is greater than or equal to",
+        ">": "is greater than",
     },
-    boolean: { "==": "equals", "!=": "not equals" },
+    boolean: { "==": "is", "!=": "is not" },
     date: {
-        "<": "before",
-        "<=": "before equal",
-        "==": "equals",
-        "!=": "not equals",
-        ">=": "after equal",
-        ">": "after",
+        "<": "is before",
+        "<=": "is on or before",
+        "==": "is on",
+        "!=": "is not on",
+        ">=": "is on or after",
+        ">": "is after",
     },
 } as Record<string, Record<string, string>>;
 
@@ -114,22 +118,22 @@ const getFilterDesc = (
                         ? colType === "number"
                             ? parseFloat(val)
                             : colType === "boolean"
-                            ? val === "1"
-                            : colType === "date"
-                            ? getDateTime(val)
-                            : val
+                                ? val === "1"
+                                : colType === "date"
+                                    ? getDateTime(val)
+                                    : val
                         : val,
                 type: colType,
                 matchCase: !!matchCase,
             } as FilterDesc;
         } catch (e) {
-            console.info("could not parse value ", val, e);
+            console.info("Could not parse value ", val, e);
         }
     }
 };
 
 const FilterRow = (props: FilterRowProps) => {
-    const { idx, setFilter, columns, colsOrder, filter } = props;
+    const { idx, fieldHeader, fieldHeaderTooltip, filter, columns, colsOrder, setFilter } = props;
 
     const [colId, setColId] = useState<string>("");
     const [action, setAction] = useState<string>("");
@@ -225,22 +229,24 @@ const FilterRow = (props: FilterRowProps) => {
         <Grid container size={12} alignItems="center">
             <Grid size={3.5}>
                 <FormControl margin="dense">
-                    <InputLabel>Column</InputLabel>
-                    <Select value={colId || ""} onChange={onColSelect} input={<OutlinedInput label="Column" />}>
-                        {colsOrder.map((col) =>
-                            columns[col].filter ? (
-                                <MenuItem key={col} value={col}>
-                                    {columns[col].title || columns[col].dfid}
-                                </MenuItem>
-                            ) : null
-                        )}
-                    </Select>
+                    <InputLabel>{fieldHeader}</InputLabel>
+                    <Tooltip title={fieldHeaderTooltip} placement="top">
+                        <Select value={colId || ""} onChange={onColSelect} input={<OutlinedInput label={fieldHeader} />}>
+                            {colsOrder.map((col) =>
+                                columns[col].filter ? (
+                                    <MenuItem key={col} value={col}>
+                                        {columns[col].title || columns[col].dfid}
+                                    </MenuItem>
+                                ) : null
+                            )}
+                        </Select>
+                    </Tooltip>
                 </FormControl>
             </Grid>
             <Grid size={3}>
                 <FormControl margin="dense">
-                    <InputLabel>Action</InputLabel>
-                    <Select value={action || ""} onChange={onActSelect} input={<OutlinedInput label="Action" />}>
+                    <InputLabel>Condition</InputLabel>
+                    <Select value={action || ""} onChange={onActSelect} input={<OutlinedInput label="Condition" />}>
                         {Object.keys(getActionsByType(colType)).map((a) => (
                             <MenuItem key={a} value={a}>
                                 {getActionsByType(colType)[a]}
@@ -304,7 +310,7 @@ const FilterRow = (props: FilterRowProps) => {
                         slotProps={{
                             input: {
                                 endAdornment: (
-                                    <Tooltip title={matchCase ? "Case sensitive" : "Case insensitive"}>
+                                    <Tooltip title={matchCase ? "Exact match" : "Ignore case"}>
                                         <IconButton onClick={toggleMatchCase} size="small">
                                             <MatchCase color={matchCase ? "primary" : "disabled"} />
                                         </IconButton>
@@ -339,7 +345,15 @@ const FilterRow = (props: FilterRowProps) => {
 };
 
 const TableFilter = (props: TableFilterProps) => {
-    const { onValidate, appliedFilters, columns, className = "", filteredCount } = props;
+    const {
+        fieldHeader = "Column",
+        fieldHeaderTooltip = "Select the column to filter",
+        columns,
+        onValidate,
+        appliedFilters,
+        className = "",
+        filteredCount
+    } = props;
 
     const [showFilter, setShowFilter] = useState(false);
     const filterRef = useRef<HTMLButtonElement | null>(null);
@@ -416,6 +430,8 @@ const TableFilter = (props: TableFilterProps) => {
                             <FilterRow
                                 key={"fd" + idx}
                                 idx={idx}
+                                fieldHeader={fieldHeader}
+                                fieldHeaderTooltip={fieldHeaderTooltip}
                                 filter={fd}
                                 columns={columns}
                                 colsOrder={colsOrder}
@@ -424,6 +440,8 @@ const TableFilter = (props: TableFilterProps) => {
                         ))}
                         <FilterRow
                             idx={-(filters.length + 1)}
+                            fieldHeader={fieldHeader}
+                            fieldHeaderTooltip={fieldHeaderTooltip}
                             columns={columns}
                             colsOrder={colsOrder}
                             setFilter={updateFilter}

+ 30 - 13
frontend/taipy-gui/src/components/Taipy/TableSort.tsx

@@ -37,6 +37,8 @@ export interface SortDesc {
 }
 
 interface TableSortProps {
+    fieldHeader?: string;
+    fieldHeaderTooltip?: string;
     columns: Record<string, ColumnDesc>;
     colsOrder?: Array<string>;
     onValidate: (data: Array<SortDesc>) => void;
@@ -47,6 +49,8 @@ interface TableSortProps {
 interface SortRowProps {
     idx: number;
     sort?: SortDesc;
+    fieldHeader?: string;
+    fieldHeaderTooltip?: string;
     columns: Record<string, ColumnDesc>;
     colsOrder: Array<string>;
     setSort: (idx: number, fd: SortDesc, remove?: boolean) => void;
@@ -72,13 +76,13 @@ const orderCaptionSx = { ml: 1 };
 const getSortDesc = (columns: Record<string, ColumnDesc>, colId?: string, asc?: boolean) =>
     colId && asc !== undefined
         ? ({
-              col: columns[colId].dfid,
-              order: !!asc,
-          } as SortDesc)
+            col: columns[colId].dfid,
+            order: !!asc,
+        } as SortDesc)
         : undefined;
 
 const SortRow = (props: SortRowProps) => {
-    const { idx, setSort, columns, colsOrder, sort, appliedSorts } = props;
+    const { idx, sort, setSort, fieldHeader, fieldHeaderTooltip, columns, colsOrder, appliedSorts } = props;
 
     const [colId, setColId] = useState("");
     const [order, setOrder] = useState(true); // true => asc
@@ -133,13 +137,15 @@ const SortRow = (props: SortRowProps) => {
             <Grid size={6}>
                 <FormControl margin="dense">
                     <InputLabel>Column</InputLabel>
-                    <Select value={colId || ""} onChange={onColSelect} input={<OutlinedInput label="Column" />}>
-                        {cols.map((col) => (
-                            <MenuItem key={col} value={col}>
-                                {columns[col].title || columns[col].dfid}
-                            </MenuItem>
-                        ))}
-                    </Select>
+                    <Tooltip title={fieldHeaderTooltip} placement="top">
+                        <Select value={colId || ""} onChange={onColSelect} input={<OutlinedInput label={fieldHeader} />}>
+                            {cols.map((col) => (
+                                <MenuItem key={col} value={col}>
+                                    {columns[col].title || columns[col].dfid}
+                                </MenuItem>
+                            ))}
+                        </Select>
+                    </Tooltip>
                 </FormControl>
             </Grid>
             <Grid size={4}>
@@ -171,7 +177,14 @@ const SortRow = (props: SortRowProps) => {
 };
 
 const TableSort = (props: TableSortProps) => {
-    const { onValidate, appliedSorts, columns, className = "" } = props;
+    const {
+        fieldHeader = "Column",
+        fieldHeaderTooltip = "Select the column to sort",
+        columns,
+        onValidate,
+        appliedSorts,
+        className = ""
+    } = props;
 
     const [showSort, setShowSort] = useState(false);
     const sortRef = useRef<HTMLButtonElement | null>(null);
@@ -207,7 +220,7 @@ const TableSort = (props: TableSortProps) => {
             });
         },
         [onValidate]
-        );
+    );
 
     useEffect(() => {
         columns &&
@@ -243,6 +256,8 @@ const TableSort = (props: TableSortProps) => {
                             key={"fd" + idx}
                             idx={idx}
                             sort={sd}
+                            fieldHeader={fieldHeader}
+                            fieldHeaderTooltip={fieldHeaderTooltip}
                             columns={columns}
                             colsOrder={colsOrder}
                             setSort={updateSort}
@@ -251,6 +266,8 @@ const TableSort = (props: TableSortProps) => {
                     ))}
                     <SortRow
                         idx={-(sorts.length + 1)}
+                        fieldHeader={fieldHeader}
+                        fieldHeaderTooltip={fieldHeaderTooltip}
                         columns={columns}
                         colsOrder={colsOrder}
                         setSort={updateSort}

+ 67 - 58
frontend/taipy/src/CoreSelector.tsx

@@ -137,19 +137,19 @@ const CoreItem = (props: {
     return !props.displayCycles && nodeType === NodeType.CYCLE ? (
         <>
             {items
-                ? items.filter(v=>v).map((item) => (
-                      <CoreItem
-                          key={item[0]}
-                          item={item}
-                          displayCycles={false}
-                          showPrimaryFlag={props.showPrimaryFlag}
-                          leafType={props.leafType}
-                          pins={props.pins}
-                          onPin={props.onPin}
-                          hideNonPinned={props.hideNonPinned}
-                          active={props.active}
-                      />
-                  ))
+                ? items.filter(v => v).map((item) => (
+                    <CoreItem
+                        key={item[0]}
+                        item={item}
+                        displayCycles={false}
+                        showPrimaryFlag={props.showPrimaryFlag}
+                        leafType={props.leafType}
+                        pins={props.pins}
+                        onPin={props.onPin}
+                        hideNonPinned={props.hideNonPinned}
+                        active={props.active}
+                    />
+                ))
                 : null}
         </>
     ) : isShown ? (
@@ -207,20 +207,20 @@ const CoreItem = (props: {
             sx={nodeType === NodeType.NODE ? undefined : ParentItemSx}
         >
             {items
-                ? items.filter(v=>v).map((item) => (
-                      <CoreItem
-                          key={item[0]}
-                          item={item}
-                          displayCycles={true}
-                          showPrimaryFlag={props.showPrimaryFlag}
-                          leafType={props.leafType}
-                          editComponent={props.editComponent}
-                          pins={props.pins}
-                          onPin={props.onPin}
-                          hideNonPinned={props.hideNonPinned}
-                          active={props.active}
-                      />
-                  ))
+                ? items.filter(v => v).map((item) => (
+                    <CoreItem
+                        key={item[0]}
+                        item={item}
+                        displayCycles={true}
+                        showPrimaryFlag={props.showPrimaryFlag}
+                        leafType={props.leafType}
+                        editComponent={props.editComponent}
+                        pins={props.pins}
+                        onPin={props.onPin}
+                        hideNonPinned={props.hideNonPinned}
+                        active={props.active}
+                    />
+                ))
                 : null}
         </TreeItem>
     ) : null;
@@ -437,7 +437,7 @@ const CoreSelector = (props: CoreSelectorProps) => {
         if (coreChanged?.scenario) {
             const updateVar = getUpdateVar(updateVars, lovPropertyName);
             updateVar && dispatch(createRequestUpdateAction(id, module, [updateVar], true));
-    }
+        }
     }, [coreChanged, updateVars, module, dispatch, id, lovPropertyName]);
 
     const treeViewSx = useMemo(() => ({ ...BaseTreeViewSx, maxHeight: props.height || "50vh" }), [props.height]);
@@ -506,17 +506,17 @@ const CoreSelector = (props: CoreSelectorProps) => {
                 : undefined;
             return Array.isArray(res)
                 ? 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>)
+                    pv[name] = {
+                        dfid: id,
+                        title: name,
+                        type: coltype,
+                        index: idx,
+                        filter: true,
+                        lov: lov,
+                        freeLov: !!lov,
+                    };
+                    return pv;
+                }, {} as Record<string, ColumnDesc>)
                 : undefined;
         } catch {
             return undefined;
@@ -557,9 +557,9 @@ const CoreSelector = (props: CoreSelectorProps) => {
             const res = props.sort ? (JSON.parse(props.sort) as Array<[string, string]>) : undefined;
             return Array.isArray(res)
                 ? res.reduce((pv, [name, id], idx) => {
-                      pv[name] = { dfid: id, title: name, type: "str", index: idx };
-                      return pv;
-                  }, {} as Record<string, ColumnDesc>)
+                    pv[name] = { dfid: id, title: name, type: "str", index: idx };
+                    return pv;
+                }, {} as Record<string, ColumnDesc>)
                 : undefined;
         } catch {
             return undefined;
@@ -626,6 +626,8 @@ const CoreSelector = (props: CoreSelectorProps) => {
                 {active && colFilters ? (
                     <Grid>
                         <TableFilter
+                            fieldHeader="Property"
+                            fieldHeaderTooltip="Select the property to filter"
                             columns={colFilters}
                             appliedFilters={filters}
                             filteredCount={0}
@@ -636,7 +638,14 @@ const CoreSelector = (props: CoreSelectorProps) => {
                 ) : null}
                 {active && colSorts ? (
                     <Grid>
-                        <TableSort columns={colSorts} appliedSorts={sorts} onValidate={applySorts} className={className}></TableSort>
+                        <TableSort
+                            fieldHeader="Property"
+                            fieldHeaderTooltip="Select the property to sort"
+                            columns={colSorts}
+                            appliedSorts={sorts}
+                            onValidate={applySorts}
+                            className={className}>
+                            </TableSort>
                     </Grid>
                 ) : null}
                 {showSearch ? (
@@ -691,21 +700,21 @@ const CoreSelector = (props: CoreSelectorProps) => {
             >
                 {foundEntities
                     ? foundEntities.map((item) =>
-                          item ? (
-                              <CoreItem
-                                  key={item[0]}
-                                  item={item}
-                                  displayCycles={displayCycles}
-                                  showPrimaryFlag={showPrimaryFlag}
-                                  leafType={leafType}
-                                  editComponent={props.editComponent}
-                                  onPin={showPins ? onPin : undefined}
-                                  pins={pins}
-                                  hideNonPinned={hideNonPinned}
-                                  active={!!active}
-                              />
-                          ) : null
-                      )
+                        item ? (
+                            <CoreItem
+                                key={item[0]}
+                                item={item}
+                                displayCycles={displayCycles}
+                                showPrimaryFlag={showPrimaryFlag}
+                                leafType={leafType}
+                                editComponent={props.editComponent}
+                                onPin={showPins ? onPin : undefined}
+                                pins={pins}
+                                hideNonPinned={hideNonPinned}
+                                active={!!active}
+                            />
+                        ) : null
+                    )
                     : null}
             </SimpleTreeView>
             {props.children}

+ 1 - 1
taipy/gui_core/_adapters.py

@@ -338,7 +338,7 @@ def _invoke_action(
         return False
     try:
         if col_type == "any":
-            # when a property is not found, return True only if action is not equals
+            # when a property is not found, return True only if action is "not equal"
             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):

+ 7 - 5
taipy/gui_core/filters.py

@@ -39,8 +39,10 @@ class _Filter(_DoNotUpdate):
 
 @dataclass
 class ScenarioFilter(_Filter):
-    """
-    used to describe a filter on a scenario property
+    """NOT DOCUMENTED
+    A filter on a scenario property.
+
+
     """
 
     property_id: str
@@ -51,7 +53,7 @@ class ScenarioFilter(_Filter):
 
 @dataclass
 class DataNodeScenarioFilter(_Filter):
-    """
+    """NOT DOCUMENTED
     used to describe a filter on a scenario datanode's property
     """
 
@@ -67,7 +69,7 @@ _CUSTOM_PREFIX = "fn:"
 
 @dataclass
 class CustomScenarioFilter(_Filter):
-    """
+    """NOT DOCUMENTED
     used to describe a custom scenario filter ie based on a user defined function
     """
 
@@ -89,7 +91,7 @@ class CustomScenarioFilter(_Filter):
 
 @dataclass
 class DataNodeFilter(_Filter):
-    """
+    """NOT DOCUMENTED
     used to describe a filter on a datanode property
     """
 

+ 95 - 94
taipy/gui_core/viselements.json

@@ -10,9 +10,39 @@
                     {
                         "name": "value",
                         "default_property": true,
-                        "type": "dynamic(Scenario)",
+                        "type": "dynamic(Union[Scenario, list[Scenario]])",
                         "doc": "Bound to the selected <code>Scenario^</code>, or None if there is none."
                     },
+                    {
+                        "name": "scenarios",
+                        "type": "dynamic(list[Union[Scenario,Cycle]])",
+                        "default_value": "None",
+                        "doc": "The list of <code>Cycle^</code> or <code>Scenario^</code> objects to show.<br/>If this is None, all Cycles and Scenarios are listed."
+                    },
+                    {
+                        "name": "multiple",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "If True, the user can select multiple scenarios, therefore the <i>value</i> property can hold a list of <code>Scenario^</code> objects."
+                    },
+                    {
+                        "name": "filter",
+                        "type": "Union[bool,str,taipy.gui_core.filters.ScenarioFilter,list[Union[str,taipy.gui_core.filters.ScenarioFilter]]]",
+                        "default_value": "\"*\"",
+                        "doc": "One or multiple <code>Scenario^</code> property names to filter on.<br/>If False, do not allow filter."
+                    },
+                    {
+                        "name": "show_search",
+                        "type": "bool",
+                        "default_value": "True",
+                        "doc": "If False, prevents users from searching for scenarios by label."
+                    },
+                    {
+                        "name": "sort",
+                        "type": "Union[bool,str,taipy.gui_core.filters.ScenarioFilter,list[Union[str,taipy.gui_core.filters.ScenarioFilter]]]",
+                        "default_value": "\"*\"",
+                        "doc": "A list of <code>Scenario^</code> property names to sort on.<br/>If False, do not allow sort."
+                    },
                     {
                         "name": "show_add_button",
                         "type": "bool",
@@ -31,6 +61,12 @@
                         "default_value": "True",
                         "doc": "If False, the primary scenarios are not identified with specific visual hint."
                     },
+                    {
+                        "name": "show_pins",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "If True, a pin is shown on each item of the selector and allows to restrict the number of displayed items."
+                    },
                     {
                         "name": "on_change",
                         "type": "Union[str, Callable]",
@@ -51,21 +87,15 @@
                         ]
                     },
                     {
-                        "name": "height",
-                        "type": "str",
-                        "default_value": "\"50vh\"",
-                        "doc": "The maximum height, in CSS units, of the control."
-                    },
-                    {
-                        "name": "show_pins",
+                        "name": "show_dialog",
                         "type": "bool",
-                        "default_value": "False",
-                        "doc": "If True, a pin is shown on each item of the selector and allows to restrict the number of displayed items."
+                        "default_value": "True",
+                        "doc": "If True, a dialog is shown when the user click on the 'Add scenario' button."
                     },
                     {
                         "name": "on_creation",
                         "type": "Union[str, Callable]",
-                        "doc": "A function or the name of a function that is triggered when a scenario is about to be created.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of this scenario selector.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>config (str): the name of the selected scenario configuration.</li>\n<li>date (datetime): the creation date for the new scenario.</li>\n<li>label (str): the user-specified label.</li>\n<li>properties (dic): a dictionary containing all the user-defined custom properties.</li>\n</ul>\n</li>\n<li>The callback function can return a scenario, a string containing an error message (a scenario will not be created), or None (then a new scenario is created with the user parameters).</li>\n</ul>",
+                        "doc": "A function or the name of a function that is triggered when a scenario is about to be created.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of this scenario selector.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>config (str): the name of the selected scenario configuration.</li>\n<li>date (datetime): the creation date for the new scenario.</li>\n<li>label (str): the user-specified label.</li>\n<li>properties (dict): a dictionary containing all the user-defined custom properties.</li>\n</ul>\n</li>\n<li>The callback function can return a scenario, a string containing an error message (a scenario will not be created), or None (then a new scenario is created with the user parameters).</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -82,39 +112,10 @@
                         ]
                     },
                     {
-                        "name": "show_dialog",
-                        "type": "bool",
-                        "default_value": "True",
-                        "doc": "If True, a dialog is shown when the user click on the 'Add scenario' button."
-                    },
-                    {
-                        "name": "scenarios",
-                        "type": "dynamic(list[Scenario|Cycle])",
-                        "doc": "TODO: The list of <code>Scenario^</code>/<code>Cycle^</code> to show. Shows all Cycle/Scenario if value is None."
-                    },
-                    {
-                        "name": "multiple",
-                        "type": "bool",
-                        "default_value": "False",
-                        "doc": "TODO: If True, the user can select multiple scenarios."
-                    },
-                    {
-                        "name": "filter",
-                        "type": "bool|str|taipy.gui_core.filters.ScenarioFilter|list[str|taipy.gui_core.filters.ScenarioFilter]",
-                        "default_value": "\"*\"",
-                        "doc": "TODO: a list of <code>Scenario^</code> attributes to filter on. If False, do not allow filter."
-                    },
-                    {
-                        "name": "show_search",
-                        "type": "bool",
-                        "default_value": "True",
-                        "doc": "TODO: If True, allows the user to search locally on label."
-                    },
-                    {
-                        "name": "sort",
-                        "type": "bool|str|taipy.gui_core.filters.ScenarioFilter|list[str|taipy.gui_core.filters.ScenarioFilter]",
-                        "default_value": "\"*\"",
-                        "doc": "TODO: a list of <code>Scenario^</code> attributes to sort on. If False, do not allow sort."
+                        "name": "height",
+                        "type": "str",
+                        "default_value": "\"50vh\"",
+                        "doc": "The maximum height, in CSS units, of the control."
                     }
                 ]
             }
@@ -129,7 +130,7 @@
                     {
                         "name": "scenario",
                         "default_property": true,
-                        "type": "dynamic(Scenario|list[Scenario])",
+                        "type": "dynamic(Union[Scenario,list[Scenario]])",
                         "doc": "The scenario to display and edit.<br/>If the value is a list, it must have a single element otherwise nothing is shown."
                     },
                     {
@@ -236,7 +237,7 @@
                     {
                         "name": "scenario",
                         "default_property": true,
-                        "type": "dynamic(Scenario|list[Scenario])",
+                        "type": "dynamic(Union[Scenario,list[Scenario]])",
                         "doc": "The <code>Scenario^</code> whose diagram is displayed.<br/>If the value is a list, it must have a single element otherwise nothing is shown."
                     },
                     {
@@ -266,7 +267,7 @@
                     {
                         "name": "on_action",
                         "type": "Union[str, Callable]",
-                        "doc": "A function or the name of a function that is triggered when a a node is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>entity (DataNode | Task): the entity (DataNode or Task) that was selected.</li>\n</ul>",
+                        "doc": "A function or the name of a function that is triggered when a a node is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>entity (DataNode or Task): the entity that was selected.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -274,7 +275,7 @@
                             ],
                             [
                                 "entity",
-                                "Task | DataNode"
+                                "Union[Task,DataNode]"
                             ]
                         ]
                     }
@@ -291,8 +292,48 @@
                     {
                         "name": "value",
                         "default_property": true,
-                        "type": "dynamic(DataNode|list[DataNode])",
-                        "doc": "Bound to the selected <code>DataNode^</code>(s), or None if there is none."
+                        "type": "dynamic(Union[DataNode,list[DataNode]])",
+                        "doc": "Bound to the selected <code>DataNode^</code> or <code>DataNode^</code>s, or None if there is none."
+                    },
+                    {
+                        "name": "scenario",
+                        "type": "dynamic(Union[Scenario,list[Scenario]])",
+                        "doc": "If set, the selector will only show the data nodes owned by this scenario or any scenario in the list."
+                    },
+                    {
+                        "name": "datanodes",
+                        "type": "dynamic(list[Union[DataNode,Scenario,Cycle]])",
+                        "doc": "The list of <code>DataNode^</code>s, <code>Scenario^</code>s, or <code>Cycle^</code>s to show.<br/>All all DataNodes, Scenarios, and Cycles are shown if this is None."
+                    },
+                    {
+                        "name": "multiple",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "If True, the user can select multiple data nodes, therefore the <i>value</i> property can hold a list of <code>DataNode^</code> objects."
+                    },
+                    {
+                        "name": "filter",
+                        "type": "Union[bool,str,taipy.gui_core.filters.DataNodeFilter,list[Union[str,taipy.gui_core.filters.DataNodeFilter]]]",
+                        "default_value": "\"*\"",
+                        "doc": "A list of <code>DataNode^</code> property names to filter on.<br/>If False, users cannot filter data nodes."
+                    },
+                    {
+                        "name": "sort",
+                        "type": "Union[bool,str,taipy.gui_core.filters.DataNodeFilter,list[Union[str,taipy.gui_core.filters.DataNodeFilter]]]",
+                        "default_value": "\"*\"",
+                        "doc": "A list of <code>DataNode^</code> property names to sort on.<br/>If False, do not allow sort."
+                    },
+                    {
+                        "name": "show_search",
+                        "type": "bool",
+                        "default_value": "True",
+                        "doc": "If False, prevents users from searching for data nodes by label."
+                    },
+                    {
+                        "name": "show_pins",
+                        "type": "bool",
+                        "default_value": "True",
+                        "doc": "If True, a pin is shown on each item of the selector and allows to restrict the number of displayed items."
                     },
                     {
                         "name": "display_cycles",
@@ -330,46 +371,6 @@
                         "type": "str",
                         "default_value": "\"50vh\"",
                         "doc": "The maximum height, in CSS units, of the control."
-                    },
-                    {
-                        "name": "show_pins",
-                        "type": "bool",
-                        "default_value": "True",
-                        "doc": "If True, a pin is shown on each item of the selector and allows to restrict the number of displayed items."
-                    },
-                    {
-                        "name": "scenario",
-                        "type": "dynamic(Scenario|list[Scenario])",
-                        "doc": "TODO: If the <code>Scenario^</code> is set, the selector will only show datanodes owned by this scenario."
-                    },
-                    {
-                        "name": "datanodes",
-                        "type": "dynamic(list[DataNode|Scenario|Cycle])",
-                        "doc": "TODO: The list of <code>DataNode^</code>/<code>Scenario^</code>/<code>Cycle^</code> to show. Shows all Cycle/Scenario/DataNode if value is None."
-                    },
-                    {
-                        "name": "multiple",
-                        "type": "bool",
-                        "default_value": "False",
-                        "doc": "TODO: If True, the user can select multiple datanodes."
-                    },
-                    {
-                        "name": "filter",
-                        "type": "bool|str|taipy.gui_core.filters.DataNodeFilter|list[str|taipy.gui_core.filters.DataNodeFilter]",
-                        "default_value": "\"*\"",
-                        "doc": "TODO: a list of <code>DataNode^</code> attributes to filter on. If False, do not allow filter."
-                    },
-                    {
-                        "name": "show_search",
-                        "type": "bool",
-                        "default_value": "True",
-                        "doc": "TODO: If True, allows the user to search locally on label."
-                    },
-                    {
-                        "name": "sort",
-                        "type": "bool|str|taipy.gui_core.filters.DataNodeFilter|list[str|taipy.gui_core.filters.DataNodeFilter]",
-                        "default_value": "\"*\"",
-                        "doc": "TODO: a list of <code>DataNode^</code> attributes to sort on. If False, do not allow sort."
                     }
                 ]
             }
@@ -384,7 +385,7 @@
                     {
                         "name": "data_node",
                         "default_property": true,
-                        "type": "dynamic(DataNode|list[DataNode])",
+                        "type": "dynamic(Union[DataNode,list[DataNode]])",
                         "doc": "The data node to display and edit.<br/>If the value is a list, it must have a single element otherwise nothing is shown."
                     },
                     {
@@ -439,7 +440,7 @@
                         "name": "show_owner_label",
                         "type": "bool",
                         "default_value": "False",
-                        "doc": "If True, the data node owner label is added to the datanode label at the top of the block."
+                        "doc": "If True, the data node owner label is added to the data node label at the top of the block."
                     },
                     {
                         "name": "show_custom_properties",
@@ -482,7 +483,7 @@
                     {
                         "name": "value",
                         "default_property": true,
-                        "type": "dynamic(Job|list[Job])",
+                        "type": "dynamic(Union[Job,list[Job]])",
                         "doc": "Bound to the selected <code>Job^</code>(s), or None if there is none."
                     },
                     {
@@ -555,7 +556,7 @@
                     {
                         "name": "on_details",
                         "type": "Union[str, Callable, bool]",
-                        "doc": "The name of a function that is triggered when the details icon is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the id of the control.</li>\n<li>payload (<code>dict</code>): a dictionary that contains the Job Id in the value for key <i>args<i>.</li>\n</ul></br>If False, the icon is not shown.",
+                        "doc": "The name of a function that is triggered when the details icon is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the id of the control.</li>\n<li>payload (<code>dict</code>): a dictionary that contains the Job Id in the value for key <i>args</i>.</li>\n</ul></br>If False, the icon is not shown.",
                         "signature": [
                             [
                                 "state",