"""HTMLDict - A dict subclass with HTML rendering capabilities."""
from __future__ import annotations
import html
import uuid
from enum import Enum
from typing import Any, Self
from animaid.css_types import (
BorderValue,
ColorValue,
CSSValue,
SizeValue,
SpacingValue,
)
from animaid.html_object import HTMLObject
def _to_css(value: object) -> str:
"""Convert a value to its CSS string representation."""
if hasattr(value, "to_css"):
return str(value.to_css())
return str(value)
class DictFormat(Enum):
"""Format for rendering the dictionary."""
DEFINITION_LIST = "dl" # <dl><dt>key</dt><dd>value</dd></dl>
TABLE = "table" # <table><tr><td>key</td><td>value</td></tr></table>
DIVS = "divs" # Flexbox divs
class DictLayout(Enum):
"""Layout direction for dictionary entries."""
VERTICAL = "vertical" # Entries stacked vertically
HORIZONTAL = "horizontal" # Entries side by side
GRID = "grid" # Grid layout
[docs]
class HTMLDict(HTMLObject, dict):
"""A dict subclass that renders as styled HTML.
HTMLDict behaves like a regular Python dict but includes
methods for applying CSS styles and rendering to HTML.
All styling methods modify the object in-place and return self
for method chaining. Supports definition list, table, and flexbox layouts.
Examples:
>>> d = HTMLDict({"name": "Alice", "age": 30})
>>> d.render()
'<dl><dt>name</dt><dd>Alice</dd><dt>age</dt><dd>30</dd></dl>'
>>> d.as_table().render()
'<table><tr><td>name</td><td>Alice</td></tr>...</table>'
>>> d.key_bold().key_color("blue").render()
'<dl><dt style="font-weight: bold; color: blue">name</dt>...'
"""
_styles: dict[str, str]
_key_styles: dict[str, str]
_value_styles: dict[str, str]
_css_classes: list[str]
_key_classes: list[str]
_value_classes: list[str]
_format: DictFormat
_layout: DictLayout
_grid_columns: int
_key_value_separator: str
_entry_separator: str | None
_show_keys: bool
_obs_id: str
def __init__(
self, data: dict[Any, Any] | None = None, **styles: str | CSSValue
) -> None:
"""Initialize an HTMLDict.
Args:
data: Initial dictionary data.
**styles: CSS styles for the container (underscores to hyphens).
Accepts both strings and CSS type objects (Color, Size, etc.)
"""
super().__init__(data or {})
self._styles = {}
self._key_styles = {}
self._value_styles = {}
self._css_classes = []
self._key_classes = []
self._value_classes = []
self._format = DictFormat.DEFINITION_LIST
self._layout = DictLayout.VERTICAL
self._grid_columns = 2
self._key_value_separator = ""
self._entry_separator = None
self._show_keys = True
self._obs_id = str(uuid.uuid4())
for key, value in styles.items():
css_key = key.replace("_", "-")
self._styles[css_key] = _to_css(value)
def _notify(self) -> None:
"""Publish change notification via pypubsub."""
try:
from pubsub import pub
pub.sendMessage("animaid.changed", obs_id=self._obs_id)
except ImportError:
pass # pypubsub not installed
def _copy_with_settings(
self,
new_styles: dict[str, str] | None = None,
new_key_styles: dict[str, str] | None = None,
new_value_styles: dict[str, str] | None = None,
new_classes: list[str] | None = None,
new_key_classes: list[str] | None = None,
new_value_classes: list[str] | None = None,
new_format: DictFormat | None = None,
new_layout: DictLayout | None = None,
new_grid_columns: int | None = None,
new_key_value_separator: str | None = None,
new_entry_separator: str | None = None,
new_show_keys: bool | None = None,
) -> Self:
"""Create a copy with modified settings.
This method is used internally for operations that must return
a new object (like merging dicts with | operator).
"""
result = HTMLDict(dict(self))
result._styles = self._styles.copy()
result._key_styles = self._key_styles.copy()
result._value_styles = self._value_styles.copy()
result._css_classes = self._css_classes.copy()
result._key_classes = self._key_classes.copy()
result._value_classes = self._value_classes.copy()
result._format = self._format
result._layout = self._layout
result._grid_columns = self._grid_columns
result._key_value_separator = self._key_value_separator
result._entry_separator = self._entry_separator
result._show_keys = self._show_keys
result._obs_id = self._obs_id # Preserve ID so updates still work
if new_styles:
result._styles.update(new_styles)
if new_key_styles:
result._key_styles.update(new_key_styles)
if new_value_styles:
result._value_styles.update(new_value_styles)
if new_classes:
result._css_classes.extend(new_classes)
if new_key_classes:
result._key_classes.extend(new_key_classes)
if new_value_classes:
result._value_classes.extend(new_value_classes)
if new_format is not None:
result._format = new_format
if new_layout is not None:
result._layout = new_layout
if new_grid_columns is not None:
result._grid_columns = new_grid_columns
if new_key_value_separator is not None:
result._key_value_separator = new_key_value_separator
if new_entry_separator is not None:
result._entry_separator = new_entry_separator
if new_show_keys is not None:
result._show_keys = new_show_keys
return result # type: ignore[return-value]
# -------------------------------------------------------------------------
# HTMLObject interface
# -------------------------------------------------------------------------
[docs]
def styled(self, **styles: str | CSSValue) -> Self:
"""Apply additional container styles in-place.
Args:
**styles: CSS property-value pairs for the container.
Accepts both strings and CSS type objects (Color, Size, etc.)
Returns:
Self for method chaining.
"""
for key, value in styles.items():
css_key = key.replace("_", "-")
self._styles[css_key] = _to_css(value)
self._notify()
return self
[docs]
def add_class(self, *class_names: str) -> Self:
"""Add CSS classes on the container in-place.
Args:
*class_names: CSS class names to add.
Returns:
Self for method chaining.
"""
self._css_classes.extend(class_names)
self._notify()
return self
# -------------------------------------------------------------------------
# Format methods
# -------------------------------------------------------------------------
[docs]
def as_definition_list(self) -> Self:
"""Apply definition list (<dl>) format in-place."""
self._format = DictFormat.DEFINITION_LIST
self._notify()
return self
[docs]
def as_table(self) -> Self:
"""Apply table (<table>) format in-place."""
self._format = DictFormat.TABLE
self._notify()
return self
[docs]
def as_divs(self) -> Self:
"""Apply flexbox divs format in-place."""
self._format = DictFormat.DIVS
self._notify()
return self
# -------------------------------------------------------------------------
# Layout methods
# -------------------------------------------------------------------------
[docs]
def vertical(self) -> Self:
"""Apply vertical layout (entries stacked) in-place."""
self._layout = DictLayout.VERTICAL
self._notify()
return self
[docs]
def horizontal(self) -> Self:
"""Apply horizontal layout (entries side by side) in-place."""
self._layout = DictLayout.HORIZONTAL
self._format = DictFormat.DIVS
self._notify()
return self
[docs]
def grid(self, columns: int = 2) -> Self:
"""Apply grid layout in-place.
Args:
columns: Number of key-value pairs per row.
"""
self._layout = DictLayout.GRID
self._format = DictFormat.DIVS
self._grid_columns = columns
self._notify()
return self
# -------------------------------------------------------------------------
# Key styling methods
# -------------------------------------------------------------------------
[docs]
def key_styled(self, **styles: str | CSSValue) -> Self:
"""Apply styles to keys in-place.
Args:
**styles: CSS property-value pairs for keys.
Accepts both strings and CSS type objects (Color, Size, etc.)
"""
for key, value in styles.items():
css_key = key.replace("_", "-")
self._key_styles[css_key] = _to_css(value)
self._notify()
return self
[docs]
def key_bold(self) -> Self:
"""Apply bold style to keys in-place."""
self._key_styles["font-weight"] = "bold"
self._notify()
return self
[docs]
def key_italic(self) -> Self:
"""Apply italic style to keys in-place."""
self._key_styles["font-style"] = "italic"
self._notify()
return self
[docs]
def key_color(self, value: ColorValue) -> Self:
"""Apply color to keys in-place.
Args:
value: CSS color value (e.g., "blue", Color.blue, Color.hex("#00f")).
"""
self._key_styles["color"] = _to_css(value)
self._notify()
return self
[docs]
def key_background(self, value: ColorValue) -> Self:
"""Apply background color to keys in-place.
Args:
value: CSS color value (e.g., "yellow", Color.yellow).
"""
self._key_styles["background-color"] = _to_css(value)
self._notify()
return self
[docs]
def key_width(self, value: SizeValue) -> Self:
"""Apply fixed key width in-place.
Args:
value: CSS width value (e.g., "100px", Size.px(100)).
"""
css_value = _to_css(value)
self._key_styles["width"] = css_value
self._key_styles["min-width"] = css_value
self._notify()
return self
[docs]
def key_padding(self, value: SpacingValue) -> Self:
"""Apply padding to keys in-place.
Args:
value: CSS padding value (e.g., "10px", Size.px(10), Spacing.all(10)).
"""
self._key_styles["padding"] = _to_css(value)
self._notify()
return self
[docs]
def add_key_class(self, *class_names: str) -> Self:
"""Add CSS classes to keys in-place.
Args:
*class_names: CSS class names to add to keys.
"""
self._key_classes.extend(class_names)
self._notify()
return self
[docs]
def hide_keys(self) -> Self:
"""Hide keys and only render values in-place."""
self._show_keys = False
self._notify()
return self
# -------------------------------------------------------------------------
# Value styling methods
# -------------------------------------------------------------------------
[docs]
def value_styled(self, **styles: str | CSSValue) -> Self:
"""Apply styles to values in-place.
Args:
**styles: CSS property-value pairs for values.
Accepts both strings and CSS type objects (Color, Size, etc.)
"""
for key, value in styles.items():
css_key = key.replace("_", "-")
self._value_styles[css_key] = _to_css(value)
self._notify()
return self
[docs]
def value_bold(self) -> Self:
"""Apply bold style to values in-place."""
self._value_styles["font-weight"] = "bold"
self._notify()
return self
[docs]
def value_italic(self) -> Self:
"""Apply italic style to values in-place."""
self._value_styles["font-style"] = "italic"
self._notify()
return self
[docs]
def value_color(self, value: ColorValue) -> Self:
"""Apply color to values in-place.
Args:
value: CSS color value (e.g., "green", Color.green).
"""
self._value_styles["color"] = _to_css(value)
self._notify()
return self
[docs]
def value_background(self, value: ColorValue) -> Self:
"""Apply background color to values in-place.
Args:
value: CSS color value (e.g., "lightgray", Color.hex("#eee")).
"""
self._value_styles["background-color"] = _to_css(value)
self._notify()
return self
[docs]
def value_padding(self, value: SpacingValue) -> Self:
"""Apply padding to values in-place.
Args:
value: CSS padding value (e.g., "10px", Size.px(10), Spacing.all(10)).
"""
self._value_styles["padding"] = _to_css(value)
self._notify()
return self
[docs]
def add_value_class(self, *class_names: str) -> Self:
"""Add CSS classes to values in-place.
Args:
*class_names: CSS class names to add to values.
"""
self._value_classes.extend(class_names)
self._notify()
return self
# -------------------------------------------------------------------------
# Separator methods
# -------------------------------------------------------------------------
[docs]
def separator(self, value: str) -> Self:
"""Apply a separator between key and value in-place.
Args:
value: Separator string (e.g., ":", " -> ", " = ").
"""
self._key_value_separator = value
self._notify()
return self
[docs]
def entry_separator(self, value: BorderValue) -> Self:
"""Apply separator between entries (border) in-place.
Args:
value: CSS border value for separator (e.g., "1px solid gray").
"""
self._entry_separator = _to_css(value)
self._notify()
return self
# -------------------------------------------------------------------------
# Container styling methods
# -------------------------------------------------------------------------
[docs]
def gap(self, value: SizeValue) -> Self:
"""Apply gap between entries in-place.
Args:
value: CSS gap value (e.g., "10px", Size.px(10), Size.rem(1)).
"""
self._styles["gap"] = _to_css(value)
self._notify()
return self
[docs]
def padding(self, value: SpacingValue) -> Self:
"""Apply container padding in-place.
Args:
value: CSS padding value (e.g., "10px", Size.px(10)).
"""
self._styles["padding"] = _to_css(value)
self._notify()
return self
[docs]
def margin(self, value: SpacingValue) -> Self:
"""Apply container margin in-place.
Args:
value: CSS margin value (e.g., "10px", Size.px(10), Spacing.all(10)).
"""
self._styles["margin"] = _to_css(value)
self._notify()
return self
[docs]
def border(self, value: BorderValue) -> Self:
"""Apply container border in-place.
Args:
value: CSS border value (e.g., "1px solid black", Border.solid()).
"""
self._styles["border"] = _to_css(value)
self._notify()
return self
[docs]
def border_radius(self, value: SizeValue) -> Self:
"""Apply rounded container corners in-place.
Args:
value: CSS border-radius value (e.g., "5px", Size.px(5), Size.percent(50)).
"""
self._styles["border-radius"] = _to_css(value)
self._notify()
return self
[docs]
def background(self, value: ColorValue) -> Self:
"""Apply container background in-place.
Args:
value: CSS color value (e.g., "white", Color.white, Color.hex("#fff")).
"""
self._styles["background-color"] = _to_css(value)
self._notify()
return self
[docs]
def color(self, value: ColorValue) -> Self:
"""Apply text color in-place.
Args:
value: CSS color value (e.g., "black", Color.black).
"""
self._styles["color"] = _to_css(value)
self._notify()
return self
[docs]
def width(self, value: SizeValue) -> Self:
"""Apply container width in-place.
Args:
value: CSS width value (e.g., "300px", Size.px(300), Size.percent(100)).
"""
self._styles["width"] = _to_css(value)
self._notify()
return self
[docs]
def max_width(self, value: SizeValue) -> Self:
"""Apply maximum width in-place.
Args:
value: CSS max-width value (e.g., "500px", Size.px(500), Size.vw(80)).
"""
self._styles["max-width"] = _to_css(value)
self._notify()
return self
# -------------------------------------------------------------------------
# Style Presets (beginner-friendly)
# -------------------------------------------------------------------------
[docs]
def card(self) -> Self:
"""Apply card style with shadow and rounded corners in-place.
Creates a visually appealing card-style display.
"""
self._format = DictFormat.DIVS
self._styles["padding"] = "16px"
self._styles["border"] = "1px solid #e0e0e0"
self._styles["border-radius"] = "8px"
self._styles["background-color"] = "white"
self._styles["gap"] = "8px"
self._key_styles["font-weight"] = "bold"
self._key_value_separator = ": "
self._notify()
return self
[docs]
def simple(self) -> Self:
"""Apply simple key: value formatting in-place.
Clean, minimal display with colon separators.
"""
self._key_value_separator = ": "
self._key_styles["font-weight"] = "bold"
self._styles["gap"] = "4px"
self._notify()
return self
[docs]
def striped(self) -> Self:
"""Apply striped table style in-place.
Creates an alternating row colors table.
"""
self._format = DictFormat.TABLE
self._styles["border"] = "1px solid #e0e0e0"
self._key_styles["padding"] = "8px 12px"
self._value_styles["padding"] = "8px 12px"
self._key_styles["background-color"] = "#f5f5f5"
self._key_styles["font-weight"] = "bold"
self._notify()
return self
[docs]
def compact(self) -> Self:
"""Apply compact spacing style in-place.
Minimal padding and spacing for dense displays.
"""
self._styles["gap"] = "2px"
self._key_styles["padding"] = "2px 4px"
self._value_styles["padding"] = "2px 4px"
self._notify()
return self
[docs]
def spaced(self) -> Self:
"""Apply generous spacing style in-place.
More padding and gaps for readability.
"""
self._styles["gap"] = "12px"
self._key_styles["padding"] = "8px"
self._value_styles["padding"] = "8px"
self._notify()
return self
[docs]
def labeled(self) -> Self:
"""Apply label-style keys in-place.
Keys are styled as small labels above values.
"""
self._format = DictFormat.DIVS
self._layout = DictLayout.VERTICAL
self._styles["gap"] = "16px"
self._key_styles["font-size"] = "0.75em"
self._key_styles["color"] = "#757575"
self._key_styles["text-transform"] = "uppercase"
self._key_styles["letter-spacing"] = "0.05em"
self._value_styles["font-size"] = "1.1em"
self._notify()
return self
[docs]
def inline(self) -> Self:
"""Apply inline horizontal display in-place.
All key-value pairs on one line.
"""
self._layout = DictLayout.HORIZONTAL
self._format = DictFormat.DIVS
self._styles["gap"] = "16px"
self._key_value_separator = ": "
self._key_styles["font-weight"] = "bold"
self._notify()
return self
[docs]
def bordered(self) -> Self:
"""Apply bordered cells style in-place.
Each key-value pair has visible borders.
"""
self._format = DictFormat.TABLE
self._styles["border"] = "1px solid #e0e0e0"
self._styles["border-collapse"] = "collapse"
self._key_styles["border"] = "1px solid #e0e0e0"
self._key_styles["padding"] = "8px"
self._value_styles["border"] = "1px solid #e0e0e0"
self._value_styles["padding"] = "8px"
self._notify()
return self
# -------------------------------------------------------------------------
# Rendering
# -------------------------------------------------------------------------
def _render_item(self, item: Any) -> str:
"""Render a single value to HTML.
Args:
item: The value to render.
Returns:
HTML string for the value.
"""
if isinstance(item, HTMLObject):
return item.render()
elif isinstance(item, str):
return html.escape(item)
else:
return html.escape(str(item))
def _build_key_style_string(self) -> str:
"""Build CSS style string for keys."""
if not self._key_styles:
return ""
return "; ".join(f"{k}: {v}" for k, v in self._key_styles.items())
def _build_value_style_string(self) -> str:
"""Build CSS style string for values."""
if not self._value_styles:
return ""
return "; ".join(f"{k}: {v}" for k, v in self._value_styles.items())
def _build_key_attributes(self) -> str:
"""Build attribute string for key elements."""
parts = []
if self._key_classes:
parts.append(f'class="{" ".join(self._key_classes)}"')
style_str = self._build_key_style_string()
if style_str:
parts.append(f'style="{style_str}"')
return " ".join(parts)
def _build_value_attributes(self, index: int, total: int) -> str:
"""Build attribute string for value elements."""
parts = []
if self._value_classes:
parts.append(f'class="{" ".join(self._value_classes)}"')
styles = self._value_styles.copy()
# Add entry separator if not last item
if self._entry_separator and index < total - 1:
if self._layout == DictLayout.HORIZONTAL:
styles["border-right"] = self._entry_separator
else:
styles["border-bottom"] = self._entry_separator
if styles:
style_str = "; ".join(f"{k}: {v}" for k, v in styles.items())
parts.append(f'style="{style_str}"')
return " ".join(parts)
def _get_container_styles(self) -> dict[str, str]:
"""Build container styles including layout."""
styles = self._styles.copy()
if self._format == DictFormat.DIVS:
if self._layout == DictLayout.HORIZONTAL:
styles.setdefault("display", "flex")
styles.setdefault("flex-direction", "row")
styles.setdefault("flex-wrap", "wrap")
elif self._layout == DictLayout.GRID:
styles.setdefault("display", "grid")
# Each entry is key + value, so multiply columns by 2
cols = self._grid_columns * 2 if self._show_keys else self._grid_columns
styles.setdefault("grid-template-columns", f"repeat({cols}, auto)")
else:
styles.setdefault("display", "flex")
styles.setdefault("flex-direction", "column")
return styles
def _render_as_definition_list(self) -> str:
"""Render as a definition list."""
container_styles = self._get_container_styles()
self._styles = container_styles
attrs = self._build_attributes()
key_attrs = self._build_key_attributes()
total = len(self)
items_html = []
for i, (key, value) in enumerate(self.items()):
key_html = html.escape(str(key))
value_html = self._render_item(value)
value_attrs = self._build_value_attributes(i, total)
if self._key_value_separator:
key_html += self._key_value_separator
if self._show_keys:
if key_attrs:
items_html.append(f"<dt {key_attrs}>{key_html}</dt>")
else:
items_html.append(f"<dt>{key_html}</dt>")
if value_attrs:
items_html.append(f"<dd {value_attrs}>{value_html}</dd>")
else:
items_html.append(f"<dd>{value_html}</dd>")
if attrs:
return f"<dl {attrs}>{''.join(items_html)}</dl>"
return f"<dl>{''.join(items_html)}</dl>"
def _render_as_table(self) -> str:
"""Render as a table."""
container_styles = self._get_container_styles()
self._styles = container_styles
attrs = self._build_attributes()
key_attrs = self._build_key_attributes()
total = len(self)
rows_html = []
for i, (key, value) in enumerate(self.items()):
key_html = html.escape(str(key))
value_html = self._render_item(value)
value_attrs = self._build_value_attributes(i, total)
if self._key_value_separator:
key_html += self._key_value_separator
row_parts = []
if self._show_keys:
if key_attrs:
row_parts.append(f"<td {key_attrs}>{key_html}</td>")
else:
row_parts.append(f"<td>{key_html}</td>")
if value_attrs:
row_parts.append(f"<td {value_attrs}>{value_html}</td>")
else:
row_parts.append(f"<td>{value_html}</td>")
rows_html.append(f"<tr>{''.join(row_parts)}</tr>")
if attrs:
return f"<table {attrs}>{''.join(rows_html)}</table>"
return f"<table>{''.join(rows_html)}</table>"
def _render_as_divs(self) -> str:
"""Render as flexbox divs."""
container_styles = self._get_container_styles()
self._styles = container_styles
attrs = self._build_attributes()
key_attrs = self._build_key_attributes()
total = len(self)
items_html = []
for i, (key, value) in enumerate(self.items()):
key_html = html.escape(str(key))
value_html = self._render_item(value)
value_attrs = self._build_value_attributes(i, total)
if self._key_value_separator:
key_html += self._key_value_separator
if self._show_keys:
if key_attrs:
items_html.append(f"<div {key_attrs}>{key_html}</div>")
else:
items_html.append(f"<div>{key_html}</div>")
if value_attrs:
items_html.append(f"<div {value_attrs}>{value_html}</div>")
else:
items_html.append(f"<div>{value_html}</div>")
if attrs:
return f"<div {attrs}>{''.join(items_html)}</div>"
return f"<div>{''.join(items_html)}</div>"
[docs]
def render(self) -> str:
"""Return HTML representation of this dictionary.
Returns:
A string containing valid HTML.
"""
if len(self) == 0:
if self._format == DictFormat.DEFINITION_LIST:
return "<dl></dl>"
elif self._format == DictFormat.TABLE:
return "<table></table>"
else:
return "<div></div>"
if self._format == DictFormat.DEFINITION_LIST:
return self._render_as_definition_list()
elif self._format == DictFormat.TABLE:
return self._render_as_table()
else:
return self._render_as_divs()
# -------------------------------------------------------------------------
# Dict operation overrides
# -------------------------------------------------------------------------
def __or__(self, other: dict[Any, Any]) -> Self:
"""Merge dicts with | operator, preserving settings."""
result = HTMLDict(dict.__or__(self, other))
result._styles = self._styles.copy()
result._key_styles = self._key_styles.copy()
result._value_styles = self._value_styles.copy()
result._css_classes = self._css_classes.copy()
result._key_classes = self._key_classes.copy()
result._value_classes = self._value_classes.copy()
result._format = self._format
result._layout = self._layout
result._grid_columns = self._grid_columns
result._key_value_separator = self._key_value_separator
result._entry_separator = self._entry_separator
result._show_keys = self._show_keys
result._obs_id = self._obs_id
return result # type: ignore[return-value]
# -------------------------------------------------------------------------
# Observable mutating methods
# -------------------------------------------------------------------------
def __setitem__(self, key: Any, value: Any) -> None:
"""Set item, notifying observers."""
super().__setitem__(key, value)
self._notify()
def __delitem__(self, key: Any) -> None:
"""Delete item, notifying observers."""
super().__delitem__(key)
self._notify()
[docs]
def update(self, *args: Any, **kwargs: Any) -> None:
"""Update dict, notifying observers."""
super().update(*args, **kwargs)
self._notify()
[docs]
def pop(self, key: Any, *default: Any) -> Any:
"""Pop item, notifying observers."""
result = super().pop(key, *default)
self._notify()
return result
[docs]
def popitem(self) -> tuple[Any, Any]:
"""Pop item, notifying observers."""
result = super().popitem()
self._notify()
return result
[docs]
def clear(self) -> None:
"""Clear dict, notifying observers."""
super().clear()
self._notify()
[docs]
def setdefault(self, key: Any, default: Any = None) -> Any:
"""Set default, notifying observers."""
result = super().setdefault(key, default)
self._notify()
return result
def __repr__(self) -> str:
"""Return a detailed representation for debugging."""
dict_repr = dict.__repr__(self)
extras = []
if self._format != DictFormat.DEFINITION_LIST:
extras.append(f"format={self._format.value}")
if self._layout != DictLayout.VERTICAL:
extras.append(f"layout={self._layout.value}")
if self._styles:
extras.append(f"styles={self._styles}")
if extras:
return f"HTMLDict({dict_repr}, {', '.join(extras)})"
return f"HTMLDict({dict_repr})"