Преглед изворни кода

- Added taipy.gui.extention entry point for API generation.
- Code reorganization.
- Hide inner properties.
- Store extension libraries element constructors in their library module.
- Added PropertyType.any and dynamic_any.
- Added optional documentation on extension library elements and their properties.

Fabien Lelaquais пре 3 месеци
родитељ
комит
f3190e0ae6

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

@@ -44,10 +44,6 @@ import { FormatConfig } from "../../context/taipyReducers";
 import { dateToString, getDateTime, getDateTimeString, getNumberString, getTimeZonedDate } from "../../utils/index";
 import { dateToString, getDateTime, getDateTimeString, getNumberString, getTimeZonedDate } from "../../utils/index";
 import { TaipyActiveProps, TaipyMultiSelectProps, getSuffixedClassNames } from "./utils";
 import { TaipyActiveProps, TaipyMultiSelectProps, getSuffixedClassNames } from "./utils";
 
 
-/**
- * A column description as received by the backend.
- */
-
 /**
 /**
  * Generates a  CSS class name for a table header.
  * Generates a  CSS class name for a table header.
  * @param columnName - The name of the column.
  * @param columnName - The name of the column.
@@ -63,6 +59,10 @@ export const generateHeaderClassName = (columnName: string | undefined): string
     return '-' + columnName.replace(/\W+/g, '-').replace(/-+/g, '-').toLowerCase();
     return '-' + columnName.replace(/\W+/g, '-').replace(/-+/g, '-').toLowerCase();
 };
 };
 
 
+/**
+ * A column description as received by the backend.
+ */
+
 export interface ColumnDesc {
 export interface ColumnDesc {
     /** The unique column identifier. */
     /** The unique column identifier. */
     dfid: string;
     dfid: string;

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

@@ -14,9 +14,19 @@
 import { MouseEvent, ReactNode } from "react";
 import { MouseEvent, ReactNode } from "react";
 import { SxProps } from "@mui/material";
 import { SxProps } from "@mui/material";
 
 
-export interface TaipyActiveProps extends TaipyDynamicProps, TaipyHoverProps {
-    defaultActive?: boolean;
-    active?: boolean;
+export interface TaipyBaseProps {
+    id?: string;
+    libClassName?: string;
+    className?: string;
+    dynamicClassName?: string;
+    privateClassName?: string;
+    children?: ReactNode;
+}
+
+interface TaipyDynamicProps extends TaipyBaseProps {
+    updateVarName?: string;
+    propagate?: boolean;
+    updateVars?: string;
 }
 }
 
 
 export interface TaipyHoverProps {
 export interface TaipyHoverProps {
@@ -24,19 +34,13 @@ export interface TaipyHoverProps {
     defaultHoverText?: string;
     defaultHoverText?: string;
 }
 }
 
 
-interface TaipyDynamicProps extends TaipyBaseProps {
-    updateVarName?: string;
-    propagate?: boolean;
-    updateVars?: string;
+export interface TaipyActiveProps extends TaipyDynamicProps, TaipyHoverProps {
+    defaultActive?: boolean;
+    active?: boolean;
 }
 }
 
 
-export interface TaipyBaseProps {
-    id?: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
-    privateClassName?: string;
-    children?: ReactNode;
+export interface TaipyLabelProps {
+    label?: string;
 }
 }
 
 
 export interface TaipyMultiSelectProps {
 export interface TaipyMultiSelectProps {
@@ -68,10 +72,6 @@ export interface TaipyInputProps extends TaipyActiveProps, TaipyChangeProps, Tai
     width?: string | number;
     width?: string | number;
 }
 }
 
 
-export interface TaipyLabelProps {
-    label?: string;
-}
-
 export interface DateProps {
 export interface DateProps {
     maxDate?: unknown;
     maxDate?: unknown;
     maxDateTime?: unknown;
     maxDateTime?: unknown;

+ 256 - 238
taipy/gui/_renderers/builder.py

@@ -85,7 +85,7 @@ class _Builder:
         gui: "Gui",
         gui: "Gui",
         control_type: str,
         control_type: str,
         element_name: str,
         element_name: str,
-        attributes: t.Optional[t.Dict[str, t.Any]],
+        prop_values: t.Optional[t.Dict[str, t.Any]],
         hash_names: t.Optional[t.Dict[str, str]] = None,
         hash_names: t.Optional[t.Dict[str, str]] = None,
         default_value: t.Optional[t.Any] = "<Empty>",
         default_value: t.Optional[t.Any] = "<Empty>",
         lib_name: str = "taipy",
         lib_name: str = "taipy",
@@ -101,20 +101,20 @@ class _Builder:
         self.__control_type = control_type
         self.__control_type = control_type
         self.__element_name = element_name
         self.__element_name = element_name
         self.__lib_name = lib_name
         self.__lib_name = lib_name
-        self.__attributes = attributes or {}
+        self.__prop_values = prop_values or {}
         self.__hashes = hash_names.copy()
         self.__hashes = hash_names.copy()
         self.__update_vars: t.List[str] = []
         self.__update_vars: t.List[str] = []
         self.__gui: Gui = gui
         self.__gui: Gui = gui
 
 
         self.__default_property_name = _Factory.get_default_property_name(control_type) or ""
         self.__default_property_name = _Factory.get_default_property_name(control_type) or ""
-        default_property_value = self.__attributes.get(self.__default_property_name, None)
+        default_property_value = self.__prop_values.get(self.__default_property_name, None)
         if default_property_value is None and default_value is not None:
         if default_property_value is None and default_value is not None:
-            self.__attributes[self.__default_property_name] = default_value
+            self.__prop_values[self.__default_property_name] = default_value
 
 
         # Bind properties dictionary to attributes if condition is matched (will
         # Bind properties dictionary to attributes if condition is matched (will
         # leave the binding for function at the builder )
         # leave the binding for function at the builder )
-        if "properties" in self.__attributes:
-            (prop_dict, prop_hash) = _Builder.__parse_attribute_value(gui, self.__attributes["properties"])
+        if "properties" in self.__prop_values:
+            (prop_dict, prop_hash) = _Builder.__parse_attribute_value(gui, self.__prop_values["properties"])
             if prop_hash is None:
             if prop_hash is None:
                 prop_hash = prop_dict
                 prop_hash = prop_dict
                 prop_hash = self.__gui._bind_var(prop_hash)
                 prop_hash = self.__gui._bind_var(prop_hash)
@@ -125,14 +125,14 @@ class _Builder:
                 var_name, _ = gui._get_real_var_name(prop_hash)
                 var_name, _ = gui._get_real_var_name(prop_hash)
                 for k, v in prop_dict.items():
                 for k, v in prop_dict.items():
                     (val, key_hash) = _Builder.__parse_attribute_value(gui, v)
                     (val, key_hash) = _Builder.__parse_attribute_value(gui, v)
-                    self.__attributes[k] = (
+                    self.__prop_values[k] = (
                         f"{{None if ({var_name}) is None else ({var_name}).get('{k}')}}" if key_hash is None else v
                         f"{{None if ({var_name}) is None else ({var_name}).get('{k}')}}" if key_hash is None else v
                     )
                     )
             else:
             else:
                 _warn(f"{self.__control_type}.properties ({prop_hash}) must be a dict.")
                 _warn(f"{self.__control_type}.properties ({prop_hash}) must be a dict.")
 
 
         # Bind potential function and expressions in self.attributes
         # Bind potential function and expressions in self.attributes
-        self.__hashes.update(_Builder._get_variable_hash_names(gui, self.__attributes, hash_names))
+        self.__hashes.update(_Builder._get_variable_hash_names(gui, self.__prop_values, hash_names))
 
 
         # set classname
         # set classname
         self.__set_class_names()
         self.__set_class_names()
@@ -206,13 +206,28 @@ class _Builder:
 
 
             name (str): The property name.
             name (str): The property name.
         """
         """
-        return _get_name_indexed_property(self.__attributes, name)
+        return _get_name_indexed_property(self.__prop_values, name)
+
+    def __set_json_attribute(self, name, value):
+        return self.set_attribute(name, json.dumps(value, cls=_TaipyJsonEncoder))
+
+    def __set_any_attribute(self, name: str, default_value: t.Optional[str] = None):
+        value = self.__prop_values.get(name, default_value)
+        return self.__set_json_attribute(_to_camel_case(name), value)
+
+    def __set_dynamic_any_attribute(self, name: str, default_value: t.Optional[str] = None):
+        value = self.__prop_values.get(name, default_value)
+        self.__set_json_attribute(_to_camel_case(f"default_{name}"), value)
+        if hash_name := self.__hashes.get(name):
+            self.__update_vars.append(f"{name}={hash_name}")
+            self.__set_react_attribute(name, hash_name)
+        return self
 
 
     def __get_boolean_attribute(self, name: str, default_value=False):
     def __get_boolean_attribute(self, name: str, default_value=False):
-        bool_attr = self.__attributes.get(name, default_value)
+        bool_attr = self.__prop_values.get(name, default_value)
         return _is_true(bool_attr) if isinstance(bool_attr, str) else bool(bool_attr)
         return _is_true(bool_attr) if isinstance(bool_attr, str) else bool(bool_attr)
 
 
-    def set_boolean_attribute(self, name: str, value: bool):
+    def __set_boolean_attribute(self, name: str, value: bool):
         """
         """
         TODO-undocumented
         TODO-undocumented
         Defines a React Boolean attribute (attr={true|false}).
         Defines a React Boolean attribute (attr={true|false}).
@@ -223,61 +238,22 @@ class _Builder:
         """
         """
         return self.__set_react_attribute(_to_camel_case(name), value)
         return self.__set_react_attribute(_to_camel_case(name), value)
 
 
-    def set_dict_attribute(self, name: str, default_value: t.Optional[t.Dict[str, t.Any]] = None):
-        """
-        TODO-undocumented
-        Defines a React attribute as a stringified json dict.
-        The original property can be a dict or a string formed as <key 1>:<value 1>;<key 2>:<value 2>.
-
-        Arguments:
-            name (str): The property name.
-            default value (dict): used if no value is specified.
-        """
-        dict_attr = self.__attributes.get(name)
-        if dict_attr is None:
-            dict_attr = default_value
-        if dict_attr is not None:
-            if isinstance(dict_attr, str):
-                vals = [x.strip().split(":") for x in dict_attr.split(";")]
-                dict_attr = {val[0].strip(): val[1].strip() for val in vals if len(val) > 1}
-            if isinstance(dict_attr, (dict, _MapDict)):
-                self.__set_json_attribute(_to_camel_case(name), dict_attr)
-            else:
-                _warn(f"{self.__element_name}: {name} should be a dict: '{str(dict_attr)}'.")
-        return self
-
-    def set_dynamic_dict_attribute(self, name: str, default_value: t.Optional[t.Dict[str, t.Any]] = None):
-        """
-        TODO-undocumented
-        Defines a React attribute as a stringified json dict.
-        The original property can be a dict or a string formed as <key 1>:<value 1>;<key 2>:<value 2>.
-
-        Arguments:
-            name (str): The property name.
-            default value (dict): used if no value is specified.
-        """
-        dict_attr = self.__attributes.get(name)
-        if dict_attr is None:
-            dict_attr = default_value
-        if dict_attr is not None:
-            if isinstance(dict_attr, str):
-                vals = [x.strip().split(":") for x in dict_attr.split(";")]
-                dict_attr = {val[0].strip(): val[1].strip() for val in vals if len(val) > 1}
-            if isinstance(dict_attr, (dict, _MapDict)):
-                self.__set_json_attribute(_to_camel_case("default_" + name), dict_attr)
-            else:
-                _warn(f"{self.__element_name}: {name} should be a dict: '{str(dict_attr)}'.")
-        if dict_hash := self.__hashes.get(name):
-            dict_hash = self.__get_typed_hash_name(dict_hash, PropertyType.dynamic_dict)
-            prop_name = _to_camel_case(name)
-            self.__update_vars.append(f"{prop_name}={dict_hash}")
-            self.__set_react_attribute(prop_name, dict_hash)
-        return self
-
-    def __set_json_attribute(self, name, value):
-        return self.set_attribute(name, json.dumps(value, cls=_TaipyJsonEncoder))
+    def __set_dynamic_bool_attribute(self, name: str, def_val: t.Any, with_update: bool, update_main=True):
+        hash_name = self.__hashes.get(name)
+        val = self.__get_boolean_attribute(name, def_val)
+        default_name = f"default_{name}" if hash_name is not None else name
+        if val != def_val:
+            self.__set_boolean_attribute(default_name, val)
+        if hash_name is not None:
+            hash_name = self.__get_typed_hash_name(hash_name, PropertyType.dynamic_boolean)
+            self.__set_react_attribute(_to_camel_case(name), _get_client_var_name(hash_name))
+            if with_update:
+                if update_main:
+                    self.__set_update_var_name(hash_name)
+                else:
+                    self.__update_vars.append(f"{_to_camel_case(name)}={hash_name}")
 
 
-    def set_number_attribute(self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True):
+    def __set_number_attribute(self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True):
         """
         """
         TODO-undocumented
         TODO-undocumented
         Defines a React number attribute (attr={<number>}).
         Defines a React number attribute (attr={<number>}).
@@ -288,7 +264,7 @@ class _Builder:
             default_value (optional(str)): the default value as a string.
             default_value (optional(str)): the default value as a string.
             optional (bool): Default to True, the property is required if False.
             optional (bool): Default to True, the property is required if False.
         """
         """
-        value = self.__attributes.get(name, default_value)
+        value = self.__prop_values.get(name, default_value)
         if value is None:
         if value is None:
             if not optional:
             if not optional:
                 _warn(f"Property {name} is required for control {self.__control_type}.")
                 _warn(f"Property {name} is required for control {self.__control_type}.")
@@ -306,25 +282,37 @@ class _Builder:
             )
             )
         return self.__set_react_attribute(_to_camel_case(name), val)
         return self.__set_react_attribute(_to_camel_case(name), val)
 
 
+    def __set_dynamic_number_attribute(self, var_name: str, default_value: t.Any):
+        hash_name = self.__hashes.get(var_name)
+        numVal = self.__prop_values.get(var_name)
+        if numVal is None:
+            numVal = default_value
+        if isinstance(numVal, str):
+            try:
+                numVal = float(numVal)
+            except Exception as e:
+                _warn(f"{self.__element_name}: {var_name} cannot be transformed into a number", e)
+                numVal = 0
+        if isinstance(numVal, numbers.Number):
+            self.__set_react_attribute(_to_camel_case(f"default_{var_name}"), numVal)
+        elif numVal is not None:
+            _warn(f"{self.__element_name}: {var_name} value is not valid ({numVal}).")
+        if hash_name:
+            hash_name = self.__get_typed_hash_name(hash_name, PropertyType.number)
+            self.__update_vars.append(f"{var_name}={hash_name}")
+            self.__set_react_attribute(var_name, hash_name)
+        return self
+
     def __set_string_attribute(
     def __set_string_attribute(
         self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True
         self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True
     ):
     ):
-        str_attr = self.__attributes.get(name, default_value)
+        str_attr = self.__prop_values.get(name, default_value)
         if str_attr is None:
         if str_attr is None:
             if not optional:
             if not optional:
                 _warn(f"Property {name} is required for control {self.__control_type}.")
                 _warn(f"Property {name} is required for control {self.__control_type}.")
             return self
             return self
         return self.set_attribute(_to_camel_case(name), str(str_attr))
         return self.set_attribute(_to_camel_case(name), str(str_attr))
 
 
-    def __set_dynamic_date_attribute(self, var_name: str, default_value: t.Optional[str] = None):
-        date_attr = self.__attributes.get(var_name, default_value)
-        if date_attr is None:
-            date_attr = default_value
-        if isinstance(date_attr, (datetime, date, time)):
-            value = _date_to_string(date_attr)
-            self.set_attribute(_to_camel_case(var_name), value)
-        return self
-
     def __set_dynamic_string_attribute(
     def __set_dynamic_string_attribute(
         self,
         self,
         name: str,
         name: str,
@@ -332,7 +320,7 @@ class _Builder:
         with_update: t.Optional[bool] = False,
         with_update: t.Optional[bool] = False,
         dynamic_property_name: t.Optional[str] = None,
         dynamic_property_name: t.Optional[str] = None,
     ):
     ):
-        str_val = self.__attributes.get(name, default_value)
+        str_val = self.__prop_values.get(name, default_value)
         if str_val is not None:
         if str_val is not None:
             self.set_attribute(
             self.set_attribute(
                 _to_camel_case(f"default_{name}" if dynamic_property_name is None else name), str(str_val)
                 _to_camel_case(f"default_{name}" if dynamic_property_name is None else name), str(str_val)
@@ -344,10 +332,115 @@ class _Builder:
             self.__set_react_attribute(prop_name, hash_name)
             self.__set_react_attribute(prop_name, hash_name)
         return self
         return self
 
 
+    def __set_string_or_number_attribute(self, name: str, default_value: t.Optional[t.Any] = None):
+        attr = self.__prop_values.get(name, default_value)
+        if attr is None:
+            return self
+        if isinstance(attr, numbers.Number):
+            return self.__set_react_attribute(_to_camel_case(name), attr)
+        else:
+            return self.set_attribute(_to_camel_case(name), attr)
+
+    def __set_dynamic_string_list(self, name: str, default_value: t.Any):
+        hash_name = self.__hashes.get(name)
+        loi = self.__prop_values.get(name)
+        if loi is None:
+            loi = default_value
+        if isinstance(loi, str):
+            loi = [s.strip() for s in loi.split(";") if s.strip()]
+        if isinstance(loi, list):
+            self.__set_json_attribute(_to_camel_case(f"default_{name}"), loi)
+        if hash_name:
+            self.__update_vars.append(f"{name}={hash_name}")
+            self.__set_react_attribute(name, hash_name)
+        return self
+
+    def __set_list_attribute(
+        self,
+        name: str,
+        hash_name: t.Optional[str],
+        val: t.Any,
+        elt_type: t.Type,
+        dynamic=True,
+        default_val: t.Optional[t.Any] = None,
+    ) -> t.List[str]:
+        val = default_val if val is None else val
+        if not hash_name and isinstance(val, str):
+            val = [elt_type(t.strip()) for t in val.split(";")]
+        if isinstance(val, list):
+            if hash_name and dynamic:
+                self.__set_react_attribute(name, hash_name)
+                return [f"{name}={hash_name}"]
+            else:
+                self.__set_json_attribute(name, val)
+        elif val is not None:
+            _warn(f"{self.__element_name}: {name} should be a list of {elt_type}.")
+        return []
+
+    def __set_dict_attribute(self, name: str, default_value: t.Optional[t.Dict[str, t.Any]] = None):
+        """
+        TODO-undocumented
+        Defines a React attribute as a stringified json dict.
+        The original property can be a dict or a string formed as <key 1>:<value 1>;<key 2>:<value 2>.
+
+        Arguments:
+            name (str): The property name.
+            default value (dict): used if no value is specified.
+        """
+        dict_attr = self.__prop_values.get(name)
+        if dict_attr is None:
+            dict_attr = default_value
+        if dict_attr is not None:
+            if isinstance(dict_attr, str):
+                vals = [x.strip().split(":") for x in dict_attr.split(";")]
+                dict_attr = {val[0].strip(): val[1].strip() for val in vals if len(val) > 1}
+            if isinstance(dict_attr, (dict, _MapDict)):
+                self.__set_json_attribute(_to_camel_case(name), dict_attr)
+            else:
+                _warn(f"{self.__element_name}: {name} should be a dict: '{str(dict_attr)}'.")
+        return self
+
+    def __set_dynamic_dict_attribute(self, name: str, default_value: t.Optional[t.Dict[str, t.Any]] = None):
+        """
+        TODO-undocumented
+        Defines a React attribute as a stringified json dict.
+        The original property can be a dict or a string formed as <key 1>:<value 1>;<key 2>:<value 2>.
+
+        Arguments:
+            name (str): The property name.
+            default value (dict): used if no value is specified.
+        """
+        dict_attr = self.__prop_values.get(name)
+        if dict_attr is None:
+            dict_attr = default_value
+        if dict_attr is not None:
+            if isinstance(dict_attr, str):
+                vals = [x.strip().split(":") for x in dict_attr.split(";")]
+                dict_attr = {val[0].strip(): val[1].strip() for val in vals if len(val) > 1}
+            if isinstance(dict_attr, (dict, _MapDict)):
+                self.__set_json_attribute(_to_camel_case("default_" + name), dict_attr)
+            else:
+                _warn(f"{self.__element_name}: {name} should be a dict: '{str(dict_attr)}'.")
+        if dict_hash := self.__hashes.get(name):
+            dict_hash = self.__get_typed_hash_name(dict_hash, PropertyType.dynamic_dict)
+            prop_name = _to_camel_case(name)
+            self.__update_vars.append(f"{prop_name}={dict_hash}")
+            self.__set_react_attribute(prop_name, dict_hash)
+        return self
+
+    def __set_dynamic_date_attribute(self, var_name: str, default_value: t.Optional[str] = None):
+        date_attr = self.__prop_values.get(var_name, default_value)
+        if date_attr is None:
+            date_attr = default_value
+        if isinstance(date_attr, (datetime, date, time)):
+            value = _date_to_string(date_attr)
+            self.set_attribute(_to_camel_case(var_name), value)
+        return self
+
     def __set_function_attribute(
     def __set_function_attribute(
         self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True
         self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True
     ):
     ):
-        str_attr = self.__attributes.get(name, default_value)
+        str_attr = self.__prop_values.get(name, default_value)
         if str_attr is None:
         if str_attr is None:
             if not optional:
             if not optional:
                 _warn(f"Property {name} is required for control {self.__control_type}.")
                 _warn(f"Property {name} is required for control {self.__control_type}.")
@@ -365,15 +458,6 @@ class _Builder:
                 _warn(f"{self.__control_type}.{name}: {str_attr} is not a function.")
                 _warn(f"{self.__control_type}.{name}: {str_attr} is not a function.")
         return self.set_attribute(_to_camel_case(name), str_attr) if str_attr else self
         return self.set_attribute(_to_camel_case(name), str_attr) if str_attr else self
 
 
-    def __set_string_or_number_attribute(self, name: str, default_value: t.Optional[t.Any] = None):
-        attr = self.__attributes.get(name, default_value)
-        if attr is None:
-            return self
-        if isinstance(attr, numbers.Number):
-            return self.__set_react_attribute(_to_camel_case(name), attr)
-        else:
-            return self.set_attribute(_to_camel_case(name), attr)
-
     def __set_react_attribute(self, name: str, value: t.Any):
     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)) + "!}")
         return self.set_attribute(name, "{!" + (str(value).lower() if isinstance(value, bool) else str(value)) + "!}")
 
 
@@ -387,7 +471,7 @@ class _Builder:
         property_name = var_name if property_name is None else property_name
         property_name = var_name if property_name is None else property_name
         lov_name = self.__hashes.get(var_name)
         lov_name = self.__hashes.get(var_name)
         real_var_name = self.__gui._get_real_var_name(lov_name)[0] if lov_name else None
         real_var_name = self.__gui._get_real_var_name(lov_name)[0] if lov_name else None
-        lov = self.__attributes.get(var_name)
+        lov = self.__prop_values.get(var_name)
         adapter: t.Any = None
         adapter: t.Any = None
         var_type: t.Optional[str] = None
         var_type: t.Optional[str] = None
         if isinstance(lov, str):
         if isinstance(lov, str):
@@ -405,13 +489,13 @@ class _Builder:
 
 
         default_lov: t.Optional[t.List[t.Any]] = [] if with_default or not lov_name else None
         default_lov: t.Optional[t.List[t.Any]] = [] if with_default or not lov_name else None
 
 
-        adapter = self.__attributes.get("adapter", adapter)
+        adapter = self.__prop_values.get("adapter", adapter)
         if adapter and isinstance(adapter, str):
         if adapter and isinstance(adapter, str):
             adapter = self.__gui._get_user_function(adapter)
             adapter = self.__gui._get_user_function(adapter)
         if adapter and not _is_function(adapter):
         if adapter and not _is_function(adapter):
             _warn(f"{self.__element_name}: adapter property value is invalid.")
             _warn(f"{self.__element_name}: adapter property value is invalid.")
             adapter = None
             adapter = None
-        var_type = self.__attributes.get("type", var_type)
+        var_type = self.__prop_values.get("type", var_type)
         if isclass(var_type):
         if isclass(var_type):
             var_type = var_type.__name__
             var_type = var_type.__name__
 
 
@@ -421,7 +505,7 @@ class _Builder:
                 if lov:
                 if lov:
                     elt = lov[0]
                     elt = lov[0]
                 else:
                 else:
-                    value = self.__attributes.get("value")
+                    value = self.__prop_values.get("value")
                     if isinstance(value, list):
                     if isinstance(value, list):
                         if len(value) > 0:
                         if len(value) > 0:
                             elt = value[0]
                             elt = value[0]
@@ -460,7 +544,7 @@ class _Builder:
                         default_lov.append(ret)
                         default_lov.append(ret)
 
 
             ret_list = []
             ret_list = []
-            value = self.__attributes.get("value")
+            value = self.__prop_values.get("value")
             val_list = value if isinstance(value, list) else [value]
             val_list = value if isinstance(value, list) else [value]
             for val in val_list:
             for val in val_list:
                 ret = self.__gui._run_adapter(
                 ret = self.__gui._run_adapter(
@@ -475,8 +559,8 @@ class _Builder:
                 self.__set_default_value("value", ret_list)
                 self.__set_default_value("value", ret_list)
             else:
             else:
                 ret_val = ret_list[0] if len(ret_list) else ""
                 ret_val = ret_list[0] if len(ret_list) else ""
-                if ret_val == "-1" and self.__attributes.get("unselected_value") is not None:
-                    ret_val = str(self.__attributes.get("unselected_value", ""))
+                if ret_val == "-1" and self.__prop_values.get("unselected_value") is not None:
+                    ret_val = str(self.__prop_values.get("unselected_value", ""))
                 self.__set_default_value("value", ret_val)
                 self.__set_default_value("value", ret_val)
 
 
         # LoV default value
         # LoV default value
@@ -500,12 +584,12 @@ class _Builder:
         return self
         return self
 
 
     def __filter_attribute_names(self, names: t.Iterable[str]):
     def __filter_attribute_names(self, names: t.Iterable[str]):
-        return [k for k in self.__attributes if k in names or any(k.startswith(n + "[") for n in names)]
+        return [k for k in self.__prop_values if k in names or any(k.startswith(n + "[") for n in names)]
 
 
     def __get_held_name(self, key: str):
     def __get_held_name(self, key: str):
         name = self.__hashes.get(key)
         name = self.__hashes.get(key)
         if name:
         if name:
-            v = self.__attributes.get(key)
+            v = self.__prop_values.get(key)
             if isinstance(v, _TaipyBase):
             if isinstance(v, _TaipyBase):
                 return name[: len(v.get_hash()) + 1]
                 return name[: len(v.get_hash()) + 1]
         return name
         return name
@@ -514,12 +598,12 @@ class _Builder:
         hash_names = [k for k in self.__hashes if k in keys]
         hash_names = [k for k in self.__hashes if k in keys]
         attr_names = [k for k in keys if k not in hash_names]
         attr_names = [k for k in keys if k not in hash_names]
         return (
         return (
-            {k: v for k, v in self.__attributes.items() if k in attr_names},
+            {k: v for k, v in self.__prop_values.items() if k in attr_names},
             {k: self.__get_held_name(k) for k in self.__hashes if k in hash_names},
             {k: self.__get_held_name(k) for k in self.__hashes if k in hash_names},
         )
         )
 
 
     def __build_rebuild_fn(self, fn_name: str, attribute_names: t.Iterable[str]):
     def __build_rebuild_fn(self, fn_name: str, attribute_names: t.Iterable[str]):
-        rebuild = self.__attributes.get("rebuild", False)
+        rebuild = self.__prop_values.get("rebuild", False)
         rebuild_hash = self.__hashes.get("rebuild")
         rebuild_hash = self.__hashes.get("rebuild")
         if rebuild_hash or _is_true(rebuild):
         if rebuild_hash or _is_true(rebuild):
             attributes, hashes = self.__filter_attributes_hashes(self.__filter_attribute_names(attribute_names))
             attributes, hashes = self.__filter_attributes_hashes(self.__filter_attribute_names(attribute_names))
@@ -536,8 +620,8 @@ class _Builder:
         return None
         return None
 
 
     def _get_dataframe_attributes(self) -> "_Builder":
     def _get_dataframe_attributes(self) -> "_Builder":
-        date_format = _add_to_dict_and_get(self.__attributes, "date_format", "MM/dd/yyyy")
-        data = self.__attributes.get("data")
+        date_format = _add_to_dict_and_get(self.__prop_values, "date_format", "MM/dd/yyyy")
+        data = self.__prop_values.get("data")
         data_hash = self.__hashes.get("data", "")
         data_hash = self.__hashes.get("data", "")
         cmp_hash = ""
         cmp_hash = ""
         if data_hash:
         if data_hash:
@@ -552,13 +636,13 @@ class _Builder:
                 cmp_hash = self.__gui._evaluate_expr(
                 cmp_hash = self.__gui._evaluate_expr(
                     "{"
                     "{"
                     + f"{self.__gui._get_call_method_name('_compare_data')}"
                     + f"{self.__gui._get_call_method_name('_compare_data')}"
-                    + f'({self.__gui._get_real_var_name(data_hash)[0]},{",".join(cmp_datas)})'
+                    + f"({self.__gui._get_real_var_name(data_hash)[0]},{','.join(cmp_datas)})"
                     + "}"
                     + "}"
                 )
                 )
                 self.__update_vars.append(f"comparedatas={','.join(cmp_datas_hash)}")
                 self.__update_vars.append(f"comparedatas={','.join(cmp_datas_hash)}")
         col_types = self.__gui._get_accessor().get_col_types(data_hash, _TaipyData(data, data_hash))
         col_types = self.__gui._get_accessor().get_col_types(data_hash, _TaipyData(data, data_hash))
         col_dict = _get_columns_dict(
         col_dict = _get_columns_dict(
-            data, self.__attributes.get("columns", {}), col_types, date_format, self.__attributes.get("number_format")
+            data, self.__prop_values.get("columns", {}), col_types, date_format, self.__prop_values.get("number_format")
         )
         )
 
 
         rebuild_fn_hash = self.__build_rebuild_fn(
         rebuild_fn_hash = self.__build_rebuild_fn(
@@ -567,7 +651,7 @@ class _Builder:
         if rebuild_fn_hash:
         if rebuild_fn_hash:
             self.__set_react_attribute("columns", rebuild_fn_hash)
             self.__set_react_attribute("columns", rebuild_fn_hash)
         if col_dict is not None:
         if col_dict is not None:
-            _enhance_columns(self.__attributes, self.__hashes, col_dict, self.__element_name)
+            _enhance_columns(self.__prop_values, self.__hashes, col_dict, self.__element_name)
             self.__set_json_attribute("defaultColumns", col_dict)
             self.__set_json_attribute("defaultColumns", col_dict)
         if cmp_hash:
         if cmp_hash:
             hash_name = self.__get_typed_hash_name(cmp_hash, PropertyType.data)
             hash_name = self.__get_typed_hash_name(cmp_hash, PropertyType.data)
@@ -576,12 +660,12 @@ class _Builder:
                 _get_client_var_name(hash_name),
                 _get_client_var_name(hash_name),
             )
             )
             self.__set_update_var_name(hash_name)
             self.__set_update_var_name(hash_name)
-            self.set_boolean_attribute("compare", True)
+            self.__set_boolean_attribute("compare", True)
             self.__set_string_attribute("on_compare")
             self.__set_string_attribute("on_compare")
 
 
-        if not isinstance(self.__attributes.get("style"), (type(None), dict, _MapDict)):
+        if not isinstance(self.__prop_values.get("style"), (type(None), dict, _MapDict)):
             _warn("Table: property 'style' has been renamed to 'row_class_name'.")
             _warn("Table: property 'style' has been renamed to 'row_class_name'.")
-        if row_class_name := self.__attributes.get("row_class_name"):
+        if row_class_name := self.__prop_values.get("row_class_name"):
             if _is_function(row_class_name):
             if _is_function(row_class_name):
                 value = self.__hashes.get("row_class_name")
                 value = self.__hashes.get("row_class_name")
             elif isinstance(row_class_name, str):
             elif isinstance(row_class_name, str):
@@ -592,7 +676,7 @@ class _Builder:
                 _warn(f"{self.__element_name}: row_class_name={value} must not be a column name.")
                 _warn(f"{self.__element_name}: row_class_name={value} must not be a column name.")
             elif value:
             elif value:
                 self.set_attribute("rowClassName", value)
                 self.set_attribute("rowClassName", value)
-        if tooltip := self.__attributes.get("tooltip"):
+        if tooltip := self.__prop_values.get("tooltip"):
             if _is_function(tooltip):
             if _is_function(tooltip):
                 value = self.__hashes.get("tooltip")
                 value = self.__hashes.get("tooltip")
             elif isinstance(tooltip, str):
             elif isinstance(tooltip, str):
@@ -606,8 +690,8 @@ class _Builder:
         return self
         return self
 
 
     def _get_chart_config(self, default_type: str, default_mode: str):
     def _get_chart_config(self, default_type: str, default_mode: str):
-        self.__attributes["_default_type"] = default_type
-        self.__attributes["_default_mode"] = default_mode
+        self.__prop_values["_default_type"] = default_type
+        self.__prop_values["_default_mode"] = default_mode
         rebuild_fn_hash = self.__build_rebuild_fn(
         rebuild_fn_hash = self.__build_rebuild_fn(
             self.__gui._get_call_method_name("_chart_conf"),
             self.__gui._get_call_method_name("_chart_conf"),
             _CHART_NAMES + ("_default_type", "_default_mode"),
             _CHART_NAMES + ("_default_type", "_default_mode"),
@@ -616,7 +700,7 @@ class _Builder:
             self.__set_react_attribute("config", rebuild_fn_hash)
             self.__set_react_attribute("config", rebuild_fn_hash)
 
 
         # read column definitions
         # read column definitions
-        data = self.__attributes.get("data")
+        data = self.__prop_values.get("data")
         data_hash = self.__hashes.get("data", "")
         data_hash = self.__hashes.get("data", "")
         col_types = [self.__gui._get_accessor().get_col_types(data_hash, _TaipyData(data, data_hash))]
         col_types = [self.__gui._get_accessor().get_col_types(data_hash, _TaipyData(data, data_hash))]
 
 
@@ -627,8 +711,8 @@ class _Builder:
             while add_data_hash := self.__hashes.get(name_idx):
             while add_data_hash := self.__hashes.get(name_idx):
                 typed_hash = self.__get_typed_hash_name(add_data_hash, _TaipyData)
                 typed_hash = self.__get_typed_hash_name(add_data_hash, _TaipyData)
                 data_updates.append(typed_hash)
                 data_updates.append(typed_hash)
-                self.__set_react_attribute(f"data{data_idx}",_get_client_var_name(typed_hash))
-                add_data = self.__attributes.get(name_idx)
+                self.__set_react_attribute(f"data{data_idx}", _get_client_var_name(typed_hash))
+                add_data = self.__prop_values.get(name_idx)
                 data_idx += 1
                 data_idx += 1
                 name_idx = f"data[{data_idx}]"
                 name_idx = f"data[{data_idx}]"
                 col_types.append(
                 col_types.append(
@@ -636,7 +720,7 @@ class _Builder:
                 )
                 )
             self.set_attribute("dataVarNames", ";".join(data_updates))
             self.set_attribute("dataVarNames", ";".join(data_updates))
 
 
-        config = _build_chart_config(self.__gui, self.__attributes, col_types)
+        config = _build_chart_config(self.__gui, self.__prop_values, col_types)
 
 
         self.__set_json_attribute("defaultConfig", config)
         self.__set_json_attribute("defaultConfig", config)
         self._set_chart_selected(max=len(config.get("traces", [])))
         self._set_chart_selected(max=len(config.get("traces", [])))
@@ -644,42 +728,20 @@ class _Builder:
         return self
         return self
 
 
     def _set_string_with_check(self, var_name: str, values: t.List[str], default_value: t.Optional[str] = None):
     def _set_string_with_check(self, var_name: str, values: t.List[str], default_value: t.Optional[str] = None):
-        value = self.__attributes.get(var_name, default_value)
+        value = self.__prop_values.get(var_name, default_value)
         if value is not None:
         if value is not None:
             value = str(value).lower()
             value = str(value).lower()
-            self.__attributes[var_name] = value
+            self.__prop_values[var_name] = value
             if value not in values:
             if value not in values:
                 _warn(f"{self.__element_name}: {var_name}={value} should be in {values}.")
                 _warn(f"{self.__element_name}: {var_name}={value} should be in {values}.")
             else:
             else:
                 self.__set_string_attribute(var_name, default_value)
                 self.__set_string_attribute(var_name, default_value)
         return self
         return self
 
 
-    def __set_list_attribute(
-        self,
-        name: str,
-        hash_name: t.Optional[str],
-        val: t.Any,
-        elt_type: t.Type,
-        dynamic=True,
-        default_val: t.Optional[t.Any] = None,
-    ) -> t.List[str]:
-        val = default_val if val is None else val
-        if not hash_name and isinstance(val, str):
-            val = [elt_type(t.strip()) for t in val.split(";")]
-        if isinstance(val, list):
-            if hash_name and dynamic:
-                self.__set_react_attribute(name, hash_name)
-                return [f"{name}={hash_name}"]
-            else:
-                self.__set_json_attribute(name, val)
-        elif val is not None:
-            _warn(f"{self.__element_name}: {name} should be a list of {elt_type}.")
-        return []
-
     def _set_chart_selected(self, max=0):
     def _set_chart_selected(self, max=0):
         name = "selected"
         name = "selected"
-        default_sel = self.__attributes.get(name)
-        if not isinstance(default_sel, list) and name in self.__attributes:
+        default_sel = self.__prop_values.get(name)
+        if not isinstance(default_sel, list) and name in self.__prop_values:
             default_sel = []
             default_sel = []
         if max == 0:
         if max == 0:
             self.__update_vars.extend(
             self.__update_vars.extend(
@@ -693,10 +755,10 @@ class _Builder:
             return
             return
         idx = 1
         idx = 1
         name_idx = f"{name}[{idx}]"
         name_idx = f"{name}[{idx}]"
-        sel = self.__attributes.get(name_idx)
-        if not isinstance(sel, list) and name_idx in self.__attributes:
+        sel = self.__prop_values.get(name_idx)
+        if not isinstance(sel, list) and name_idx in self.__prop_values:
             sel = []
             sel = []
-        while idx <= max or name_idx in self.__attributes:
+        while idx <= max or name_idx in self.__prop_values:
             if sel is not None or default_sel is not None:
             if sel is not None or default_sel is not None:
                 self.__update_vars.extend(
                 self.__update_vars.extend(
                     self.__set_list_attribute(
                     self.__set_list_attribute(
@@ -708,14 +770,14 @@ class _Builder:
                 )
                 )
             idx += 1
             idx += 1
             name_idx = f"{name}[{idx}]"
             name_idx = f"{name}[{idx}]"
-            sel = self.__attributes.get(name_idx)
-            if not isinstance(sel, list) and name_idx in self.__attributes:
+            sel = self.__prop_values.get(name_idx)
+            if not isinstance(sel, list) and name_idx in self.__prop_values:
                 sel = []
                 sel = []
 
 
     def _get_list_attribute(self, name: str, list_type: PropertyType):
     def _get_list_attribute(self, name: str, list_type: PropertyType):
         hash_name = self.__hashes.get(name)
         hash_name = self.__hashes.get(name)
         if hash_name is None:
         if hash_name is None:
-            list_val = self.__attributes.get(name)
+            list_val = self.__prop_values.get(name)
             if isinstance(list_val, str):
             if isinstance(list_val, str):
                 list_val = list(list_val.split(";"))
                 list_val = list(list_val.split(";"))
             if isinstance(list_val, list):
             if isinstance(list_val, list):
@@ -736,7 +798,7 @@ class _Builder:
 
 
     def __set_class_names(self):
     def __set_class_names(self):
         self.set_attribute("libClassName", self.__lib_name + "-" + self.__control_type.replace("_", "-"))
         self.set_attribute("libClassName", self.__lib_name + "-" + self.__control_type.replace("_", "-"))
-        if (private_css := self.__attributes.get("style")) and isinstance(private_css, (dict, _MapDict)):
+        if (private_css := self.__prop_values.get("style")) and isinstance(private_css, (dict, _MapDict)):
             taipy_style = etree.Element("TaipyStyle")
             taipy_style = etree.Element("TaipyStyle")
             taipy_style.set("className", f"tpcss-{id(private_css)}")
             taipy_style.set("className", f"tpcss-{id(private_css)}")
             taipy_style.set(
             taipy_style.set(
@@ -748,7 +810,7 @@ class _Builder:
         return self.__set_dynamic_string_attribute("class_name", dynamic_property_name="dynamic_class_name")
         return self.__set_dynamic_string_attribute("class_name", dynamic_property_name="dynamic_class_name")
 
 
     def _set_dataType(self):
     def _set_dataType(self):
-        value = self.__attributes.get("value")
+        value = self.__prop_values.get("value")
         return self.set_attribute("dataType", _get_data_type(value))
         return self.set_attribute("dataType", _get_data_type(value))
 
 
     def _set_file_content(self, var_name: str = "content"):
     def _set_file_content(self, var_name: str = "content"):
@@ -759,7 +821,7 @@ class _Builder:
         return self
         return self
 
 
     def _set_content(self, var_name: str = "content", image=True):
     def _set_content(self, var_name: str = "content", image=True):
-        content = self.__attributes.get(var_name)
+        content = self.__prop_values.get(var_name)
         hash_name = self.__hashes.get(var_name)
         hash_name = self.__hashes.get(var_name)
         if content is None and hash_name is None:
         if content is None and hash_name is None:
             return self
             return self
@@ -773,41 +835,6 @@ class _Builder:
             )
             )
         return self.set_attribute(_to_camel_case(f"default_{var_name}"), value)
         return self.set_attribute(_to_camel_case(f"default_{var_name}"), value)
 
 
-    def __set_dynamic_string_list(self, var_name: str, default_value: t.Any):
-        hash_name = self.__hashes.get(var_name)
-        loi = self.__attributes.get(var_name)
-        if loi is None:
-            loi = default_value
-        if isinstance(loi, str):
-            loi = [s.strip() for s in loi.split(";") if s.strip()]
-        if isinstance(loi, list):
-            self.__set_json_attribute(_to_camel_case(f"default_{var_name}"), loi)
-        if hash_name:
-            self.__update_vars.append(f"{var_name}={hash_name}")
-            self.__set_react_attribute(var_name, hash_name)
-        return self
-
-    def __set_dynamic_number_attribute(self, var_name: str, default_value: t.Any):
-        hash_name = self.__hashes.get(var_name)
-        numVal = self.__attributes.get(var_name)
-        if numVal is None:
-            numVal = default_value
-        if isinstance(numVal, str):
-            try:
-                numVal = float(numVal)
-            except Exception as e:
-                _warn(f"{self.__element_name}: {var_name} cannot be transformed into a number", e)
-                numVal = 0
-        if isinstance(numVal, numbers.Number):
-            self.__set_react_attribute(_to_camel_case(f"default_{var_name}"), numVal)
-        elif numVal is not None:
-            _warn(f"{self.__element_name}: {var_name} value is not valid ({numVal}).")
-        if hash_name:
-            hash_name = self.__get_typed_hash_name(hash_name, PropertyType.number)
-            self.__update_vars.append(f"{var_name}={hash_name}")
-            self.__set_react_attribute(var_name, hash_name)
-        return self
-
     def __set_default_value(
     def __set_default_value(
         self,
         self,
         var_name: str,
         var_name: str,
@@ -816,7 +843,7 @@ class _Builder:
         var_type: t.Optional[t.Union[PropertyType, t.Type[_TaipyBase]]] = None,
         var_type: t.Optional[t.Union[PropertyType, t.Type[_TaipyBase]]] = None,
     ):
     ):
         if value is None:
         if value is None:
-            value = self.__attributes.get(var_name)
+            value = self.__prop_values.get(var_name)
         default_var_name = _to_camel_case(f"default_{var_name}")
         default_var_name = _to_camel_case(f"default_{var_name}")
         if isinstance(value, (datetime, date, time)):
         if isinstance(value, (datetime, date, time)):
             return self.set_attribute(default_var_name, _date_to_string(value))
             return self.set_attribute(default_var_name, _date_to_string(value))
@@ -861,7 +888,7 @@ class _Builder:
         """
         """
         var_name = self.__default_property_name if var_name is None else var_name
         var_name = self.__default_property_name if var_name is None else var_name
         if var_type == PropertyType.slider_value or var_type == PropertyType.toggle_value:
         if var_type == PropertyType.slider_value or var_type == PropertyType.toggle_value:
-            if self.__attributes.get("lov"):
+            if self.__prop_values.get("lov"):
                 var_type = PropertyType.lov_value
                 var_type = PropertyType.lov_value
                 native_type = False
                 native_type = False
             elif var_type == PropertyType.toggle_value:
             elif var_type == PropertyType.toggle_value:
@@ -871,7 +898,7 @@ class _Builder:
             else:
             else:
                 var_type = (
                 var_type = (
                     PropertyType.dynamic_lo_numbers
                     PropertyType.dynamic_lo_numbers
-                    if isinstance(self.__attributes.get("value"), list)
+                    if isinstance(self.__prop_values.get("value"), list)
                     else PropertyType.dynamic_number
                     else PropertyType.dynamic_number
                 )
                 )
                 native_type = True
                 native_type = True
@@ -887,7 +914,7 @@ class _Builder:
                 self.__set_update_var_name(hash_name)
                 self.__set_update_var_name(hash_name)
             if with_default:
             if with_default:
                 if native_type:
                 if native_type:
-                    val = self.__attributes.get(var_name)
+                    val = self.__prop_values.get(var_name)
                     if native_type and isinstance(val, str):
                     if native_type and isinstance(val, str):
                         with contextlib.suppress(Exception):
                         with contextlib.suppress(Exception):
                             val = float(val)
                             val = float(val)
@@ -895,9 +922,9 @@ class _Builder:
                 else:
                 else:
                     self.__set_default_value(var_name, var_type=var_type)
                     self.__set_default_value(var_name, var_type=var_type)
         else:
         else:
-            if var_type == PropertyType.data and (self.__control_type != "chart" or "figure" not in self.__attributes):
+            if var_type == PropertyType.data and (self.__control_type != "chart" or "figure" not in self.__prop_values):
                 _warn(f"{self.__control_type}.{var_name} property should be bound.")
                 _warn(f"{self.__control_type}.{var_name} property should be bound.")
-            value = self.__attributes.get(var_name)
+            value = self.__prop_values.get(var_name)
             if value is not None:
             if value is not None:
                 if native_type:
                 if native_type:
                     if isinstance(value, str):
                     if isinstance(value, str):
@@ -911,28 +938,28 @@ class _Builder:
         return self
         return self
 
 
     def _set_labels(self, var_name: str = "labels"):
     def _set_labels(self, var_name: str = "labels"):
-        if value := self.__attributes.get(var_name):
+        if value := self.__prop_values.get(var_name):
             if _is_true(value):
             if _is_true(value):
                 return self.__set_react_attribute(_to_camel_case(var_name), True)
                 return self.__set_react_attribute(_to_camel_case(var_name), True)
             elif isinstance(value, (dict, _MapDict)):
             elif isinstance(value, (dict, _MapDict)):
-                return self.set_dict_attribute(var_name)
+                return self.__set_dict_attribute(var_name)
         return self
         return self
 
 
     def _set_partial(self):
     def _set_partial(self):
         if self.__control_type not in _Builder.__BLOCK_CONTROLS:
         if self.__control_type not in _Builder.__BLOCK_CONTROLS:
             return self
             return self
-        if partial := self.__attributes.get("partial"):
-            if self.__attributes.get("page"):
+        if partial := self.__prop_values.get("partial"):
+            if self.__prop_values.get("page"):
                 _warn(f"{self.__element_name} control: page and partial should not be both defined.")
                 _warn(f"{self.__element_name} control: page and partial should not be both defined.")
             if isinstance(partial, Partial):
             if isinstance(partial, Partial):
-                self.__attributes["page"] = partial._route
+                self.__prop_values["page"] = partial._route
                 self.__set_react_attribute("partial", partial._route)
                 self.__set_react_attribute("partial", partial._route)
                 self.__set_react_attribute("defaultPartial", True)
                 self.__set_react_attribute("defaultPartial", True)
         return self
         return self
 
 
     def _set_propagate(self):
     def _set_propagate(self):
         val = self.__get_boolean_attribute("propagate", t.cast(bool, self.__gui._config.config.get("propagate")))
         val = self.__get_boolean_attribute("propagate", t.cast(bool, self.__gui._config.config.get("propagate")))
-        return self if val else self.set_boolean_attribute("propagate", False)
+        return self if val else self.__set_boolean_attribute("propagate", False)
 
 
     def __set_refresh_on_update(self):
     def __set_refresh_on_update(self):
         if self.__update_vars:
         if self.__update_vars:
@@ -942,7 +969,7 @@ class _Builder:
     def _set_table_pagesize_options(self, default_size=None):
     def _set_table_pagesize_options(self, default_size=None):
         if default_size is None:
         if default_size is None:
             default_size = [50, 100, 500]
             default_size = [50, 100, 500]
-        page_size_options = self.__attributes.get("page_size_options", default_size)
+        page_size_options = self.__prop_values.get("page_size_options", default_size)
         if isinstance(page_size_options, str):
         if isinstance(page_size_options, str):
             try:
             try:
                 page_size_options = [int(s.strip()) for s in page_size_options.split(";")]
                 page_size_options = [int(s.strip()) for s in page_size_options.split(";")]
@@ -957,10 +984,10 @@ class _Builder:
     def _set_input_type(self, type_name: str, allow_password=False):
     def _set_input_type(self, type_name: str, allow_password=False):
         if allow_password and self.__get_boolean_attribute("password", False):
         if allow_password and self.__get_boolean_attribute("password", False):
             return self.set_attribute("type", "password")
             return self.set_attribute("type", "password")
-        return self.set_attribute("type", self.__attributes.get("type", type_name))
+        return self.set_attribute("type", self.__prop_values.get("type", type_name))
 
 
     def _set_kind(self):
     def _set_kind(self):
-        if self.__attributes.get("theme", False):
+        if self.__prop_values.get("theme", False):
             self.set_attribute("mode", "theme")
             self.set_attribute("mode", "theme")
         return self
         return self
 
 
@@ -972,21 +999,6 @@ class _Builder:
             hash_name = self.__gui._evaluate_bind_holder(t.cast(t.Type[_TaipyBase], taipy_type), expr)
             hash_name = self.__gui._evaluate_bind_holder(t.cast(t.Type[_TaipyBase], taipy_type), expr)
         return hash_name
         return hash_name
 
 
-    def __set_dynamic_bool_attribute(self, name: str, def_val: t.Any, with_update: bool, update_main=True):
-        hash_name = self.__hashes.get(name)
-        val = self.__get_boolean_attribute(name, def_val)
-        default_name = f"default_{name}" if hash_name is not None else name
-        if val != def_val:
-            self.set_boolean_attribute(default_name, val)
-        if hash_name is not None:
-            hash_name = self.__get_typed_hash_name(hash_name, PropertyType.dynamic_boolean)
-            self.__set_react_attribute(_to_camel_case(name), _get_client_var_name(hash_name))
-            if with_update:
-                if update_main:
-                    self.__set_update_var_name(hash_name)
-                else:
-                    self.__update_vars.append(f"{_to_camel_case(name)}={hash_name}")
-
     def __set_dynamic_property_without_default(
     def __set_dynamic_property_without_default(
         self, name: str, property_type: PropertyType, optional: t.Optional[bool] = False
         self, name: str, property_type: PropertyType, optional: t.Optional[bool] = False
     ):
     ):
@@ -1018,12 +1030,12 @@ class _Builder:
         return self.__set_react_attribute(_to_camel_case(property_name), _get_client_var_name(front_var))
         return self.__set_react_attribute(_to_camel_case(property_name), _get_client_var_name(front_var))
 
 
     def _set_indexed_icons(self, name="use_icon"):
     def _set_indexed_icons(self, name="use_icon"):
-        global_icon = self.__attributes.get(name)
+        global_icon = self.__prop_values.get(name)
         indexed = self.get_name_indexed_property(name)
         indexed = self.get_name_indexed_property(name)
         global_bool = _is_true(global_icon) if global_icon is not None and _is_boolean(global_icon) else None
         global_bool = _is_true(global_icon) if global_icon is not None and _is_boolean(global_icon) else None
         if global_icon is not None and not indexed:
         if global_icon is not None and not indexed:
             if global_bool is not None:
             if global_bool is not None:
-                self.set_boolean_attribute(name, global_bool)
+                self.__set_boolean_attribute(name, global_bool)
             else:
             else:
                 self.__set_json_attribute(_to_camel_case(name), {"__default": str(global_icon)})
                 self.__set_json_attribute(_to_camel_case(name), {"__default": str(global_icon)})
         elif indexed:
         elif indexed:
@@ -1044,19 +1056,25 @@ class _Builder:
 
 
             attributes (list(tuple)): The list of attributes as (property name, property type, default value).
             attributes (list(tuple)): The list of attributes as (property name, property type, default value).
         """
         """
-        attributes.append(("id",))  # Every element should have an id attribute
+        # Every element must have an id attribute
+        if not any(attr[0] == "id" for attr in attributes):
+            attributes.append(("id",))
         for attr in attributes:
         for attr in attributes:
             if not isinstance(attr, tuple):
             if not isinstance(attr, tuple):
                 attr = (attr,)
                 attr = (attr,)
             var_type = _get_tuple_val(attr, 1, PropertyType.string)
             var_type = _get_tuple_val(attr, 1, PropertyType.string)
             if var_type == PropertyType.to_json:
             if var_type == PropertyType.to_json:
                 var_type = _TaipyToJson
                 var_type = _TaipyToJson
-            if var_type == PropertyType.boolean:
+            if var_type == PropertyType.any:
+                self.__set_any_attribute(attr[0], _get_tuple_val(attr, 2, None))
+            elif var_type == PropertyType.dynamic_any:
+                self.__set_dynamic_any_attribute(attr[0], _get_tuple_val(attr, 2, None))
+            elif var_type == PropertyType.boolean:
                 def_val = _get_tuple_val(attr, 2, False)
                 def_val = _get_tuple_val(attr, 2, False)
-                if isinstance(def_val, bool) or self.__attributes.get(attr[0], None) is not None:
+                if isinstance(def_val, bool) or self.__prop_values.get(attr[0], None) is not None:
                     val = self.__get_boolean_attribute(attr[0], def_val)
                     val = self.__get_boolean_attribute(attr[0], def_val)
                     if val != def_val:
                     if val != def_val:
-                        self.set_boolean_attribute(attr[0], val)
+                        self.__set_boolean_attribute(attr[0], val)
             elif var_type == PropertyType.dynamic_boolean:
             elif var_type == PropertyType.dynamic_boolean:
                 self.__set_dynamic_bool_attribute(
                 self.__set_dynamic_bool_attribute(
                     attr[0],
                     attr[0],
@@ -1065,7 +1083,7 @@ class _Builder:
                     _get_tuple_val(attr, 4, True),
                     _get_tuple_val(attr, 4, True),
                 )
                 )
             elif var_type == PropertyType.number:
             elif var_type == PropertyType.number:
-                self.set_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
+                self.__set_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
             elif var_type == PropertyType.dynamic_number:
             elif var_type == PropertyType.dynamic_number:
                 self.__set_dynamic_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
                 self.__set_dynamic_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
             elif var_type == PropertyType.string:
             elif var_type == PropertyType.string:
@@ -1079,12 +1097,27 @@ class _Builder:
                     self.__set_list_attribute(
                     self.__set_list_attribute(
                         attr[0],
                         attr[0],
                         self.__hashes.get(attr[0]),
                         self.__hashes.get(attr[0]),
-                        self.__attributes.get(attr[0]),
+                        self.__prop_values.get(attr[0]),
                         str,
                         str,
                         False,
                         False,
                         _get_tuple_val(attr, 2, None),
                         _get_tuple_val(attr, 2, None),
                     )
                     )
                 )
                 )
+            elif var_type == PropertyType.string_or_number:
+                self.__set_string_or_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
+            elif var_type == PropertyType.dynamic_list:
+                self.__set_dynamic_string_list(attr[0], _get_tuple_val(attr, 2, None))
+            elif var_type == PropertyType.dict:
+                self.__set_dict_attribute(attr[0], _get_tuple_val(attr, 2, None))
+            elif var_type == PropertyType.dynamic_dict:
+                self.__set_dynamic_dict_attribute(attr[0], _get_tuple_val(attr, 2, None))
+            elif var_type == PropertyType.boolean_or_list:
+                if _is_boolean(self.__prop_values.get(attr[0])):
+                    self.__set_dynamic_bool_attribute(attr[0], _get_tuple_val(attr, 2, False), True, update_main=False)
+                else:
+                    self.__set_dynamic_string_list(attr[0], _get_tuple_val(attr, 2, None))
+            elif var_type == PropertyType.dynamic_date:
+                self.__set_dynamic_date_attribute(attr[0], _get_tuple_val(attr, 2, None))
             elif var_type == PropertyType.function:
             elif var_type == PropertyType.function:
                 self.__set_function_attribute(attr[0], _get_tuple_val(attr, 2, None), _get_tuple_val(attr, 3, True))
                 self.__set_function_attribute(attr[0], _get_tuple_val(attr, 2, None), _get_tuple_val(attr, 3, True))
             elif var_type == PropertyType.react:
             elif var_type == PropertyType.react:
@@ -1093,26 +1126,11 @@ class _Builder:
                     self.__update_vars.append(f"{prop_name}={hash_name}")
                     self.__update_vars.append(f"{prop_name}={hash_name}")
                     self.__set_react_attribute(prop_name, hash_name)
                     self.__set_react_attribute(prop_name, hash_name)
                 else:
                 else:
-                    self.__set_react_attribute(prop_name, self.__attributes.get(attr[0], _get_tuple_val(attr, 2, None)))
+                    self.__set_react_attribute(prop_name, self.__prop_values.get(attr[0], _get_tuple_val(attr, 2, None)))
             elif var_type == PropertyType.broadcast:
             elif var_type == PropertyType.broadcast:
                 self.__set_react_attribute(
                 self.__set_react_attribute(
                     _to_camel_case(attr[0]), _get_broadcast_var_name(_get_tuple_val(attr, 2, None))
                     _to_camel_case(attr[0]), _get_broadcast_var_name(_get_tuple_val(attr, 2, None))
                 )
                 )
-            elif var_type == PropertyType.string_or_number:
-                self.__set_string_or_number_attribute(attr[0], _get_tuple_val(attr, 2, None))
-            elif var_type == PropertyType.dict:
-                self.set_dict_attribute(attr[0], _get_tuple_val(attr, 2, None))
-            elif var_type == PropertyType.dynamic_dict:
-                self.set_dynamic_dict_attribute(attr[0], _get_tuple_val(attr, 2, None))
-            elif var_type == PropertyType.dynamic_list:
-                self.__set_dynamic_string_list(attr[0], _get_tuple_val(attr, 2, None))
-            elif var_type == PropertyType.boolean_or_list:
-                if _is_boolean(self.__attributes.get(attr[0])):
-                    self.__set_dynamic_bool_attribute(attr[0], _get_tuple_val(attr, 2, False), True, update_main=False)
-                else:
-                    self.__set_dynamic_string_list(attr[0], _get_tuple_val(attr, 2, None))
-            elif var_type == PropertyType.dynamic_date:
-                self.__set_dynamic_date_attribute(attr[0], _get_tuple_val(attr, 2, None))
             elif var_type == PropertyType.data:
             elif var_type == PropertyType.data:
                 self.__set_dynamic_property_without_default(attr[0], t.cast(PropertyType, var_type))
                 self.__set_dynamic_property_without_default(attr[0], t.cast(PropertyType, var_type))
             elif (
             elif (
@@ -1139,7 +1157,7 @@ class _Builder:
                     self.__update_vars.append(f"{prop_name}={hash_name}")
                     self.__update_vars.append(f"{prop_name}={hash_name}")
                     self.__set_react_attribute(prop_name, hash_name)
                     self.__set_react_attribute(prop_name, hash_name)
                 else:
                 else:
-                    val = self.__attributes.get(attr[0])
+                    val = self.__prop_values.get(attr[0])
                     self.set_attribute(
                     self.set_attribute(
                         prop_name, var_type(_get_tuple_val(attr, 2, None) if val is None else val, "").get()
                         prop_name, var_type(_get_tuple_val(attr, 2, None) if val is None else val, "").get()
                     )
                     )

+ 31 - 31
taipy/gui/_renderers/factory.py

@@ -75,7 +75,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Alert",
             element_name="Alert",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(var_type=PropertyType.dynamic_string)
         .set_value_and_default(var_type=PropertyType.dynamic_string)
         .set_attributes(
         .set_attributes(
@@ -89,7 +89,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Button",
             element_name="Button",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(with_update=False)
         .set_value_and_default(with_update=False)
         .set_attributes(
         .set_attributes(
@@ -103,7 +103,7 @@ class _Factory:
             ]
             ]
         ),
         ),
         "chat": lambda gui, control_type, attrs: _Builder(
         "chat": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Chat", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Chat", prop_values=attrs, default_value=None
         )
         )
         .set_value_and_default(with_update=True, with_default=False, var_type=PropertyType.data)
         .set_value_and_default(with_update=True, with_default=False, var_type=PropertyType.data)
         .set_attributes(
         .set_attributes(
@@ -123,7 +123,7 @@ class _Factory:
             ]
             ]
         ),
         ),
         "chart": lambda gui, control_type, attrs: _Builder(
         "chart": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Chart", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Chart", prop_values=attrs, default_value=None
         )
         )
         .set_value_and_default(with_default=False, var_type=PropertyType.data)
         .set_value_and_default(with_default=False, var_type=PropertyType.data)
         .set_attributes(
         .set_attributes(
@@ -148,13 +148,13 @@ class _Factory:
         ._get_chart_config("scatter", "lines+markers")
         ._get_chart_config("scatter", "lines+markers")
         ._set_propagate(),
         ._set_propagate(),
         "content": lambda gui, control_type, attrs: _Builder(
         "content": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="PageContent", attributes=attrs
+            gui=gui, control_type=control_type, element_name="PageContent", prop_values=attrs
         ),
         ),
         "date": lambda gui, control_type, attrs: _Builder(
         "date": lambda gui, control_type, attrs: _Builder(
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="DateSelector",
             element_name="DateSelector",
-            attributes=attrs,
+            prop_values=attrs,
             default_value=datetime.fromtimestamp(0),
             default_value=datetime.fromtimestamp(0),
         )
         )
         .set_value_and_default(var_type=PropertyType.date)
         .set_value_and_default(var_type=PropertyType.date)
@@ -178,7 +178,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="DateRange",
             element_name="DateRange",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(var_type=PropertyType.date_range)
         .set_value_and_default(var_type=PropertyType.date_range)
         .set_attributes(
         .set_attributes(
@@ -200,7 +200,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Dialog",
             element_name="Dialog",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(var_type=PropertyType.dynamic_boolean)
         .set_value_and_default(var_type=PropertyType.dynamic_boolean)
         ._set_partial()  # partial should be set before page
         ._set_partial()  # partial should be set before page
@@ -221,7 +221,7 @@ class _Factory:
         )
         )
         ._set_propagate(),
         ._set_propagate(),
         "expandable": lambda gui, control_type, attrs: _Builder(
         "expandable": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Expandable", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Expandable", prop_values=attrs, default_value=None
         )
         )
         .set_value_and_default()
         .set_value_and_default()
         ._set_partial()  # partial should be set before page
         ._set_partial()  # partial should be set before page
@@ -237,7 +237,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="FileDownload",
             element_name="FileDownload",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(var_name="label", with_update=False)
         .set_value_and_default(var_name="label", with_update=False)
         ._set_content("content", image=False)
         ._set_content("content", image=False)
@@ -257,7 +257,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="FileSelector",
             element_name="FileSelector",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(var_name="label", with_update=False)
         .set_value_and_default(var_name="label", with_update=False)
         ._set_file_content()
         ._set_file_content()
@@ -278,7 +278,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Image",
             element_name="Image",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(var_name="label", with_update=False)
         .set_value_and_default(var_name="label", with_update=False)
         ._set_content("content")
         ._set_content("content")
@@ -295,7 +295,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Indicator",
             element_name="Indicator",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(with_update=False, native_type=True)
         .set_value_and_default(with_update=False, native_type=True)
         .set_attributes(
         .set_attributes(
@@ -313,7 +313,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Input",
             element_name="Input",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         ._set_input_type("text", True)
         ._set_input_type("text", True)
         .set_value_and_default()
         .set_value_and_default()
@@ -334,7 +334,7 @@ class _Factory:
             ]
             ]
         ),
         ),
         "layout": lambda gui, control_type, attrs: _Builder(
         "layout": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Layout", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Layout", prop_values=attrs, default_value=None
         )
         )
         .set_value_and_default(with_default=False)
         .set_value_and_default(with_default=False)
         .set_attributes(
         .set_attributes(
@@ -344,7 +344,7 @@ class _Factory:
             ]
             ]
         ),
         ),
         "login": lambda gui, control_type, attrs: _Builder(
         "login": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Login", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Login", prop_values=attrs, default_value=None
         )
         )
         .set_value_and_default(default_val="Log-in")
         .set_value_and_default(default_val="Log-in")
         .set_attributes(
         .set_attributes(
@@ -358,7 +358,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="MenuCtl",
             element_name="MenuCtl",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_attributes(
         .set_attributes(
             [
             [
@@ -378,7 +378,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Metric",
             element_name="Metric",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(var_type=PropertyType.dynamic_number, native_type=True)
         .set_value_and_default(var_type=PropertyType.dynamic_number, native_type=True)
         .set_attributes(
         .set_attributes(
@@ -407,7 +407,7 @@ class _Factory:
             ]
             ]
         ),
         ),
         "navbar": lambda gui, control_type, attrs: _Builder(
         "navbar": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="NavBar", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="NavBar", prop_values=attrs, default_value=None
         ).set_attributes(
         ).set_attributes(
             [
             [
                 ("active", PropertyType.dynamic_boolean, True),
                 ("active", PropertyType.dynamic_boolean, True),
@@ -419,7 +419,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Input",
             element_name="Input",
-            attributes=attrs,
+            prop_values=attrs,
             default_value=0,
             default_value=0,
         )
         )
         ._set_input_type("number")
         ._set_input_type("number")
@@ -442,7 +442,7 @@ class _Factory:
             ]
             ]
         ),
         ),
         "pane": lambda gui, control_type, attrs: _Builder(
         "pane": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Pane", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Pane", prop_values=attrs, default_value=None
         )
         )
         .set_value_and_default(var_type=PropertyType.dynamic_boolean)
         .set_value_and_default(var_type=PropertyType.dynamic_boolean)
         ._set_partial()  # partial should be set before page
         ._set_partial()  # partial should be set before page
