Optical refinement for calendar

This commit is contained in:
2026-02-18 21:26:22 +01:00
parent c90d7e18bc
commit 75423b8d50
3 changed files with 131 additions and 21 deletions

View File

@@ -43,12 +43,62 @@
calendar.removeAttribute('data-luxtools-loading'); calendar.removeAttribute('data-luxtools-loading');
} }
var buttons = calendar.querySelectorAll('button.luxtools-calendar-nav-button'); var buttons = calendar.querySelectorAll('button.luxtools-calendar-nav-button, button.luxtools-calendar-today-button');
for (var i = 0; i < buttons.length; i++) { for (var i = 0; i < buttons.length; i++) {
buttons[i].disabled = !!busy; buttons[i].disabled = !!busy;
} }
} }
function getCalendarStateKey(calendar) {
var baseNs = calendar.getAttribute('data-base-ns') || 'chronological';
return 'luxtools.calendar.month.' + baseNs;
}
function readSavedCalendarMonth(calendar) {
if (!window.localStorage) return null;
try {
var raw = window.localStorage.getItem(getCalendarStateKey(calendar));
if (!raw) return null;
var parsed = JSON.parse(raw);
var year = parseInt(parsed && parsed.year, 10);
var month = parseInt(parsed && parsed.month, 10);
if (!year || month < 1 || month > 12) return null;
return { year: year, month: month };
} catch (e) {
return null;
}
}
function saveCalendarMonth(calendar) {
if (!window.localStorage) return;
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
if (!year || month < 1 || month > 12) return;
try {
window.localStorage.setItem(getCalendarStateKey(calendar), JSON.stringify({
year: year,
month: month
}));
} catch (e) {
// ignore storage failures
}
}
function clearSavedCalendarMonth(calendar) {
if (!window.localStorage) return;
try {
window.localStorage.removeItem(getCalendarStateKey(calendar));
} catch (e) {
// ignore storage failures
}
}
function fetchCalendarMonth(calendar, year, month) { function fetchCalendarMonth(calendar, year, month) {
var ajaxUrl = calendar.getAttribute('data-luxtools-ajax-url') || ''; var ajaxUrl = calendar.getAttribute('data-luxtools-ajax-url') || '';
if (!ajaxUrl) return Promise.reject(new Error('Missing calendar ajax url')); if (!ajaxUrl) return Promise.reject(new Error('Missing calendar ajax url'));
@@ -105,53 +155,99 @@
} }
} }
function navigateCalendarMonth(calendar, direction) { function replaceCalendar(calendar, replacement, persistState) {
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10); if (persistState !== false) {
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10); saveCalendarMonth(replacement);
if (!year || !month) return; }
syncCalendarToday(replacement);
calendar.replaceWith(replacement);
}
var next = getNextMonth(year, month, direction); function loadCalendarMonth(calendar, targetYear, targetMonth, persistState) {
setCalendarBusy(calendar, true); setCalendarBusy(calendar, true);
fetchCalendarMonth(calendar, next.year, next.month) fetchCalendarMonth(calendar, targetYear, targetMonth)
.then(function (html) { .then(function (html) {
var replacement = parseCalendarFromHtml(html); var replacement = parseCalendarFromHtml(html);
if (!replacement) { if (!replacement) {
throw new Error('Calendar markup missing in response'); throw new Error('Calendar markup missing in response');
} }
syncCalendarToday(replacement); replaceCalendar(calendar, replacement, persistState);
calendar.replaceWith(replacement);
}) })
.catch(function () { .catch(function () {
var fallbackLink = calendar.querySelector('a.luxtools-calendar-month-link'); window.location.reload();
if (fallbackLink && fallbackLink.href) {
window.location.href = fallbackLink.href;
}
}) })
.finally(function () { .finally(function () {
setCalendarBusy(calendar, false); setCalendarBusy(calendar, false);
}); });
} }
function navigateCalendarMonth(calendar, direction, persistState) {
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
if (!year || !month) return;
var next = getNextMonth(year, month, direction);
loadCalendarMonth(calendar, next.year, next.month, persistState);
}
function navigateCalendarToday(calendar) {
clearSavedCalendarMonth(calendar);
var now = new Date();
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
if (!year || !month) return;
var targetYear = now.getFullYear();
var targetMonth = now.getMonth() + 1;
var direction = ((targetYear - year) * 12) + (targetMonth - month);
if (!direction) {
syncCalendarToday(calendar);
return;
}
navigateCalendarMonth(calendar, direction, false);
}
function restoreCalendarMonth(calendar) {
var saved = readSavedCalendarMonth(calendar);
if (!saved) return;
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
if (saved.year === year && saved.month === month) return;
loadCalendarMonth(calendar, saved.year, saved.month, true);
}
function onCalendarClick(event) { function onCalendarClick(event) {
var target = event.target; var target = event.target;
if (!target || !target.classList || !target.classList.contains('luxtools-calendar-nav-button')) return; if (!target || !target.classList) return;
if (!target.classList.contains('luxtools-calendar-nav-button') && !target.classList.contains('luxtools-calendar-today-button')) return;
var calendar = findCalendarRoot(target); var calendar = findCalendarRoot(target);
if (!calendar) return; if (!calendar) return;
var direction = parseInt(target.getAttribute('data-luxtools-dir') || '0', 10);
if (direction !== -1 && direction !== 1) return;
event.preventDefault(); event.preventDefault();
navigateCalendarMonth(calendar, direction);
if (target.getAttribute('data-luxtools-today') === '1') {
navigateCalendarToday(calendar);
return;
}
var direction = parseInt(target.getAttribute('data-luxtools-dir') || '0', 10);
if (!direction) return;
navigateCalendarMonth(calendar, direction, true);
} }
function initCalendarWidgets() { function initCalendarWidgets() {
var calendars = document.querySelectorAll('div.luxtools-calendar[data-luxtools-calendar="1"]'); var calendars = document.querySelectorAll('div.luxtools-calendar[data-luxtools-calendar="1"]');
for (var i = 0; i < calendars.length; i++) { for (var i = 0; i < calendars.length; i++) {
syncCalendarToday(calendars[i]); syncCalendarToday(calendars[i]);
restoreCalendarMonth(calendars[i]);
} }
document.addEventListener('click', onCalendarClick, false); document.addEventListener('click', onCalendarClick, false);

View File

@@ -64,6 +64,7 @@ class ChronologicalCalendarWidget
$html .= '<div class="luxtools-calendar-nav">'; $html .= '<div class="luxtools-calendar-nav">';
$html .= '<div class="luxtools-calendar-nav-prev">'; $html .= '<div class="luxtools-calendar-nav-prev">';
$html .= '<button type="button" class="luxtools-calendar-nav-button" data-luxtools-dir="-12" aria-label="Previous year">⏮</button>';
$html .= '<button type="button" class="luxtools-calendar-nav-button" data-luxtools-dir="-1" aria-label="Previous month">◀</button>'; $html .= '<button type="button" class="luxtools-calendar-nav-button" data-luxtools-dir="-1" aria-label="Previous month">◀</button>';
$html .= '</div>'; $html .= '</div>';
@@ -84,6 +85,7 @@ class ChronologicalCalendarWidget
$html .= '<div class="luxtools-calendar-nav-next">'; $html .= '<div class="luxtools-calendar-nav-next">';
$html .= '<button type="button" class="luxtools-calendar-nav-button" data-luxtools-dir="1" aria-label="Next month">▶</button>'; $html .= '<button type="button" class="luxtools-calendar-nav-button" data-luxtools-dir="1" aria-label="Next month">▶</button>';
$html .= '<button type="button" class="luxtools-calendar-nav-button" data-luxtools-dir="12" aria-label="Next year">⏭</button>';
$html .= '</div>'; $html .= '</div>';
$html .= '</div>'; $html .= '</div>';
@@ -118,7 +120,10 @@ class ChronologicalCalendarWidget
if ($cell % 7 === 6) $html .= '</tr>'; if ($cell % 7 === 6) $html .= '</tr>';
} }
$html .= '</tbody></table></div>'; $html .= '</tbody></table>';
$html .= '<div class="luxtools-calendar-footer">';
$html .= '<button type="button" class="luxtools-calendar-nav-button luxtools-calendar-today-button" data-luxtools-today="1" aria-label="Current month">Today</button>';
$html .= '</div></div>';
return $html; return $html;
} }

View File

@@ -534,11 +534,15 @@ div.luxtools-calendar .luxtools-calendar-nav {
} }
div.luxtools-calendar .luxtools-calendar-nav-prev { div.luxtools-calendar .luxtools-calendar-nav-prev {
text-align: left; display: flex;
justify-content: flex-start;
gap: 0.25em;
} }
div.luxtools-calendar .luxtools-calendar-nav-next { div.luxtools-calendar .luxtools-calendar-nav-next {
text-align: right; display: flex;
justify-content: flex-end;
gap: 0.25em;
} }
div.luxtools-calendar .luxtools-calendar-nav-prev a, div.luxtools-calendar .luxtools-calendar-nav-prev a,
@@ -562,6 +566,11 @@ div.luxtools-calendar .luxtools-calendar-nav-button:focus {
outline: none; outline: none;
} }
div.luxtools-calendar .luxtools-calendar-footer {
margin-top: 0.35em;
text-align: right;
}
div.luxtools-calendar table.luxtools-calendar-table { div.luxtools-calendar table.luxtools-calendar-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;