| 1 | /*! Fiesta 1.0.0 | MPL-2.0 | https://fiesta.rastrillo.org */ |
| 2 | (function (global) { |
| 3 | "use strict"; |
| 4 | |
| 5 | |
| 6 | function firstDayOfWeek(locale) { |
| 7 | var info = null, loc; |
| 8 | try { |
| 9 | loc = new Intl.Locale(locale || "en"); |
| 10 | info = typeof loc.getWeekInfo === "function" ? loc.getWeekInfo() : loc.weekInfo; |
| 11 | } catch (e) { |
| 12 | info = null; |
| 13 | } |
| 14 | if (info && typeof info.firstDay === "number" && info.firstDay >= 1 && info.firstDay <= 7) { |
| 15 | return info.firstDay % 7; |
| 16 | } |
| 17 | return 1; |
| 18 | } |
| 19 | |
| 20 | function monthGrid(year, month, first) { |
| 21 | var start = new Date(year, month, 1); |
| 22 | var back = (start.getDay() - first + 7) % 7; |
| 23 | var out = [], i; |
| 24 | for (i = 0; i < 42; i++) out.push(new Date(year, month, 1 - back + i)); |
| 25 | return out; |
| 26 | } |
| 27 | |
| 28 | function weekdayNames(locale, first) { |
| 29 | var out = [], i, day, probe, shortFmt = null, longFmt = null; |
| 30 | try { |
| 31 | shortFmt = new Intl.DateTimeFormat(locale, { weekday: "short", timeZone: "UTC" }); |
| 32 | longFmt = new Intl.DateTimeFormat(locale, { weekday: "long", timeZone: "UTC" }); |
| 33 | } catch (e) { |
| 34 | shortFmt = null; |
| 35 | longFmt = null; |
| 36 | } |
| 37 | for (i = 0; i < 7; i++) { |
| 38 | day = (first + i) % 7; |
| 39 | probe = new Date(Date.UTC(2026, 1, 1 + day)); |
| 40 | out.push({ |
| 41 | day: day, |
| 42 | short: shortFmt ? shortFmt.format(probe) : String(day), |
| 43 | long: longFmt ? longFmt.format(probe) : String(day) |
| 44 | }); |
| 45 | } |
| 46 | return out; |
| 47 | } |
| 48 | |
| 49 | |
| 50 | if (typeof document !== "undefined") { |
| 51 | |
| 52 | var CHEVRON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' + |
| 53 | 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">' + |
| 54 | '<path d="M15 5l-7 7 7 7"/></svg>'; |
| 55 | |
| 56 | |
| 57 | function pad(n) { return (n < 10 ? "0" : "") + n; } |
| 58 | |
| 59 | function dayKey(d) { |
| 60 | return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()); |
| 61 | } |
| 62 | |
| 63 | function sameDay(a, b) { return !!a && !!b && dayKey(a) === dayKey(b); } |
| 64 | |
| 65 | function shift(d, days) { |
| 66 | return new Date(d.getFullYear(), d.getMonth(), d.getDate() + days); |
| 67 | } |
| 68 | |
| 69 | function shiftMonths(d, months) { |
| 70 | var y = d.getFullYear(), m = d.getMonth() + months, day = d.getDate(); |
| 71 | var last = new Date(y, m + 1, 0).getDate(); |
| 72 | return new Date(y, m, day > last ? last : day); |
| 73 | } |
| 74 | |
| 75 | function fmt(locale, opts) { |
| 76 | try { |
| 77 | return new Intl.DateTimeFormat(locale, opts); |
| 78 | } catch (e) { |
| 79 | return null; |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | function makePanel(cfg) { |
| 84 | var locale = cfg.locale; |
| 85 | var first = firstDayOfWeek(locale); |
| 86 | var heads = weekdayNames(locale, first); |
| 87 | var titleFmt = fmt(locale, { month: "long", year: "numeric" }); |
| 88 | var fullFmt = fmt(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" }); |
| 89 | var numFmt; |
| 90 | try { |
| 91 | numFmt = new Intl.NumberFormat(locale, { useGrouping: false }); |
| 92 | } catch (e) { |
| 93 | numFmt = null; |
| 94 | } |
| 95 | |
| 96 | var view = null; |
| 97 | var cursor = null; |
| 98 | var preview = null; |
| 99 | var cells = []; |
| 100 | |
| 101 | var el = document.createElement("div"); |
| 102 | el.setAttribute("rst-cal", ""); |
| 103 | el.hidden = true; |
| 104 | el.setAttribute("role", "dialog"); |
| 105 | el.setAttribute("aria-label", cfg.labels.calendar); |
| 106 | |
| 107 | var head = document.createElement("div"); |
| 108 | head.setAttribute("rst-cal-head", ""); |
| 109 | |
| 110 | function navButton(label, which) { |
| 111 | var b = document.createElement("button"); |
| 112 | b.type = "button"; |
| 113 | b.setAttribute("rst-cal-nav", which); |
| 114 | b.setAttribute("aria-label", label); |
| 115 | b.innerHTML = CHEVRON; |
| 116 | return b; |
| 117 | } |
| 118 | |
| 119 | var prevBtn = navButton(cfg.labels.prev, "prev"); |
| 120 | var nextBtn = navButton(cfg.labels.next, "next"); |
| 121 | |
| 122 | var title = document.createElement("div"); |
| 123 | title.setAttribute("rst-cal-title", ""); |
| 124 | title.id = cfg.id + "-cal-title"; |
| 125 | title.setAttribute("aria-live", "polite"); |
| 126 | |
| 127 | head.appendChild(prevBtn); |
| 128 | head.appendChild(title); |
| 129 | head.appendChild(nextBtn); |
| 130 | |
| 131 | var table = document.createElement("table"); |
| 132 | table.setAttribute("rst-cal-grid", ""); |
| 133 | table.setAttribute("role", "grid"); |
| 134 | table.setAttribute("aria-labelledby", title.id); |
| 135 | |
| 136 | var thead = document.createElement("thead"); |
| 137 | var headRow = document.createElement("tr"); |
| 138 | headRow.setAttribute("role", "row"); |
| 139 | for (var h = 0; h < heads.length; h++) { |
| 140 | var th = document.createElement("th"); |
| 141 | th.setAttribute("scope", "col"); |
| 142 | th.setAttribute("role", "columnheader"); |
| 143 | var eye = document.createElement("span"); |
| 144 | eye.setAttribute("aria-hidden", "true"); |
| 145 | eye.textContent = heads[h].short; |
| 146 | var said = document.createElement("span"); |
| 147 | said.className = "rst-sr-only"; |
| 148 | said.textContent = heads[h].long; |
| 149 | th.appendChild(eye); |
| 150 | th.appendChild(said); |
| 151 | headRow.appendChild(th); |
| 152 | } |
| 153 | thead.appendChild(headRow); |
| 154 | |
| 155 | var tbody = document.createElement("tbody"); |
| 156 | table.appendChild(thead); |
| 157 | table.appendChild(tbody); |
| 158 | el.appendChild(head); |
| 159 | el.appendChild(table); |
| 160 | |
| 161 | function outOfBounds(d) { |
| 162 | var b = cfg.bounds ? cfg.bounds() : null; |
| 163 | if (!b) return false; |
| 164 | if (b.min && dayKey(d) < dayKey(b.min)) return true; |
| 165 | if (b.max && dayKey(d) > dayKey(b.max)) return true; |
| 166 | return false; |
| 167 | } |
| 168 | |
| 169 | function monthReachable(d) { |
| 170 | var b = cfg.bounds ? cfg.bounds() : null; |
| 171 | if (!b) return true; |
| 172 | var lo = new Date(d.getFullYear(), d.getMonth(), 1); |
| 173 | var hi = new Date(d.getFullYear(), d.getMonth() + 1, 0); |
| 174 | if (b.min && dayKey(hi) < dayKey(b.min)) return false; |
| 175 | if (b.max && dayKey(lo) > dayKey(b.max)) return false; |
| 176 | return true; |
| 177 | } |
| 178 | |
| 179 | function render() { |
| 180 | var today = new Date(); |
| 181 | var chosen = cfg.value ? cfg.value() : null; |
| 182 | var days = monthGrid(view.getFullYear(), view.getMonth(), first); |
| 183 | var i, row, td, d, focusable = -1; |
| 184 | |
| 185 | title.textContent = titleFmt ? titleFmt.format(view) |
| 186 | : view.getFullYear() + "-" + pad(view.getMonth() + 1); |
| 187 | |
| 188 | prevBtn.disabled = !monthReachable(shiftMonths(new Date(view.getFullYear(), view.getMonth(), 1), -1)); |
| 189 | nextBtn.disabled = !monthReachable(shiftMonths(new Date(view.getFullYear(), view.getMonth(), 1), 1)); |
| 190 | |
| 191 | tbody.textContent = ""; |
| 192 | cells = []; |
| 193 | for (i = 0; i < days.length; i++) { |
| 194 | if (i % 7 === 0) { |
| 195 | row = document.createElement("tr"); |
| 196 | row.setAttribute("role", "row"); |
| 197 | tbody.appendChild(row); |
| 198 | } |
| 199 | d = days[i]; |
| 200 | td = document.createElement("td"); |
| 201 | td.setAttribute("rst-cal-day", ""); |
| 202 | td.setAttribute("role", "gridcell"); |
| 203 | td.tabIndex = -1; |
| 204 | td.textContent = numFmt ? numFmt.format(d.getDate()) : String(d.getDate()); |
| 205 | td.setAttribute("aria-label", fullFmt ? fullFmt.format(d) : dayKey(d)); |
| 206 | td.setAttribute("data-rst-day", dayKey(d)); |
| 207 | |
| 208 | if (d.getMonth() !== view.getMonth()) td.classList.add("is-outside"); |
| 209 | if (sameDay(d, today)) { |
| 210 | td.classList.add("is-today"); |
| 211 | td.setAttribute("aria-current", "date"); |
| 212 | } |
| 213 | td.setAttribute("aria-selected", sameDay(d, chosen) ? "true" : "false"); |
| 214 | if (sameDay(d, chosen)) td.classList.add("is-selected"); |
| 215 | if (sameDay(d, preview)) td.classList.add("is-preview"); |
| 216 | if (outOfBounds(d)) { |
| 217 | td.classList.add("is-disabled"); |
| 218 | td.setAttribute("aria-disabled", "true"); |
| 219 | } |
| 220 | if (sameDay(d, cursor)) focusable = i; |
| 221 | row.appendChild(td); |
| 222 | cells.push({ td: td, date: d }); |
| 223 | } |
| 224 | if (focusable < 0) { |
| 225 | for (i = 0; i < cells.length; i++) { |
| 226 | if (cells[i].date.getMonth() === view.getMonth()) { focusable = i; break; } |
| 227 | } |
| 228 | if (focusable >= 0) cursor = cells[focusable].date; |
| 229 | } |
| 230 | if (focusable >= 0) cells[focusable].td.tabIndex = 0; |
| 231 | } |
| 232 | |
| 233 | function cellFor(d) { |
| 234 | var key = dayKey(d), i; |
| 235 | for (i = 0; i < cells.length; i++) if (dayKey(cells[i].date) === key) return cells[i].td; |
| 236 | return null; |
| 237 | } |
| 238 | |
| 239 | function moveTo(d, focus) { |
| 240 | cursor = d; |
| 241 | if (d.getFullYear() !== view.getFullYear() || d.getMonth() !== view.getMonth()) { |
| 242 | view = new Date(d.getFullYear(), d.getMonth(), 1); |
| 243 | } |
| 244 | render(); |
| 245 | if (focus) { |
| 246 | var td = cellFor(d); |
| 247 | if (td) td.focus(); |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | function pick(d) { |
| 252 | if (outOfBounds(d)) return; |
| 253 | if (cfg.onPick) cfg.onPick(new Date(d.getFullYear(), d.getMonth(), d.getDate())); |
| 254 | } |
| 255 | |
| 256 | function dayAt(node) { |
| 257 | while (node && node !== tbody) { |
| 258 | if (node.getAttribute && node.getAttribute("data-rst-day")) return node; |
| 259 | node = node.parentNode; |
| 260 | } |
| 261 | return null; |
| 262 | } |
| 263 | |
| 264 | tbody.addEventListener("mousedown", function (e) { |
| 265 | var td = dayAt(e.target); |
| 266 | if (td) e.preventDefault(); |
| 267 | }); |
| 268 | |
| 269 | tbody.addEventListener("click", function (e) { |
| 270 | var td = dayAt(e.target), i; |
| 271 | if (!td) return; |
| 272 | for (i = 0; i < cells.length; i++) if (cells[i].td === td) return pick(cells[i].date); |
| 273 | }); |
| 274 | |
| 275 | function rtl() { |
| 276 | try { |
| 277 | return getComputedStyle(el).direction === "rtl"; |
| 278 | } catch (e) { |
| 279 | return false; |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | table.addEventListener("keydown", function (e) { |
| 284 | var step = rtl() ? -1 : 1, at = cursor || view; |
| 285 | switch (e.key) { |
| 286 | case "ArrowLeft": e.preventDefault(); return moveTo(shift(at, -step), true); |
| 287 | case "ArrowRight": e.preventDefault(); return moveTo(shift(at, step), true); |
| 288 | case "ArrowUp": e.preventDefault(); return moveTo(shift(at, -7), true); |
| 289 | case "ArrowDown": e.preventDefault(); return moveTo(shift(at, 7), true); |
| 290 | case "Home": |
| 291 | e.preventDefault(); |
| 292 | return moveTo(shift(at, -((at.getDay() - first + 7) % 7)), true); |
| 293 | case "End": |
| 294 | e.preventDefault(); |
| 295 | return moveTo(shift(at, 6 - ((at.getDay() - first + 7) % 7)), true); |
| 296 | case "PageUp": |
| 297 | e.preventDefault(); |
| 298 | return moveTo(shiftMonths(at, e.shiftKey ? -12 : -1), true); |
| 299 | case "PageDown": |
| 300 | e.preventDefault(); |
| 301 | return moveTo(shiftMonths(at, e.shiftKey ? 12 : 1), true); |
| 302 | case "Enter": |
| 303 | case " ": |
| 304 | e.preventDefault(); |
| 305 | return pick(at); |
| 306 | case "Escape": |
| 307 | e.preventDefault(); |
| 308 | if (cfg.onDismiss) cfg.onDismiss(); |
| 309 | return; |
| 310 | } |
| 311 | }); |
| 312 | |
| 313 | prevBtn.addEventListener("mousedown", function (e) { e.preventDefault(); }); |
| 314 | nextBtn.addEventListener("mousedown", function (e) { e.preventDefault(); }); |
| 315 | prevBtn.addEventListener("click", function () { |
| 316 | view = shiftMonths(new Date(view.getFullYear(), view.getMonth(), 1), -1); |
| 317 | render(); |
| 318 | }); |
| 319 | nextBtn.addEventListener("click", function () { |
| 320 | view = shiftMonths(new Date(view.getFullYear(), view.getMonth(), 1), 1); |
| 321 | render(); |
| 322 | }); |
| 323 | |
| 324 | return { |
| 325 | el: el, |
| 326 | |
| 327 | open: function (at) { |
| 328 | var start = at || (cfg.value ? cfg.value() : null) || new Date(); |
| 329 | preview = at ? new Date(at.getFullYear(), at.getMonth(), at.getDate()) : null; |
| 330 | cursor = new Date(start.getFullYear(), start.getMonth(), start.getDate()); |
| 331 | view = new Date(start.getFullYear(), start.getMonth(), 1); |
| 332 | render(); |
| 333 | el.hidden = false; |
| 334 | }, |
| 335 | |
| 336 | close: function () { |
| 337 | el.hidden = true; |
| 338 | preview = null; |
| 339 | }, |
| 340 | |
| 341 | isOpen: function () { return !el.hidden; }, |
| 342 | |
| 343 | show: function (d) { |
| 344 | if (el.hidden) return; |
| 345 | preview = d ? new Date(d.getFullYear(), d.getMonth(), d.getDate()) : null; |
| 346 | if (preview) moveTo(preview, false); |
| 347 | else render(); |
| 348 | }, |
| 349 | |
| 350 | focusGrid: function () { |
| 351 | if (el.hidden) return false; |
| 352 | var td = cursor ? cellFor(cursor) : null; |
| 353 | if (!td) return false; |
| 354 | td.focus(); |
| 355 | return true; |
| 356 | } |
| 357 | }; |
| 358 | } |
| 359 | |
| 360 | global.fiestaCalendar = makePanel; |
| 361 | } |
| 362 | |
| 363 | if (typeof module !== "undefined" && module && module.exports) { |
| 364 | module.exports = { |
| 365 | firstDayOfWeek: firstDayOfWeek, |
| 366 | monthGrid: monthGrid, |
| 367 | weekdayNames: weekdayNames |
| 368 | }; |
| 369 | } |
| 370 | })(typeof globalThis !== "undefined" ? globalThis : this); |
| 371 | |