@@ -462,7 +462,7 @@ class _Factory:
         )
         )
         ._set_propagate(),
         ._set_propagate(),
         "part": lambda gui, control_type, attrs: _Builder(
         "part": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Part", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Part", prop_values=attrs, default_value=None
         )
         )
         ._set_partial()  # partial should be set before page
         ._set_partial()  # partial should be set before page
         .set_attributes(
         .set_attributes(
@@ -478,7 +478,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Progress",
             element_name="Progress",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(var_type=PropertyType.dynamic_number, native_type=True)
         .set_value_and_default(var_type=PropertyType.dynamic_number, native_type=True)
         .set_attributes(
         .set_attributes(
@@ -492,7 +492,7 @@ class _Factory:
             ]
             ]
         ),
         ),
         "selector": lambda gui, control_type, attrs: _Builder(
         "selector": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Selector", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Selector", prop_values=attrs, default_value=None
         )
         )
         .set_value_and_default(with_default=False, var_type=PropertyType.lov_value)
         .set_value_and_default(with_default=False, var_type=PropertyType.lov_value)
         .set_attributes(
         .set_attributes(
@@ -518,7 +518,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Slider",
             element_name="Slider",
-            attributes=attrs,
+            prop_values=attrs,
             default_value=0,
             default_value=0,
         )
         )
         .set_value_and_default(native_type=True, var_type=PropertyType.slider_value)
         .set_value_and_default(native_type=True, var_type=PropertyType.slider_value)
