"""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", "height": "", "min_height": "", "max_height": "", "min_width": "", "box_shadow": "", "opacity": "", } def render_box_style(a: dict[str, Any]) -> str: """Construit les déclarations CSS inline communes à plusieurs kinds à partir des attributs `padding`/`margin`/`background_color`/ `border_radius`/`height`/`min_height`/`max_height`/`min_width`/ `box_shadow`/`opacity`/`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"}` — un côté à `style="none"` (ou absent) ne produit aucune déclaration pour ce côté, jamais un `border-top:none` explicite. - **Retour** : les déclarations CSS (`"propriete:valeur; ..."`), jamais vide ni `None`. - **Exceptions** : aucune.""" parts: list[str] = [] padding = str(a.get("padding", "")).strip() if padding: parts.append(f"padding:{html_lib.escape(padding)};") margin = str(a.get("margin", "")).strip() if margin: parts.append(f"margin:{html_lib.escape(margin)};") background_color = str(a.get("background_color", "")).strip() if background_color: parts.append(f"background-color:{html_lib.escape(background_color)};") border_radius = str(a.get("border_radius", "")).strip() if border_radius: parts.append(f"border-radius:{html_lib.escape(border_radius)};") height = str(a.get("height", "")).strip() if height: parts.append(f"height:{html_lib.escape(height)};") min_height = str(a.get("min_height", "")).strip() if min_height: parts.append(f"min-height:{html_lib.escape(min_height)};") max_height = str(a.get("max_height", "")).strip() if max_height: parts.append(f"max-height:{html_lib.escape(max_height)};") min_width = str(a.get("min_width", "")).strip() if min_width: parts.append(f"min-width:{html_lib.escape(min_width)};") box_shadow = str(a.get("box_shadow", "")).strip() if box_shadow: parts.append(f"box-shadow:{html_lib.escape(box_shadow)};") opacity = str(a.get("opacity", "")).strip() if opacity: parts.append(f"opacity:{html_lib.escape(opacity)};") 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};") # 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)