runtimeCalendarJs top-level constant
String
const runtimeCalendarJs
Implementation
const String runtimeCalendarJs = r'''
function _calRoot(target){
if (!target) return null;
if (target.matches && target.matches('[data-arcane-calendar]')) return target;
return target.querySelector ? target.querySelector('[data-arcane-calendar]') : null;
}
function _calMonthLabel(d){
const months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
return months[d.getMonth()] + ' ' + d.getFullYear();
}
function _calParseDate(s){
if (!s) return null;
const parts = String(s).split('-');
if (parts.length !== 3) return null;
const y = parseInt(parts[0], 10);
const m = parseInt(parts[1], 10) - 1;
const d = parseInt(parts[2], 10);
if (isNaN(y) || isNaN(m) || isNaN(d)) return null;
return new Date(y, m, d);
}
function _calFmtDate(d){
if (!d) return '';
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return y + '-' + m + '-' + dd;
}
function _calIsSameDay(a, b){
if (!a || !b) return false;
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}
function _calInRange(d, start, end){
if (!d || !start || !end) return false;
const t = d.getTime();
return t >= start.getTime() && t <= end.getTime();
}
function _calDisabled(d, root){
const min = _calParseDate(root.getAttribute('data-arcane-min'));
const max = _calParseDate(root.getAttribute('data-arcane-max'));
if (min && d.getTime() < min.getTime()) return true;
if (max && d.getTime() > max.getTime()) return true;
return false;
}
function _calWeekNumber(date){
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
function _calHumanize(d){
if (!d) return '';
const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
return months[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
}
function _calDisplay(root){
const mode = root.getAttribute('data-arcane-mode') || 'single';
if (mode === 'range'){
const rs = root.getAttribute('data-arcane-range-start');
const re = root.getAttribute('data-arcane-range-end');
if (!rs || !re) return '';
const sd = _calParseDate(rs); const ed = _calParseDate(re);
return _calHumanize(sd) + ' - ' + _calHumanize(ed);
}
const sel = root.getAttribute('data-arcane-selected');
if (!sel) return '';
return _calHumanize(_calParseDate(sel));
}
function renderCalendar(root){
if (!root) return;
const year = parseInt(root.getAttribute('data-arcane-year') || String(new Date().getFullYear()), 10);
const month = parseInt(root.getAttribute('data-arcane-month') || String(new Date().getMonth()), 10);
const firstDayOfWeek = parseInt(root.getAttribute('data-arcane-first-day') || '0', 10);
const showWeekNumbers = root.getAttribute('data-arcane-show-week-numbers') === 'true';
const showToday = root.getAttribute('data-arcane-show-today') !== 'false';
const mode = root.getAttribute('data-arcane-mode') || 'single';
const selected = _calParseDate(root.getAttribute('data-arcane-selected'));
const rangeStart = _calParseDate(root.getAttribute('data-arcane-range-start'));
const rangeEnd = _calParseDate(root.getAttribute('data-arcane-range-end'));
const pendingStart = _calParseDate(root.getAttribute('data-arcane-pending-start'));
const displayMonth = new Date(year, month, 1);
const today = new Date();
today.setHours(0, 0, 0, 0);
const monthStart = new Date(year, month, 1);
const monthEnd = new Date(year, month + 1, 0);
const startOffset = (monthStart.getDay() - firstDayOfWeek + 7) % 7;
const gridStart = new Date(year, month, 1 - startOffset);
const id = root.getAttribute('data-arcane-id') || '';
let html = '';
html += '<div class="arcane-calendar-header" data-arcane-calendar-header>';
html += '<button type="button" class="arcane-calendar-nav arcane-calendar-prev" data-arcane-action="calendar.prev:' + id + '" aria-label="Previous month">‹</button>';
html += '<div class="arcane-calendar-label" data-arcane-calendar-label>' + _calMonthLabel(displayMonth) + '</div>';
html += '<button type="button" class="arcane-calendar-nav arcane-calendar-next" data-arcane-action="calendar.next:' + id + '" aria-label="Next month">›</button>';
html += '</div>';
if (showToday){
html += '<div class="arcane-calendar-today-row"><button type="button" class="arcane-calendar-today" data-arcane-action="calendar.today:' + id + '">Today</button></div>';
}
const weekdays = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
html += '<div class="arcane-calendar-weekdays">';
if (showWeekNumbers) html += '<div class="arcane-calendar-weekday arcane-calendar-week-num">#</div>';
for (let i = 0; i < 7; i++){
html += '<div class="arcane-calendar-weekday">' + weekdays[(firstDayOfWeek + i) % 7] + '</div>';
}
html += '</div>';
html += '<div class="arcane-calendar-grid">';
let iter = new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate());
for (let w = 0; w < 6; w++){
if (showWeekNumbers){
const weekNo = _calWeekNumber(iter);
html += '<div class="arcane-calendar-weeknum">' + weekNo + '</div>';
}
for (let d = 0; d < 7; d++){
const inMonth = iter.getMonth() === month;
const disabled = _calDisabled(iter, root);
const isToday = _calIsSameDay(iter, today);
const isSelected = mode === 'single' && _calIsSameDay(iter, selected);
const inRange = mode === 'range' && rangeStart && rangeEnd && _calInRange(iter, rangeStart, rangeEnd);
const isRangeStart = mode === 'range' && _calIsSameDay(iter, rangeStart);
const isRangeEnd = mode === 'range' && _calIsSameDay(iter, rangeEnd);
const isPendingStart = mode === 'range' && _calIsSameDay(iter, pendingStart);
const classes = ['arcane-calendar-day'];
if (!inMonth) classes.push('arcane-calendar-day-other-month');
if (disabled) classes.push('arcane-calendar-day-disabled');
if (isToday) classes.push('arcane-calendar-day-today');
if (isSelected) classes.push('arcane-calendar-day-selected');
if (inRange) classes.push('arcane-calendar-day-in-range');
if (isRangeStart) classes.push('arcane-calendar-day-range-start');
if (isRangeEnd) classes.push('arcane-calendar-day-range-end');
if (isPendingStart) classes.push('arcane-calendar-day-pending');
const iso = _calFmtDate(iter);
const label = iter.getDate();
if (disabled){
html += '<button type="button" class="' + classes.join(' ') + '" disabled aria-disabled="true">' + label + '</button>';
} else {
html += '<button type="button" class="' + classes.join(' ') + '" data-arcane-action="calendar.select:' + id + '" data-arcane-value="' + iso + '" aria-pressed="' + (isSelected || isRangeStart || isRangeEnd ? 'true' : 'false') + '">' + label + '</button>';
}
iter = new Date(iter.getFullYear(), iter.getMonth(), iter.getDate() + 1);
}
if (iter.getMonth() !== month && iter > monthEnd) break;
}
html += '</div>';
root.innerHTML = html;
}
function _calEmitChange(root, value){
let evt;
try { evt = new CustomEvent('arcane:change', { detail: { value: value }, bubbles: true }); }
catch (e){ evt = document.createEvent('CustomEvent'); evt.initCustomEvent('arcane:change', true, false, { value: value }); }
root.dispatchEvent(evt);
const id = root.getAttribute('data-arcane-id');
if (!id) return;
const trigger = document.querySelector('[data-arcane-calendar-trigger="' + cssEscape(id) + '"]');
if (trigger){
const label = trigger.querySelector('[data-arcane-calendar-display]');
if (label) label.textContent = _calDisplay(root) || (trigger.getAttribute('data-arcane-placeholder') || '');
}
}
function _calResolveRoot(target){
if (!target) return null;
if (typeof target === 'string'){
return _calRoot(document.querySelector('[data-arcane-id="' + cssEscape(target) + '"]'));
}
return _calRoot(target);
}
function _calClosePicker(id){
const picker = document.querySelector('[data-arcane-surface][data-arcane-calendar-anchor="' + cssEscape(id) + '"]');
if (picker) closeSurface(surfaceType(picker), surfaceId(picker));
}
function calendarPrev(target){
const root = _calResolveRoot(target);
if (!root) return;
const year = parseInt(root.getAttribute('data-arcane-year'), 10);
const month = parseInt(root.getAttribute('data-arcane-month'), 10);
const d = new Date(year, month - 1, 1);
root.setAttribute('data-arcane-year', String(d.getFullYear()));
root.setAttribute('data-arcane-month', String(d.getMonth()));
renderCalendar(root);
}
function calendarNext(target){
const root = _calResolveRoot(target);
if (!root) return;
const year = parseInt(root.getAttribute('data-arcane-year'), 10);
const month = parseInt(root.getAttribute('data-arcane-month'), 10);
const d = new Date(year, month + 1, 1);
root.setAttribute('data-arcane-year', String(d.getFullYear()));
root.setAttribute('data-arcane-month', String(d.getMonth()));
renderCalendar(root);
}
function calendarToday(target){
const root = _calResolveRoot(target);
if (!root) return;
const now = new Date();
root.setAttribute('data-arcane-year', String(now.getFullYear()));
root.setAttribute('data-arcane-month', String(now.getMonth()));
renderCalendar(root);
}
function calendarSelect(target, value){
const root = _calResolveRoot(target);
if (!root) return;
const picked = _calParseDate(value);
if (!picked || _calDisabled(picked, root)) return;
const mode = root.getAttribute('data-arcane-mode') || 'single';
const id = root.getAttribute('data-arcane-id');
if (mode === 'range'){
const pendingStart = root.getAttribute('data-arcane-pending-start');
if (!pendingStart){
root.setAttribute('data-arcane-pending-start', value);
root.removeAttribute('data-arcane-range-start');
root.removeAttribute('data-arcane-range-end');
renderCalendar(root);
} else {
let startD = _calParseDate(pendingStart);
let endD = picked;
if (startD.getTime() > endD.getTime()){
const tmp = startD; startD = endD; endD = tmp;
}
root.setAttribute('data-arcane-range-start', _calFmtDate(startD));
root.setAttribute('data-arcane-range-end', _calFmtDate(endD));
root.removeAttribute('data-arcane-pending-start');
renderCalendar(root);
_calEmitChange(root, _calFmtDate(startD) + '/' + _calFmtDate(endD));
_calClosePicker(id);
}
} else {
root.setAttribute('data-arcane-selected', value);
renderCalendar(root);
_calEmitChange(root, value);
_calClosePicker(id);
}
}
function bindCalendars(){
const nodes = document.querySelectorAll('[data-arcane-calendar]:not([data-arcane-calendar-bound])');
for (let i = 0; i < nodes.length; i++){
nodes[i].setAttribute('data-arcane-calendar-bound', 'true');
if (!nodes[i].innerHTML.trim()) renderCalendar(nodes[i]);
}
}
''';