@@ -546,7 +546,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Status",
             element_name="Status",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(with_update=False)
         .set_value_and_default(with_update=False)
         .set_attributes(
         .set_attributes(
@@ -560,7 +560,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Table",
             element_name="Table",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(with_default=False, var_type=PropertyType.data)
         .set_value_and_default(with_default=False, var_type=PropertyType.data)
         ._get_dataframe_attributes()
         ._get_dataframe_attributes()
@@ -594,7 +594,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="Field",
             element_name="Field",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(with_update=False)
         .set_value_and_default(with_update=False)
         ._set_dataType()
         ._set_dataType()
@@ -611,7 +611,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="TimeSelector",
             element_name="TimeSelector",
-            attributes=attrs,
+            prop_values=attrs,
             default_value=datetime.today().time(),
             default_value=datetime.today().time(),
         )
         )
         .set_value_and_default(var_type=PropertyType.time)
         .set_value_and_default(var_type=PropertyType.time)
@@ -628,7 +628,7 @@ class _Factory:
         )
         )
         ._set_propagate(),
         ._set_propagate(),
         "toggle": lambda gui, control_type, attrs: _Builder(
         "toggle": lambda gui, control_type, attrs: _Builder(
-            gui=gui, control_type=control_type, element_name="Toggle", attributes=attrs, default_value=None
+            gui=gui, control_type=control_type, element_name="Toggle", prop_values=attrs, default_value=None
         )
         )
         .set_value_and_default(with_default=False, var_type=PropertyType.toggle_value)
         .set_value_and_default(with_default=False, var_type=PropertyType.toggle_value)
         .set_attributes(
         .set_attributes(
@@ -650,7 +650,7 @@ class _Factory:
             gui=gui,
             gui=gui,
             control_type=control_type,
             control_type=control_type,
             element_name="TreeView",
             element_name="TreeView",
-            attributes=attrs,
+            prop_values=attrs,
         )
         )
         .set_value_and_default(with_default=False, var_type=PropertyType.lov_value)
         .set_value_and_default(with_default=False, var_type=PropertyType.lov_value)
         .set_attributes(
         .set_attributes(

+ 4 - 7
taipy/gui/builder/_api_generator.py

@@ -89,10 +89,7 @@ class _ElementApiGenerator(object, metaclass=_Singleton):
                 f"Python API for extension library '{library_name}' is not available. To fix this, import 'taipy.gui.builder' before importing the extension library."  # noqa: E501
                 f"Python API for extension library '{library_name}' is not available. To fix this, import 'taipy.gui.builder' before importing the extension library."  # noqa: E501
             )
             )
             return
             return
