Skip to content

pygx.views.html.controls

controls

Common HTML controls.

HtmlControl

HtmlControl(
    *,
    allow_partial: bool = False,
    sealed: bool | None = None,
    root_path: KeyPath | None = None,
    explicit_init: bool = False,
    **kwargs: Any
)

Bases: Object

Base class for HTML controls.

An HTML control is a symbolic object that renders itself via to_html(). When constructed with interactive=True and displayed in a notebook environment (IPython/Jupyter), a control can update the already-rendered HTML in place — e.g. Label.update() or ProgressBar.update() patch the live DOM via injected JavaScript.

Source code in pygx/symbolic/_object.py
@_filter_typing_traceback_on_raise
def __init__(
    self,
    *,
    allow_partial: bool = False,
    sealed: bool | None = None,
    root_path: topology.KeyPath | None = None,
    explicit_init: bool = False,
    **kwargs: Any,
):
    """Create an Object instance.

    ``pg.Object`` synthesizes a keyword-only ``__init__``. Subclasses
    that need positional arguments must override ``__init__``
    explicitly and translate to keyword arguments before forwarding
    to ``super().__init__(**kwargs)``.

    Args:
      allow_partial: If True, the object can be partial.
      sealed: If True, seal the object from future modification (unless under
        a `pg.as_sealed(False)` context manager). If False, treat the object as
        unsealed. If None, it's determined by the class's ``frozen`` option.
      root_path: The symbolic path for current object. By default it's None,
        which indicates that newly constructed object does not have a parent.
      explicit_init: Should set to `True` when `__init__` is called via
        `pg.Object.__init__` instead of `super().__init__`.
      **kwargs: key/value arguments that align with the schema. All required
        keys in the schema must be specified, and values should be acceptable
        according to their value spec.

    Raises:
      TypeError: When a required field is missing, a value has an
        unacceptable type, or an unknown keyword argument is given.
      ValueError: When a value violates its value spec's constraints
        (e.g. out of range).
    """
    # Placeholder for Google-internal usage instrumentation.

    cls = self.__class__
    # The ever-instantiated latch `apply_schema`'s post-instance guard
    # reads (#432: `update_schema` is only legal before instances exist).
    # Own-`__dict__`, latched on the construction ATTEMPT — mirroring the
    # native core, whose `__native_ctor_meta__` (the equivalent marker) is
    # created on the first instance op. One dict read per construct.
    meta = cls.__sym_meta__
    if not meta.instantiated:
        meta.instantiated = True
    if sealed is None:
        sealed = cls.__sym_options__.frozen

    if not isinstance(allow_partial, bool):
        raise TypeError(
            f"Expect bool type for argument 'allow_partial' in "
            f'symbolic.Object.__init__ but encountered {allow_partial}.'
        )

    # Fast path: const-keyed schema.
    if cls._sym_init_fields is not None and self._init_fast_path(
        kwargs,
        allow_partial,
        sealed,
        root_path,
        explicit_init,
    ):
        return

    # Slow path: handles varargs and schemas with non-const keys.

    # Model before-hook (#516) — the fast path was not taken
    # (`_sym_init_fields is None`), so the hook has not run yet.
    if cls._sym_preinit_overridden:
        kwargs = _apply_preinit(cls, kwargs)

    # We delay the seal attempt until members are all set.
    super().__init__(
        allow_partial=allow_partial,
        accessor_writable=cls.__sym_options__.attr_write,
        sealed=sealed,
        root_path=root_path,
        init_super=not explicit_init,
    )

    # Fill field_args and init_args from **kwargs.
    _, unmatched_keys = cls.__schema__.resolve(list(kwargs.keys()))
    # Fields opted out of `__init__` via `enable_init=False` resolve
    # against the schema but must still be rejected as kwargs.
    get_field = cls.__schema__.get_field
    unmatched_keys = list(unmatched_keys) + [
        k
        for k in kwargs
        if (f := get_field(k)) is not None and not f.enable_init
    ]
    if unmatched_keys:
        _screen_unexpected_init_keys(cls, kwargs, unmatched_keys)

    # Declared aliases are RESERVED wire spellings (#517, owner ruling):
    # an open (extra) schema must not accept an extra kwarg equal to a
    # field's alias — the wire would become ambiguous. (Const-keyed
    # schemas never reach here with an alias-spelled kwarg; their
    # unknown-key screen already raised — or, under `extra='ignore'`,
    # raised for alias keys before dropping the rest.)
    if cls._sym_alias_maps is not None:
        alias_to_name = cls._sym_alias_maps[0]
        for k in kwargs:
            if k in alias_to_name:
                raise TypeError(
                    f'{cls.__name__}.__init__() got keyword argument '
                    f'{k!r}, which is reserved as the alias of field '
                    f'{alias_to_name[k]!r}.'
                )

    # All field values come from kwargs — `pg.Object.__init__` is
    # keyword-only.
    field_args = dict(kwargs)

    # Check missing arguments when partial binding is disallowed.
    if not base.accepts_partial(self):
        missing_args = []
        for field in self.__class__.__schema__.fields.values():
            if (
                not field.value.has_default
                and isinstance(field.key, pg_typing.ConstStrKey)
                and field.key not in field_args
            ):
                missing_args.append(str(field.key))
        if missing_args:
            arg_phrase = formatting.auto_plural(
                len(missing_args), 'argument'
            )
            keys_str = formatting.comma_delimited_str(missing_args)
            raise TypeError(
                f'{self.__class__.__name__}.__init__() missing {len(missing_args)} '
                f'required {arg_phrase}: {keys_str}.'
            )

    # NOTE(daiyip): Accessor writable is honored by `Object.__setattr__` so
    # we can always make `_sym_attributes` accessor writable. This prevents
    # a child object's attribute access from being changed when it's
    # attached to a parent whose symbolic attributes are not writable.
    # Per-field validation is gated by `field.enable_validation` inside
    # `Schema.apply` / `Dict._formalized_value` — so a class-wide
    # `validate=False` (resolved into each field) and per-field overrides
    # both flow through naturally here.
    vars(self)['_sym_attributes'] = pg_dict.Dict(
        field_args,  # pyright: ignore[reportArgumentType]
        value_spec=self.__class__.sym_fields,
        allow_partial=allow_partial,
        sealed=sealed,
        accessor_writable=True,
        root_path=root_path,
        as_object_attributes_container=True,
        under_topo=cls.__sym_options__.topo,
    )
    # The write-time `needs_topo` refusal (see `_init_fast_path` Phase 5):
    # the slow path formalizes inside the container, so scan its items.
    if not cls.__sym_options__.topo:
        for k, v in self._sym_attributes.sym_items():
            base.refuse_tree_walking_inferential(cls, k, v)
    self._sym_attributes.topo_setparent(self)
    # Seed `init=False` fields from their defaults before `on_sym_post_init`
    # runs so `on_sym_ready`/`on_sym_bound` overrides start from a known baseline.
    _seed_non_symbolic_fields(self, cls._non_symbolic_fields)
    # Model after-validator (#516): fires before any lifecycle hook can
    # observe the object, gated like `on_sym_ready` (concrete only).
    if cls._sym_validate_overridden:
        abstract = (
            self._allow_partial and self.sym_partial
        ) or self.sym_puresymbolic
        if not abstract:
            self.on_sym_validate()
    self.on_sym_post_init()
    self.sym_seal(sealed)

