@vanduo-oss/framework 1.3.5 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- /*! Vanduo v1.3.5 | Built: 2026-04-15T18:39:53.955Z | git:3ca4f62 | development */
1
+ /*! Vanduo v1.3.7 | Built: 2026-04-18T12:05:32.603Z | git:20b2d08 | development */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -132,7 +132,7 @@ module.exports = __toCommonJS(index_exports);
132
132
  // js/vanduo.js
133
133
  (function() {
134
134
  "use strict";
135
- const VANDUO_VERSION = true ? "1.3.5" : "0.0.0-dev";
135
+ const VANDUO_VERSION = true ? "1.3.7" : "0.0.0-dev";
136
136
  const Vanduo2 = {
137
137
  version: VANDUO_VERSION,
138
138
  components: {},
@@ -6694,10 +6694,6 @@ module.exports = __toCommonJS(index_exports);
6694
6694
  next.classList.remove("vd-morph-next");
6695
6695
  next.classList.add("vd-morph-current");
6696
6696
  }
6697
- el.classList.add("morph-done");
6698
- setTimeout(function() {
6699
- el.classList.remove("morph-done");
6700
- }, 350);
6701
6697
  if (typeof onComplete === "function") onComplete();
6702
6698
  }, duration);
6703
6699
  }
@@ -6708,6 +6704,303 @@ module.exports = __toCommonJS(index_exports);
6708
6704
  window.VanduoMorph = Morph;
6709
6705
  })();
6710
6706
 