-        library_module = getattr(self.__module, library_name, None)
-        if library_module is None:
-            library_module = types.ModuleType(library_name)
-            setattr(self.__module, library_name, library_module)
+        library_module = sys.modules[library.__module__]
         for element_name, element in library.get_elements().items():
         for element_name, element in library.get_elements().items():
             setattr(
             setattr(
                 library_module,
                 library_module,
@@ -104,13 +101,13 @@ class _ElementApiGenerator(object, metaclass=_Singleton):
                     {name: str(prop.property_type) for name, prop in element.attributes.items()},
                     {name: str(prop.property_type) for name, prop in element.attributes.items()},
                 ),
                 ),
             )
             )
-            # Allow element to be accessed from the root module
+            # Allow element to be accessed from this module (taipy.gui.builder)
             if hasattr(self.__module, element_name):
             if hasattr(self.__module, element_name):
                 _TaipyLogger._get_logger().info(
                 _TaipyLogger._get_logger().info(
                     f"Can't add element `{element_name}` of library `{library_name}` to the root of Builder API as another element with the same name already exists."  # noqa: E501
                     f"Can't add element `{element_name}` of library `{library_name}` to the root of Builder API as another element with the same name already exists."  # noqa: E501
                 )
                 )
-                continue
-            setattr(self.__module, element_name, getattr(library_module, element_name))
+            else:
+                setattr(self.__module, element_name, getattr(library_module, element_name))
 
 
     @staticmethod
     @staticmethod
     def create_block_api(
     def create_block_api(

+ 128 - 0
taipy/gui/extension/__main__.py

@@ -0,0 +1,128 @@
+# © 2021-2025, Avaiga Pte Ltd. All Rights Reserved. The use of the Taipy software and any part thereof is governed by
+# Avaiga Pte Ltd's Software License and Maintenance Agreement. Unauthorised use, reproduction and modification is
+# strictly not allowed.
+
+import argparse
+import os
+from taipy.gui.extension import ElementLibrary
+import typing as t
+
+
+def error(message):
+    print(message)
+    exit(1)
+
+
+def generate_tgb(args):
+    from importlib import import_module
+    from inspect import getmembers, isclass
+
+    from taipy.gui.types import PropertyType
+
+    package_root_dir = args.package_root_dir[0]
+    # Remove potential directory separator at the end of the package root dir
+    if package_root_dir[-1] == "/" or package_root_dir[-1] == "\\":
+        package_root_dir = package_root_dir[:-1]
+    module = None
+    try:
+        module = import_module(package_root_dir)
+    except Exception as e:
+        error(f"Couldn't open module '{package_root_dir}' ({e})")
+    library: t.Optional[ElementLibrary] = None
+    for _, member in getmembers(module, lambda o: isclass(o) and issubclass(o, ElementLibrary)):
+        if library:
+            error("Extension contains more than one ElementLibrary")
+        library = member()
+    if library is None:
+        error("Extension does not contain any ElementLibrary")
+        return
+    pyi_path = os.path.join(package_root_dir, "__init__.pyi")
+    pyi_file = None
+    try:
+        pyi_file = open(pyi_path, "w")
+    except Exception as e:
+        error(f"Couldn't open Python Interface Definition file '{pyi_file}' ({e})")
+
+    def clean_doc_string(doc_string) -> t.Optional[str]:
+        if not doc_string:
+            return None
+        lines = doc_string.splitlines()
+        min_indent = min((len(line) - len(line.lstrip())) for line in lines if line.strip())
+        lines = [line[min_indent:] if line.strip() else "" for line in lines]
+        while lines and not lines[0].strip():
+            lines.pop(0)
+        while lines and not lines[-1].strip():
+            lines.pop()
+        return "\n".join(lines) if lines else None
+
+    print(f"Inspecting extension library '{library.get_name()}'")  # noqa: T201
+    print("# ----------------------------------------------------------------------", file=pyi_file)
+    print("# Generated by taipy.gui.extension module", file=pyi_file)
+    print("# ----------------------------------------------------------------------", file=pyi_file)
+    for element_name, element in library.get_elements().items():
+        properties = []
+        property_doc = {}
+        default_property_found = False
+        for property_name, property in element.attributes.items():
+            desc = property_name
+            # Could use 'match' with Python >= 3.10
+            if property.property_type in [PropertyType.boolean, PropertyType.dynamic_boolean]:
+                desc = desc + ": bool"
+            elif property.property_type in [PropertyType.string, PropertyType.dynamic_string]:
+                desc = desc + ": str"
+            elif property.property_type in [PropertyType.dict, PropertyType.dynamic_dict]:
+                desc = desc + ": dict"
+            if property_name == element.default_attribute:
+                properties.insert(0, desc)
+                default_property_found = True
+            else:
+                properties.append(desc)
+            if doc_string := clean_doc_string(property.doc_string):
+                property_doc[property_name] = doc_string
+        if default_property_found and len(properties) > 1:
+            properties.insert(1, "*")
+        doc_string = clean_doc_string(element.doc_string)
+        documentation = ""
+        if doc_string:
+            lines = doc_string.splitlines()
+            documentation = f"    \"\"\"{lines.pop(0)}\n"
+            while lines:
+                line = lines.pop(0)
+                documentation += f"    {line}\n" if line else "\n"
+        if property_doc:
+            documentation += "\n    Arguments:\n"
+            for property_name, doc_string in property_doc.items():
+                lines = doc_string.splitlines()
+                documentation += f"        {property_name}: {lines.pop(0)}\n"
+                while lines:
+                    line = lines.pop(0)
+                    if line:
+                        documentation += f"        {line}\n"
+        if documentation:
+            documentation += '    """\n'
+        print(f"def {element_name}({', '.join(properties)}):\n{documentation}    ...\n\n", file=pyi_file)
+    if pyi_file:
+        pyi_file.close()
+    print(f"File '{pyi_path}' was updated.")  # noqa: T201
+
+
+def main(arg_strings=None):
+    parser = argparse.ArgumentParser(description="taipy.gui.extensions entry point.")
+    sub_parser = parser.add_subparsers(dest="command", help="Commands to run", required=True)
+
+    tgb_generation = sub_parser.add_parser(
+        "generate_tgb", aliases=["api"], help="Generate Page Builder API for a Taipy GUI extension package."
+    )
+    tgb_generation.add_argument(
+        dest="package_root_dir",
+        nargs=1,
+        help="The root dir of the extension package." + " This directory must contain a __init__.py file.",
+    )
+    tgb_generation.set_defaults(func=generate_tgb)
+
+    args = parser.parse_args(arg_strings)
+    args.func(args)
+
+
+if __name__ == "__main__":
+    main()

+ 89 - 47
taipy/gui/extension/library.py

@@ -43,6 +43,8 @@ class ElementProperty:
         default_value: t.Optional[t.Any] = None,
         default_value: t.Optional[t.Any] = None,
         js_name: t.Optional[str] = None,
         js_name: t.Optional[str] = None,
         with_update: t.Optional[bool] = None,
         with_update: t.Optional[bool] = None,
+        *,
+        doc_string: t.Optional[str] = None,
     ) -> None:
     ) -> None:
         """Initializes a new custom property declaration for an `Element^`.
         """Initializes a new custom property declaration for an `Element^`.
 
 
