瀏覽代碼

Support enum in lov (#1860)

* Support enum in lov

* support non bound lov

* fix test and json

* Fab's comments
resolves #1845
rename style to row_class_name and cell_class_name

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 7 月之前
父節點
當前提交
71aabfd99a

+ 18 - 10
doc/gui/examples/controls/table_styling_cells/builder.py

@@ -23,23 +23,23 @@ x_range = range(0, 11)
 # The dataset is made of three arrays:
 # x: an integer value from 0 to 5
 # y: the square of the value in the "x" column
-# z: a random integer betweewn 0 and 5
+# z: a random integer between 0 and 5
 data = {"x": x_range, "y": [x * x for x in x_range], "z": [randrange(6) for _ in x_range]}
 
 
-def xy_style(_1, _2, index, _3, column_name):
+def xy_class(_1, _2, index, _3, column_name):
     return (
         # The background color of the 'x' column alternates between two shades of green,
         # varying in lightness.
-        ("greenish" if index % 2 else "lightgreenish")
+        ("greenish" if index % 2 else "light-greenish")
         if column_name == "x"
         # The background color of the 'y' column alternates between two shades of green,
         # varying in lightness
-        else ("reddish" if index % 2 else "lightreddish")
+        else ("reddish" if index % 2 else "light-reddish")
     )
 
 
-def z_style(_, value):
+def z_class(_, value):
     # Build a CSS classname from the value.
     # The lower the value is, the lighter the color is.
     return f"col{value}"
@@ -48,9 +48,9 @@ def z_style(_, value):
 with tgb.Page(
     style={
         ".reddish": {"color": "white", "background-color": "#bf1313"},
-        ".lightreddish": {"color": "black", "background-color": "#ff1919", "font-weight": "bold"},
+        ".light-reddish": {"color": "black", "background-color": "#ff1919", "font-weight": "bold"},
         ".greenish": {"color": "white", "background-color": "#75bf75"},
-        ".lightgreenish": {"color": "black", "background-color": "#9cff9c", "font-weight": "bold"},
+        ".light-greenish": {"color": "black", "background-color": "#9cff9c", "font-weight": "bold"},
         ".col0": {"background-color": "#d0d0d0"},
         ".col1": {"background-color": "#a4a0cf"},
         ".col2": {"background-color": "#7970cf"},
@@ -59,9 +59,17 @@ with tgb.Page(
         ".col5": {"background-color": "#1b02a8", "color": "white"},
     }
 ) as page:
-    tgb.table("{data}", style__x=xy_style, style__y=xy_style, style__z=z_style, show_all=True)
-    # Using a lambda function instead of z_style:
-    #tgb.table("{data}", style__x=xy_style, style__y=xy_style, style__z=lambda _, v: f"col{v}", show_all=True)
+    tgb.table(
+        "{data}", cell_class_name__x=xy_class, cell_class_name__y=xy_class, cell_class_name__z=z_class, show_all=True
+    )
+    # Using a lambda function instead of z_class:
+    # tgb.table(
+    #     "{data}",
+    #     cell_class_name__x=xy_class,
+    #     cell_class_name__y=xy_class,
+    #     cell_class_name__z=lambda _, v: f"col{v}",
+    #     show_all=True,
+    # )
 
 
 if __name__ == "__main__":

+ 11 - 11
doc/gui/examples/controls/table_styling_cells/markdown.py

@@ -22,44 +22,44 @@ x_range = range(0, 11)
 # The dataset is made of three arrays:
 # x: an integer value from 0 to 5
 # y: the square of the value in the "x" column
-# z: a random integer betweewn 0 and 5
+# z: a random integer between 0 and 5
 data = {"x": x_range, "y": [x * x for x in x_range], "z": [randrange(6) for _ in x_range]}
 
 
-def xy_style(_1, _2, index, _3, column_name):
+def xy_class(_1, _2, index, _3, column_name):
     return (
         # The background color of the 'x' column alternates between two shades of green,
         # varying in lightness.
-        ("greenish" if index % 2 else "lightgreenish")
+        ("greenish" if index % 2 else "light-greenish")
         if column_name == "x"
         # The background color of the 'y' column alternates between two shades of green,
         # varying in lightness
-        else ("reddish" if index % 2 else "lightreddish")
+        else ("reddish" if index % 2 else "light-reddish")
     )
 
 
-def z_style(_, value):
+def z_class(_, value):
     # Build a CSS classname from the value.
     # The lower the value is, the lighter the color is.
     return f"col{value}"
 
 
 page = Markdown(
-    "<|{data}|table|style[x]=xy_style|style[y]=xy_style|style[z]=z_style|show_all|>",
-    # Using a lambda function instead of z_style:
-    #"<|{data}|table|style[x]=xy_style|style[y]=xy_style|style[z]={lambda _, v: f'col{v}'}|show_all|>",
+    "<|{data}|table|cell_class_name[x]=xy_class|cell_class_name[y]=xy_class|cell_class_name[z]=z_class|show_all|>",
+    # Using a lambda function instead of z_class:
+    # "<|{data}|table|cell_class_name[x]=xy_class|cell_class_name[y]=xy_class|cell_class_name[z]={lambda _, v: f'col{v}'}|show_all|>", # noqa: E501
     style={
         ".reddish": {"color": "white", "background-color": "#bf1313"},
-        ".lightreddish": {"color": "black", "background-color": "#ff1919", "font-weight": "bold"},
+        ".light-reddish": {"color": "black", "background-color": "#ff1919", "font-weight": "bold"},
         ".greenish": {"color": "white", "background-color": "#75bf75"},
-        ".lightgreenish": {"color": "black", "background-color": "#9cff9c", "font-weight": "bold"},
+        ".light-greenish": {"color": "black", "background-color": "#9cff9c", "font-weight": "bold"},
         ".col0": {"background-color": "#d0d0d0"},
         ".col1": {"background-color": "#a4a0cf"},
         ".col2": {"background-color": "#7970cf"},
         ".col3": {"background-color": "#4e40cf", "color": "white"},
         ".col4": {"background-color": "#2410cf", "color": "white"},
         ".col5": {"background-color": "#1b02a8", "color": "white"},
-    }
+    },
 )
 
 

+ 4 - 4
doc/gui/examples/controls/table_styling_rows/builder.py

@@ -21,7 +21,7 @@ x_range = range(-10, 11, 4)
 data = {"x": x_range, "y": [x * x for x in x_range]}
 
 
-def even_odd_style(_, row):
+def even_odd_class(_, row):
     if row % 2:
         # Odd rows are blue
         return "blue-row"
@@ -36,9 +36,9 @@ with tgb.Page(
         ".red-row>td": {"color": "yellow", "background-color": "red"},
     }
 ) as page:
-    tgb.table("{data}", style=even_odd_style, show_all=True)
-    # Lambda version, getting rid of even_odd_style():
-    # tgb.table("{data}", style=lambda _, row: "blue-row" if row % 2 else "red-row", show_all=True)
+    tgb.table("{data}", row_class_name=even_odd_class, show_all=True)
+    # Lambda version, getting rid of even_odd_class():
+    # tgb.table("{data}", row_class_name=lambda _, row: "blue-row" if row % 2 else "red-row", show_all=True)
 
 
 if __name__ == "__main__":

+ 4 - 4
doc/gui/examples/controls/table_styling_rows/markdown.py

@@ -20,7 +20,7 @@ x_range = range(-10, 11, 4)
 data = {"x": x_range, "y": [x * x for x in x_range]}
 
 
-def even_odd_style(_1, row):
+def even_odd_class(_, row):
     if row % 2:
         # Odd rows are blue
         return "blue-row"
@@ -29,11 +29,11 @@ def even_odd_style(_1, row):
         return "red-row"
 
 
-# Lambda version, getting rid of even_odd_style():
+# Lambda version, getting rid of even_odd_class():
 # Replace the table control definition with
-#    <|{data}|table|style={lambda _, row: 'blue-row' if row % 2 else 'red-row'}|show_all|>
+#    <|{data}|table|row_class_name={lambda _, row: 'blue-row' if row % 2 else 'red-row'}|show_all|>
 page = Markdown(
-    "<|{data}|table|style=even_odd_style|show_all|>",
+    "<|{data}|table|row_class_name=even_odd_class|show_all|>",
     style={
         ".blue-row>td": {"color": "white", "background-color": "blue"},
         ".red-row>td": {"color": "yellow", "background-color": "red"},

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

@@ -343,10 +343,13 @@ export interface ColumnDesc {
     notEditable?: boolean;
     /** The name of the column that holds the CSS classname to
      *  apply to the cells. */
-    style?: string;
+    className?: string;
     /** The name of the column that holds the tooltip to
      *  show on the cells. */
     tooltip?: string;
+    /** The name of the column that holds the formatted value to
+     *  show on the cells. */
+    formatFn?: string;
     /** The value that would replace a NaN value. */
     nanValue?: string;
     /** The TimeZone identifier used if the type is `date`. */
@@ -362,9 +365,6 @@ export interface ColumnDesc {
     lov?: string[];
     /** If true the user can enter any value besides the lov values. */
     freeLov?: boolean;
-    /** The name of the column that holds the formatted value to
-     *  show on the cells. */
-    formatFn?: string;
 }
 /**
  * A cell value type.

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

@@ -161,7 +161,7 @@ describe("AutoLoadingTable Component", () => {
                 start: 0,
                 aggregates: [],
                 applies: undefined,
-                styles: undefined,
+                cellClassNames: undefined,
                 tooltips: undefined,
                 filters: []
             },
@@ -211,9 +211,9 @@ describe("AutoLoadingTable Component", () => {
                 />
             </TaipyContext.Provider>
         );
-        const elts = getAllByText("Austria");
-        expect(elts.length).toBeGreaterThan(1);
-        expect(elts[0].tagName).toBe("SPAN");
+        const elements = getAllByText("Austria");
+        expect(elements.length).toBeGreaterThan(1);
+        expect(elements[0].tagName).toBe("SPAN");
     });
     it("selects the rows", async () => {
         const dispatch = jest.fn();
@@ -235,8 +235,8 @@ describe("AutoLoadingTable Component", () => {
                 />
             </TaipyContext.Provider>
         );
-        const elts = getAllByText("Austria");
-        elts.forEach((elt: HTMLElement, idx: number) =>
+        const elements = getAllByText("Austria");
+        elements.forEach((elt: HTMLElement, idx: number) =>
             selected.indexOf(idx) == -1
                 ? expect(elt.parentElement?.parentElement?.parentElement?.parentElement).not.toHaveClass("Mui-selected")
                 : expect(elt.parentElement?.parentElement?.parentElement?.parentElement).toHaveClass("Mui-selected")

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

@@ -55,7 +55,7 @@ import {
     addActionColumn,
     headBoxSx,
     getClassName,
-    LINE_STYLE,
+    ROW_CLASS_NAME,
     iconInRowSx,
     DEFAULT_SIZE,
     OnRowSelection,
@@ -94,7 +94,7 @@ interface RowData {
     onDeletion?: OnRowDeletion;
     onRowSelection?: OnRowSelection;
     onRowClick?: OnRowClick;
-    lineStyle?: string;
+    rowClassName?: string;
     nanValue?: string;
     compRows?: RowType[];
     useCheckbox?: boolean;
@@ -102,7 +102,7 @@ interface RowData {
 
 const Row = ({
     index,
-    style,
+    style: rowSx,
     data: {
         colsOrder,
         columns,
@@ -117,7 +117,7 @@ const Row = ({
         onDeletion,
         onRowSelection,
         onRowClick,
-        lineStyle,
+        rowClassName,
         nanValue,
         compRows,
         useCheckbox,
@@ -133,8 +133,8 @@ const Row = ({
             tabIndex={-1}
             key={`row${index}`}
             component="div"
-            sx={style}
-            className={(classes && classes.row) + " " + getClassName(rows[index], lineStyle)}
+            sx={rowSx}
+            className={(classes && classes.row) + " " + getClassName(rows[index], rowClassName)}
             data-index={index}
             selected={selection.indexOf(index) > -1}
             onClick={onRowClick}
@@ -142,7 +142,7 @@ const Row = ({
             {colsOrder.map((col, cIdx) => (
                 <EditableCell
                     key={`cell${index}${columns[col].dfid}`}
-                    className={getClassName(rows[index], columns[col].style, col)}
+                    className={getClassName(rows[index], columns[col].className, col)}
                     tableClassName={tableClassName}
                     colDesc={columns[col]}
                     value={rows[index][col]}
@@ -161,7 +161,7 @@ const Row = ({
             ))}
         </TableRow>
     ) : (
-        <Skeleton sx={style} key={"Skeleton" + index} />
+        <Skeleton sx={rowSx} key={"Skeleton" + index} />
     );
 
 interface PromiseProps {
@@ -285,7 +285,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
         e.stopPropagation();
     }, []);
 
-    const [colsOrder, columns, styles, tooltips, formats, handleNan, filter, partialEditable] = useMemo(() => {
+    const [colsOrder, columns, cellClassNames, tooltips, formats, handleNan, filter, partialEditable] = useMemo(() => {
         let hNan = !!props.nanValue;
         if (baseColumns) {
             try {
@@ -315,9 +315,9 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                 );
                 const colsOrder = Object.keys(newCols).sort(getSortByIndex(newCols));
                 const styTt = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
-                    if (newCols[col].style) {
-                        pv.styles = pv.styles || {};
-                        pv.styles[newCols[col].dfid] = newCols[col].style as string;
+                    if (newCols[col].className) {
+                        pv.classNames = pv.classNames || {};
+                        pv.classNames[newCols[col].dfid] = newCols[col].className as string;
                     }
                     hNan = hNan || !!newCols[col].nanValue;
                     if (newCols[col].tooltip) {
@@ -330,11 +330,11 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                     }
                     return pv;
                 }, {});
-                if (props.lineStyle) {
-                    styTt.styles = styTt.styles || {};
-                    styTt.styles[LINE_STYLE] = props.lineStyle;
+                if (props.rowClassName) {
+                    styTt.classNames = styTt.classNames || {};
+                    styTt.classNames[ROW_CLASS_NAME] = props.rowClassName;
                 }
-                return [colsOrder, newCols, styTt.styles, styTt.tooltips, styTt.formats, hNan, filter, partialEditable];
+                return [colsOrder, newCols, styTt.classNames, styTt.tooltips, styTt.formats, hNan, filter, partialEditable];
             } catch (e) {
                 console.info("ATable.columns: " + ((e as Error).message || e));
             }
@@ -355,7 +355,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
         onAdd,
         onDelete,
         baseColumns,
-        props.lineStyle,
+        props.rowClassName,
         props.tooltip,
         props.nanValue,
         props.filter,
@@ -387,7 +387,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
             return new Promise<void>((resolve, reject) => {
                 const cols = colsOrder.map((col) => columns[col].dfid).filter((c) => c != EDIT_COL);
                 const afs = appliedFilters.filter((fd) => Object.values(columns).some((cd) => cd.dfid === fd.col));
-                const key = getPageKey(columns, "Infinite", cols, orderBy, order, afs, aggregates, styles, tooltips, formats);
+                const key = getPageKey(columns, "Infinite", cols, orderBy, order, afs, aggregates, cellClassNames, tooltips, formats);
                 page.current = {
                     key: key,
                     promises: { ...page.current.promises, [startIndex]: { resolve: resolve, reject: reject } },
@@ -413,7 +413,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                         order,
                         aggregates,
                         applies,
-                        styles,
+                        cellClassNames,
                         tooltips,
                         formats,
                         handleNan,
@@ -429,7 +429,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
         },
         [
             aggregates,
-            styles,
+            cellClassNames,
             tooltips,
             formats,
             updateVarName,
@@ -558,7 +558,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                 onDeletion: active && (editable || partialEditable) && onDelete ? onRowDeletion : undefined,
                 onRowSelection: active && onAction ? onRowSelection : undefined,
                 onRowClick: active && onAction ? onRowClick : undefined,
-                lineStyle: props.lineStyle,
+                rowClassName: props.rowClassName,
                 nanValue: props.nanValue,
                 compRows: compRows,
                 useCheckbox: useCheckbox,
@@ -583,7 +583,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
             onAction,
             onRowSelection,
             onRowClick,
-            props.lineStyle,
+            props.rowClassName,
             props.nanValue,
             size,
         ]

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

@@ -765,7 +765,7 @@ describe("PaginatedTable Component", () => {
                     <PaginatedTable
                         data={tableValue}
                         defaultColumns={styledColumns}
-                        lineStyle={"class_name=rows-bordered"}
+                        rowClassName={"class_name=rows-bordered"}
                     />
                 </TaipyContext.Provider>
             );

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

@@ -51,7 +51,7 @@ import {
     getClassName,
     getSortByIndex,
     headBoxSx,
-    LINE_STYLE,
+    ROW_CLASS_NAME,
     OnCellValidation,
     OnRowDeletion,
     Order,
@@ -135,7 +135,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const baseColumns = useDynamicJsonProperty(props.columns, props.defaultColumns, defaultColumns);
 
-    const [colsOrder, columns, styles, tooltips, formats, handleNan, filter, partialEditable, nbWidth] = useMemo(() => {
+    const [colsOrder, columns, cellClassNames, tooltips, formats, handleNan, filter, partialEditable, nbWidth] = useMemo(() => {
         let hNan = !!props.nanValue;
         if (baseColumns) {
             try {
@@ -165,10 +165,10 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                 );
                 const colsOrder = Object.keys(newCols).sort(getSortByIndex(newCols));
                 let nbWidth = 0;
-                const styTt = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
-                    if (newCols[col].style) {
-                        pv.styles = pv.styles || {};
-                        pv.styles[newCols[col].dfid] = newCols[col].style;
+                const functions = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
+                    if (newCols[col].className) {
+                        pv.classNames = pv.classNames || {};
+                        pv.classNames[newCols[col].dfid] = newCols[col].className;
                     }
                     hNan = hNan || !!newCols[col].nanValue;
                     if (newCols[col].tooltip) {
@@ -185,16 +185,16 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                     return pv;
                 }, {});
                 nbWidth = colsOrder.length - nbWidth;
-                if (props.lineStyle) {
-                    styTt.styles = styTt.styles || {};
-                    styTt.styles[LINE_STYLE] = props.lineStyle;
+                if (props.rowClassName) {
+                    functions.classNames = functions.classNames || {};
+                    functions.classNames[ROW_CLASS_NAME] = props.rowClassName;
                 }
                 return [
                     colsOrder,
                     newCols,
-                    styTt.styles,
-                    styTt.tooltips,
-                    styTt.formats,
+                    functions.classNames,
+                    functions.tooltips,
+                    functions.formats,
                     hNan,
                     filter,
                     partialEditable,
@@ -221,7 +221,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
         onAdd,
         onDelete,
         baseColumns,
-        props.lineStyle,
+        props.rowClassName,
         props.tooltip,
         props.nanValue,
         props.filter,
@@ -255,7 +255,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
         const endIndex = showAll ? -1 : startIndex + rowsPerPage - 1;
         const cols = colsOrder.map((col) => columns[col].dfid).filter((c) => c != EDIT_COL);
         const afs = appliedFilters.filter((fd) => Object.values(columns).some((cd) => cd.dfid === fd.col));
-        pageKey.current = getPageKey(columns, `${startIndex}-${endIndex}`, cols, orderBy, order, afs, aggregates, styles, tooltips, formats);
+        pageKey.current = getPageKey(columns, `${startIndex}-${endIndex}`, cols, orderBy, order, afs, aggregates, cellClassNames, tooltips, formats);
         if (refresh || !props.data || props.data[pageKey.current] === undefined) {
             setLoading(true);
             const applies = aggregates.length
@@ -279,7 +279,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                     order,
                     aggregates,
                     applies,
-                    styles,
+                    cellClassNames,
                     tooltips,
                     formats,
                     handleNan,
@@ -303,7 +303,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
         colsOrder,
         columns,
         showAll,
-        styles,
+        cellClassNames,
         tooltips,
         formats,
         rowsPerPage,
@@ -605,14 +605,14 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                                             key={`row${index}`}
                                             selected={sel > -1}
                                             ref={sel == 0 ? selectedRowRef : undefined}
-                                            className={getClassName(row, props.lineStyle)}
+                                            className={getClassName(row, props.rowClassName)}
                                             data-index={index}
                                             onClick={active && onAction ? onRowClick : undefined}
                                         >
                                             {colsOrder.map((col) => (
                                                 <EditableCell
                                                     key={`cell${index}${columns[col].dfid}`}
-                                                    className={getClassName(row, columns[col].style, col)}
+                                                    className={getClassName(row, columns[col].className, col)}
                                                     tableClassName={className}
                                                     colDesc={columns[col]}
                                                     value={row[col]}

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

@@ -62,12 +62,15 @@ export interface ColumnDesc {
     width?: number | string;
     /** If true, the column cannot be edited. */
     notEditable?: boolean;
-    /** The name of the column that holds the CSS classname to
+    /** The name of the column that holds the CSS className to
      *  apply to the cells. */
-    style?: string;
+    className?: string;
     /** The name of the column that holds the tooltip to
      *  show on the cells. */
     tooltip?: string;
+    /** The name of the column that holds the formatted value to
+     *  show on the cells. */
+    formatFn?: string;
     /** The value that would replace a NaN value. */
     nanValue?: string;
     /** The TimeZone identifier used if the type is `date`. */
@@ -83,9 +86,6 @@ export interface ColumnDesc {
     lov?: string[];
     /** If true the user can enter any value besides the lov values. */
     freeLov?: boolean;
-    /** The name of the column that holds the formatted value to
-     *  show on the cells. */
-    formatFn?: string;
 }
 
 export const DEFAULT_SIZE = "small";
@@ -106,7 +106,7 @@ export type RowType = Record<string, RowValue>;
 
 export const EDIT_COL = "taipy_edit";
 
-export const LINE_STYLE = "__taipy_line_style__";
+export const ROW_CLASS_NAME = "__taipyRowClassName__";
 
 export const defaultDateFormat = "yyyy/MM/dd";
 
@@ -128,7 +128,7 @@ export interface TaipyTableProps extends TaipyActiveProps, TaipyMultiSelectProps
     onAction?: string;
     editable?: boolean;
     defaultEditable?: boolean;
-    lineStyle?: string;
+    rowClassName?: string;
     tooltip?: string;
     cellTooltip?: string;
     nanValue?: string;
@@ -273,8 +273,8 @@ export const addActionColumn = (nbToRender: number, columns: Record<string, Colu
     return columns;
 };
 
-export const getClassName = (row: Record<string, unknown>, style?: string, col?: string) =>
-    getToolFn("tps", row, style, col);
+export const getClassName = (row: Record<string, unknown>, className?: string, col?: string) =>
+    getToolFn("tps", row, className, col);
 
 export const getTooltip = (row: Record<string, unknown>, tooltip?: string, col?: string) =>
     getToolFn("tpt", row, tooltip, col);
@@ -293,7 +293,7 @@ export const getPageKey = (
     order: string,
     filters: FilterDesc[],
     aggregates?: string[],
-    styles?: Record<string, string>,
+    cellClassNames?: Record<string, string>,
     tooltips?: Record<string, string>,
     formats?: Record<string, string>
 ) =>
@@ -312,9 +312,9 @@ export const getPageKey = (
             : undefined,
         filters.map((filter) => `${filter.col}${filter.action}${filter.value}`).join(),
         [
-            styles &&
-                Object.entries(styles)
-                    .map((col, style) => `${col}:${style}`)
+            cellClassNames &&
+                Object.entries(cellClassNames)
+                    .map((col, className) => `${col}:${className}`)
                     .join(),
             tooltips &&
                 Object.entries(tooltips)

+ 39 - 32
taipy/gui/_renderers/builder.py

@@ -16,7 +16,8 @@ import time as _time
 import typing as t
 import xml.etree.ElementTree as etree
 from datetime import date, datetime, time
-from inspect import isclass
+from enum import Enum
+from inspect import isclass, isfunction
 from urllib.parse import quote
 
 from .._warnings import _warn
@@ -138,7 +139,7 @@ class _Builder:
             hash_value = gui._evaluate_expr(value)
             try:
                 func = gui._get_user_function(hash_value)
-                if callable(func):
+                if isfunction(func):
                     return (func, hash_value)
                 return (_getscopeattr_drill(gui, hash_value), hash_value)
             except AttributeError:
@@ -163,7 +164,7 @@ class _Builder:
                 else:
                     looks_like_a_lambda = False
                     val = v
-                if callable(val):
+                if isfunction(val):
                     # if it's not a callable (and not a string), forget it
                     if val.__name__ == "<lambda>":
                         # if it is a lambda and it has already a hash_name, we're fine
@@ -193,17 +194,6 @@ class _Builder:
     def _reset_key() -> None:
         _Builder.__keys = {}
 
-    def __get_list_of_(self, name: str):
-        lof = self.__attributes.get(name)
-        if isinstance(lof, str):
-            lof = list(lof.split(";"))
-        if not isinstance(lof, list) and hasattr(lof, "tolist"):
-            try:
-                return lof.tolist()  # type: ignore[union-attr]
-            except Exception as e:
-                _warn("Error accessing List of values", e)
-        return lof
-
     def get_name_indexed_property(self, name: str) -> t.Dict[str, t.Any]:
         """
         TODO-undocumented
@@ -359,11 +349,11 @@ class _Builder:
             if not optional:
                 _warn(f"Property {name} is required for control {self.__control_type}.")
             return self
-        elif callable(str_attr):
+        elif isfunction(str_attr):
             str_attr = self.__hashes.get(name)
             if str_attr is None:
                 return self
-        elif _is_boolean(str_attr) and not _is_true(str_attr):
+        elif _is_boolean(str_attr) and not _is_true(t.cast(str, str_attr)):
             return self.__set_react_attribute(_to_camel_case(name), False)
         elif str_attr:
             str_attr = str(str_attr)
@@ -384,21 +374,41 @@ class _Builder:
     def __set_react_attribute(self, name: str, value: t.Any):
         return self.set_attribute(name, "{!" + (str(value).lower() if isinstance(value, bool) else str(value)) + "!}")
 
+    @staticmethod
+    def enum_adapter(e: t.Union[Enum, str]):
+        return (e.value, e.name) if isinstance(e, Enum) else e
+
     def _get_lov_adapter(  # noqa: C901
         self, var_name: str, property_name: t.Optional[str] = None, multi_selection=True, with_default=True
     ):
         property_name = var_name if property_name is None else property_name
         lov_name = self.__hashes.get(var_name)
-        lov = self.__get_list_of_(var_name)
+        real_var_name = self.__gui._get_real_var_name(lov_name)[0] if lov_name else None
+        lov = self.__attributes.get(var_name)
+        adapter: t.Any = None
+        var_type: t.Optional[str] = None
+        if isinstance(lov, str):
+            lov = list(lov.split(";"))
+        if isclass(lov) and issubclass(lov, Enum):
+            adapter = _Builder.enum_adapter
+            var_type = "Enum"
+            lov = list(lov)
+        if not isinstance(lov, list) and hasattr(lov, "tolist"):
+            try:
+                lov = lov.tolist()  # type: ignore[union-attr]
+            except Exception as e:
+                _warn(f"Error accessing List of values for '{real_var_name or property_name}'", e)
+                lov = None
+
         default_lov: t.Optional[t.List[t.Any]] = [] if with_default or not lov_name else None
 
-        adapter = self.__attributes.get("adapter")
+        adapter = self.__attributes.get("adapter", adapter)
         if adapter and isinstance(adapter, str):
             adapter = self.__gui._get_user_function(adapter)
         if adapter and not callable(adapter):
             _warn(f"{self.__element_name}: adapter property value is invalid.")
             adapter = None
-        var_type = self.__attributes.get("type")
+        var_type = self.__attributes.get("type", var_type)
         if isclass(var_type):
             var_type = var_type.__name__  # type: ignore
 
@@ -466,13 +476,10 @@ class _Builder:
             self.__set_json_attribute(_to_camel_case(f"default_{property_name}"), default_lov)
 
         # LoV expression binding
-        if lov_name:
+        if lov_name and real_var_name:
             typed_lov_hash = (
                 self.__gui._evaluate_expr(
-                    "{"
-                    + f"{self.__gui._get_call_method_name('_get_adapted_lov')}"
-                    + f"({self.__gui._get_real_var_name(lov_name)[0]},'{var_type}')"
-                    + "}"
+                    f"{{{self.__gui._get_call_method_name('_get_adapted_lov')}({real_var_name},'{var_type}')}}"
                 )
                 if var_type
                 else lov_name
@@ -564,19 +571,19 @@ class _Builder:
             self.set_boolean_attribute("compare", True)
             self.__set_string_attribute("on_compare")
 
-        if line_style := self.__attributes.get("style"):
-            if callable(line_style):
-                value = self.__hashes.get("style")
-            elif isinstance(line_style, str):
-                value = line_style.strip()
+        if row_class_name := self.__attributes.get("row_class_name"):
+            if isfunction(row_class_name):
+                value = self.__hashes.get("row_class_name")
+            elif isinstance(row_class_name, str):
+                value = row_class_name.strip()
             else:
                 value = None
             if value in col_types.keys():
-                _warn(f"{self.__element_name}: style={value} must not be a column name.")
+                _warn(f"{self.__element_name}: row_class_name={value} must not be a column name.")
             elif value:
-                self.set_attribute("lineStyle", value)
+                self.set_attribute("rowClassName", value)
         if tooltip := self.__attributes.get("tooltip"):
-            if callable(tooltip):
+            if isfunction(tooltip):
                 value = self.__hashes.get("tooltip")
             elif isinstance(tooltip, str):
                 value = tooltip.strip()

+ 4 - 0
taipy/gui/utils/_adapter.py

@@ -12,6 +12,8 @@
 from __future__ import annotations
 
 import typing as t
+from enum import Enum
+from inspect import isclass
 from operator import add
 
 from .._warnings import _warn
@@ -66,6 +68,8 @@ class _Adapter:
     def run(self, var_name: str, value: t.Any, id_only=False) -> t.Any:
         lov = _AdaptedLov.get_lov(value)
         adapter = self.__get_for_var(var_name, value)
+        if isclass(lov) and issubclass(lov, Enum):
+            lov = list(lov)
         if isinstance(lov, (list, tuple)):
             res = []
             for elt in lov:

+ 8 - 11
taipy/gui/utils/table_col_builder.py

@@ -82,24 +82,21 @@ def _enhance_columns(  # noqa: C901
                 col_desc["apply"] = value
         else:
             _warn(f"{elt_name}: '{k}' is not a displayed column in apply[].")
-    # TODO: rename to cell_class_name
-    styles = _get_name_indexed_property(attributes, "style")
-    for k, v in styles.items():  # pragma: no cover
+    cell_class_names = _get_name_indexed_property(attributes, "cell_class_name")
+    for k, v in cell_class_names.items():  # pragma: no cover
         if col_desc := _get_column_desc(columns, k):
             if callable(v):
-                # TODO: rename to row_class_name
-                value = hash_names.get(f"style[{k}]")
+                value = hash_names.get(f"cell_class_name[{k}]")
             elif isinstance(v, str):
                 value = v.strip()
             else:
                 value = None
             if value in columns.keys():
-                # TODO: rename to cell_class_name
-                _warn(f"{elt_name}: '{k}' is not a valid column in style[].")
+                _warn(f"{elt_name}: cell_class_name[{k}] cannot be set to an column name '{value}'.")
             elif value:
-                col_desc["style"] = value
+                col_desc["className"] = value
         else:
-            _warn(f"{elt_name}: '{k}' is not a displayed column in style[].")
+            _warn(f"{elt_name}: '{k}' is not a displayed column in cell_class_name[].")
     tooltips = _get_name_indexed_property(attributes, "tooltip")
     for k, v in tooltips.items():  # pragma: no cover
         if col_desc := _get_column_desc(columns, k):
@@ -110,7 +107,7 @@ def _enhance_columns(  # noqa: C901
             else:
                 value = None
             if value in columns.keys():
-                _warn(f"{elt_name}: '{k}' is not a valid column in tooltip[].")
+                _warn(f"{elt_name}: tooltip[{k}] cannot be set to an column name '{value}'.")
             elif value:
                 col_desc["tooltip"] = value
         else:
@@ -125,7 +122,7 @@ def _enhance_columns(  # noqa: C901
             else:
                 value = None
             if value in columns.keys():
-                _warn(f"{elt_name}: '{k}' is not a valid column in format_fn[].")
+                _warn(f"{elt_name}: format_fn[{k}] cannot be set to an column name '{value}'.")
             elif value:
                 col_desc["formatFn"] = value
         else:

+ 6 - 8
taipy/gui/viselements.json

@@ -782,14 +782,12 @@
                         "doc": "The name of the aggregation function to use.<br/>This is used only if <i>group_by[column_name]</i> is set to True.<br/>See <a href=\"#aggregation\">below</a> for more details."
                     },
                     {
-                        "//TODO": "rename to 'row_class_name",
-                        "name": "style",
+                        "name": "row_class_name",
                         "type": "Union[str, Callable]",
                         "doc": "Allows for styling rows.<br/>This property must be a function or the name of a function that return the name of a CSS class for table rows.<br/>This function is invoked with the following parameters:<ul><li><i>state</i> (<code>State^</code>): the state instance.</li><li><i>index</i> (int): the index of the row.</li><li><i>row</i> (Any): all the values for this row.</li></ul><br/>See <a href=\"#dynamic-styling\">below</a> for more details."
                     },
                     {
-                        "//TODO": "rename to 'cell_class_name",
-                        "name": "style[<i>column_name</i>]",
+                        "name": "cell_class_name[<i>column_name</i>]",
                         "type": "Union[str, Callable]",
                         "doc": "Allows for styling cells.<br/>This property must be a function or the name of a function that return the name of a CSS class for table cells.<br/>This function is invoked with the following parameters:<ul><li><i>state</i> (<code>State^</code>): the state instance.</li><li><i>value</i> (Any): the value of the cell.</li><li><i>index</i> (int): the index of the row.</li><li><i>row</i> (Any): all the values for this row.</li><li><i>column_name</i> (str): the name of the column.</li></ul><br/>See <a href=\"#dynamic-styling\">below</a> for more details."
                     },
@@ -1494,9 +1492,9 @@
                     },
                     {
                         "name": "adapter",
-                        "type": "Function",
+                        "type": "Union[str, Callable]",
                         "default_value": "<tt>lambda x: str(x)</tt>",
-                        "doc": "The function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:Union[str,Icon])</i>.<br/>The default value is a function that returns the string representation of the <i>lov</i> element."
+                        "doc": "A function or the name of the function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:Union[str,Icon])</i>.<br/>The default value is a function that returns the string representation of the <i>lov</i> element."
                     },
                     {
                         "name": "type",
@@ -1990,9 +1988,9 @@
                     },
                     {
                         "name": "adapter",
-                        "type": "Function",
+                        "type": "Union[str, Callable]",
                         "default_value": "<tt>lambda x: str(x)</tt>",
-                        "doc": "The function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:Union[str,Icon])</i>.<br/>The default value is a function that returns the string representation of the <i>lov</i> element."
+                        "doc": "A function or the name of the function  that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:Union[str,Icon])</i>.<br/>The default value is a function that returns the string representation of the <i>lov</i> element."
                     },
                     {
                         "name": "type",

+ 13 - 13
taipy/gui_core/viselements.json

@@ -33,8 +33,8 @@
                     },
                     {
                         "name": "on_change",
-                        "type": "Callable",
-                        "doc": "The name of a function that is triggered when the value is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Scenario^</code>): the selected scenario.</li>\n</ul>",
+                        "type": "Union[str, Callable]",
+                        "doc": "A function or the name of a function that is triggered when the value is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Scenario^</code>): the selected scenario.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -64,8 +64,8 @@
                     },
                     {
                         "name": "on_creation",
-                        "type": "Callable",
-                        "doc": "The name of the function that is triggered when a scenario is about to be created.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of this scenario selector.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>config (str): the name of the selected scenario configuration.</li>\n<li>date (datetime): the creation date for the new scenario.</li>\n<li>label (str): the user-specified label.</li>\n<li>properties (dic): a dictionary containing all the user-defined custom properties.</li>\n</ul>\n</li>\n<li>The callback function can return a scenario, a string containing an error message (a scenario will not be created), or None (then a new scenario is created with the user parameters).</li>\n</ul>",
+                        "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>",
                         "signature": [
                             [
                                 "state",
@@ -206,8 +206,8 @@
                     },
                     {
                         "name": "on_submission_change",
-                        "type": "Callback",
-                        "doc": "The name of the function that is triggered when a submission status is changed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>submission (Submission): the submission entity containing submission information.</li>\n<li>details (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>submission_status (str): the new status of the submission (possible values are: \"SUBMITTED\", \"COMPLETED\", \"CANCELED\", \"FAILED\", \"BLOCKED\", \"WAITING\", or \"RUNNING\").</li>\n<li>job: the Job (if any) that is at the origin of the submission status change.</li>\n<li>submittable_entity (Submittable): the entity (usually a Scenario) that was submitted.</li>\n</ul>",
+                        "type": "Union[str, Callable]",
+                        "doc": "A function or the name of a function that is triggered when a submission status is changed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>submission (Submission): the submission entity containing submission information.</li>\n<li>details (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>submission_status (str): the new status of the submission (possible values are: \"SUBMITTED\", \"COMPLETED\", \"CANCELED\", \"FAILED\", \"BLOCKED\", \"WAITING\", or \"RUNNING\").</li>\n<li>job: the Job (if any) that is at the origin of the submission status change.</li>\n<li>submittable_entity (Submittable): the entity (usually a Scenario) that was submitted.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -265,8 +265,8 @@
                     },
                     {
                         "name": "on_action",
-                        "type": "Callable",
-                        "doc": "The name of the function that is triggered when a a node is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>entity (DataNode | Task): the entity (DataNode or Task) that was selected.</li>\n</ul>",
+                        "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>",
                         "signature": [
                             [
                                 "state",
@@ -308,8 +308,8 @@
                     },
                     {
                         "name": "on_change",
-                        "type": "Callable",
-                        "doc": "The name of a function that is triggered when a data node is selected.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>DataNode^</code>): the selected data node.</li>\n</ul>",
+                        "type": "Union[str, Callable]",
+                        "doc": "A function or the name of a function that is triggered when a data node is selected.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>DataNode^</code>): the selected data node.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -523,8 +523,8 @@
                     },
                     {
                         "name": "on_change",
-                        "type": "Callable",
-                        "doc": "The name of a function that is triggered when the selection is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Job^</code>): the selected job.</li>\n</ul>",
+                        "type": "Union[str, Callable]",
+                        "doc": "A function or the name of a function that is triggered when the selection is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Job^</code>): the selected job.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -548,7 +548,7 @@
                     },
                     {
                         "name": "on_details",
-                        "type": "Union[Callback, bool]",
+                        "type": "Union[str, Callback, 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.",
                         "signature": [
                             [