6707
+ // js/components/expanding-cards.js
6708
+ (function() {
6709
+ "use strict";
6710
+ const ExpandingCards = {
6711
+ instances: /* @__PURE__ */ new Map(),
6712
+ init: function() {
6713
+ document.querySelectorAll(".vd-expanding-cards").forEach(function(el) {
6714
+ if (el.getAttribute("data-vd-expanding-cards") === "manual") return;
6715
+ if (ExpandingCards.instances.has(el)) return;
6716
+ ExpandingCards.initContainer(el);
6717
+ });
6718
+ },
6719
+ initContainer: function(container) {
6720
+ const cleanup = [];
6721
+ const getCards = function() {
6722
+ return Array.prototype.slice.call(container.querySelectorAll(".vd-expanding-card"));
6723
+ };
6724
+ const setActive = function(card) {
6725
+ const cards = getCards();
6726
+ if (!card || cards.indexOf(card) === -1) return;
6727
+ cards.forEach(function(c) {
6728
+ c.classList.toggle("is-active", c === card);
6729
+ });
6730
+ card.focus({ preventScroll: true });
6731
+ };
6732
+ const onClick = function(e) {
6733
+ const t = e.target;
6734
+ const card = t.closest ? t.closest(".vd-expanding-card") : null;
6735
+ if (!card || !container.contains(card)) return;
6736
+ setActive(card);
6737
+ };
6738
+ const onKeydown = function(e) {
6739
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight" && e.key !== "Home" && e.key !== "End") {
6740
+ return;
6741
+ }
6742
+ const cards = getCards().filter(function(c) {
6743
+ return c.offsetParent !== null || c.getClientRects().length > 0;
6744
+ });
6745
+ if (!cards.length) return;
6746
+ const activeEl = document.activeElement;
6747
+ let idx = cards.indexOf(activeEl);
6748
+ if (idx < 0) {
6749
+ idx = cards.findIndex(function(c) {
6750
+ return c.classList.contains("is-active");
6751
+ });
6752
+ }
6753
+ if (idx < 0) idx = 0;
6754
+ if (e.key === "ArrowLeft") {
6755
+ e.preventDefault();
6756
+ setActive(cards[Math.max(0, idx - 1)]);
6757
+ } else if (e.key === "ArrowRight") {
6758
+ e.preventDefault();
6759
+ setActive(cards[Math.min(cards.length - 1, idx + 1)]);
6760
+ } else if (e.key === "Home") {
6761
+ e.preventDefault();
6762
+ setActive(cards[0]);
6763
+ } else if (e.key === "End") {
6764
+ e.preventDefault();
6765
+ setActive(cards[cards.length - 1]);
6766
+ }
6767
+ };
6768
+ container.addEventListener("click", onClick);
6769
+ cleanup.push(function() {
6770
+ container.removeEventListener("click", onClick);
6771
+ });
6772
+ container.addEventListener("keydown", onKeydown);
6773
+ cleanup.push(function() {
6774
+ container.removeEventListener("keydown", onKeydown);
6775
+ });
6776
+ getCards().forEach(function(card) {
6777
+ if (!card.hasAttribute("tabindex")) {
6778
+ card.setAttribute("tabindex", "0");
6779
+ }
6780
+ card.setAttribute("role", "button");
6781
+ if (!card.hasAttribute("aria-pressed")) {
6782
+ card.setAttribute("aria-pressed", card.classList.contains("is-active") ? "true" : "false");
6783
+ }
6784
+ });
6785
+ const syncAria = function() {
6786
+ getCards().forEach(function(card) {
6787
+ card.setAttribute("aria-pressed", card.classList.contains("is-active") ? "true" : "false");
6788
+ });
6789
+ };
6790
+ const mo = new MutationObserver(syncAria);
6791
+ mo.observe(container, { attributes: true, subtree: true, attributeFilter: ["class"] });
6792
+ cleanup.push(function() {
6793
+ mo.disconnect();
6794
+ });
6795
+ syncAria();
6796
+ ExpandingCards.instances.set(container, { cleanup });
6797
+ },
6798
+ destroy: function(container) {
6799
+ const inst = this.instances.get(container);
6800
+ if (!inst) return;
6801
+ inst.cleanup.forEach(function(fn) {
6802
+ fn();
6803
+ });
6804
+ this.instances.delete(container);
6805
+ },
6806
+ destroyAll: function() {
6807
+ this.instances.forEach(function(_, el) {
6808
+ ExpandingCards.destroy(el);
6809
+ });
6810
+ }
6811
+ };
6812
+ if (typeof window.Vanduo !== "undefined") {
6813
+ window.Vanduo.register("expandingCards", ExpandingCards);
6814
+ }
6815
+ window.VanduoExpandingCards = ExpandingCards;
6816
+ })();
6817
+
6818
+ // js/components/timeline.js
6819
+ (function() {
6820
+ "use strict";
6821
+ const STAGGER_MS = 140;
6822
+ const MAX_STAGGER_INDEX = 7;
6823
+ const PLAY_INTERVAL_MS = 800;
6824
+ function countRevealedPrefix(items) {
6825
+ let count = 0;
6826
+ for (let i = 0; i < items.length; i++) {
6827
+ if (!items[i].classList.contains("is-revealed")) break;
6828
+ count++;
6829
+ }
6830
+ return count;
6831
+ }
6832
+ function findPlaybackControls(container) {
6833
+ return container.parentElement || document.body;
6834
+ }
6835
+ function initPlayback(container, items, cleanup) {
6836
+ items.forEach(function(item) {
6837
+ item.classList.remove("is-revealed");
6838
+ });
6839
+ const scope = findPlaybackControls(container);
6840
+ const prevBtn = scope.querySelector("[data-vd-timeline-prev]");
6841
+ const nextBtn = scope.querySelector("[data-vd-timeline-next]");
6842
+ const playBtn = scope.querySelector("[data-vd-timeline-play]");
6843
+ const pauseBtn = scope.querySelector("[data-vd-timeline-pause]");
6844
+ let playTimer = null;
6845
+ function updateNavButtons() {
6846
+ const k = countRevealedPrefix(items);
6847
+ const n = items.length;
6848
+ if (prevBtn) {
6849
+ const atStart = k === 0;
6850
+ prevBtn.disabled = atStart;
6851
+ prevBtn.setAttribute("aria-disabled", atStart ? "true" : "false");
6852
+ }
6853
+ if (nextBtn) {
6854
+ const atEnd = k >= n;
6855
+ nextBtn.disabled = atEnd;
6856
+ nextBtn.setAttribute("aria-disabled", atEnd ? "true" : "false");
6857
+ }
6858
+ if (playBtn) {
6859
+ playBtn.setAttribute("aria-pressed", playTimer ? "true" : "false");
6860
+ }
6861
+ if (pauseBtn) {
6862
+ pauseBtn.disabled = !playTimer;
6863
+ }
6864
+ }
6865
+ function stepNext() {
6866
+ const k = countRevealedPrefix(items);
6867
+ if (k < items.length) {
6868
+ items[k].classList.add("is-revealed");
6869
+ }
6870
+ updateNavButtons();
6871
+ }
6872
+ function stepPrev() {
6873
+ const k = countRevealedPrefix(items);
6874
+ if (k > 0) {
6875
+ items[k - 1].classList.remove("is-revealed");
6876
+ }
6877
+ updateNavButtons();
6878
+ }
6879
+ function play() {
6880
+ if (playTimer) return;
6881
+ playTimer = setInterval(function() {
6882
+ if (countRevealedPrefix(items) >= items.length) {
6883
+ pause();
6884
+ return;
6885
+ }
6886
+ stepNext();
6887
+ }, PLAY_INTERVAL_MS);
6888
+ updateNavButtons();
6889
+ }
6890
+ function pause() {
6891
+ if (playTimer) {
6892
+ clearInterval(playTimer);
6893
+ playTimer = null;
6894
+ }
6895
+ updateNavButtons();
6896
+ }
6897
+ function addClick(el, fn) {
6898
+ if (!el) return;
6899
+ const handler = function(e) {
6900
+ e.preventDefault();
6901
+ fn();
6902
+ };
6903
+ el.addEventListener("click", handler);
6904
+ cleanup.push(function() {
6905
+ el.removeEventListener("click", handler);
6906
+ });
6907
+ }
6908
+ addClick(prevBtn, stepPrev);
6909
+ addClick(nextBtn, stepNext);
6910
+ addClick(playBtn, play);
6911
+ addClick(pauseBtn, pause);
6912
+ cleanup.push(function() {
6913
+ pause();
6914
+ });
6915
+ updateNavButtons();
6916
+ return {
6917
+ stepNext,
6918
+ stepPrev,
6919
+ play,
6920
+ pause
6921
+ };
6922
+ }
6923
+ const Timeline = {
6924
+ instances: /* @__PURE__ */ new Map(),
6925
+ init: function() {
6926
+ document.querySelectorAll(".vd-timeline.vd-timeline-animated").forEach(function(el) {
6927
+ if (Timeline.instances.has(el)) return;
6928
+ Timeline.initInstance(el);
6929
+ });
6930
+ },
6931
+ reinit: function() {
6932
+ Timeline.destroyAll();
6933
+ Timeline.init();
6934
+ },
6935
+ initInstance: function(container) {
6936
+ const cleanup = [];
6937
+ const items = Array.prototype.filter.call(container.children, function(child) {
6938
+ return child.classList && child.classList.contains("vd-timeline-item");
6939
+ });
6940
+ items.forEach(function(item, i) {
6941
+ const idx = Math.min(i, MAX_STAGGER_INDEX);
6942
+ item.style.setProperty("--vd-timeline-reveal-delay", idx * STAGGER_MS + "ms");
6943
+ });
6944
+ const reducedMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
6945
+ if (reducedMotion) {
6946
+ items.forEach(function(item) {
6947
+ item.classList.add("is-revealed");
6948
+ });
6949
+ Timeline.instances.set(container, { cleanup });
6950
+ return;
6951
+ }
6952
+ const playback = container.classList && container.classList.contains("vd-timeline-playback");
6953
+ if (playback) {
6954
+ const playbackApi = initPlayback(container, items, cleanup);
6955
+ Timeline.instances.set(container, { cleanup, playback: playbackApi });
6956
+ return;
6957
+ }
6958
+ if (typeof IntersectionObserver === "undefined") {
6959
+ items.forEach(function(item) {
6960
+ item.classList.add("is-revealed");
6961
+ });
6962
+ Timeline.instances.set(container, { cleanup });
6963
+ return;
6964
+ }
6965
+ const observer = new IntersectionObserver(function(entries) {
6966
+ entries.forEach(function(entry) {
6967
+ if (!entry.isIntersecting) return;
6968
+ entry.target.classList.add("is-revealed");
6969
+ observer.unobserve(entry.target);
6970
+ });
6971
+ }, {
6972
+ root: null,
6973
+ rootMargin: "0px 0px -10% 0px",
6974
+ threshold: 0.15
6975
+ });
6976
+ items.forEach(function(item) {
6977
+ observer.observe(item);
6978
+ });
6979
+ cleanup.push(function() {
6980
+ observer.disconnect();
6981
+ });
6982
+ Timeline.instances.set(container, { cleanup });
6983
+ },
6984
+ destroy: function(container) {
6985
+ const inst = this.instances.get(container);
6986
+ if (!inst) return;
6987
+ inst.cleanup.forEach(function(fn) {
6988
+ fn();
6989
+ });
6990
+ this.instances.delete(container);
6991
+ },
6992
+ destroyAll: function() {
6993
+ this.instances.forEach(function(_, el) {
6994
+ Timeline.destroy(el);
6995
+ });
6996
+ }
6997
+ };
6998
+ if (typeof window.Vanduo !== "undefined") {
6999
+ window.Vanduo.register("timeline", Timeline);
7000
+ }
7001
+ window.VanduoTimeline = Timeline;
7002
+ })();
7003
+
6711
7004
  // js/components/flow.js