@@ -53,6 +55,8 @@ class ElementProperty:
                 If unspecified, a camel case version of `name` is generated: for example, if `name` is
                 If unspecified, a camel case version of `name` is generated: for example, if `name` is
                 "my_property_name", then this property is referred to as "myPropertyName" in the
                 "my_property_name", then this property is referred to as "myPropertyName" in the
                 JavaScript code.
                 JavaScript code.
+            doc_string: An optional string that holds documentation for that property.<br/>
+                This is used when generating the stub classes for extension libraries.
         """
         """
         self.default_value = default_value
         self.default_value = default_value
         self.property_type: t.Union[PropertyType, t.Type[_TaipyBase]]
         self.property_type: t.Union[PropertyType, t.Type[_TaipyBase]]
@@ -66,6 +70,7 @@ class ElementProperty:
             self.property_type = property_type
             self.property_type = property_type
         self._js_name = js_name
         self._js_name = js_name
         self.with_update = with_update
         self.with_update = with_update
+        self.doc_string = doc_string
         super().__init__()
         super().__init__()
 
 
     def check(self, element_name: str, prop_name: str):
     def check(self, element_name: str, prop_name: str):
@@ -103,8 +108,9 @@ class Element:
         default_property: str,
         default_property: str,
         properties: t.Dict[str, ElementProperty],
         properties: t.Dict[str, ElementProperty],
         react_component: t.Optional[str] = None,
         react_component: t.Optional[str] = None,
