Convert year_picker to TypeScript; drop inline f-string script (issue #17)
- Add ts/year_picker.ts: typed onSwap port of the year-picker glue. Datepicker declared as an ambient global (vendored UMD); PickerElement types the _pickerInstance prop the Alpine toggle button reaches - Remove the duplicate inline <script> from the YearPicker component (was a JS blob in a Python f-string — the CLAUDE.md anti-pattern) and the orphaned games/static/js/year_picker.js that nothing loaded; the component now declares dist/year_picker.js as media alongside the datepicker UMD bundle - Module defer semantics keep the classic UMD bundle running before the deferred year_picker module, so Datepicker is defined in time Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -542,9 +542,15 @@ def StaticScript(filename: str) -> SafeText:
|
|||||||
return mark_safe(f'<script src="{static("js/" + filename)}"></script>')
|
return mark_safe(f'<script src="{static("js/" + filename)}"></script>')
|
||||||
|
|
||||||
|
|
||||||
# Media for the Flowbite-datepicker year picker (vendored UMD bundle). Declared
|
# Media for the Flowbite-datepicker year picker: the vendored UMD bundle
|
||||||
# on the YearPicker node so Page() loads it wherever a YearPicker appears.
|
# (classic script) plus the ts/year_picker.ts glue (compiled module). Declared
|
||||||
_YEAR_PICKER_MEDIA = Media(js_external=("datepicker.umd.js",))
|
# on the YearPicker node so Page() loads both wherever a YearPicker appears. The
|
||||||
|
# UMD bundle is a classic script (runs during parse) while year_picker.js is a
|
||||||
|
# deferred module (runs after parse), so Datepicker is defined by the time the
|
||||||
|
# module executes regardless of tag order.
|
||||||
|
_YEAR_PICKER_MEDIA = Media(
|
||||||
|
js_external=("datepicker.umd.js",), js=("dist/year_picker.js",)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def YearPicker(
|
def YearPicker(
|
||||||
@@ -589,44 +595,7 @@ def YearPicker(
|
|||||||
data-available-years="{years_csv}"
|
data-available-years="{years_csv}"
|
||||||
data-selected-year="{selected}"
|
data-selected-year="{selected}"
|
||||||
data-url-template="{url_template}">
|
data-url-template="{url_template}">
|
||||||
</div>
|
</div>""",
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {{
|
|
||||||
const pickerEl = document.getElementById('year-picker-input');
|
|
||||||
if (!pickerEl || pickerEl._pickerInstance) return;
|
|
||||||
|
|
||||||
const selectedYear = pickerEl.dataset.selectedYear;
|
|
||||||
const urlTemplate = pickerEl.dataset.urlTemplate;
|
|
||||||
const currentYear = new Date().getFullYear();
|
|
||||||
const availableYears = new Set(pickerEl.dataset.availableYears
|
|
||||||
.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n)));
|
|
||||||
|
|
||||||
const picker = new Datepicker(pickerEl, {{
|
|
||||||
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) => ({{ enabled: availableYears.has(date.getFullYear()) }})
|
|
||||||
}});
|
|
||||||
pickerEl._pickerInstance = picker;
|
|
||||||
|
|
||||||
picker.element.addEventListener('changeDate', (e) => {{
|
|
||||||
const year = e.detail.date?.getFullYear();
|
|
||||||
if (year && urlTemplate) {{
|
|
||||||
window.location.href = urlTemplate.replace('__year__', year);
|
|
||||||
}}
|
|
||||||
}});
|
|
||||||
|
|
||||||
if (selectedYear) {{
|
|
||||||
picker.dates = [new Date(parseInt(selectedYear), 0, 1)];
|
|
||||||
picker.update();
|
|
||||||
}}
|
|
||||||
}});
|
|
||||||
</script>""",
|
|
||||||
media=_YEAR_PICKER_MEDIA,
|
media=_YEAR_PICKER_MEDIA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { onSwap } from "./utils.js";
|
|
||||||
|
|
||||||
onSwap("#year-picker-input", function(pickerEl) {
|
|
||||||
const selectedYear = pickerEl.dataset.selectedYear;
|
|
||||||
const urlTemplate = pickerEl.dataset.urlTemplate;
|
|
||||||
const currentYear = new Date().getFullYear();
|
|
||||||
const availableYears = new Set(
|
|
||||||
pickerEl.dataset.availableYears
|
|
||||||
.split(",")
|
|
||||||
.map(s => parseInt(s.trim()))
|
|
||||||
.filter(n => !isNaN(n))
|
|
||||||
);
|
|
||||||
|
|
||||||
const picker = new Datepicker(pickerEl, {
|
|
||||||
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) => ({ enabled: availableYears.has(date.getFullYear()) }),
|
|
||||||
});
|
|
||||||
pickerEl._pickerInstance = picker;
|
|
||||||
|
|
||||||
picker.element.addEventListener("changeDate", (event) => {
|
|
||||||
const year = event.detail.date?.getFullYear();
|
|
||||||
if (year && urlTemplate) {
|
|
||||||
window.location.href = urlTemplate.replace("__year__", year);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selectedYear) {
|
|
||||||
picker.dates = [new Date(parseInt(selectedYear), 0, 1)];
|
|
||||||
picker.update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* YearPicker — wires the Flowbite-datepicker year grid behind the YearPicker
|
||||||
|
* component (common/components/primitives.py). The component renders a hidden
|
||||||
|
* #year-picker-input carrying data-available-years / data-selected-year /
|
||||||
|
* data-url-template; this turns it into a year-level Datepicker and navigates
|
||||||
|
* to the chosen year's URL. Datepicker comes from the vendored UMD bundle
|
||||||
|
* (datepicker.umd.js), loaded as a classic script before this module runs.
|
||||||
|
*/
|
||||||
|
import { onSwap } from "./utils.js";
|
||||||
|
|
||||||
|
declare const Datepicker: any;
|
||||||
|
|
||||||
|
// The Alpine toggle button reaches the Datepicker instance through this prop.
|
||||||
|
interface PickerElement extends HTMLInputElement {
|
||||||
|
_pickerInstance?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSwap("#year-picker-input", (element) => {
|
||||||
|
const pickerElement = element as PickerElement;
|
||||||
|
const selectedYear = pickerElement.dataset.selectedYear;
|
||||||
|
const urlTemplate = pickerElement.dataset.urlTemplate;
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const availableYears = new Set(
|
||||||
|
(pickerElement.dataset.availableYears ?? "")
|
||||||
|
.split(",")
|
||||||
|
.map((part) => parseInt(part.trim(), 10))
|
||||||
|
.filter((year) => !isNaN(year))
|
||||||
|
);
|
||||||
|
|
||||||
|
const picker = new Datepicker(pickerElement, {
|
||||||
|
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: availableYears.has(date.getFullYear()) }),
|
||||||
|
});
|
||||||
|
pickerElement._pickerInstance = picker;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user