Phase 3: declare component media that bubbles through the node tree
The JS-bearing widgets now declare their script dependencies, so a view no longer needs to know which scripts a component requires: - SearchSelect / FilterSelect → search_select.js - RangeSlider → range_slider.js - DateRangePicker → date_range_picker.js - YearPicker → datepicker.umd.js (external, from Phase 2) - FilterBar chrome → filter_bar.js Because the filter-bar internals now build a node tree (the legacy Component() string-builder calls became Element/Div), each bar's collect_media() returns its own filter_bar.js merged with the scripts that bubble up from the FilterSelect / RangeSlider / DateRangePicker widgets it contains — exactly the set the views thread by hand today. Adds Node.with_media() so a function-built node can declare media without a full BaseComponent subclass, and tests proving the bubbling. Note: the six *FilterBar functions still share the _filter_bar chrome helper rather than a BaseComponent class hierarchy; folding them into one is a follow-up that does not affect media collection (Phase 4). https://claude.ai/code/session_01BKurBhE3Qj25p7Bfsg7EeK
This commit is contained in:
@@ -19,10 +19,13 @@ widget into a ``DateCriterion`` unchanged. All behaviour is wired by
|
||||
|
||||
from django.utils.safestring import SafeText, mark_safe
|
||||
|
||||
from common.components.core import Component, HTMLAttribute
|
||||
from common.components.core import Element, HTMLAttribute, Media, Node
|
||||
from common.components.primitives import Div, Input, Span
|
||||
from common.time import DatePartSpec, date_parts
|
||||
|
||||
# Wired by date_range_picker.js.
|
||||
_DATE_RANGE_MEDIA = Media(js=("date_range_picker.js",))
|
||||
|
||||
_FIELD_CONTAINER_CLASS = (
|
||||
"flex items-center gap-0.5 w-full rounded-base border border-default-medium "
|
||||
"bg-neutral-secondary-medium text-sm text-heading p-1.5 cursor-text "
|
||||
@@ -195,8 +198,8 @@ def DateRangeField(
|
||||
children=["–"],
|
||||
),
|
||||
_segment_group(side="max", label=label, iso_value=max_value),
|
||||
Component(
|
||||
tag_name="button",
|
||||
Element(
|
||||
"button",
|
||||
attributes=[
|
||||
("type", "button"),
|
||||
("data-date-range-calendar-toggle", ""),
|
||||
@@ -214,8 +217,8 @@ def DateRangeField(
|
||||
|
||||
|
||||
def _calendar_nav_button(direction: str, arrow: str, label: str) -> SafeText:
|
||||
return Component(
|
||||
tag_name="button",
|
||||
return Element(
|
||||
"button",
|
||||
attributes=[
|
||||
("type", "button"),
|
||||
(f"data-date-range-{direction}", ""),
|
||||
@@ -227,8 +230,8 @@ def _calendar_nav_button(direction: str, arrow: str, label: str) -> SafeText:
|
||||
|
||||
|
||||
def _footer_button(action: str, label: str, button_class: str) -> SafeText:
|
||||
return Component(
|
||||
tag_name="button",
|
||||
return Element(
|
||||
"button",
|
||||
attributes=[
|
||||
("type", "button"),
|
||||
(f"data-date-range-{action}", ""),
|
||||
@@ -243,8 +246,8 @@ def DateRangeCalendar(*, input_name_prefix: str) -> SafeText:
|
||||
(filled client-side into ``[data-date-range-grid]``), and the
|
||||
Cancel / Clear / Select footer. Hidden until the calendar toggle opens it."""
|
||||
preset_buttons = [
|
||||
Component(
|
||||
tag_name="button",
|
||||
Element(
|
||||
"button",
|
||||
attributes=[
|
||||
("type", "button"),
|
||||
("data-date-range-preset", preset_value),
|
||||
@@ -328,7 +331,7 @@ def DateRangePicker(
|
||||
input_name_prefix: str,
|
||||
min_value: str = "",
|
||||
max_value: str = "",
|
||||
) -> SafeText:
|
||||
) -> Node:
|
||||
"""A date-range widget: segmented manual entry plus a calendar popup.
|
||||
|
||||
Drop-in replacement for ``DateRangeFilter`` — exposes the same hidden
|
||||
@@ -352,4 +355,4 @@ def DateRangePicker(
|
||||
),
|
||||
DateRangeCalendar(input_name_prefix=input_name_prefix),
|
||||
],
|
||||
)
|
||||
).with_media(_DATE_RANGE_MEDIA)
|
||||
|
||||
Reference in New Issue
Block a user