+        *,
         render_xhtml: t.Optional[t.Callable[[t.Dict[str, t.Any]], str]] = None,
         render_xhtml: t.Optional[t.Callable[[t.Dict[str, t.Any]], str]] = None,
-        inner_properties: t.Optional[t.Dict[str, ElementProperty]] = None,
+        doc_string: t.Optional[str] = None,
     ) -> None:
     ) -> None:
         """Initializes a new custom element declaration.
         """Initializes a new custom element declaration.
 
 
@@ -112,21 +118,24 @@ class Element:
         *react_component* is ignored.
         *react_component* is ignored.
 
 
         Arguments:
         Arguments:
-            default_property (str): The name of the default property for this element.
-            properties (Dict[str, ElementProperty]): The dictionary containing the properties of this element, where the keys are the property names and the values are instances of ElementProperty.
-            inner_properties (Optional[List[ElementProperty]]): The optional list of inner properties for this element.<br/>
-                Default values are set/bound automatically.
-            react_component (Optional[str]): The name of the component to be created on the front-end.<br/>
+            default_property: The name of the default property for this element.
+            properties: The dictionary containing the properties of this element, where
+                the keys are the property names and the values are instances of ElementProperty.
+            react_component: The name of the component to be created on the front-end.<br/>
                 If not specified, it is set to a camel case version of the element's name
                 If not specified, it is set to a camel case version of the element's name
                 ("one_name" is transformed to "OneName").
                 ("one_name" is transformed to "OneName").
