b3fa7fac96
Finish the behavioural refactor from #28: no first-party JS lives on the global object solely to be reachable from a server-rendered inline on* attribute, and no inline Alpine blobs remain in the filter bar / year picker. - Filter-bar collapse: drop the inline onclick for a delegated click listener on the persistent <filter-bar> custom element (data-filter-bar-toggle). The inner #filter-bar body is htmx-swapped while connectedCallback does not re-run, so delegation on the host preserves the swap-survival the inline handler had. - YearPicker: convert the Alpine x-data/x-on/x-ref/_pickerInstance f-string into a <year-picker> custom element with typed props (YearPickerProps). Behavior moves to ts/elements/year-picker.ts; ts/year_picker.ts and _YEAR_PICKER_MEDIA are removed. The builder lives in primitives.py (next to YearPicker) to avoid a circular import; registration stays in custom_elements.py for codegen. - Add bindPopupDismiss (ts/utils.ts): shared Escape + outside-click dismiss with a cleanup return and an extraInside hook for popups mounted on document.body. Adopted by date-range-picker.ts (1:1) and year-picker.ts (Datepicker popup is body-mounted, passed as an extra inside root). Follow-up #49 tracks unifying popup/dismiss/positioning across the remaining dropdown/search-select/Flowbite cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
/**
|
|
* YearPicker — custom element wrapping the Flowbite-datepicker year grid behind
|
|
* the YearPicker component (common/components/primitives.py).
|
|
*
|
|
* The component renders a toggle <button> plus a hidden #year-picker-input and
|
|
* carries selected-year / available-years / url-template as typed props. This
|
|
* turns the input into a year-level Datepicker, toggles it from the button, and
|
|
* navigates to the chosen year's URL. Datepicker comes from the vendored UMD
|
|
* bundle (datepicker.umd.js), a classic script loaded before this module runs.
|
|
*
|
|
* The Datepicker popup is appended to document.body, so its built-in
|
|
* outside-click handler is bypassed (it only fires when the input is focused,
|
|
* and our input is unfocusable). bindPopupDismiss handles Escape + outside
|
|
* click instead, treating the body-mounted popup as "inside".
|
|
*/
|
|
import { readYearPickerProps } from "../generated/props.js";
|
|
import { bindPopupDismiss } from "../utils.js";
|
|
|
|
declare const Datepicker: any;
|
|
|
|
class YearPickerElement extends HTMLElement {
|
|
private cleanup: (() => void) | null = null;
|
|
|
|
connectedCallback(): void {
|
|
const { selectedYear, availableYears, urlTemplate } = readYearPickerProps(this);
|
|
const input = this.querySelector<HTMLInputElement>("#year-picker-input");
|
|
const toggle = this.querySelector<HTMLElement>("[data-year-picker-toggle]");
|
|
if (!input || !toggle) return;
|
|
|
|
const currentYear = new Date().getFullYear();
|
|
const enabledYears = new Set(
|
|
availableYears
|
|
.split(",")
|
|
.map((part) => parseInt(part.trim(), 10))
|
|
.filter((year) => !isNaN(year))
|
|
);
|
|
|
|
const picker = new Datepicker(input, {
|
|
pickLevel: 2,
|
|
format: "yyyy",
|
|
minDate: new Date(1999, 0, 1),
|
|
maxDate: new Date(currentYear, 11, 31),
|
|
autohide: false,
|
|
orientation: "bottom end",
|
|
showOnClick: false,
|
|
showOnFocus: false,
|
|
beforeShowYear: (date: Date) => ({ enabled: enabledYears.has(date.getFullYear()) }),
|
|
});
|
|
|
|
picker.element.addEventListener("changeDate", (event: Event) => {
|
|
const year = (event as CustomEvent).detail.date?.getFullYear();
|
|
if (year && urlTemplate) {
|
|
window.location.href = urlTemplate.replace("__year__", String(year));
|
|
}
|
|
});
|
|
|
|
if (selectedYear) {
|
|
picker.dates = [new Date(parseInt(selectedYear, 10), 0, 1)];
|
|
picker.update();
|
|
}
|
|
|
|
toggle.addEventListener("click", () => {
|
|
if (picker.active) picker.hide();
|
|
else picker.show();
|
|
});
|
|
|
|
this.cleanup = bindPopupDismiss({
|
|
host: this,
|
|
isOpen: () => picker.active,
|
|
close: () => picker.hide(),
|
|
extraInside: () => [picker.picker?.element],
|
|
});
|
|
}
|
|
|
|
disconnectedCallback(): void {
|
|
this.cleanup?.();
|
|
this.cleanup = null;
|
|
}
|
|
}
|
|
|
|
customElements.define("year-picker", YearPickerElement);
|