HEL-373: 主题化 combobox 覆盖夜间下拉全状态,日期面板支持年月直选

原生 select 弹层无法可靠套黑金 token,改为可访问 listbox;日期标题增加年份/月份下拉,越月不提交非法日。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
总工
2026-09-02 11:07:16 +08:00
co-authored by Cursor multica-agent
parent 1061f125a8
commit 6eccbe9765
12 changed files with 1150 additions and 28 deletions
+102 -3
View File
@@ -4,10 +4,14 @@
var panel = null;
var grid = null;
var titleEl = null;
var yearSelect = null;
var monthSelect = null;
var activeInput = null;
var viewYear = 0;
var viewMonth = 0;
var bound = false;
var YEAR_PAD = 80;
var YEAR_AHEAD = 20;
function pad(n) {
return n < 10 ? "0" + n : String(n);
@@ -46,7 +50,10 @@
panel.innerHTML =
'<div class="ds-dp-head">' +
'<button type="button" class="ds-dp-nav" data-dp-nav="-1" aria-label="上一月"></button>' +
'<div class="ds-dp-title"></div>' +
'<div class="ds-dp-title">' +
'<select class="select ds-dp-ym" data-dp-year aria-label="年份"></select>' +
'<select class="select ds-dp-ym" data-dp-month aria-label="月份"></select>' +
"</div>" +
'<button type="button" class="ds-dp-nav" data-dp-nav="1" aria-label="下一月"></button>' +
"</div>" +
'<div class="ds-dp-week">' + WEEK.map(function (d) { return "<span>" + d + "</span>"; }).join("") + "</div>" +
@@ -57,11 +64,27 @@
"</div>";
document.body.appendChild(panel);
titleEl = panel.querySelector(".ds-dp-title");
yearSelect = panel.querySelector("[data-dp-year]");
monthSelect = panel.querySelector("[data-dp-month]");
grid = panel.querySelector(".ds-dp-grid");
yearSelect.addEventListener("change", function () {
var next = Number(yearSelect.value);
if (!next) return;
viewYear = next;
render();
});
monthSelect.addEventListener("change", function () {
var next = Number(monthSelect.value);
if (Number.isNaN(next)) return;
viewMonth = next;
render();
});
panel.addEventListener("mousedown", function (event) {
if (event.target.closest(".ds-combo, select, input, textarea")) return;
event.preventDefault();
});
panel.addEventListener("click", function (event) {
if (event.target.closest(".ds-combo, .ds-listbox, select")) return;
var nav = event.target.closest("[data-dp-nav]");
if (nav) {
shiftMonth(Number(nav.getAttribute("data-dp-nav")));
@@ -105,9 +128,75 @@
document.querySelectorAll('input[type="date"]').forEach(enhance);
}
function boundYears() {
var now = new Date().getFullYear();
var minY = now - YEAR_PAD;
var maxY = now + YEAR_AHEAD;
var hasMin = false;
var hasMax = false;
if (activeInput && activeInput.min) {
var minDate = parseISO(activeInput.min);
if (minDate) {
minY = minDate.getFullYear();
hasMin = true;
}
}
if (activeInput && activeInput.max) {
var maxDate = parseISO(activeInput.max);
if (maxDate) {
maxY = maxDate.getFullYear();
hasMax = true;
}
}
if (!hasMin && viewYear < minY) minY = viewYear;
if (!hasMax && viewYear > maxY) maxY = viewYear;
if (hasMin && viewYear < minY) minY = viewYear;
if (hasMax && viewYear > maxY) maxY = viewYear;
return { minY: minY, maxY: maxY };
}
function lastDayOfMonth(year, month) {
return new Date(year, month + 1, 0).getDate();
}
function monthHasValidDay(year, month) {
var min = activeInput && activeInput.min ? activeInput.min : "";
var max = activeInput && activeInput.max ? activeInput.max : "";
var start = toISO(year, month, 1);
var end = toISO(year, month, lastDayOfMonth(year, month));
if (min && end < min) return false;
if (max && start > max) return false;
return true;
}
function syncYearMonthSelects() {
if (!yearSelect || !monthSelect) return;
var years = boundYears();
var yearHtml = "";
for (var year = years.minY; year <= years.maxY; year += 1) {
yearHtml += '<option value="' + year + '"' + (year === viewYear ? " selected" : "") + ">" + year + "年</option>";
}
yearSelect.innerHTML = yearHtml;
yearSelect.value = String(viewYear);
var monthHtml = "";
for (var month = 0; month < 12; month += 1) {
var disabled = !monthHasValidDay(viewYear, month);
monthHtml +=
'<option value="' + month + '"' +
(month === viewMonth ? " selected" : "") +
(disabled ? " disabled" : "") +
">" + pad(month + 1) + "月</option>";
}
monthSelect.innerHTML = monthHtml;
monthSelect.value = String(viewMonth);
if (global.JinniuSelect && typeof global.JinniuSelect.scan === "function") {
global.JinniuSelect.scan(panel);
}
}
function render() {
if (!grid || !titleEl) return;
titleEl.textContent = viewYear + "年" + pad(viewMonth + 1) + "月";
syncYearMonthSelects();
var first = new Date(viewYear, viewMonth, 1);
var start = first.getDay();
var selected = activeInput ? parseISO(activeInput.value) : null;
@@ -186,6 +275,9 @@
function closePicker() {
if (!panel || panel.hidden) return;
if (global.JinniuSelect && typeof global.JinniuSelect.close === "function") {
global.JinniuSelect.close();
}
panel.hidden = true;
if (activeInput) activeInput.setAttribute("aria-expanded", "false");
activeInput = null;
@@ -221,7 +313,14 @@
openPicker(input);
return;
}
if (panel && !panel.hidden && !panel.contains(event.target)) closePicker();
if (
panel &&
!panel.hidden &&
!panel.contains(event.target) &&
!(event.target.closest && event.target.closest(".ds-listbox"))
) {
closePicker();
}
}
function onKey(event) {