style(web): polish native date, time and file inputs
This commit is contained in:
@@ -554,6 +554,271 @@
|
|||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
* { transition: none !important; }
|
* { transition: none !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Native form control polish (date / time / file) --- */
|
||||||
|
/* HTML5 date and time inputs have a native popup that is drawn by
|
||||||
|
the OS and cannot be themed. We polish the input itself so it
|
||||||
|
matches the rest of the design and replace the OS picker
|
||||||
|
indicator with a themed one. The JS below replaces the popup
|
||||||
|
with a fully custom one when JavaScript runs. */
|
||||||
|
input[type="date"],
|
||||||
|
input[type="time"],
|
||||||
|
input[type="datetime-local"] {
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--fg);
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.55rem 0.75rem;
|
||||||
|
width: 100%;
|
||||||
|
font-feature-settings: normal;
|
||||||
|
transition: border-color 0.12s, box-shadow 0.12s;
|
||||||
|
}
|
||||||
|
input[type="date"]:focus,
|
||||||
|
input[type="time"]:focus,
|
||||||
|
input[type="datetime-local"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: var(--ring);
|
||||||
|
}
|
||||||
|
input[type="date"]::-webkit-calendar-picker-indicator,
|
||||||
|
input[type="time"]::-webkit-calendar-picker-indicator,
|
||||||
|
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
margin: 0;
|
||||||
|
filter: invert(0.45);
|
||||||
|
transition: opacity 0.12s;
|
||||||
|
}
|
||||||
|
input[type="date"]:hover::-webkit-calendar-picker-indicator,
|
||||||
|
input[type="time"]:hover::-webkit-calendar-picker-indicator,
|
||||||
|
input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
input[type="date"]::-webkit-calendar-picker-indicator,
|
||||||
|
input[type="time"]::-webkit-calendar-picker-indicator,
|
||||||
|
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
|
||||||
|
filter: invert(0.85);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Firefox draws a small inner "X" clear button — tone it down. */
|
||||||
|
input[type="date"]::-moz-clear,
|
||||||
|
input[type="time"]::-moz-clear { opacity: 0.5; }
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.12s, box-shadow 0.12s;
|
||||||
|
}
|
||||||
|
input[type="file"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: var(--ring);
|
||||||
|
}
|
||||||
|
input[type="file"]::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 550;
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
margin-right: 0.7rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--surface-2);
|
||||||
|
color: var(--fg);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.12s, border-color 0.12s, color 0.12s;
|
||||||
|
}
|
||||||
|
input[type="file"]::file-selector-button:hover {
|
||||||
|
background: var(--surface);
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
}
|
||||||
|
input[type="file"]::file-selector-button:active { transform: translateY(1px); }
|
||||||
|
/* Firefox variant of the file selector button pseudo-element. */
|
||||||
|
input[type="file"]::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 550;
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
margin-right: 0.7rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--surface-2);
|
||||||
|
color: var(--fg);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Custom date picker (progressive enhancement) --- */
|
||||||
|
.date-input { position: relative; }
|
||||||
|
.date-input__native { display: none; }
|
||||||
|
.date-input__btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
width: 100%;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-align: left;
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.55rem 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: border-color 0.12s, box-shadow 0.12s, background 0.12s;
|
||||||
|
}
|
||||||
|
.date-input__btn:hover { border-color: var(--border-strong); }
|
||||||
|
.date-input__btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: var(--ring);
|
||||||
|
}
|
||||||
|
.date-input__btn[data-empty="1"] { color: var(--fg-subtle); }
|
||||||
|
.date-input__label { flex: 1; min-width: 0; }
|
||||||
|
.date-input__icon {
|
||||||
|
display: inline-flex;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.date-input__icon svg { width: 16px; height: 16px; display: block; }
|
||||||
|
|
||||||
|
.date-input__popover {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
left: 0;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 0.85rem;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
min-width: 280px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.date-input__nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.date-input__nav-title {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fg);
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
.date-input__nav-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--fg-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
||||||
|
}
|
||||||
|
.date-input__nav-btn:hover {
|
||||||
|
background: var(--surface-2);
|
||||||
|
color: var(--fg);
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
}
|
||||||
|
.date-input__nav-btn svg { width: 14px; height: 14px; }
|
||||||
|
.date-input__weekdays,
|
||||||
|
.date-input__days {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.date-input__weekday {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--fg-subtle);
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.35rem 0 0.4rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
.date-input__day {
|
||||||
|
height: 32px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--fg);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.1s, color 0.1s;
|
||||||
|
}
|
||||||
|
.date-input__day:hover { background: var(--surface-2); }
|
||||||
|
.date-input__day[data-other-month="1"] { color: var(--fg-subtle); }
|
||||||
|
.date-input__day[data-today="1"] {
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
}
|
||||||
|
.date-input__day[data-selected="1"] {
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--on-accent);
|
||||||
|
}
|
||||||
|
.date-input__day[data-selected="1"]:hover { background: var(--accent-hover); }
|
||||||
|
.date-input__day:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: var(--ring);
|
||||||
|
}
|
||||||
|
.date-input__footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-top: 0.85rem;
|
||||||
|
padding-top: 0.65rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.date-input__footer-btn {
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 550;
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--fg-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
||||||
|
}
|
||||||
|
.date-input__footer-btn:hover {
|
||||||
|
background: var(--surface-2);
|
||||||
|
color: var(--fg);
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
}
|
||||||
|
.date-input__footer-btn--primary {
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--on-accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.date-input__footer-btn--primary:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
border-color: var(--accent-hover);
|
||||||
|
color: var(--on-accent);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -660,5 +925,333 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/* Custom date picker — replaces <input type="date" class="input"> with
|
||||||
|
a styled trigger that opens a popover calendar matching the design
|
||||||
|
system. The native input is hidden but kept in the DOM so form
|
||||||
|
submission works the same way with or without JavaScript. */
|
||||||
|
(function () {
|
||||||
|
var MONTH_CACHE = {};
|
||||||
|
var WEEK_CACHE = {};
|
||||||
|
var FIRSTDAY_CACHE = {};
|
||||||
|
|
||||||
|
function langFor(node) {
|
||||||
|
var n = node;
|
||||||
|
while (n && n.nodeType === 1) {
|
||||||
|
var l = n.getAttribute("lang");
|
||||||
|
if (l) return l;
|
||||||
|
n = n.parentNode;
|
||||||
|
}
|
||||||
|
return document.documentElement.lang || "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
function monthNames(lang) {
|
||||||
|
if (!MONTH_CACHE[lang]) {
|
||||||
|
try {
|
||||||
|
var fmt = new Intl.DateTimeFormat(lang, { month: "long" });
|
||||||
|
MONTH_CACHE[lang] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(function (i) {
|
||||||
|
return fmt.format(new Date(2000, i, 15));
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
MONTH_CACHE[lang] = [
|
||||||
|
"January", "February", "March", "April", "May", "June",
|
||||||
|
"July", "August", "September", "October", "November", "December"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MONTH_CACHE[lang];
|
||||||
|
}
|
||||||
|
|
||||||
|
function weekdayNames(lang, firstDay) {
|
||||||
|
var key = lang + "|" + firstDay;
|
||||||
|
if (!WEEK_CACHE[key]) {
|
||||||
|
try {
|
||||||
|
var fmt = new Intl.DateTimeFormat(lang, { weekday: "short" });
|
||||||
|
var base = [1, 2, 3, 4, 5, 6, 7]; // 2001-01-01 was Monday, so 1..7 = Mon..Sun
|
||||||
|
var all = base.map(function (d) { return fmt.format(new Date(2001, 0, d)); });
|
||||||
|
var offset = firstDay - 1;
|
||||||
|
WEEK_CACHE[key] = all.slice(offset).concat(all.slice(0, offset));
|
||||||
|
} catch (e) {
|
||||||
|
WEEK_CACHE[key] = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return WEEK_CACHE[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstDay(lang) {
|
||||||
|
if (!FIRSTDAY_CACHE[lang]) {
|
||||||
|
try {
|
||||||
|
var info = new Intl.Locale(lang).getWeekInfo();
|
||||||
|
FIRSTDAY_CACHE[lang] = (info && typeof info.firstDay === "number")
|
||||||
|
? info.firstDay : 1;
|
||||||
|
} catch (e) {
|
||||||
|
var monFirst = {
|
||||||
|
cs: 1, sk: 1, sl: 1, hr: 1, sr: 1, bs: 1, de: 1, fr: 1, es: 1,
|
||||||
|
it: 1, nl: 1, pl: 1, pt: 1, ru: 1, uk: 1, sq: 1, sv: 1, no: 1, da: 1
|
||||||
|
};
|
||||||
|
FIRSTDAY_CACHE[lang] = monFirst[lang] || 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FIRSTDAY_CACHE[lang];
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(lang, d) {
|
||||||
|
try {
|
||||||
|
return new Intl.DateTimeFormat(lang, {
|
||||||
|
day: "numeric", month: "long", year: "numeric"
|
||||||
|
}).format(d);
|
||||||
|
} catch (e) {
|
||||||
|
return d.toDateString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseISO(s) {
|
||||||
|
if (!s || !/^\d{4}-\d{2}-\d{2}$/.test(s)) return null;
|
||||||
|
var p = s.split("-");
|
||||||
|
var d = new Date(Number(p[0]), Number(p[1]) - 1, Number(p[2]));
|
||||||
|
return isNaN(d.getTime()) ? null : d;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toISO(d) {
|
||||||
|
var y = d.getFullYear();
|
||||||
|
var m = String(d.getMonth() + 1).padStart(2, "0");
|
||||||
|
var dd = String(d.getDate()).padStart(2, "0");
|
||||||
|
return y + "-" + m + "-" + dd;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameDay(a, b) {
|
||||||
|
return !!a && !!b &&
|
||||||
|
a.getFullYear() === b.getFullYear() &&
|
||||||
|
a.getMonth() === b.getMonth() &&
|
||||||
|
a.getDate() === b.getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function todayMidnight() {
|
||||||
|
var n = new Date();
|
||||||
|
return new Date(n.getFullYear(), n.getMonth(), n.getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
function svg(icon) {
|
||||||
|
var paths = {
|
||||||
|
chevronLeft: '<polyline points="15 18 9 12 15 6"/>',
|
||||||
|
chevronRight: '<polyline points="9 18 15 12 9 6"/>',
|
||||||
|
calendar:
|
||||||
|
'<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>' +
|
||||||
|
'<line x1="16" y1="2" x2="16" y2="6"/>' +
|
||||||
|
'<line x1="8" y1="2" x2="8" y2="6"/>' +
|
||||||
|
'<line x1="3" y1="10" x2="21" y2="10"/>'
|
||||||
|
};
|
||||||
|
return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" '
|
||||||
|
+ 'stroke-width="2" stroke-linecap="round" stroke-linejoin="round" '
|
||||||
|
+ 'aria-hidden="true">' + (paths[icon] || "") + "</svg>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPicker(input) {
|
||||||
|
if (input.dataset.volumenPicker === "1") return;
|
||||||
|
input.dataset.volumenPicker = "1";
|
||||||
|
|
||||||
|
var lang = langFor(input);
|
||||||
|
var initial = parseISO(input.value || "");
|
||||||
|
var weekdayStart = firstDay(lang);
|
||||||
|
|
||||||
|
var wrap = document.createElement("div");
|
||||||
|
wrap.className = "date-input";
|
||||||
|
|
||||||
|
var button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = "date-input__btn";
|
||||||
|
button.setAttribute("aria-haspopup", "dialog");
|
||||||
|
button.setAttribute("aria-expanded", "false");
|
||||||
|
|
||||||
|
var labelEl = document.createElement("span");
|
||||||
|
labelEl.className = "date-input__label";
|
||||||
|
button.appendChild(labelEl);
|
||||||
|
|
||||||
|
var iconEl = document.createElement("span");
|
||||||
|
iconEl.className = "date-input__icon";
|
||||||
|
iconEl.innerHTML = svg("calendar");
|
||||||
|
button.appendChild(iconEl);
|
||||||
|
|
||||||
|
var pop = document.createElement("div");
|
||||||
|
pop.className = "date-input__popover";
|
||||||
|
pop.setAttribute("role", "dialog");
|
||||||
|
pop.setAttribute("aria-label", "Pick a date");
|
||||||
|
pop.hidden = true;
|
||||||
|
pop.innerHTML =
|
||||||
|
'<div class="date-input__nav">' +
|
||||||
|
'<button type="button" class="date-input__nav-btn" data-prev '
|
||||||
|
+ 'aria-label="Previous month">' + svg("chevronLeft") + "</button>" +
|
||||||
|
'<div class="date-input__nav-title"></div>' +
|
||||||
|
'<button type="button" class="date-input__nav-btn" data-next '
|
||||||
|
+ 'aria-label="Next month">' + svg("chevronRight") + "</button>" +
|
||||||
|
"</div>" +
|
||||||
|
'<div class="date-input__weekdays"></div>' +
|
||||||
|
'<div class="date-input__days"></div>' +
|
||||||
|
'<div class="date-input__footer">' +
|
||||||
|
'<button type="button" class="date-input__footer-btn" data-clear>'
|
||||||
|
+ (lang === "cs" ? "Vymazat" : "Clear") + "</button>" +
|
||||||
|
'<button type="button" class="date-input__footer-btn '
|
||||||
|
+ 'date-input__footer-btn--primary" data-today>'
|
||||||
|
+ (lang === "cs" ? "Dnes" : "Today") + "</button>" +
|
||||||
|
"</div>";
|
||||||
|
|
||||||
|
var weekdaysEl = pop.querySelector(".date-input__weekdays");
|
||||||
|
var daysEl = pop.querySelector(".date-input__days");
|
||||||
|
var titleEl = pop.querySelector(".date-input__nav-title");
|
||||||
|
|
||||||
|
var viewYear, viewMonth, selected = initial;
|
||||||
|
|
||||||
|
function resetView() {
|
||||||
|
var ref = selected || todayMidnight();
|
||||||
|
viewYear = ref.getFullYear();
|
||||||
|
viewMonth = ref.getMonth();
|
||||||
|
}
|
||||||
|
resetView();
|
||||||
|
|
||||||
|
function renderLabel() {
|
||||||
|
if (selected) {
|
||||||
|
labelEl.textContent = formatDate(lang, selected);
|
||||||
|
button.dataset.empty = "0";
|
||||||
|
} else {
|
||||||
|
labelEl.textContent = input.placeholder || (lang === "cs" ? "Vyberte datum" : "Pick a date");
|
||||||
|
button.dataset.empty = "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMonth() {
|
||||||
|
titleEl.textContent = monthNames(lang)[viewMonth] + " " + viewYear;
|
||||||
|
weekdaysEl.innerHTML = weekdayNames(lang, weekdayStart)
|
||||||
|
.map(function (w) { return '<div class="date-input__weekday">' + w + "</div>"; })
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
var first = new Date(viewYear, viewMonth, 1);
|
||||||
|
var firstWd = ((first.getDay() - weekdayStart + 7) % 7) + 1; // 1..7
|
||||||
|
var start = new Date(viewYear, viewMonth, 1 - (firstWd - 1));
|
||||||
|
var today = todayMidnight();
|
||||||
|
var cells = [];
|
||||||
|
for (var i = 0; i < 42; i++) {
|
||||||
|
var d = new Date(start.getFullYear(), start.getMonth(), start.getDate() + i);
|
||||||
|
var attrs = ' class="date-input__day" data-iso="' + toISO(d) + '"';
|
||||||
|
if (d.getMonth() !== viewMonth) attrs += ' data-other-month="1"';
|
||||||
|
if (sameDay(d, today)) attrs += ' data-today="1"';
|
||||||
|
if (sameDay(d, selected)) attrs += ' data-selected="1"';
|
||||||
|
attrs += ' tabindex="-1" type="button"';
|
||||||
|
cells.push("<button" + attrs + ">" + d.getDate() + "</button>");
|
||||||
|
}
|
||||||
|
daysEl.innerHTML = cells.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit(d) {
|
||||||
|
selected = d;
|
||||||
|
input.value = toISO(d);
|
||||||
|
renderLabel();
|
||||||
|
renderMonth();
|
||||||
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
if (!pop.hidden) return;
|
||||||
|
resetView();
|
||||||
|
renderMonth();
|
||||||
|
pop.hidden = false;
|
||||||
|
button.setAttribute("aria-expanded", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
if (pop.hidden) return;
|
||||||
|
pop.hidden = true;
|
||||||
|
button.setAttribute("aria-expanded", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDocClick(e) {
|
||||||
|
if (!wrap.contains(e.target)) close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDocKey(e) {
|
||||||
|
if (e.key === "Escape" && !pop.hidden) {
|
||||||
|
close();
|
||||||
|
button.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (pop.hidden) open(); else close();
|
||||||
|
});
|
||||||
|
|
||||||
|
pop.addEventListener("click", function (e) {
|
||||||
|
var day = e.target.closest(".date-input__day");
|
||||||
|
if (day && day.dataset.iso) {
|
||||||
|
e.preventDefault();
|
||||||
|
var d = parseISO(day.dataset.iso);
|
||||||
|
if (d) { commit(d); close(); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.target.closest("[data-prev]")) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (--viewMonth < 0) { viewMonth = 11; viewYear--; }
|
||||||
|
renderMonth();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.target.closest("[data-next]")) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (++viewMonth > 11) { viewMonth = 0; viewYear++; }
|
||||||
|
renderMonth();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.target.closest("[data-clear]")) {
|
||||||
|
e.preventDefault();
|
||||||
|
selected = null;
|
||||||
|
input.value = "";
|
||||||
|
renderLabel();
|
||||||
|
renderMonth();
|
||||||
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.target.closest("[data-today]")) {
|
||||||
|
e.preventDefault();
|
||||||
|
var t = todayMidnight();
|
||||||
|
commit(t);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", onDocClick, true);
|
||||||
|
document.addEventListener("keydown", onDocKey, true);
|
||||||
|
|
||||||
|
input.parentNode.insertBefore(wrap, input);
|
||||||
|
wrap.appendChild(input);
|
||||||
|
input.classList.add("date-input__native");
|
||||||
|
wrap.appendChild(button);
|
||||||
|
wrap.appendChild(pop);
|
||||||
|
renderLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function enhanceAll(root) {
|
||||||
|
(root || document).querySelectorAll(
|
||||||
|
'input.input[type="date"], input.input[type="datetime-local"]'
|
||||||
|
).forEach(buildPicker);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
enhanceAll();
|
||||||
|
// Re-scan on Turbolinks-style page swaps, should the admin adopt them.
|
||||||
|
if (typeof MutationObserver !== "undefined") {
|
||||||
|
var obs = new MutationObserver(function () {
|
||||||
|
enhanceAll(document);
|
||||||
|
});
|
||||||
|
obs.observe(document.body, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user