@vanduo-oss/framework 1.3.4 → 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.4 | Built: 2026-04-14T21:21:55.517Z | git:73e3db5 | 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.4" : "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: {},
@@ -6597,6 +6597,410 @@ module.exports = __toCommonJS(index_exports);
6597
6597
  window.VanduoGlassScroll = GlassScroll;
6598
6598
  })();
6599
6599
 
6600
+ // js/components/morph.js
6601
+ (function() {
6602
+ "use strict";
6603
+ const MORPH_DURATION_MS = 750;
6604
+ const Morph = {
6605
+ instances: /* @__PURE__ */ new Map(),
6606
+ init: function() {
6607
+ const elements = document.querySelectorAll(".vd-morph, [data-vd-morph]");
6608
+ elements.forEach(function(el) {
6609
+ if (Morph.instances.has(el)) return;
6610
+ if (el.getAttribute("data-vd-morph") === "manual") return;
6611
+ Morph.initInstance(el);
6612
+ });
6613
+ },
6614
+ initInstance: function(el) {
6615
+ Morph._ensureLayers(el);
6616
+ const cleanup = [];
6617
+ let morphing = false;
6618
+ const handleClick = function(e) {
6619
+ if (morphing) return;
6620
+ Morph._runMorph(el, e, function() {
6621
+ morphing = false;
6622
+ });
6623
+ morphing = true;
6624
+ };
6625
+ el.addEventListener("click", handleClick);
6626
+ cleanup.push(function() {
6627
+ el.removeEventListener("click", handleClick);
6628
+ });
6629
+ this.instances.set(el, { cleanup });
6630
+ },
6631
+ morph: function(el) {
6632
+ if (!el) return;
6633
+ if (!this.instances.has(el)) this.initInstance(el);
6634
+ this._runMorph(el, null, null);
6635
+ },
6636
+ destroy: function(el) {
6637
+ const instance = this.instances.get(el);
6638
+ if (!instance) return;
6639
+ instance.cleanup.forEach(function(fn) {
6640
+ fn();
6641
+ });
6642
+ this.instances.delete(el);
6643
+ },
6644
+ destroyAll: function() {
6645
+ this.instances.forEach(function(_, el) {
6646
+ Morph.destroy(el);
6647
+ });
6648
+ },
6649
+ /* ── Internal helpers ── */
6650
+ _ensureLayers: function(el) {
6651
+ if (!el.querySelector(".vd-morph-wave")) {
6652
+ const wave = document.createElement("span");
6653
+ wave.className = "vd-morph-wave";
6654
+ wave.setAttribute("aria-hidden", "true");
6655
+ el.insertBefore(wave, el.firstChild);
6656
+ }
6657
+ if (!el.querySelector(".vd-morph-shine")) {
6658
+ const shine = document.createElement("span");
6659
+ shine.className = "vd-morph-shine";
6660
+ shine.setAttribute("aria-hidden", "true");
6661
+ const waveEl = el.querySelector(".vd-morph-wave");
6662
+ if (waveEl && waveEl.nextSibling) {
6663
+ el.insertBefore(shine, waveEl.nextSibling);
6664
+ } else {
6665
+ el.insertBefore(shine, el.firstChild);
6666
+ }
6667
+ }
6668
+ },
6669
+ _runMorph: function(el, pointerEvent, onComplete) {
6670
+ const wave = el.querySelector(".vd-morph-wave");
6671
+ if (wave) {
6672
+ const rect = el.getBoundingClientRect();
6673
+ const cx = rect.left + rect.width / 2;
6674
+ const cy = rect.top + rect.height / 2;
6675
+ const px = pointerEvent ? pointerEvent.clientX || cx : cx;
6676
+ const py = pointerEvent ? pointerEvent.clientY || cy : cy;
6677
+ wave.style.left = px - rect.left + "px";
6678
+ wave.style.top = py - rect.top + "px";
6679
+ }
6680
+ el.classList.add("is-morphing");
6681
+ let duration = MORPH_DURATION_MS;
6682
+ const custom = getComputedStyle(el).getPropertyValue("--morph-duration");
6683
+ if (custom) {
6684
+ const parsed = parseFloat(custom);
6685
+ if (!isNaN(parsed)) duration = parsed * (custom.indexOf("ms") !== -1 ? 1 : 1e3);
6686
+ }
6687
+ setTimeout(function() {
6688
+ el.classList.remove("is-morphing");
6689
+ const current = el.querySelector(".vd-morph-current");
6690
+ const next = el.querySelector(".vd-morph-next");
6691
+ if (current && next) {
6692
+ current.classList.remove("vd-morph-current");
6693
+ current.classList.add("vd-morph-next");
6694
+ next.classList.remove("vd-morph-next");
6695
+ next.classList.add("vd-morph-current");
6696
+ }
6697
+ if (typeof onComplete === "function") onComplete();
6698
+ }, duration);
6699
+ }
6700
+ };
6701
+ if (typeof window.Vanduo !== "undefined") {
6702
+ window.Vanduo.register("morph", Morph);
6703
+ }
6704
+ window.VanduoMorph = Morph;
6705
+ })();
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
+
6600
7004
  // js/components/flow.js