add_style

add_style(*css: str) -> HtmlControl

Adds CSS styles to the HTML.

Source code in pygx/views/html/controls/_base.py
def add_style(self, *css: str) -> 'HtmlControl':
    """Adds CSS styles to the HTML."""
    self._css_styles.extend(css)
    return self

to_html

to_html(**kwargs) -> Html

Returns the HTML representation of the control.

Source code in pygx/views/html/controls/_base.py
def to_html(self, **kwargs) -> Html:
    """Returns the HTML representation of the control."""
    self._rendered = True
    self._dynamic_injected_css = set()
    html = self._to_html(**kwargs)
    return html.add_style(*self._css_styles).add_script(*self._scripts)

element_id

element_id(child: str | None = None) -> str | None

Returns the element id of this control or a child.

An explicit id replaces only the control's OWN id — a child element still derives its id from it ({id}-{child}). Returning self.id for children collided every child element (and the JS selectors built from them) with the control itself (#781).

Source code in pygx/views/html/controls/_base.py
def element_id(self, child: str | None = None) -> str | None:
    """Returns the element id of this control or a child.

    An explicit ``id`` replaces only the control's OWN id — a child
    element still derives its id from it (``{id}-{child}``). Returning
    ``self.id`` for children collided every child element (and the JS
    selectors built from them) with the control itself (#781).
    """
    if self.id is not None:
        base_id = self.id
    elif not self.interactive:
        return None
    else:
        base_id = f'control-{id(self)}'
    return base_id if child is None else f'{base_id}-{child}'

Badge

Badge(
    text: str | Html,
    tooltip: TooltipLike | None = None,
    link: str | None = None,
    target: str | None = None,
    **kwargs
)