6712
7005
  (function() {
6713
7006
  "use strict";
@@ -7724,6 +8017,115 @@ module.exports = __toCommonJS(index_exports);
7724
8017
  "use strict";
7725
8018
  const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
7726
8019
  const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
8020
+ function escapeRegexChar(c) {
8021
+ return c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8022
+ }
8023
+ function buildParseFormat(format) {
8024
+ let regex = "^";
8025
+ const order = [];
8026
+ let i = 0;
8027
+ while (i < format.length) {
8028
+ const slice = format.slice(i);
8029
+ if (slice.toLowerCase().startsWith("yyyy")) {
8030
+ regex += "(\\d{4})";
8031
+ order.push("y");
8032
+ i += 4;
8033
+ } else if (slice.toLowerCase().startsWith("mm")) {
8034
+ regex += "(\\d{2})";
8035
+ order.push("m");
8036
+ i += 2;
8037
+ } else if (slice.toLowerCase().startsWith("dd")) {
8038
+ regex += "(\\d{2})";
8039
+ order.push("d");
8040
+ i += 2;
8041
+ } else {
8042
+ regex += escapeRegexChar(format[i]);
8043
+ i++;
8044
+ }
8045
+ }
8046
+ regex += "$";
8047
+ return { regex: new RegExp(regex), order };
8048
+ }
8049
+ function parseDateFromFormat(value, format) {
8050
+ if (!value || !format) return null;
8051
+ const { regex, order } = buildParseFormat(format);
8052
+ const m = value.trim().match(regex);
8053
+ if (!m) return null;
8054
+ let y;
8055
+ let mo;
8056
+ let d;
8057
+ let ci = 1;
8058
+ for (let k = 0; k < order.length; k++) {
8059
+ const part = order[k];
8060
+ const v = parseInt(m[ci++], 10);
8061
+ if (Number.isNaN(v)) return null;
8062
+ if (part === "y") y = v;
8063
+ else if (part === "m") mo = v - 1;
8064
+ else if (part === "d") d = v;
8065
+ }
8066
+ if (y === void 0 || mo === void 0 || d === void 0) return null;
8067
+ const dt = new Date(y, mo, d);
8068
+ if (dt.getFullYear() !== y || dt.getMonth() !== mo || dt.getDate() !== d) return null;
8069
+ return dt;
8070
+ }
8071
+ function formatDate(d, format) {
8072
+ const yyyy = String(d.getFullYear());
8073
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
8074
+ const dd = String(d.getDate()).padStart(2, "0");
8075
+ let out = "";
8076
+ let i = 0;
8077
+ while (i < format.length) {
8078
+ const slice = format.slice(i);
8079
+ if (slice.toLowerCase().startsWith("yyyy")) {
8080
+ out += yyyy;
8081
+ i += 4;
8082
+ } else if (slice.toLowerCase().startsWith("mm")) {
8083
+ out += mm;
8084
+ i += 2;
8085
+ } else if (slice.toLowerCase().startsWith("dd")) {
8086
+ out += dd;
8087
+ i += 2;
8088
+ } else {
8089
+ out += format[i];
8090
+ i++;
8091
+ }
8092
+ }
8093
+ return out;
8094
+ }
8095
+ function dateKey(d) {
8096
+ return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
8097
+ }
8098
+ function addDays(d, n) {
8099
+ const x = new Date(d.getFullYear(), d.getMonth(), d.getDate());
8100
+ x.setDate(x.getDate() + n);
8101
+ return x;
8102
+ }
8103
+ function addMonthsClamped(d, n) {
8104
+ return new Date(d.getFullYear(), d.getMonth() + n, d.getDate());
8105
+ }
8106
+ function parseYmdLocal(ymd) {
8107
+ if (!ymd || typeof ymd !== "string") return null;
8108
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd.trim());
8109
+ if (!m) return null;
8110
+ const y = +m[1];
8111
+ const mo = +m[2] - 1;
8112
+ const day = +m[3];
8113
+ const dt = new Date(y, mo, day);
8114
+ if (dt.getFullYear() !== y || dt.getMonth() !== mo || dt.getDate() !== day) return null;
8115
+ return dt;
8116
+ }
8117
+ function startOfWeekSunday(d) {
8118
+ const x = new Date(d.getFullYear(), d.getMonth(), d.getDate());
8119
+ const day = x.getDay();
8120
+ x.setDate(x.getDate() - day);
8121
+ return x;
8122
+ }
8123
+ function endOfWeekSunday(d) {
8124
+ const x = new Date(d.getFullYear(), d.getMonth(), d.getDate());
8125
+ const day = x.getDay();
8126
+ x.setDate(x.getDate() + (6 - day));
8127
+ return x;
8128
+ }
7727
8129
  const Datepicker = {
7728
8130
  instances: /* @__PURE__ */ new Map(),
7729
8131
  init: function() {
@@ -7735,28 +8137,70 @@ module.exports = __toCommonJS(index_exports);
7735
8137
  },
7736
8138
  initInstance: function(input) {
7737
8139
  const cleanup = [];
7738
- const format = input.getAttribute("data-vd-datepicker-format") || "yyyy-mm-dd";
8140
+ const format = input.getAttribute("data-vd-datepicker-format") || "YYYY-MM-DD";
7739
8141
  const minStr = input.getAttribute("data-vd-datepicker-min");
7740
8142
  const maxStr = input.getAttribute("data-vd-datepicker-max");
7741
- const minDate = minStr ? new Date(minStr) : null;
7742
- const maxDate = maxStr ? new Date(maxStr) : null;
8143
+ const minDate = minStr ? parseYmdLocal(minStr) : null;
8144
+ const maxDate = maxStr ? parseYmdLocal(maxStr) : null;
7743
8145
  const today = /* @__PURE__ */ new Date();
7744
8146
  let viewYear = today.getFullYear();
7745
8147
  let viewMonth = today.getMonth();
7746
8148
  let selectedDate = null;
7747
8149
  let viewMode = "days";
8150
+ let focusedDate = null;
8151
+ let skipNextFocusOpen = false;
8152
+ const isDisabled = (d) => {
8153
+ if (minDate) {
8154
+ const t = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
8155
+ if (t < minDate.getTime()) return true;
8156
+ }
8157
+ if (maxDate) {
8158
+ const t = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
8159
+ if (t > maxDate.getTime()) return true;
8160
+ }
8161
+ return false;
8162
+ };
8163
+ const ensureMonthInRange = (y, m) => {
8164
+ if (!minDate && !maxDate) return { y, m };
8165
+ const first = new Date(y, m, 1);
8166
+ const last = new Date(y, m + 1, 0);
8167
+ if (minDate && last.getTime() < minDate.getTime()) {
8168
+ return { y: minDate.getFullYear(), m: minDate.getMonth() };
8169
+ }
8170
+ if (maxDate && first.getTime() > maxDate.getTime()) {
8171
+ return { y: maxDate.getFullYear(), m: maxDate.getMonth() };
8172
+ }
8173
+ return { y, m };
8174
+ };
8175
+ const firstSelectableInMonth = (y, m) => {
8176
+ const last = new Date(y, m + 1, 0).getDate();
8177
+ for (let day = 1; day <= last; day++) {
8178
+ const dt = new Date(y, m, day);
8179
+ if (!isDisabled(dt)) return dt;
8180
+ }
8181
+ return new Date(y, m, 1);
8182
+ };
7748
8183
  if (input.value) {
7749
- const parsed = new Date(input.value);
7750
- if (!isNaN(parsed.getTime())) {
8184
+ const trimmed = input.value.trim();
8185
+ let parsed = parseDateFromFormat(trimmed, format);
8186
+ if (!parsed) {
8187
+ const fallback = new Date(trimmed);
8188
+ if (!isNaN(fallback.getTime())) parsed = fallback;
8189
+ }
8190
+ if (parsed) {
7751
8191
  selectedDate = parsed;
7752
8192
  viewYear = parsed.getFullYear();
7753
8193
  viewMonth = parsed.getMonth();
7754
8194
  }
7755
8195
  }
8196
+ const clampedInit = ensureMonthInRange(viewYear, viewMonth);
8197
+ viewYear = clampedInit.y;
8198
+ viewMonth = clampedInit.m;
7756
8199
  const popup = document.createElement("div");
7757
8200
  popup.className = "vd-datepicker-popup";
7758
8201
  popup.setAttribute("role", "dialog");
7759
8202
  popup.setAttribute("aria-label", "Choose date");
8203
+ popup.tabIndex = -1;
7760
8204
  const wrapper = document.createElement("div");
7761
8205
  wrapper.className = "vd-suggest-wrapper";
7762
8206
  wrapper.style.position = "relative";
@@ -7764,18 +8208,80 @@ module.exports = __toCommonJS(index_exports);
7764
8208
  input.parentNode.insertBefore(wrapper, input);
7765
8209
  wrapper.appendChild(input);
7766
8210
  wrapper.appendChild(popup);
7767
- const formatDate = (d) => {
7768
- const yyyy = d.getFullYear();
7769
- const mm = String(d.getMonth() + 1).padStart(2, "0");
7770
- const dd = String(d.getDate()).padStart(2, "0");
7771
- return format.replace("yyyy", yyyy).replace("mm", mm).replace("dd", dd);
8211
+ const isSameDay = (a, b) => a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
8212
+ const selectDate = (date) => {
8213
+ selectedDate = date;
8214
+ viewYear = date.getFullYear();
8215
+ viewMonth = date.getMonth();
8216
+ input.value = formatDate(date, format);
8217
+ skipNextFocusOpen = true;
8218
+ close();
8219
+ input.dispatchEvent(new CustomEvent("datepicker:select", {
8220
+ detail: { date, formatted: input.value },
8221
+ bubbles: true
8222
+ }));
8223
+ input.dispatchEvent(new Event("change", { bubbles: true }));
8224
+ input.focus();
7772
8225
  };
7773
- const isDisabled = (d) => {
7774
- if (minDate && d < minDate) return true;
7775
- if (maxDate && d > maxDate) return true;
7776
- return false;
8226
+ const focusFocusedDay = () => {
8227
+ if (viewMode !== "days" || !focusedDate) return;
8228
+ const key = dateKey(focusedDate);
8229
+ const btn = popup.querySelector('[data-vd-date="' + key + '"]');
8230
+ if (btn && !btn.classList.contains("is-outside") && btn.getAttribute("aria-disabled") !== "true") {
8231
+ btn.focus();
8232
+ }
8233
+ };
8234
+ const skipDisabled = (d, stepDir, maxSteps) => {
8235
+ let x = new Date(d.getFullYear(), d.getMonth(), d.getDate());
8236
+ const step = stepDir > 0 ? 1 : -1;
8237
+ for (let i = 0; i < maxSteps; i++) {
8238
+ if (!isDisabled(x)) return x;
8239
+ x = addDays(x, step);
8240
+ }
8241
+ return d;
8242
+ };
8243
+ const createDayBtn = (day, outside, date) => {
8244
+ const btn = document.createElement("button");
8245
+ btn.type = "button";
8246
+ btn.className = "vd-datepicker-day";
8247
+ btn.textContent = day;
8248
+ btn.setAttribute("role", "gridcell");
8249
+ if (outside) {
8250
+ btn.classList.add("is-outside");
8251
+ btn.tabIndex = -1;
8252
+ btn.setAttribute("aria-disabled", "true");
8253
+ return btn;
8254
+ }
8255
+ btn.setAttribute("data-vd-date", dateKey(date));
8256
+ if (date && isSameDay(date, today)) btn.classList.add("is-today");
8257
+ if (date && isSameDay(date, selectedDate)) btn.classList.add("is-selected");
8258
+ if (date && isDisabled(date)) {
8259
+ btn.classList.add("is-disabled");
8260
+ btn.setAttribute("aria-disabled", "true");
8261
+ btn.tabIndex = -1;
8262
+ return btn;
8263
+ }
8264
+ if (date) {
8265
+ const isFocused = focusedDate && isSameDay(date, focusedDate);
8266
+ btn.tabIndex = isFocused ? 0 : -1;
8267
+ btn.addEventListener("click", () => {
8268
+ selectedDate = date;
8269
+ viewYear = date.getFullYear();
8270
+ viewMonth = date.getMonth();
8271
+ focusedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
8272
+ input.value = formatDate(date, format);
8273
+ skipNextFocusOpen = true;
8274
+ close();
8275
+ input.dispatchEvent(new CustomEvent("datepicker:select", {
8276
+ detail: { date, formatted: input.value },
8277
+ bubbles: true
8278
+ }));
8279
+ input.dispatchEvent(new Event("change", { bubbles: true }));
8280
+ input.focus();
8281
+ });
8282
+ }
8283
+ return btn;
7777
8284
  };
7778
- const isSameDay = (a, b) => a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
7779
8285
  const render = () => {
7780
8286
  popup.innerHTML = "";
7781
8287
  const header = document.createElement("div");
@@ -7845,35 +8351,53 @@ module.exports = __toCommonJS(index_exports);
7845
8351
  header.appendChild(nextBtn);
7846
8352
  popup.appendChild(header);
7847
8353
  if (viewMode === "days") {
8354
+ const gridWrap = document.createElement("div");
8355
+ gridWrap.className = "vd-datepicker-grid";
8356
+ gridWrap.setAttribute("role", "grid");
8357
+ gridWrap.setAttribute("aria-label", "Calendar");
7848
8358
  const weekdays = document.createElement("div");
7849
8359
  weekdays.className = "vd-datepicker-weekdays";
7850
- DAYS.forEach((d) => {
8360
+ weekdays.setAttribute("role", "row");
8361
+ DAYS.forEach(function(d) {
7851
8362
  const span = document.createElement("span");
8363
+ span.setAttribute("role", "columnheader");
8364
+ span.setAttribute("aria-label", d);
7852
8365
  span.textContent = d;
7853
8366
  weekdays.appendChild(span);
7854
8367
  });
7855
- popup.appendChild(weekdays);
7856
- const grid = document.createElement("div");
7857
- grid.className = "vd-datepicker-days";
8368
+ gridWrap.appendChild(weekdays);
7858
8369
  const firstDay = new Date(viewYear, viewMonth, 1).getDay();
7859
8370
  const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
7860
8371
  const daysInPrev = new Date(viewYear, viewMonth, 0).getDate();
8372
+ const cells = [];
7861
8373
  for (let i = firstDay - 1; i >= 0; i--) {
7862
- const btn = createDayBtn(daysInPrev - i, true);
7863
- grid.appendChild(btn);
8374
+ const dayNum = daysInPrev - i;
8375
+ const prevMonth = viewMonth === 0 ? 11 : viewMonth - 1;
8376
+ const prevYear = viewMonth === 0 ? viewYear - 1 : viewYear;
8377
+ const date = new Date(prevYear, prevMonth, dayNum);
8378
+ cells.push({ day: dayNum, outside: true, date });
7864
8379
  }
7865
8380
  for (let d = 1; d <= daysInMonth; d++) {
7866
8381
  const date = new Date(viewYear, viewMonth, d);
7867
- const btn = createDayBtn(d, false, date);
7868
- grid.appendChild(btn);
8382
+ cells.push({ day: d, outside: false, date });
7869
8383
  }
7870
8384
  const totalCells = firstDay + daysInMonth;
7871
8385
  const remaining = totalCells % 7 === 0 ? 0 : 7 - totalCells % 7;
7872
8386
  for (let i = 1; i <= remaining; i++) {
7873
- const btn = createDayBtn(i, true);
7874
- grid.appendChild(btn);
8387
+ const date = new Date(viewYear, viewMonth + 1, i);
8388
+ cells.push({ day: i, outside: true, date });
7875
8389
  }
7876
- popup.appendChild(grid);
8390
+ for (let r = 0; r < cells.length; r += 7) {
8391
+ const row = document.createElement("div");
8392
+ row.className = "vd-datepicker-row";
8393
+ row.setAttribute("role", "row");
8394
+ for (let c = 0; c < 7; c++) {
8395
+ const cell = cells[r + c];
8396
+ row.appendChild(createDayBtn(cell.day, cell.outside, cell.date));
8397
+ }
8398
+ gridWrap.appendChild(row);
8399
+ }
8400
+ popup.appendChild(gridWrap);
7877
8401
  } else if (viewMode === "months") {
7878
8402
  const grid = document.createElement("div");
7879
8403
  grid.className = "vd-datepicker-months";
@@ -7914,65 +8438,125 @@ module.exports = __toCommonJS(index_exports);
7914
8438
  popup.appendChild(grid);
7915
8439
  }
7916
8440
  };
7917
- const createDayBtn = (day, outside, date) => {
7918
- const btn = document.createElement("button");
7919
- btn.type = "button";
7920
- btn.className = "vd-datepicker-day";
7921
- btn.textContent = day;
7922
- if (outside) {
7923
- btn.classList.add("is-outside");
7924
- btn.tabIndex = -1;
7925
- return btn;
8441
+ const handleGridKeydown = (e) => {
8442
+ if (!popup.classList.contains("is-open") || viewMode !== "days") return;
8443
+ const grid = popup.querySelector(".vd-datepicker-grid");
8444
+ if (!grid || !grid.contains(e.target)) return;
8445
+ const key = e.key;
8446
+ if (key !== "ArrowLeft" && key !== "ArrowRight" && key !== "ArrowUp" && key !== "ArrowDown" && key !== "Home" && key !== "End" && key !== "PageUp" && key !== "PageDown" && key !== "Enter" && key !== " " && key !== "Escape") {
8447
+ return;
7926
8448
  }
7927
- if (date && isSameDay(date, today)) btn.classList.add("is-today");
7928
- if (date && isSameDay(date, selectedDate)) btn.classList.add("is-selected");
7929
- if (date && isDisabled(date)) {
7930
- btn.classList.add("is-disabled");
7931
- return btn;
8449
+ if (key === "Escape") {
8450
+ e.preventDefault();
8451
+ e.stopPropagation();
8452
+ skipNextFocusOpen = true;
8453
+ close();
8454
+ input.focus();
8455
+ return;
7932
8456
  }
7933
- if (date) {
7934
- btn.addEventListener("click", () => {
7935
- selectedDate = date;
7936
- viewYear = date.getFullYear();
7937
- viewMonth = date.getMonth();
7938
- input.value = formatDate(date);
7939
- close();
7940
- input.dispatchEvent(new CustomEvent("datepicker:select", {
7941
- detail: { date, formatted: input.value },
7942
- bubbles: true
7943
- }));
7944
- input.dispatchEvent(new Event("change", { bubbles: true }));
7945
- });
8457
+ if (!focusedDate) {
8458
+ focusedDate = firstSelectableInMonth(viewYear, viewMonth);
7946
8459
  }
7947
- return btn;
8460
+ if (key === "Enter" || key === " ") {
8461
+ e.preventDefault();
8462
+ if (focusedDate && !isDisabled(focusedDate)) {
8463
+ selectDate(new Date(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate()));
8464
+ }
8465
+ return;
8466
+ }
8467
+ e.preventDefault();
8468
+ let next = new Date(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate());
8469
+ let skipDir = 1;
8470
+ if (key === "ArrowLeft") {
8471
+ next = addDays(next, -1);
8472
+ skipDir = -1;
8473
+ } else if (key === "ArrowRight") {
8474
+ next = addDays(next, 1);
8475
+ skipDir = 1;
8476
+ } else if (key === "ArrowUp") {
8477
+ next = addDays(next, -7);
8478
+ skipDir = -1;
8479
+ } else if (key === "ArrowDown") {
8480
+ next = addDays(next, 7);
8481
+ skipDir = 1;
8482
+ } else if (key === "Home") {
8483
+ next = startOfWeekSunday(next);
8484
+ skipDir = 1;
8485
+ } else if (key === "End") {
8486
+ next = endOfWeekSunday(next);
8487
+ skipDir = -1;
8488
+ } else if (key === "PageUp") {
8489
+ next = addMonthsClamped(next, -1);
8490
+ skipDir = -1;
8491
+ } else if (key === "PageDown") {
8492
+ next = addMonthsClamped(next, 1);
8493
+ skipDir = 1;
8494
+ }
8495
+ next = skipDisabled(next, skipDir, 400);
8496
+ if (next.getMonth() !== viewMonth || next.getFullYear() !== viewYear) {
8497
+ viewYear = next.getFullYear();
8498
+ viewMonth = next.getMonth();
8499
+ const cl = ensureMonthInRange(viewYear, viewMonth);
8500
+ viewYear = cl.y;
8501
+ viewMonth = cl.m;
8502
+ }
8503
+ focusedDate = next;
8504
+ render();
8505
+ requestAnimationFrame(focusFocusedDay);
7948
8506
  };
7949
8507
  const open = () => {
8508
+ viewMode = "days";
8509
+ if (selectedDate) {
8510
+ viewYear = selectedDate.getFullYear();
8511
+ viewMonth = selectedDate.getMonth();
8512
+ }
8513
+ const cl = ensureMonthInRange(viewYear, viewMonth);
8514
+ viewYear = cl.y;
8515
+ viewMonth = cl.m;
8516
+ if (selectedDate) {
8517
+ focusedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
8518
+ } else {
8519
+ focusedDate = firstSelectableInMonth(viewYear, viewMonth);
8520
+ }
7950
8521
  render();
7951
8522
  popup.classList.add("is-open");
7952
8523
  input.setAttribute("aria-expanded", "true");
8524
+ requestAnimationFrame(focusFocusedDay);
7953
8525
  };
7954
8526
  const close = () => {
7955
8527
  popup.classList.remove("is-open");
7956
8528
  input.setAttribute("aria-expanded", "false");
7957
8529
  viewMode = "days";
7958
8530
  };
7959
- const focusHandler = () => open();
8531
+ const focusHandler = () => {
8532
+ if (skipNextFocusOpen) {
8533
+ skipNextFocusOpen = false;
8534
+ return;
8535
+ }
8536
+ open();
8537
+ };
7960
8538
  const outsideHandler = (e) => {
7961
8539
  if (!wrapper.contains(e.target)) close();
7962
8540
  };
7963
8541
  const escHandler = (e) => {
7964
- if (e.key === "Escape") close();
8542
+ if (e.key === "Escape" && popup.classList.contains("is-open")) {
8543
+ skipNextFocusOpen = true;
8544
+ close();
8545
+ input.focus();
8546
+ }
7965
8547
  };
7966
8548
  input.addEventListener("focus", focusHandler);
7967
8549
  document.addEventListener("click", outsideHandler, true);
7968
8550
  document.addEventListener("keydown", escHandler);
8551
+ popup.addEventListener("keydown", handleGridKeydown);
7969
8552
  input.setAttribute("aria-haspopup", "dialog");
7970
8553
  input.setAttribute("aria-expanded", "false");
7971
8554
  input.setAttribute("autocomplete", "off");
7972
8555
  cleanup.push(
7973
8556
  () => input.removeEventListener("focus", focusHandler),
7974
8557
  () => document.removeEventListener("click", outsideHandler, true),
7975
- () => document.removeEventListener("keydown", escHandler)
8558
+ () => document.removeEventListener("keydown", escHandler),
8559
+ () => popup.removeEventListener("keydown", handleGridKeydown)
7976
8560
  );
7977
8561
  this.instances.set(input, { cleanup, open, close, popup });
7978
8562
  },