"""Attributs de mise en forme de "boîte" PARTAGÉS par plusieurs kinds de contenu (padding/margin/background_color/border_radius/border/align_self) — un seul et même jeu d'attributs et une seule fonction de rendu pour ne jamais dupliquer cette logique entre `_render_text`/`_render_image`/ `_render_button`/etc. (voir retour utilisateur du 26/09/2026 : audit complet des réglages manquants, à ajouter élément par élément en réutilisant CE module à chaque fois plutôt que de le réécrire).""" import html as html_lib from typing import Any BORDER_SIDES = ("top", "right", "bottom", "left") _DEFAULT_BORDER_SIDE = {"style": "none", "width": "1px", "color": "var(--doc-border)"} def default_border() -> dict[str, dict[str, str]]: """Nouveau dict à chaque appel (jamais un littéral partagé/muté par référence entre plusieurs éléments, même précaution que DEFAULT_QUIZ_CONFIG côté labels).""" return {side: dict(_DEFAULT_BORDER_SIDE) for side in BORDER_SIDES} BOX_DEFAULTS = { "padding": "", "margin": "", "background_color": "", "border_radius": "", "align_self": "stretch", "width": "", "max_width": "", "height": "", "min_height": "", "max_height": "", "min_width": "", "box_shadow": "", "opacity": "", } # (clé d'attribut, propriété CSS) — chaque paire suit exactement le même # patron (lire/nettoyer/ajouter si non vide) ; une simple table de # correspondance ici évite un enchaînement de blocs `if` identiques # (complexité cognitive réduite, voir _render_simple_properties). _SIMPLE_PROPERTIES = ( ("padding", "padding"), ("margin", "margin"), ("background_color", "background-color"), ("border_radius", "border-radius"), ("width", "width"), ("max_width", "max-width"), ("height", "height"), ("min_height", "min-height"), ("max_height", "max-height"), ("min_width", "min-width"), ("box_shadow", "box-shadow"), ("opacity", "opacity"), ) def _render_simple_properties(a: dict[str, Any]) -> list[str]: parts = [] for attr_key, css_prop in _SIMPLE_PROPERTIES: value = str(a.get(attr_key, "")).strip() if value: parts.append(f"{css_prop}:{html_lib.escape(value)};") return parts def _render_border(a: dict[str, Any]) -> list[str]: """Un côté à `style="none"` (ou absent) ne produit aucune déclaration pour ce côté, jamais un `border-top:none` explicite.""" parts = [] border = a.get("border") or {} for side in BORDER_SIDES: side_border = border.get(side) or {} style = str(side_border.get("style", "none")) if style and style != "none": width = html_lib.escape(str(side_border.get("width", "1px"))) color = html_lib.escape(str(side_border.get("color", "var(--doc-border)"))) parts.append(f"border-{side}:{width} {html_lib.escape(style)} {color};") return parts def render_box_style(a: dict[str, Any]) -> str: """Construit les déclarations CSS inline communes à plusieurs kinds à partir des attributs listés dans `_SIMPLE_PROPERTIES` + `border`/ `align_self` de `a` — chaîne vide pour tout attribut absent ou à sa valeur par défaut (aucun style ajouté, comportement historique inchangé). `border` est un dict à 4 clés (`BORDER_SIDES`), chacune `{"style", "width", "color"}`. - **Retour** : les déclarations CSS (`"propriete:valeur; ..."`), jamais vide ni `None`. - **Exceptions** : aucune.""" parts = _render_simple_properties(a) + _render_border(a) # align-self ne fait quoi que ce soit d'utile QUE si l'élément a par # ailleurs une taille bornée (max_width/width) — voir la note dans # element_kind_labels.md — mais reste toujours sûr à poser seul # ("stretch" est déjà le comportement par défaut d'un enfant flex en # colonne, donc jamais ajouté explicitement pour ne rien changer). align_self = str(a.get("align_self", "stretch")) if align_self and align_self != "stretch": parts.append(f"align-self:{html_lib.escape(align_self)};") return " ".join(parts)