6601
7005
  (function() {
6602
7006
  "use strict";
@@ -7613,6 +8017,115 @@ module.exports = __toCommonJS(index_exports);
7613
8017
  "use strict";
7614
8018
  const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
7615
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
+ }
7616
8129
  const Datepicker = {
7617
8130
  instances: /* @__PURE__ */ new Map(),
7618
8131
  init: function() {
@@ -7624,28 +8137,70 @@ module.exports = __toCommonJS(index_exports);
7624
8137
  },
7625
8138
  initInstance: function(input) {
7626
8139
  const cleanup = [];
7627
- const format = input.getAttribute("data-vd-datepicker-format") || "yyyy-mm-dd";
8140
+ const format = input.getAttribute("data-vd-datepicker-format") || "YYYY-MM-DD";
7628
8141
  const minStr = input.getAttribute("data-vd-datepicker-min");
7629
8142
  const maxStr = input.getAttribute("data-vd-datepicker-max");
7630
- const minDate = minStr ? new Date(minStr) : null;
7631
- const maxDate = maxStr ? new Date(maxStr) : null;
8143
+ const minDate = minStr ? parseYmdLocal(minStr) : null;
8144
+ const maxDate = maxStr ? parseYmdLocal(maxStr) : null;
7632
8145
  const today = /* @__PURE__ */ new Date();
7633
8146
  let viewYear = today.getFullYear();
7634
8147
  let viewMonth = today.getMonth();
7635
8148
  let selectedDate = null;
7636
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
+ };
7637
8183
  if (input.value) {
7638
- const parsed = new Date(input.value);
7639
- 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) {
7640
8191
  selectedDate = parsed;
7641
8192
  viewYear = parsed.getFullYear();
7642
8193
  viewMonth = parsed.getMonth();
7643
8194
  }
7644
8195
  }
8196
+ const clampedInit = ensureMonthInRange(viewYear, viewMonth);
8197
+ viewYear = clampedInit.y;
8198
+ viewMonth = clampedInit.m;
7645
8199
  const popup = document.createElement("div");
7646
8200
  popup.className = "vd-datepicker-popup";
7647
8201
  popup.setAttribute("role", "dialog");
7648
8202
  popup.setAttribute("aria-label", "Choose date");
8203
+ popup.tabIndex = -1;
7649
8204
  const wrapper = document.createElement("div");
7650
8205
  wrapper.className = "vd-suggest-wrapper";
7651
8206
  wrapper.style.position = "relative";
@@ -7653,18 +8208,80 @@ module.exports = __toCommonJS(index_exports);
7653
8208
  input.parentNode.insertBefore(wrapper, input);
7654
8209
  wrapper.appendChild(input);
7655
8210
  wrapper.appendChild(popup);
7656
- const formatDate = (d) => {
7657
- const yyyy = d.getFullYear();
7658
- const mm = String(d.getMonth() + 1).padStart(2, "0");
7659
- const dd = String(d.getDate()).padStart(2, "0");
7660
- 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();
7661
8225
  };
7662
- const isDisabled = (d) => {
7663
- if (minDate && d < minDate) return true;
7664
- if (maxDate && d > maxDate) return true;
7665
- 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;
7666
8284
  };
7667
- const isSameDay = (a, b) => a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
7668
8285
  const render = () => {
7669
8286
  popup.innerHTML = "";
7670
8287
  const header = document.createElement("div");
@@ -7734,35 +8351,53 @@ module.exports = __toCommonJS(index_exports);
7734
8351
  header.appendChild(nextBtn);
7735
8352
  popup.appendChild(header);
7736
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");
7737
8358
  const weekdays = document.createElement("div");
7738
8359
  weekdays.className = "vd-datepicker-weekdays";
7739
- DAYS.forEach((d) => {
8360
+ weekdays.setAttribute("role", "row");
8361
+ DAYS.forEach(function(d) {
7740
8362
  const span = document.createElement("span");
8363
+ span.setAttribute("role", "columnheader");
8364
+ span.setAttribute("aria-label", d);
7741
8365
  span.textContent = d;
7742
8366
  weekdays.appendChild(span);
7743
8367
  });
7744
- popup.appendChild(weekdays);
7745
- const grid = document.createElement("div");
7746
- grid.className = "vd-datepicker-days";
8368
+ gridWrap.appendChild(weekdays);
7747
8369
  const firstDay = new Date(viewYear, viewMonth, 1).getDay();
7748
8370
  const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
7749
8371
  const daysInPrev = new Date(viewYear, viewMonth, 0).getDate();
8372
+ const cells = [];
7750
8373
  for (let i = firstDay - 1; i >= 0; i--) {
7751
- const btn = createDayBtn(daysInPrev - i, true);
7752
- 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 });
7753
8379
  }
7754
8380
  for (let d = 1; d <= daysInMonth; d++) {
7755
8381
  const date = new Date(viewYear, viewMonth, d);
7756
- const btn = createDayBtn(d, false, date);
7757
- grid.appendChild(btn);
8382
+ cells.push({ day: d, outside: false, date });
7758
8383
  }
7759
8384
  const totalCells = firstDay + daysInMonth;
7760
8385
  const remaining = totalCells % 7 === 0 ? 0 : 7 - totalCells % 7;
7761
8386
  for (let i = 1; i <= remaining; i++) {
7762
- const btn = createDayBtn(i, true);
7763
- grid.appendChild(btn);
8387
+ const date = new Date(viewYear, viewMonth + 1, i);
8388
+ cells.push({ day: i, outside: true, date });
7764
8389
  }
7765
- 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);
7766
8401
  } else if (viewMode === "months") {
7767
8402
  const grid = document.createElement("div");
7768
8403
  grid.className = "vd-datepicker-months";
@@ -7803,65 +8438,125 @@ module.exports = __toCommonJS(index_exports);
7803
8438
  popup.appendChild(grid);
7804
8439
  }
