02798f8858
Form controls were styled "at a distance": Django renders bare <input>/<select>/<textarea>/<label>, so input.css reached in with ID-scoped #add-form descendant rules plus a global form *:disabled rule and .errorlist. The #add-form ID specificity forced state rules to climb, needed :not([data-search-select-search]) carve-outs, and broke on markup changes — it surfaced as the add_purchase Name/related_game fields not reading as disabled. Components now own all form styling via utilities on the elements themselves: - PrimitiveWidgetsMixin stamps INPUT/SELECT/TEXTAREA_CLASS (incl. disabled: variants) onto native widgets by type, skipping SearchSelect (self-styled) and checkboxes. - New FormFields(form, *, extras=...) renders label + control + errors + row layout with their own classes (replaces form.as_div()); the <form> owns its flex layout. extras appends a node into a named field's row (session timestamp buttons). - AddForm/purchase/session render via FormFields; login too — a new LoginForm(PrimitiveWidgetsMixin, AuthenticationForm) styles its inputs and auth.py renders it via FormFields + a StyledButton (was as_table). - input.css loses the entire #add-form block, the global :disabled rule, and .errorlist. State (disabled:) now lives on the element — no specificity wars, no carve-outs, robust to markup edits. Tests: error rendering uses the component class (not .errorlist); add-form labels/inputs carry their own classes; e2e login fixtures click the Login button by text (submit is now a <button>); Name disabled cursor asserted. CLAUDE.md documents the no-styling-at-a-distance + FormFields conventions. 513 passed; lint/format/ts-check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
176 lines
3.2 KiB
Python
176 lines
3.2 KiB
Python
"""Server-side HTML component library.
|
|
|
|
Split into core / primitives / domain / filters submodules; this package
|
|
re-exports the public API so ``from common.components import X`` keeps working.
|
|
"""
|
|
|
|
from common.components.core import (
|
|
BaseComponent,
|
|
Element,
|
|
Fragment,
|
|
HTMLAttribute,
|
|
HTMLTag,
|
|
Media,
|
|
Node,
|
|
Safe,
|
|
_render_element,
|
|
collect_media,
|
|
randomid,
|
|
render,
|
|
)
|
|
from common.components.custom_elements import (
|
|
SelectionFields,
|
|
SessionTimestampButtons,
|
|
register_element,
|
|
)
|
|
from common.components.date_range_picker import (
|
|
DateRangeCalendar,
|
|
DateRangeField,
|
|
DateRangePicker,
|
|
)
|
|
from common.components.domain import (
|
|
GameLink,
|
|
GameStatus,
|
|
GameStatusSelector,
|
|
LinkedPurchase,
|
|
NameWithIcon,
|
|
PriceConverted,
|
|
PurchasePrice,
|
|
SessionDeviceSelector,
|
|
_resolve_name_with_icon,
|
|
)
|
|
from common.components.filters import (
|
|
DeviceFilterBar,
|
|
FilterBar,
|
|
PlatformFilterBar,
|
|
PlayEventFilterBar,
|
|
PurchaseFilterBar,
|
|
SessionFilterBar,
|
|
StringFilter,
|
|
)
|
|
from common.components.primitives import (
|
|
H1,
|
|
A,
|
|
AddForm,
|
|
ButtonGroup,
|
|
FormFields,
|
|
Checkbox,
|
|
CsrfInput,
|
|
Div,
|
|
ExternalScript,
|
|
Icon,
|
|
Input,
|
|
Label,
|
|
Li,
|
|
Modal,
|
|
ModuleScript,
|
|
Pill,
|
|
Popover,
|
|
PopoverTruncated,
|
|
Radio,
|
|
SearchField,
|
|
SimpleTable,
|
|
Span,
|
|
StaticScript,
|
|
StyledButton,
|
|
TableHeader,
|
|
TableRow,
|
|
TableTd,
|
|
Td,
|
|
Template,
|
|
Th,
|
|
Tr,
|
|
Ul,
|
|
YearPicker,
|
|
custom_element_builder,
|
|
paginated_table_content,
|
|
)
|
|
from common.components.search_select import (
|
|
DEFAULT_PREFETCH,
|
|
FilterSelect,
|
|
LabeledOption,
|
|
SearchSelect,
|
|
SearchSelectOption,
|
|
searchselect_selected,
|
|
)
|
|
from common.utils import truncate
|
|
|
|
__all__ = [
|
|
"truncate",
|
|
"BaseComponent",
|
|
"register_element",
|
|
"SelectionFields",
|
|
"SessionTimestampButtons",
|
|
"custom_element_builder",
|
|
"Element",
|
|
"Fragment",
|
|
"Media",
|
|
"Node",
|
|
"Safe",
|
|
"collect_media",
|
|
"render",
|
|
"HTMLAttribute",
|
|
"HTMLTag",
|
|
"_render_element",
|
|
"randomid",
|
|
"A",
|
|
"AddForm",
|
|
"FormFields",
|
|
"StyledButton",
|
|
"ButtonGroup",
|
|
"Checkbox",
|
|
"CsrfInput",
|
|
"Div",
|
|
"ExternalScript",
|
|
"H1",
|
|
"Icon",
|
|
"Input",
|
|
"Modal",
|
|
"ModuleScript",
|
|
"Pill",
|
|
"Popover",
|
|
"PopoverTruncated",
|
|
"Radio",
|
|
"SearchField",
|
|
"DEFAULT_PREFETCH",
|
|
"FilterSelect",
|
|
"LabeledOption",
|
|
"SearchSelect",
|
|
"SearchSelectOption",
|
|
"searchselect_selected",
|
|
"SimpleTable",
|
|
"Span",
|
|
"StaticScript",
|
|
"Label",
|
|
"Li",
|
|
"Td",
|
|
"Th",
|
|
"Tr",
|
|
"Ul",
|
|
"TableHeader",
|
|
"TableRow",
|
|
"TableTd",
|
|
"Template",
|
|
"YearPicker",
|
|
"paginated_table_content",
|
|
"GameLink",
|
|
"GameStatus",
|
|
"GameStatusSelector",
|
|
"LinkedPurchase",
|
|
"NameWithIcon",
|
|
"PriceConverted",
|
|
"PurchasePrice",
|
|
"SessionDeviceSelector",
|
|
"_resolve_name_with_icon",
|
|
"DateRangeCalendar",
|
|
"DateRangeField",
|
|
"DateRangePicker",
|
|
"FilterBar",
|
|
"PurchaseFilterBar",
|
|
"SessionFilterBar",
|
|
"DeviceFilterBar",
|
|
"PlatformFilterBar",
|
|
"PlayEventFilterBar",
|
|
"StringFilter",
|
|
]
|