Bases: Label

A badge.

Source code in pygx/views/html/controls/_label.py
def __init__(
    self,
    text: str | Html,
    tooltip: TooltipLike | None = None,
    link: str | None = None,
    target: str | None = None,
    **kwargs,
):
    super().__init__(
        **dict(text=text, tooltip=tooltip, link=link, target=target),
        **kwargs,
    )

Label

Label(
    text: str | Html,
    tooltip: TooltipLike | None = None,
    link: str | None = None,
    target: str | None = None,
    **kwargs
)

Bases: HtmlControl

Html label.

Source code in pygx/views/html/controls/_label.py
def __init__(
    self,
    text: str | Html,
    tooltip: TooltipLike | None = None,
    link: str | None = None,
    target: str | None = None,
    **kwargs,
):
    super().__init__(
        **dict(text=text, tooltip=tooltip, link=link, target=target),
        **kwargs,
    )

LabelGroup

LabelGroup(
    labels: list[LabelLike | None | list[Any]],
    name: LabelLike | None = None,
    id: str | None = None,
    css_classes: list[str] | None = None,
    styles: dict[str, str] | None = None,
    **kwargs
)

Bases: HtmlControl

Label group.

Source code in pygx/views/html/controls/_label.py
def __init__(
    self,
    labels: list[LabelLike | None | list[Any]],
    name: LabelLike | None = None,
    id: str | None = None,  # pylint: disable=redefined-builtin
    css_classes: list[str] | None = None,
    styles: dict[str, str] | None = None,
    **kwargs,
) -> None:
    if labels:
        labels = [
            l for l in topology.flatten(labels).values() if l is not None
        ]
    super().__init__(
        **dict(
            labels=labels,
            name=name,
            id=id,
            css_classes=css_classes or [],
            styles=styles or {},
        ),
        **kwargs,
    )

ProgressBar

ProgressBar(
    subprogresses: list[SubProgress], total: int | None = None, **kwargs
)

Bases: HtmlControl

A progress bar control.

Source code in pygx/views/html/controls/_progress_bar.py
def __init__(
    self,
    subprogresses: list[SubProgress],
    total: int | None = None,
    **kwargs,
):
    super().__init__(
        **dict(subprogresses=subprogresses, total=total),
        **kwargs,
    )

SubProgress

SubProgress(name: str, value: int = 0, **kwargs)

Bases: HtmlControl

A sub progress bar control.

Source code in pygx/views/html/controls/_progress_bar.py
def __init__(self, name: str, value: int = 0, **kwargs):
    super().__init__(
        **dict(name=name, value=value),
        **kwargs,
    )

parent cached property

parent: Optional[ProgressBar]

Returns the parent progress bar.

total property

total: int | None

Returns the total number of the sub progress bar.

width property

width: str | None

Returns the width of the sub progress bar.

increment

increment(delta: int = 1)

Increments the value of the sub progress bar.

Source code in pygx/views/html/controls/_progress_bar.py
def increment(self, delta: int = 1):
    """Increments the value of the sub progress bar."""
    self.update(self.value + delta)

Tab

Tab(label: LabelLike, content: HtmlLike | HtmlConvertible, **kwargs)

Bases: Object

A tab.

Source code in pygx/views/html/controls/_tab.py
def __init__(
    self,
    label: LabelLike,
    content: HtmlLike | HtmlConvertible,
    **kwargs,
):
    super().__init__(label=label, content=content, **kwargs)

TabControl

TabControl(
    tabs: list[Tab | None | list[Any]],
    selected: int | str | list[str] = 0,
    tab_position: Literal["top", "left"] = "top",
    id: str | None = None,
    css_classes: list[str] | None = None,
    styles: dict[str, str] | None = None,
    **kwargs
)

Bases: HtmlControl

A tab control.

Source code in pygx/views/html/controls/_tab.py
def __init__(
    self,
    tabs: list[Tab | None | list[Any]],
    selected: int | str | list[str] = 0,
    tab_position: Literal['top', 'left'] = 'top',
    id: str | None = None,  # pylint: disable=redefined-builtin
    css_classes: list[str] | None = None,
    styles: dict[str, str] | None = None,
    **kwargs,
):
    if tabs:
        tabs = [t for t in topology.flatten(tabs).values() if t is not None]
    selected = self._find_tab_index(
        tabs,  # pyright: ignore[reportArgumentType]
        selected,
    )
    if selected == -1:
        selected = 0
    super().__init__(
        **dict(
            tabs=tabs,
            selected=selected,
            tab_position=tab_position,
            id=id,
            css_classes=css_classes or [],
            styles=styles or {},
        ),
        **kwargs,
    )