7805
8440
  };
7806
- const createDayBtn = (day, outside, date) => {
7807
- const btn = document.createElement("button");
7808
- btn.type = "button";
7809
- btn.className = "vd-datepicker-day";
7810
- btn.textContent = day;
7811
- if (outside) {
7812
- btn.classList.add("is-outside");
7813
- btn.tabIndex = -1;
7814
- 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;
7815
8448
  }
7816
- if (date && isSameDay(date, today)) btn.classList.add("is-today");
7817
- if (date && isSameDay(date, selectedDate)) btn.classList.add("is-selected");
7818
- if (date && isDisabled(date)) {
7819
- btn.classList.add("is-disabled");
7820
- return btn;
8449
+ if (key === "Escape") {
8450
+ e.preventDefault();
8451
+ e.stopPropagation();
8452
+ skipNextFocusOpen = true;
8453
+ close();
8454
+ input.focus();
8455
+ return;
7821
8456
  }
7822
- if (date) {
7823
- btn.addEventListener("click", () => {
7824
- selectedDate = date;
7825
- viewYear = date.getFullYear();
7826
- viewMonth = date.getMonth();
7827
- input.value = formatDate(date);
7828
- close();
7829
- input.dispatchEvent(new CustomEvent("datepicker:select", {
7830
- detail: { date, formatted: input.value },
7831
- bubbles: true
7832
- }));
7833
- input.dispatchEvent(new Event("change", { bubbles: true }));
7834
- });
8457
+ if (!focusedDate) {
8458
+ focusedDate = firstSelectableInMonth(viewYear, viewMonth);
7835
8459
  }
7836
- 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);
7837
8506
  };
7838
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
+ }
7839
8521
  render();
7840
8522
  popup.classList.add("is-open");
7841
8523
  input.setAttribute("aria-expanded", "true");
8524
+ requestAnimationFrame(focusFocusedDay);
7842
8525
  };
7843
8526
  const close = () => {
7844
8527
  popup.classList.remove("is-open");
7845
8528
  input.setAttribute("aria-expanded", "false");
7846
8529
  viewMode = "days";
7847
8530
  };
7848
- const focusHandler = () => open();
8531
+ const focusHandler = () => {
8532
+ if (skipNextFocusOpen) {
8533
+ skipNextFocusOpen = false;
8534
+ return;
8535
+ }
8536
+ open();
8537
+ };
7849
8538
  const outsideHandler = (e) => {
7850
8539
  if (!wrapper.contains(e.target)) close();
7851
8540
  };
7852
8541
  const escHandler = (e) => {
7853
- if (e.key === "Escape") close();
8542
+ if (e.key === "Escape" && popup.classList.contains("is-open")) {
8543
+ skipNextFocusOpen = true;
8544
+ close();
8545
+ input.focus();
8546
+ }
7854
8547
  };
7855
8548
  input.addEventListener("focus", focusHandler);
7856
8549
  document.addEventListener("click", outsideHandler, true);
7857
8550
  document.addEventListener("keydown", escHandler);
8551
+ popup.addEventListener("keydown", handleGridKeydown);
7858
8552
  input.setAttribute("aria-haspopup", "dialog");
7859
8553
  input.setAttribute("aria-expanded", "false");
7860
8554
  input.setAttribute("autocomplete", "off");
7861
8555
  cleanup.push(
7862
8556
  () => input.removeEventListener("focus", focusHandler),
7863
8557
  () => document.removeEventListener("click", outsideHandler, true),
7864
- () => document.removeEventListener("keydown", escHandler)
8558
+ () => document.removeEventListener("keydown", escHandler),
8559
+ () => popup.removeEventListener("keydown", handleGridKeydown)
7865
8560
  );
7866
8561
  this.instances.set(input, { cleanup, open, close, popup });
7867
8562
  },