Make component return types honest; drop str/mark_safe leftovers

Cleanup of hacky leftovers from the node-tree migration (no behaviour
change):

- Return annotations: the component builders return Node subtrees, not
  SafeText strings, but ~40 functions still declared `-> SafeText`. Correct
  them to `-> Node` across filters / search_select / date_range_picker /
  domain. The genuine string returners keep `-> SafeText`: the Alpine
  selectors (GameStatusSelector / SessionDeviceSelector, which build f-string
  markup) and the script-tag helpers (CsrfInput / ModuleScript /
  ExternalScript / StaticScript).
- layout.render_page / layout.Page / AddForm now accept `Node` in their
  `content` / `scripts` / `fields` parameters (TYPE_CHECKING import in
  layout to avoid the components import cycle), matching what views already
  pass.
- session._session_fields builds a `Fragment(*rows, separator="\n")` instead
  of `mark_safe("\n".join(str(row) ...))` — keeps the tree intact so media
  could bubble, per the Fragment convention.
- Inline SVG icon children use `Safe(...)` nodes instead of `mark_safe(...)`
  strings (filters mode-toggle + collapse icons, date_range_picker calendar
  icon).
- _filter_field reads the widget's own id from its node `.attributes`
  (`_widget_id`) for the label's `for`, dropping the superfluous `for_widget`
  argument that always rendered `for="None"`. Removes the two TODOs whose
  premise ("the Component function can't expose the id") the class/node
  refactor retired, plus RangeSlider's dead commented-out Label block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 15:12:52 +02:00
parent 1c5bff8651
commit 022d43a5a5
7 changed files with 86 additions and 80 deletions
+9 -10
View File
@@ -17,9 +17,8 @@ widget into a ``DateCriterion`` unchanged. All behaviour is wired by
``games/static/js/date_range_picker.js``.
"""
from django.utils.safestring import SafeText, mark_safe
from common.components.core import Element, HTMLAttribute, Media, Node
from common.components.core import Element, HTMLAttribute, Media, Node, Safe
from common.components.primitives import Div, Input, Span
from common.time import DatePartSpec, date_parts
@@ -104,7 +103,7 @@ def _iso_part_values(iso_value: str, parts: list[DatePartSpec]) -> dict[str, str
def _segment_input(
*, part: DatePartSpec, side: str, label: str, value: str
) -> SafeText:
) -> Node:
side_label = "from" if side == "min" else "to"
return Input(
attributes=[
@@ -125,11 +124,11 @@ def _segment_input(
)
def _segment_group(*, side: str, label: str, iso_value: str) -> SafeText:
def _segment_group(*, side: str, label: str, iso_value: str) -> Node:
"""One date's worth of segments (``DD - MM - YYYY``) for a range side."""
parts = date_parts()
initial_values = _iso_part_values(iso_value, parts)
children: list[SafeText] = []
children: list[Node] = []
for index, part in enumerate(parts):
if index > 0:
children.append(
@@ -161,7 +160,7 @@ def DateRangeField(
input_name_prefix: str,
min_value: str = "",
max_value: str = "",
) -> SafeText:
) -> Node:
"""The visible half of the DateRangePicker: a single-input-looking
container holding two segmented dates, a calendar toggle, and the two
hidden ISO inputs (``{prefix}-min`` / ``{prefix}-max``) that carry the
@@ -210,13 +209,13 @@ def DateRangeField(
"cursor-pointer shrink-0",
),
],
children=[mark_safe(_CALENDAR_ICON_SVG)],
children=[Safe(_CALENDAR_ICON_SVG)],
),
],
)
def _calendar_nav_button(direction: str, arrow: str, label: str) -> SafeText:
def _calendar_nav_button(direction: str, arrow: str, label: str) -> Node:
return Element(
"button",
attributes=[
@@ -229,7 +228,7 @@ def _calendar_nav_button(direction: str, arrow: str, label: str) -> SafeText:
)
def _footer_button(action: str, label: str, button_class: str) -> SafeText:
def _footer_button(action: str, label: str, button_class: str) -> Node:
return Element(
"button",
attributes=[
@@ -241,7 +240,7 @@ def _footer_button(action: str, label: str, button_class: str) -> SafeText:
)
def DateRangeCalendar(*, input_name_prefix: str) -> SafeText:
def DateRangeCalendar(*, input_name_prefix: str) -> Node:
"""The popup half of the DateRangePicker: preset column, month grid
(filled client-side into ``[data-date-range-grid]``), and the
Cancel / Clear / Select footer. Hidden until the calendar toggle opens it."""