-            render_xhtml (Optional[callable[[dict[str, Any]], str]]): A function that receives a
-                dictionary containing the element's properties and their values
-                and that must return a valid XHTML string.
+            render_xhtml: A function that receives a dictionary containing the element's properties and their values
+                and that must return a valid XHTML string.<br/>
+                This is used to implement static elements.
+            doc_string: The documentation text for this element or None if there is note, which is
+                the default.<br/>
+                This string is used when generating stub functions so elements of extension libraries
+                can be used with the Page Builder API.
         """  # noqa: E501
         """  # noqa: E501
         self.default_attribute = default_property
         self.default_attribute = default_property
         self.attributes = properties
         self.attributes = properties
-        self.inner_properties = inner_properties
         self.js_name = react_component
         self.js_name = react_component
+        self.doc_string = doc_string
         if callable(render_xhtml):
         if callable(render_xhtml):
             self._render_xhtml = render_xhtml
             self._render_xhtml = render_xhtml
         super().__init__()
         super().__init__()
@@ -152,6 +161,9 @@ class Element:
     def _is_server_only(self):
     def _is_server_only(self):
         return hasattr(self, "_render_xhtml") and callable(self._render_xhtml)
         return hasattr(self, "_render_xhtml") and callable(self._render_xhtml)
 
 