insert

insert(index_or_name: int | str, tab: Tab) -> None

Inserts a tab before a tab identified by index or name.

Source code in pygx/views/html/controls/_tab.py
def insert(self, index_or_name: int | str, tab: Tab) -> None:
    """Inserts a tab before a tab identified by index or name."""
    index = self.indexof(index_or_name)
    if index == -1:
        raise ValueError(f'Tab not found: {index_or_name!r}')
    with pg_flags.notify_on_change(False):
        self.tabs.insert(index, tab)

    self._insert_adjacent_html(
        f"""
    const elem = document.querySelectorAll('#{self.element_id()}-button-group > .tab-button')[{index}];
    """,
        self._tab_button(tab, selected=False),
        position='beforebegin',
    )
    self._insert_adjacent_html(
        f"""
    const elem = document.querySelectorAll('#{self.element_id()}-content-group > .tab-content')[{index}];
    """,
        self._tab_content(tab, selected=False),
        position='beforebegin',
    )
    # Selection follows the TAB, not the position: inserting at or
    # before the selected tab shifts it one slot right.
    if index <= self.selected:
        self._sync_members(selected=self.selected + 1)

remove

remove(index_or_name: int | str) -> Tab

Removes a tab identified by index or name.

Source code in pygx/views/html/controls/_tab.py
def remove(self, index_or_name: int | str) -> Tab:
    """Removes a tab identified by index or name."""
    index = self.indexof(index_or_name)
    if index == -1:
        raise ValueError(f'Tab not found: {index_or_name!r}')

    with pg_flags.notify_on_change(False):
        tab = self.tabs.pop(index)

    self._run_javascript(
        f"""
    const button = document.querySelectorAll('#{self.element_id()}-button-group > .tab-button')[{index}];
    if (button) {{
      button.remove();
    }}
    const content = document.querySelectorAll('#{self.element_id()}-content-group > .tab-content')[{index}];
    if (content) {{
      content.remove();
    }}
    """
    )

    if not self.tabs:
        self._sync_members(selected=0)
        return tab

    if self.selected == index:
        new_selected = index - 1 if index == len(self.tabs) else index
        self.select(max(0, new_selected))
    elif self.selected > index:
        self._sync_members(selected=self.selected - 1)
    return tab

select

select(index_or_name: int | str | list[str]) -> int | str

Selects a tab identified by an index or name.

Parameters:

Name Type Description Default
index_or_name int | str | list[str]

The index or name of the tab to select. If a list of names is provided, the first name in the list that is found will be selected.

required

Returns:

Type Description
int | str

The index (if the index was provided) or name of the selected tab.

Source code in pygx/views/html/controls/_tab.py
def select(self, index_or_name: int | str | list[str]) -> int | str:
    """Selects a tab identified by an index or name.

    Args:
      index_or_name: The index or name of the tab to select. If a list of names
        is provided, the first name in the list that is found will be selected.

    Returns:
      The index (if the index was provided) or name of the selected tab.
    """
    selected_name = (
        index_or_name if isinstance(index_or_name, str) else None
    )
    index = -1
    if isinstance(index_or_name, list):
        for name in index_or_name:
            index = self.indexof(name)
            if index != -1:
                selected_name = name
                break
    else:
        index = self.indexof(index_or_name)
    if index == -1:
        raise ValueError(f'Tab not found: {index_or_name!r}')
    self._sync_members(selected=index)
    self._run_javascript(
        f"""
    const tabButtons = document.querySelectorAll('#{self.element_id()}-button-group > .tab-button');
    tabButtons[{index}].click();
    """
    )
    return selected_name or index

Tooltip

Tooltip(content: str | Html, for_element: str | None = None, **kwargs)

Bases: HtmlControl

A tooltip control.

Attributes:

Name Type Description
content str | Html

The content of the tooltip. It could be a string or an Html object.

id str | None

The id of the tooltip.

css_classes list[str]

The CSS classes for the tooltip.

for_element str | None

The CSS selector for the element to attach the tooltip to. e.g. '.my-element' or '#my-element'.

Source code in pygx/views/html/controls/_tooltip.py
def __init__(
    self,
    content: str | Html,
    for_element: str | None = None,
    **kwargs,
):
    super().__init__(
        **dict(content=content, for_element=for_element),
        **kwargs,
    )

options: show_root_heading: false show_root_toc_entry: false members_order: source heading_level: 2