+    def _process_inner_properties(self, _attributes: t.Dict[str, t.Any], _counter: int):
+        pass
+
     def _call_builder(
     def _call_builder(
         self,
         self,
         name,
         name,
@@ -162,40 +174,7 @@ class Element:
         counter: int = 0,
         counter: int = 0,
     ) -> t.Union[t.Any, t.Tuple[str, str]]:
     ) -> t.Union[t.Any, t.Tuple[str, str]]:
         attributes = properties if isinstance(properties, dict) else {}
         attributes = properties if isinstance(properties, dict) else {}
-        if self.inner_properties:
-            uniques: t.Dict[str, int] = {}
-            self.attributes.update(
-                {
-                    prop: ElementProperty(attr.property_type, None, attr._js_name, attr.with_update)
-                    for prop, attr in self.inner_properties.items()
-                }
-            )
-            for prop, attr in self.inner_properties.items():
-                val = attr.default_value
-                if val:
-                    # handling property replacement in inner properties <tp:prop:...>
-                    while m := Element.__RE_PROP_VAR.search(val):
-                        var = attributes.get(m.group(1))
-                        hash_value = None if var is None else gui._evaluate_expr(var)
-                        if hash_value:
-                            names = gui._get_real_var_name(hash_value)
-                            hash_value = names[0] if isinstance(names, tuple) else names
-                        else:
-                            hash_value = "None"
-                        val = val[: m.start()] + hash_value + val[m.end() :]
-                    # handling unique id replacement in inner properties <tp:uniq:...>
-                    has_uniq = False
-                    while m := Element.__RE_UNIQUE_VAR.search(val):
-                        has_uniq = True
-                        id = uniques.get(m.group(1))
-                        if id is None:
-                            id = len(uniques) + 1
-                            uniques[m.group(1)] = id
-                        val = f"{val[: m.start()]}{counter}{id}{val[m.end() :]}"
-                    if has_uniq and gui._is_expression(val):
-                        gui._evaluate_expr(val, True)
-
-                attributes[prop] = val
+        self._process_inner_properties(attributes, counter)
         # this modifies attributes
         # this modifies attributes
         hash_names = _Builder._get_variable_hash_names(gui, attributes)  # variable replacement
         hash_names = _Builder._get_variable_hash_names(gui, attributes)  # variable replacement
         # call user render if any
         # call user render if any
@@ -226,7 +205,7 @@ class Element:
                 gui=gui,
                 gui=gui,
                 control_type=name,
                 control_type=name,
                 element_name=f"{lib.get_js_module_name()}_{self._get_js_name(name)}",
                 element_name=f"{lib.get_js_module_name()}_{self._get_js_name(name)}",
-                attributes=properties,
+                prop_values=properties,
                 hash_names=hash_names,
                 hash_names=hash_names,
                 lib_name=lib.get_name(),
                 lib_name=lib.get_name(),
                 default_value=default_value,
                 default_value=default_value,
@@ -265,7 +244,7 @@ class ElementLibrary(ABC):
         The default implementation returns an empty dictionary, indicating that this library
         The default implementation returns an empty dictionary, indicating that this library
         contains no custom visual elements.
         contains no custom visual elements.
         """
         """
-        return {}
+        pass
 
 
     @abstractmethod
     @abstractmethod
     def get_name(self) -> str:
     def get_name(self) -> str:
@@ -295,7 +274,7 @@ class ElementLibrary(ABC):
             because each JavaScript module will have to have a unique name.
             because each JavaScript module will have to have a unique name.
 
 
         """
         """
-        raise NotImplementedError
+        pass
 
 
     def get_js_module_name(self) -> str:
     def get_js_module_name(self) -> str:
         """
         """
@@ -464,3 +443,66 @@ class ElementLibrary(ABC):
             This version will be appended to the resource URL as a query arg (?v=<version>)
             This version will be appended to the resource URL as a query arg (?v=<version>)
         """
         """
         return None
         return None
+
+
+class _Element_with_inner_props(Element):
+    def __init__(
+        self,
+        default_property: str,
+        properties: t.Dict[str, ElementProperty],
+        react_component: t.Optional[str] = None,
+        render_xhtml: t.Optional[t.Callable[[t.Dict[str, t.Any]], str]] = None,
+        doc_string: t.Optional[str] = None,
+        *,
+        inner_properties: t.Optional[t.Dict[str, ElementProperty]] = None,
+    ) -> None:
+        """NOT DOCUMENTED
+
+        Arguments:
+            inner_properties (Optional[List[ElementProperty]]): The optional list of inner properties for this element.<br/>
+                Default values are set/bound automatically.
+        """
+        super().__init__(
+            default_property=default_property,
+            properties=properties,
+            react_component=react_component,
+            render_xhtml=render_xhtml,
+            doc_string=doc_string,
+        )
+        self.inner_properties = inner_properties
+
+    def _process_inner_properties(self, attributes: t.Dict[str, t.Any], counter: int):
+        if self.inner_properties:
+            uniques: t.Dict[str, int] = {}
+            self.attributes.update(
+                {
+                    prop: ElementProperty(attr.property_type, None, attr._js_name, attr.with_update)
+                    for prop, attr in self.inner_properties.items()
+                }
+            )
+            for prop, attr in self.inner_properties.items():
+                val = attr.default_value
+                if val:
+                    # handling property replacement in inner properties <tp:prop:...>
+                    while m := Element.__RE_PROP_VAR.search(val):
+                        var = attributes.get(m.group(1))
+                        hash_value = None if var is None else gui._evaluate_expr(var)
+                        if hash_value:
+                            names = gui._get_real_var_name(hash_value)
+                            hash_value = names[0] if isinstance(names, tuple) else names
+                        else:
+                            hash_value = "None"
+                        val = val[: m.start()] + hash_value + val[m.end() :]
+                    # handling unique id replacement in inner properties <tp:uniq:...>
+                    has_uniq = False
+                    while m := Element.__RE_UNIQUE_VAR.search(val):
+                        has_uniq = True
+                        id = uniques.get(m.group(1))
+                        if id is None:
+                            id = len(uniques) + 1
+                            uniques[m.group(1)] = id
+                        val = f"{val[: m.start()]}{counter}{id}{val[m.end() :]}"
+                    if has_uniq and gui._is_expression(val):
+                        gui._evaluate_expr(val, True)
+
+                attributes[prop] = val

+ 8 - 0
taipy/gui/types.py

@@ -72,6 +72,14 @@ class PropertyType(Enum):
     See `ElementProperty^` for more details.
     See `ElementProperty^` for more details.
     """
     """
 
 
+    any = "any"
+    """
+    The property holds a value of any serializable type.
+    """
+    dynamic_any = "dynamicany"
+    """
+    The property is dynamic and holds a value of any serializable type.
+    """
     boolean = "boolean"
     boolean = "boolean"
     """
     """
     The property holds a Boolean value.
     The property holds a Boolean value.

+ 8 - 7
taipy/gui_core/_GuiCoreLib.py

@@ -15,6 +15,7 @@ from datetime import datetime
 from taipy.core import Cycle, DataNode, Job, Scenario, Sequence, Task
 from taipy.core import Cycle, DataNode, Job, Scenario, Sequence, Task
 from taipy.gui import Gui, State
 from taipy.gui import Gui, State
 from taipy.gui.extension import Element, ElementLibrary, ElementProperty, PropertyType
 from taipy.gui.extension import Element, ElementLibrary, ElementProperty, PropertyType
+from taipy.gui.extension.library import _Element_with_inner_props
 
 
 from ..version import _get_version
 from ..version import _get_version
 from ._adapters import (
 from ._adapters import (
@@ -65,8 +66,8 @@ class _GuiCore(ElementLibrary):
     __DATANODE_SELECTOR_SORT_VAR = "__tpgc_dn_sort"
     __DATANODE_SELECTOR_SORT_VAR = "__tpgc_dn_sort"
     __DATANODE_SELECTOR_ERROR_VAR = "__tpgc_dn_error"
     __DATANODE_SELECTOR_ERROR_VAR = "__tpgc_dn_error"
 
 
-    __elements = {
-        "scenario_selector": Element(
+    __elements: dict[str, Element] = {
+        "scenario_selector": _Element_with_inner_props(
             "value",
             "value",
             {
             {
                 "id": ElementProperty(PropertyType.string),
                 "id": ElementProperty(PropertyType.string),
@@ -113,7 +114,7 @@ class _GuiCore(ElementLibrary):
                 ),
                 ),
             },
             },
         ),
         ),
-        "scenario": Element(
+        "scenario": _Element_with_inner_props(
             "scenario",
             "scenario",
             {
             {
                 "id": ElementProperty(PropertyType.string),
                 "id": ElementProperty(PropertyType.string),
@@ -144,7 +145,7 @@ class _GuiCore(ElementLibrary):
                 ),
                 ),
             },
             },
         ),
         ),
-        "scenario_dag": Element(
+        "scenario_dag": _Element_with_inner_props(
             "scenario",
             "scenario",
             {
             {
                 "id": ElementProperty(PropertyType.string),
                 "id": ElementProperty(PropertyType.string),
@@ -161,7 +162,7 @@ class _GuiCore(ElementLibrary):
                 "on_select": ElementProperty(PropertyType.function, f"{{{__CTX_VAR_NAME}.on_dag_select}}"),
                 "on_select": ElementProperty(PropertyType.function, f"{{{__CTX_VAR_NAME}.on_dag_select}}"),
             },
             },
         ),
         ),
-        "data_node_selector": Element(
+        "data_node_selector": _Element_with_inner_props(
             "value",
             "value",
             {
             {
                 "id": ElementProperty(PropertyType.string),
                 "id": ElementProperty(PropertyType.string),
@@ -198,7 +199,7 @@ class _GuiCore(ElementLibrary):
                 ),
                 ),
             },
             },
         ),
         ),
-        "data_node": Element(
+        "data_node": _Element_with_inner_props(
             __DATANODE_VIZ_DATA_NODE_PROP,
             __DATANODE_VIZ_DATA_NODE_PROP,
             {
             {
                 "id": ElementProperty(PropertyType.string),
                 "id": ElementProperty(PropertyType.string),
@@ -274,7 +275,7 @@ class _GuiCore(ElementLibrary):
                 ),
                 ),
             },
             },
         ),
         ),
-        "job_selector": Element(
+        "job_selector": _Element_with_inner_props(
             "value",
             "value",
             {
             {
                 "id": ElementProperty(PropertyType.string),
                 "id": ElementProperty(PropertyType.string),