@vanduo-oss/framework 1.2.6 → 1.2.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.
Files changed (52) hide show
  1. package/README.md +31 -5
  2. package/css/components/affix.css +53 -0
  3. package/css/components/bubble.css +165 -0
  4. package/css/components/datepicker.css +216 -0
  5. package/css/components/fab.css +225 -0
  6. package/css/components/flow.css +265 -0
  7. package/css/components/rating.css +112 -0
  8. package/css/components/ripple.css +63 -0
  9. package/css/components/sidenav.css +70 -0
  10. package/css/components/spotlight.css +119 -0
  11. package/css/components/stepper.css +176 -0
  12. package/css/components/suggest.css +119 -0
  13. package/css/components/timeline.css +201 -0
  14. package/css/components/timepicker.css +80 -0
  15. package/css/components/transfer.css +165 -0
  16. package/css/components/tree.css +173 -0
  17. package/css/components/waypoint.css +59 -0
  18. package/css/vanduo.css +17 -0
  19. package/dist/build-info.json +3 -3
  20. package/dist/vanduo.cjs.js +2152 -4
  21. package/dist/vanduo.cjs.js.map +4 -4
  22. package/dist/vanduo.cjs.min.js +5 -5
  23. package/dist/vanduo.cjs.min.js.map +4 -4
  24. package/dist/vanduo.css +1943 -1
  25. package/dist/vanduo.css.map +1 -1
  26. package/dist/vanduo.esm.js +2152 -4
  27. package/dist/vanduo.esm.js.map +4 -4
  28. package/dist/vanduo.esm.min.js +5 -5
  29. package/dist/vanduo.esm.min.js.map +4 -4
  30. package/dist/vanduo.js +2152 -4
  31. package/dist/vanduo.js.map +4 -4
  32. package/dist/vanduo.min.css +2 -2
  33. package/dist/vanduo.min.css.map +1 -1
  34. package/dist/vanduo.min.js +5 -5
  35. package/dist/vanduo.min.js.map +4 -4
  36. package/js/components/affix.js +129 -0
  37. package/js/components/bubble.js +203 -0
  38. package/js/components/datepicker.js +287 -0
  39. package/js/components/flow.js +264 -0
  40. package/js/components/rating.js +160 -0
  41. package/js/components/ripple.js +74 -0
  42. package/js/components/sidenav.js +9 -2
  43. package/js/components/spotlight.js +295 -0
  44. package/js/components/stepper.js +97 -0
  45. package/js/components/suggest.js +219 -0
  46. package/js/components/timepicker.js +142 -0
  47. package/js/components/transfer.js +206 -0
  48. package/js/components/tree.js +191 -0
  49. package/js/components/validate.js +185 -0
  50. package/js/components/waypoint.js +120 -0
  51. package/js/index.js +16 -0
  52. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- /*! Vanduo v1.2.6 | Built: 2026-03-11T06:15:19.191Z | git:3283a0d | development */
1
+ /*! Vanduo v1.2.7 | Built: 2026-03-12T16:17:38.253Z | git:2c1277a | 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.2.6" : "0.0.0-dev";
135
+ const VANDUO_VERSION = true ? "1.2.7" : "0.0.0-dev";
136
136
  const Vanduo2 = {
137
137
  version: VANDUO_VERSION,
138
138
  components: {},
@@ -3253,7 +3253,7 @@ module.exports = __toCommonJS(index_exports);
3253
3253
  * Initialize sidenav components
3254
3254
  */
3255
3255
  init: function() {
3256
- const sidenavs = document.querySelectorAll(".vd-sidenav");
3256
+ const sidenavs = document.querySelectorAll(".vd-sidenav, .vd-offcanvas");
3257
3257
  sidenavs.forEach((sidenav) => {
3258
3258
  if (this.sidenavs.has(sidenav)) {
3259
3259
  return;
@@ -3287,8 +3287,13 @@ module.exports = __toCommonJS(index_exports);
3287
3287
  * @param {HTMLElement} sidenav - Sidenav element
3288
3288
  */
3289
3289
  initSidenav: function(sidenav) {
3290
+ const position = sidenav.getAttribute("data-vd-position");
3291
+ if (position) {
3292
+ const prefix = sidenav.classList.contains("vd-offcanvas") ? "vd-offcanvas" : "vd-sidenav";
3293
+ sidenav.classList.add(prefix + "-" + position);
3294
+ }
3290
3295
  const overlay = this.createOverlay(sidenav);
3291
- const closeButton = sidenav.querySelector(".vd-sidenav-close");
3296
+ const closeButton = sidenav.querySelector(".vd-sidenav-close, .vd-offcanvas-close");
3292
3297
  const cleanupFunctions = [];
3293
3298
  sidenav.setAttribute("role", "navigation");
3294
3299
  sidenav.setAttribute("aria-hidden", "true");
@@ -6381,6 +6386,2149 @@ module.exports = __toCommonJS(index_exports);
6381
6386
  window.VanduoLazyLoad = VanduoLazyLoad;
6382
6387
  })();
6383
6388
 
6389
+ // js/components/flow.js
6390
+ (function() {
6391
+ "use strict";
6392
+ const Flow = {
6393
+ instances: /* @__PURE__ */ new Map(),
6394
+ init: function() {
6395
+ const carousels = document.querySelectorAll(".vd-flow, .vd-carousel");
6396
+ carousels.forEach((el) => {
6397
+ if (this.instances.has(el)) return;
6398
+ this.initInstance(el);
6399
+ });
6400
+ },
6401
+ initInstance: function(el) {
6402
+ const track = el.querySelector(".vd-flow-track");
6403
+ if (!track) return;
6404
+ const slides = Array.from(track.querySelectorAll(".vd-flow-slide"));
6405
+ if (slides.length === 0) return;
6406
+ const isFade = el.classList.contains("vd-flow-fade");
6407
+ const autoplay = el.hasAttribute("data-vd-autoplay");
6408
+ const interval = parseInt(el.getAttribute("data-vd-interval"), 10) || 5e3;
6409
+ const loop = el.getAttribute("data-vd-loop") !== "false";
6410
+ const state = {
6411
+ current: 0,
6412
+ total: slides.length,
6413
+ autoplayTimer: null,
6414
+ isFade,
6415
+ loop,
6416
+ isDragging: false,
6417
+ startX: 0,
6418
+ currentX: 0,
6419
+ threshold: 50
6420
+ };
6421
+ const cleanup = [];
6422
+ slides.forEach((slide, i) => {
6423
+ slide.setAttribute("role", "group");
6424
+ slide.setAttribute("aria-roledescription", "slide");
6425
+ slide.setAttribute("aria-label", "Slide " + (i + 1) + " of " + slides.length);
6426
+ if (i === 0) slide.classList.add("is-active");
6427
+ });
6428
+ el.setAttribute("role", "region");
6429
+ el.setAttribute("aria-roledescription", "carousel");
6430
+ if (!el.getAttribute("aria-label")) {
6431
+ el.setAttribute("aria-label", "Carousel");
6432
+ }
6433
+ const liveRegion = document.createElement("div");
6434
+ liveRegion.setAttribute("aria-live", "polite");
6435
+ liveRegion.setAttribute("aria-atomic", "true");
6436
+ liveRegion.className = "sr-only";
6437
+ liveRegion.style.cssText = "position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);";
6438
+ el.appendChild(liveRegion);
6439
+ const goTo = (index, announce) => {
6440
+ if (announce === void 0) announce = true;
6441
+ let target = index;
6442
+ if (state.loop) {
6443
+ target = (index % state.total + state.total) % state.total;
6444
+ } else {
6445
+ target = Math.max(0, Math.min(index, state.total - 1));
6446
+ }
6447
+ const prev2 = state.current;
6448
+ state.current = target;
6449
+ if (state.isFade) {
6450
+ slides.forEach((s, i) => {
6451
+ s.classList.toggle("is-active", i === target);
6452
+ });
6453
+ } else {
6454
+ track.style.transform = "translateX(-" + target * 100 + "%)";
6455
+ }
6456
+ const indicators2 = el.querySelectorAll(".vd-flow-indicator");
6457
+ indicators2.forEach((ind, i) => {
6458
+ ind.classList.toggle("is-active", i === target);
6459
+ ind.setAttribute("aria-selected", i === target ? "true" : "false");
6460
+ });
6461
+ slides.forEach((s, i) => {
6462
+ s.setAttribute("aria-hidden", i !== target ? "true" : "false");
6463
+ });
6464
+ if (announce) {
6465
+ liveRegion.textContent = "Slide " + (target + 1) + " of " + state.total;
6466
+ }
6467
+ el.dispatchEvent(new CustomEvent("flow:change", {
6468
+ detail: { current: target, previous: prev2, total: state.total }
6469
+ }));
6470
+ };
6471
+ const next = () => goTo(state.current + 1);
6472
+ const prev = () => goTo(state.current - 1);
6473
+ const prevBtn = el.querySelector(".vd-flow-prev");
6474
+ const nextBtn = el.querySelector(".vd-flow-next");
6475
+ if (prevBtn) {
6476
+ const h = () => prev();
6477
+ prevBtn.addEventListener("click", h);
6478
+ cleanup.push(() => prevBtn.removeEventListener("click", h));
6479
+ }
6480
+ if (nextBtn) {
6481
+ const h = () => next();
6482
+ nextBtn.addEventListener("click", h);
6483
+ cleanup.push(() => nextBtn.removeEventListener("click", h));
6484
+ }
6485
+ const indicators = el.querySelectorAll(".vd-flow-indicator");
6486
+ indicators.forEach((ind, i) => {
6487
+ ind.setAttribute("role", "tab");
6488
+ ind.setAttribute("aria-selected", i === 0 ? "true" : "false");
6489
+ ind.setAttribute("aria-label", "Go to slide " + (i + 1));
6490
+ const h = () => goTo(i);
6491
+ ind.addEventListener("click", h);
6492
+ cleanup.push(() => ind.removeEventListener("click", h));
6493
+ });
6494
+ const keyHandler = (e) => {
6495
+ if (e.key === "ArrowLeft") {
6496
+ prev();
6497
+ e.preventDefault();
6498
+ }
6499
+ if (e.key === "ArrowRight") {
6500
+ next();
6501
+ e.preventDefault();
6502
+ }
6503
+ };
6504
+ el.setAttribute("tabindex", "0");
6505
+ el.addEventListener("keydown", keyHandler);
6506
+ cleanup.push(() => el.removeEventListener("keydown", keyHandler));
6507
+ const pointerDown = (e) => {
6508
+ state.isDragging = true;
6509
+ state.startX = e.clientX || e.touches && e.touches[0].clientX || 0;
6510
+ state.currentX = state.startX;
6511
+ el.classList.add("is-dragging");
6512
+ };
6513
+ const pointerMove = (e) => {
6514
+ if (!state.isDragging) return;
6515
+ state.currentX = e.clientX || e.touches && e.touches[0].clientX || 0;
6516
+ };
6517
+ const pointerUp = () => {
6518
+ if (!state.isDragging) return;
6519
+ state.isDragging = false;
6520
+ el.classList.remove("is-dragging");
6521
+ const diff = state.startX - state.currentX;
6522
+ if (Math.abs(diff) > state.threshold) {
6523
+ if (diff > 0) next();
6524
+ else prev();
6525
+ }
6526
+ };
6527
+ el.addEventListener("mousedown", pointerDown);
6528
+ el.addEventListener("mousemove", pointerMove);
6529
+ el.addEventListener("mouseup", pointerUp);
6530
+ el.addEventListener("mouseleave", pointerUp);
6531
+ el.addEventListener("touchstart", pointerDown, { passive: true });
6532
+ el.addEventListener("touchmove", pointerMove, { passive: true });
6533
+ el.addEventListener("touchend", pointerUp);
6534
+ cleanup.push(
6535
+ () => el.removeEventListener("mousedown", pointerDown),
6536
+ () => el.removeEventListener("mousemove", pointerMove),
6537
+ () => el.removeEventListener("mouseup", pointerUp),
6538
+ () => el.removeEventListener("mouseleave", pointerUp),
6539
+ () => el.removeEventListener("touchstart", pointerDown),
6540
+ () => el.removeEventListener("touchmove", pointerMove),
6541
+ () => el.removeEventListener("touchend", pointerUp)
6542
+ );
6543
+ const startAutoplay = () => {
6544
+ stopAutoplay();
6545
+ state.autoplayTimer = setInterval(next, interval);
6546
+ };
6547
+ const stopAutoplay = () => {
6548
+ if (state.autoplayTimer) {
6549
+ clearInterval(state.autoplayTimer);
6550
+ state.autoplayTimer = null;
6551
+ }
6552
+ };
6553
+ if (autoplay) {
6554
+ startAutoplay();
6555
+ const pauseHandler = () => stopAutoplay();
6556
+ const resumeHandler = () => startAutoplay();
6557
+ el.addEventListener("mouseenter", pauseHandler);
6558
+ el.addEventListener("mouseleave", resumeHandler);
6559
+ el.addEventListener("focusin", pauseHandler);
6560
+ el.addEventListener("focusout", resumeHandler);
6561
+ cleanup.push(
6562
+ () => el.removeEventListener("mouseenter", pauseHandler),
6563
+ () => el.removeEventListener("mouseleave", resumeHandler),
6564
+ () => el.removeEventListener("focusin", pauseHandler),
6565
+ () => el.removeEventListener("focusout", resumeHandler),
6566
+ () => stopAutoplay()
6567
+ );
6568
+ }
6569
+ goTo(0, false);
6570
+ this.instances.set(el, {
6571
+ cleanup,
6572
+ goTo,
6573
+ next,
6574
+ prev,
6575
+ getState: () => ({ ...state })
6576
+ });
6577
+ },
6578
+ goTo: function(el, index) {
6579
+ const instance = this.instances.get(el);
6580
+ if (instance) instance.goTo(index);
6581
+ },
6582
+ next: function(el) {
6583
+ const instance = this.instances.get(el);
6584
+ if (instance) instance.next();
6585
+ },
6586
+ prev: function(el) {
6587
+ const instance = this.instances.get(el);
6588
+ if (instance) instance.prev();
6589
+ },
6590
+ destroy: function(el) {
6591
+ const instance = this.instances.get(el);
6592
+ if (!instance) return;
6593
+ instance.cleanup.forEach((fn) => fn());
6594
+ this.instances.delete(el);
6595
+ },
6596
+ destroyAll: function() {
6597
+ this.instances.forEach((_, el) => this.destroy(el));
6598
+ }
6599
+ };
6600
+ if (typeof window.Vanduo !== "undefined") {
6601
+ window.Vanduo.register("flow", Flow);
6602
+ }
6603
+ window.VanduoFlow = Flow;
6604
+ })();
6605
+
6606
+ // js/components/bubble.js
6607
+ (function() {
6608
+ "use strict";
6609
+ const Bubble = {
6610
+ instances: /* @__PURE__ */ new Map(),
6611
+ _globalCleanups: [],
6612
+ init: function() {
6613
+ const triggers = document.querySelectorAll("[data-vd-bubble], [data-vd-popover]");
6614
+ triggers.forEach((el) => {
6615
+ if (this.instances.has(el)) return;
6616
+ this.initInstance(el);
6617
+ });
6618
+ if (this._globalCleanups.length === 0) {
6619
+ const outsideClick = (e) => {
6620
+ this.instances.forEach((inst, trigger) => {
6621
+ if (!inst.popover.contains(e.target) && !trigger.contains(e.target)) {
6622
+ this.hide(trigger);
6623
+ }
6624
+ });
6625
+ };
6626
+ const escHandler = (e) => {
6627
+ if (e.key === "Escape") {
6628
+ this.instances.forEach((_, trigger) => this.hide(trigger));
6629
+ }
6630
+ };
6631
+ document.addEventListener("click", outsideClick, true);
6632
+ document.addEventListener("keydown", escHandler);
6633
+ this._globalCleanups.push(
6634
+ () => document.removeEventListener("click", outsideClick, true),
6635
+ () => document.removeEventListener("keydown", escHandler)
6636
+ );
6637
+ }
6638
+ },
6639
+ initInstance: function(trigger) {
6640
+ const cleanup = [];
6641
+ const placement = trigger.getAttribute("data-vd-bubble-placement") || trigger.getAttribute("data-vd-popover-placement") || "bottom";
6642
+ const popover = document.createElement("div");
6643
+ popover.className = "vd-bubble-content";
6644
+ popover.setAttribute("role", "dialog");
6645
+ popover.setAttribute("aria-modal", "false");
6646
+ popover.setAttribute("data-placement", placement);
6647
+ const title = trigger.getAttribute("data-vd-bubble-title") || trigger.getAttribute("data-vd-popover-title");
6648
+ const content = trigger.getAttribute("data-vd-bubble") || trigger.getAttribute("data-vd-popover") || "";
6649
+ const htmlContent = trigger.getAttribute("data-vd-bubble-html") || trigger.getAttribute("data-vd-popover-html");
6650
+ if (title) {
6651
+ const header = document.createElement("div");
6652
+ header.className = "vd-bubble-header";
6653
+ const titleSpan = document.createElement("span");
6654
+ titleSpan.textContent = title;
6655
+ const closeBtn = document.createElement("button");
6656
+ closeBtn.className = "vd-bubble-close";
6657
+ closeBtn.setAttribute("aria-label", "Close");
6658
+ closeBtn.innerHTML = "×";
6659
+ header.appendChild(titleSpan);
6660
+ header.appendChild(closeBtn);
6661
+ popover.appendChild(header);
6662
+ const closeHandler = (e) => {
6663
+ e.stopPropagation();
6664
+ this.hide(trigger);
6665
+ };
6666
+ closeBtn.addEventListener("click", closeHandler);
6667
+ cleanup.push(() => closeBtn.removeEventListener("click", closeHandler));
6668
+ }
6669
+ const body = document.createElement("div");
6670
+ body.className = "vd-bubble-body";
6671
+ if (htmlContent) {
6672
+ if (typeof sanitizeHtml === "function") {
6673
+ body.innerHTML = sanitizeHtml(htmlContent);
6674
+ } else {
6675
+ body.textContent = htmlContent;
6676
+ }
6677
+ } else {
6678
+ body.textContent = content;
6679
+ }
6680
+ popover.appendChild(body);
6681
+ document.body.appendChild(popover);
6682
+ const popId = "vd-bubble-" + Math.random().toString(36).slice(2, 9);
6683
+ popover.id = popId;
6684
+ trigger.setAttribute("aria-haspopup", "dialog");
6685
+ trigger.setAttribute("aria-expanded", "false");
6686
+ trigger.setAttribute("aria-controls", popId);
6687
+ const toggleHandler = (e) => {
6688
+ e.stopPropagation();
6689
+ if (popover.classList.contains("is-visible")) {
6690
+ this.hide(trigger);
6691
+ } else {
6692
+ this.hideAll();
6693
+ this.show(trigger);
6694
+ }
6695
+ };
6696
+ trigger.addEventListener("click", toggleHandler);
6697
+ cleanup.push(() => trigger.removeEventListener("click", toggleHandler));
6698
+ this.instances.set(trigger, { popover, cleanup, placement });
6699
+ },
6700
+ position: function(trigger, popover, placement) {
6701
+ const rect = trigger.getBoundingClientRect();
6702
+ const popRect = popover.getBoundingClientRect();
6703
+ const gap = 10;
6704
+ let top, left;
6705
+ switch (placement) {
6706
+ case "top":
6707
+ top = rect.top - popRect.height - gap + window.scrollY;
6708
+ left = rect.left + (rect.width - popRect.width) / 2 + window.scrollX;
6709
+ break;
6710
+ case "left":
6711
+ top = rect.top + (rect.height - popRect.height) / 2 + window.scrollY;
6712
+ left = rect.left - popRect.width - gap + window.scrollX;
6713
+ break;
6714
+ case "right":
6715
+ top = rect.top + (rect.height - popRect.height) / 2 + window.scrollY;
6716
+ left = rect.right + gap + window.scrollX;
6717
+ break;
6718
+ default:
6719
+ top = rect.bottom + gap + window.scrollY;
6720
+ left = rect.left + (rect.width - popRect.width) / 2 + window.scrollX;
6721
+ }
6722
+ left = Math.max(8, Math.min(left, window.innerWidth - popRect.width - 8));
6723
+ top = Math.max(8, top);
6724
+ popover.style.top = top + "px";
6725
+ popover.style.left = left + "px";
6726
+ },
6727
+ show: function(trigger) {
6728
+ const instance = this.instances.get(trigger);
6729
+ if (!instance) return;
6730
+ const { popover, placement } = instance;
6731
+ popover.style.display = "block";
6732
+ popover.classList.add("is-visible");
6733
+ trigger.setAttribute("aria-expanded", "true");
6734
+ requestAnimationFrame(() => {
6735
+ this.position(trigger, popover, placement);
6736
+ });
6737
+ trigger.dispatchEvent(new CustomEvent("bubble:show", { bubbles: true }));
6738
+ },
6739
+ hide: function(trigger) {
6740
+ const instance = this.instances.get(trigger);
6741
+ if (!instance) return;
6742
+ instance.popover.classList.remove("is-visible");
6743
+ trigger.setAttribute("aria-expanded", "false");
6744
+ trigger.dispatchEvent(new CustomEvent("bubble:hide", { bubbles: true }));
6745
+ },
6746
+ hideAll: function() {
6747
+ this.instances.forEach((_, trigger) => this.hide(trigger));
6748
+ },
6749
+ destroy: function(trigger) {
6750
+ const instance = this.instances.get(trigger);
6751
+ if (!instance) return;
6752
+ instance.cleanup.forEach((fn) => fn());
6753
+ if (instance.popover.parentNode) {
6754
+ instance.popover.parentNode.removeChild(instance.popover);
6755
+ }
6756
+ trigger.removeAttribute("aria-haspopup");
6757
+ trigger.removeAttribute("aria-expanded");
6758
+ trigger.removeAttribute("aria-controls");
6759
+ this.instances.delete(trigger);
6760
+ },
6761
+ destroyAll: function() {
6762
+ this.instances.forEach((_, trigger) => this.destroy(trigger));
6763
+ this._globalCleanups.forEach((fn) => fn());
6764
+ this._globalCleanups = [];
6765
+ }
6766
+ };
6767
+ if (typeof window.Vanduo !== "undefined") {
6768
+ window.Vanduo.register("bubble", Bubble);
6769
+ }
6770
+ window.VanduoBubble = Bubble;
6771
+ })();
6772
+
6773
+ // js/components/waypoint.js
6774
+ (function() {
6775
+ "use strict";
6776
+ const Waypoint = {
6777
+ instances: /* @__PURE__ */ new Map(),
6778
+ init: function() {
6779
+ const navs = document.querySelectorAll("[data-vd-waypoint-nav], [data-vd-scrollspy-nav]");
6780
+ navs.forEach((nav) => {
6781
+ if (this.instances.has(nav)) return;
6782
+ this.initInstance(nav);
6783
+ });
6784
+ },
6785
+ initInstance: function(nav) {
6786
+ const links = Array.from(nav.querySelectorAll('a[href^="#"]'));
6787
+ if (links.length === 0) return;
6788
+ const cleanup = [];
6789
+ const offset = parseInt(nav.getAttribute("data-vd-waypoint-offset") || "80", 10);
6790
+ const sections = [];
6791
+ links.forEach((link) => {
6792
+ const id = link.getAttribute("href").slice(1);
6793
+ const section = document.getElementById(id);
6794
+ if (section) {
6795
+ section.setAttribute("data-vd-waypoint-section", "");
6796
+ sections.push({ id, link, section });
6797
+ }
6798
+ });
6799
+ if (sections.length === 0) return;
6800
+ const activeSections = /* @__PURE__ */ new Set();
6801
+ const setActive = (id) => {
6802
+ links.forEach((l) => l.classList.remove("is-active"));
6803
+ const target = links.find((l) => l.getAttribute("href") === "#" + id);
6804
+ if (target) {
6805
+ target.classList.add("is-active");
6806
+ nav.dispatchEvent(new CustomEvent("waypoint:change", {
6807
+ detail: { activeId: id, link: target }
6808
+ }));
6809
+ }
6810
+ };
6811
+ const rootMargin = "-" + offset + "px 0px -40% 0px";
6812
+ const observer = new IntersectionObserver((entries) => {
6813
+ entries.forEach((entry) => {
6814
+ if (entry.isIntersecting) {
6815
+ activeSections.add(entry.target.id);
6816
+ } else {
6817
+ activeSections.delete(entry.target.id);
6818
+ }
6819
+ });
6820
+ for (let i = 0; i < sections.length; i++) {
6821
+ if (activeSections.has(sections[i].id)) {
6822
+ setActive(sections[i].id);
6823
+ return;
6824
+ }
6825
+ }
6826
+ }, {
6827
+ rootMargin,
6828
+ threshold: 0
6829
+ });
6830
+ sections.forEach((s) => observer.observe(s.section));
6831
+ links.forEach((link) => {
6832
+ const clickHandler = (e) => {
6833
+ e.preventDefault();
6834
+ const id = link.getAttribute("href").slice(1);
6835
+ const section = document.getElementById(id);
6836
+ if (section) {
6837
+ section.scrollIntoView({ behavior: "smooth" });
6838
+ setActive(id);
6839
+ }
6840
+ };
6841
+ link.addEventListener("click", clickHandler);
6842
+ cleanup.push(() => link.removeEventListener("click", clickHandler));
6843
+ });
6844
+ cleanup.push(() => observer.disconnect());
6845
+ this.instances.set(nav, { observer, cleanup, sections, setActive });
6846
+ },
6847
+ refresh: function(nav) {
6848
+ this.destroy(nav);
6849
+ this.initInstance(nav);
6850
+ },
6851
+ destroy: function(nav) {
6852
+ const instance = this.instances.get(nav);
6853
+ if (!instance) return;
6854
+ instance.cleanup.forEach((fn) => fn());
6855
+ this.instances.delete(nav);
6856
+ },
6857
+ destroyAll: function() {
6858
+ this.instances.forEach((_, nav) => this.destroy(nav));
6859
+ }
6860
+ };
6861
+ if (typeof window.Vanduo !== "undefined") {
6862
+ window.Vanduo.register("waypoint", Waypoint);
6863
+ }
6864
+ window.VanduoWaypoint = Waypoint;
6865
+ })();
6866
+
6867
+ // js/components/ripple.js
6868
+ (function() {
6869
+ "use strict";
6870
+ const Ripple = {
6871
+ instances: /* @__PURE__ */ new Map(),
6872
+ init: function() {
6873
+ const elements = document.querySelectorAll(".vd-ripple, [data-vd-ripple]");
6874
+ elements.forEach((el) => {
6875
+ if (this.instances.has(el)) return;
6876
+ this.initInstance(el);
6877
+ });
6878
+ },
6879
+ initInstance: function(el) {
6880
+ const cleanup = [];
6881
+ const createWave = (e) => {
6882
+ const rect = el.getBoundingClientRect();
6883
+ const size = Math.max(rect.width, rect.height);
6884
+ const x = (e.clientX || e.touches && e.touches[0].clientX || rect.left + rect.width / 2) - rect.left - size / 2;
6885
+ const y = (e.clientY || e.touches && e.touches[0].clientY || rect.top + rect.height / 2) - rect.top - size / 2;
6886
+ const wave = document.createElement("span");
6887
+ wave.className = "vd-ripple-wave";
6888
+ wave.style.width = size + "px";
6889
+ wave.style.height = size + "px";
6890
+ wave.style.left = x + "px";
6891
+ wave.style.top = y + "px";
6892
+ el.appendChild(wave);
6893
+ wave.addEventListener("animationend", () => {
6894
+ if (wave.parentNode) wave.parentNode.removeChild(wave);
6895
+ });
6896
+ };
6897
+ el.addEventListener("mousedown", createWave);
6898
+ el.addEventListener("touchstart", createWave, { passive: true });
6899
+ cleanup.push(
6900
+ () => el.removeEventListener("mousedown", createWave),
6901
+ () => el.removeEventListener("touchstart", createWave)
6902
+ );
6903
+ this.instances.set(el, { cleanup });
6904
+ },
6905
+ destroy: function(el) {
6906
+ const instance = this.instances.get(el);
6907
+ if (!instance) return;
6908
+ instance.cleanup.forEach((fn) => fn());
6909
+ el.querySelectorAll(".vd-ripple-wave").forEach((w) => w.remove());
6910
+ this.instances.delete(el);
6911
+ },
6912
+ destroyAll: function() {
6913
+ this.instances.forEach((_, el) => this.destroy(el));
6914
+ }
6915
+ };
6916
+ if (typeof window.Vanduo !== "undefined") {
6917
+ window.Vanduo.register("ripple", Ripple);
6918
+ }
6919
+ window.VanduoRipple = Ripple;
6920
+ })();
6921
+
6922
+ // js/components/affix.js
6923
+ (function() {
6924
+ "use strict";
6925
+ function isScrollable(element) {
6926
+ if (!element || element === document.body) return false;
6927
+ const style = window.getComputedStyle(element);
6928
+ const overflowY = style.overflowY;
6929
+ const overflowX = style.overflowX;
6930
+ const canScrollY = /(auto|scroll|overlay)/.test(overflowY) && element.scrollHeight > element.clientHeight;
6931
+ const canScrollX = /(auto|scroll|overlay)/.test(overflowX) && element.scrollWidth > element.clientWidth;
6932
+ return canScrollY || canScrollX;
6933
+ }
6934
+ function getScrollParent(element) {
6935
+ let parent = element.parentElement;
6936
+ while (parent && parent !== document.body && parent !== document.documentElement) {
6937
+ if (isScrollable(parent)) return parent;
6938
+ parent = parent.parentElement;
6939
+ }
6940
+ return null;
6941
+ }
6942
+ const Affix = {
6943
+ instances: /* @__PURE__ */ new Map(),
6944
+ init: function() {
6945
+ const elements = document.querySelectorAll(".vd-affix, .vd-sticky, [data-vd-affix]");
6946
+ elements.forEach((el) => {
6947
+ if (this.instances.has(el)) return;
6948
+ this.initInstance(el);
6949
+ });
6950
+ },
6951
+ initInstance: function(el) {
6952
+ const cleanup = [];
6953
+ const parsedOffset = parseInt(el.getAttribute("data-vd-affix-offset") || "0", 10);
6954
+ const offset = Number.isNaN(parsedOffset) ? 0 : parsedOffset;
6955
+ const scrollParent = getScrollParent(el);
6956
+ let isStuck = false;
6957
+ const sentinel = document.createElement("div");
6958
+ sentinel.style.cssText = "display:block;height:1px;margin-bottom:-1px;visibility:hidden;pointer-events:none;";
6959
+ el.parentNode.insertBefore(sentinel, el);
6960
+ el.style.setProperty("--affix-top-offset", offset + "px");
6961
+ function stick() {
6962
+ if (isStuck) return;
6963
+ isStuck = true;
6964
+ el.classList.add("is-stuck");
6965
+ el.dispatchEvent(new CustomEvent("affix:stuck", {
6966
+ bubbles: true,
6967
+ detail: {
6968
+ offset,
6969
+ root: scrollParent || window
6970
+ }
6971
+ }));
6972
+ }
6973
+ function unstick() {
6974
+ if (!isStuck) return;
6975
+ isStuck = false;
6976
+ el.classList.remove("is-stuck");
6977
+ el.dispatchEvent(new CustomEvent("affix:unstuck", {
6978
+ bubbles: true,
6979
+ detail: {
6980
+ offset,
6981
+ root: scrollParent || window
6982
+ }
6983
+ }));
6984
+ }
6985
+ const observer = new IntersectionObserver(function(entries) {
6986
+ entries.forEach((entry) => {
6987
+ if (!entry.isIntersecting) {
6988
+ stick();
6989
+ } else {
6990
+ unstick();
6991
+ }
6992
+ });
6993
+ }, {
6994
+ root: scrollParent,
6995
+ rootMargin: "-" + offset + "px 0px 0px 0px",
6996
+ threshold: 0
6997
+ });
6998
+ observer.observe(sentinel);
6999
+ cleanup.push(
7000
+ () => observer.disconnect(),
7001
+ () => {
7002
+ if (sentinel.parentNode) sentinel.parentNode.removeChild(sentinel);
7003
+ },
7004
+ () => {
7005
+ el.classList.remove("is-stuck");
7006
+ el.style.removeProperty("--affix-top-offset");
7007
+ }
7008
+ );
7009
+ this.instances.set(el, { cleanup, observer, sentinel, scrollParent });
7010
+ },
7011
+ destroy: function(el) {
7012
+ const instance = this.instances.get(el);
7013
+ if (!instance) return;
7014
+ instance.cleanup.forEach((fn) => fn());
7015
+ el.classList.remove("is-stuck");
7016
+ this.instances.delete(el);
7017
+ },
7018
+ destroyAll: function() {
7019
+ this.instances.forEach((_, el) => this.destroy(el));
7020
+ }
7021
+ };
7022
+ if (typeof window.Vanduo !== "undefined") {
7023
+ window.Vanduo.register("affix", Affix);
7024
+ }
7025
+ window.VanduoAffix = Affix;
7026
+ })();
7027
+
7028
+ // js/components/suggest.js
7029
+ (function() {
7030
+ "use strict";
7031
+ const Suggest = {
7032
+ instances: /* @__PURE__ */ new Map(),
7033
+ init: function() {
7034
+ const inputs = document.querySelectorAll("[data-vd-suggest], [data-vd-autocomplete]");
7035
+ inputs.forEach((el) => {
7036
+ if (this.instances.has(el)) return;
7037
+ this.initInstance(el);
7038
+ });
7039
+ },
7040
+ initInstance: function(input) {
7041
+ const cleanup = [];
7042
+ const minChars = parseInt(input.getAttribute("data-vd-suggest-min-chars") || "1", 10);
7043
+ const url = input.getAttribute("data-vd-suggest-url") || "";
7044
+ const staticData = input.getAttribute("data-vd-suggest") || input.getAttribute("data-vd-autocomplete") || "";
7045
+ let items = [];
7046
+ try {
7047
+ items = JSON.parse(staticData);
7048
+ } catch (_e) {
7049
+ items = staticData.split(",").map((s) => s.trim()).filter(Boolean);
7050
+ }
7051
+ let wrapper = input.closest(".vd-suggest-wrapper, .vd-autocomplete-wrapper");
7052
+ if (!wrapper) {
7053
+ wrapper = document.createElement("div");
7054
+ wrapper.className = "vd-suggest-wrapper";
7055
+ input.parentNode.insertBefore(wrapper, input);
7056
+ wrapper.appendChild(input);
7057
+ }
7058
+ const list = document.createElement("ul");
7059
+ list.className = "vd-suggest-list";
7060
+ list.setAttribute("role", "listbox");
7061
+ const listId = "vd-suggest-" + Math.random().toString(36).slice(2, 9);
7062
+ list.id = listId;
7063
+ wrapper.appendChild(list);
7064
+ input.setAttribute("role", "combobox");
7065
+ input.setAttribute("aria-autocomplete", "list");
7066
+ input.setAttribute("aria-expanded", "false");
7067
+ input.setAttribute("aria-controls", listId);
7068
+ input.setAttribute("autocomplete", "off");
7069
+ let highlighted = -1;
7070
+ let currentItems = [];
7071
+ let debounceTimer = null;
7072
+ const renderItems = (filtered, query) => {
7073
+ list.innerHTML = "";
7074
+ currentItems = filtered;
7075
+ highlighted = -1;
7076
+ if (filtered.length === 0) {
7077
+ const empty = document.createElement("li");
7078
+ empty.className = "vd-suggest-empty";
7079
+ empty.textContent = "No results";
7080
+ list.appendChild(empty);
7081
+ return;
7082
+ }
7083
+ filtered.forEach((item, i) => {
7084
+ const li = document.createElement("li");
7085
+ li.className = "vd-suggest-item";
7086
+ li.setAttribute("role", "option");
7087
+ li.id = listId + "-item-" + i;
7088
+ const text = typeof item === "object" ? item.label || item.text || String(item) : String(item);
7089
+ if (query) {
7090
+ const re = new RegExp("(" + query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + ")", "gi");
7091
+ li.innerHTML = text.replace(re, '<span class="vd-suggest-match">$1</span>');
7092
+ } else {
7093
+ li.textContent = text;
7094
+ }
7095
+ li.addEventListener("click", () => selectItem(i));
7096
+ list.appendChild(li);
7097
+ });
7098
+ };
7099
+ const open = () => {
7100
+ list.classList.add("is-open");
7101
+ input.setAttribute("aria-expanded", "true");
7102
+ };
7103
+ const close = () => {
7104
+ list.classList.remove("is-open");
7105
+ input.setAttribute("aria-expanded", "false");
7106
+ highlighted = -1;
7107
+ input.removeAttribute("aria-activedescendant");
7108
+ };
7109
+ const selectItem = (index) => {
7110
+ const item = currentItems[index];
7111
+ const value = typeof item === "object" ? item.value || item.label || String(item) : String(item);
7112
+ input.value = value;
7113
+ close();
7114
+ input.dispatchEvent(new CustomEvent("suggest:select", {
7115
+ detail: { value, item, index },
7116
+ bubbles: true
7117
+ }));
7118
+ };
7119
+ const highlight = (index) => {
7120
+ const listItems = list.querySelectorAll(".vd-suggest-item");
7121
+ listItems.forEach((li) => li.classList.remove("is-highlighted"));
7122
+ if (index >= 0 && index < listItems.length) {
7123
+ highlighted = index;
7124
+ listItems[index].classList.add("is-highlighted");
7125
+ input.setAttribute("aria-activedescendant", listItems[index].id);
7126
+ listItems[index].scrollIntoView({ block: "nearest" });
7127
+ }
7128
+ };
7129
+ const doSearch = async (query) => {
7130
+ if (query.length < minChars) {
7131
+ close();
7132
+ return;
7133
+ }
7134
+ let filtered;
7135
+ if (url) {
7136
+ try {
7137
+ const separator = url.includes("?") ? "&" : "?";
7138
+ const res = await window.fetch(url + separator + "q=" + encodeURIComponent(query));
7139
+ filtered = await res.json();
7140
+ } catch (_e) {
7141
+ filtered = [];
7142
+ }
7143
+ } else {
7144
+ const lower = query.toLowerCase();
7145
+ filtered = items.filter((item) => {
7146
+ const text = typeof item === "object" ? item.label || item.text || String(item) : String(item);
7147
+ return text.toLowerCase().includes(lower);
7148
+ });
7149
+ }
7150
+ renderItems(filtered, query);
7151
+ if (filtered.length > 0) open();
7152
+ else open();
7153
+ };
7154
+ const inputHandler = () => {
7155
+ clearTimeout(debounceTimer);
7156
+ debounceTimer = setTimeout(() => doSearch(input.value), 200);
7157
+ };
7158
+ const keyHandler = (e) => {
7159
+ if (!list.classList.contains("is-open")) {
7160
+ if (e.key === "ArrowDown") {
7161
+ doSearch(input.value);
7162
+ e.preventDefault();
7163
+ }
7164
+ return;
7165
+ }
7166
+ const total = currentItems.length;
7167
+ switch (e.key) {
7168
+ case "ArrowDown":
7169
+ e.preventDefault();
7170
+ highlight(highlighted < total - 1 ? highlighted + 1 : 0);
7171
+ break;
7172
+ case "ArrowUp":
7173
+ e.preventDefault();
7174
+ highlight(highlighted > 0 ? highlighted - 1 : total - 1);
7175
+ break;
7176
+ case "Enter":
7177
+ e.preventDefault();
7178
+ if (highlighted >= 0) selectItem(highlighted);
7179
+ break;
7180
+ case "Escape":
7181
+ close();
7182
+ break;
7183
+ }
7184
+ };
7185
+ const blurHandler = () => {
7186
+ setTimeout(close, 200);
7187
+ };
7188
+ input.addEventListener("input", inputHandler);
7189
+ input.addEventListener("keydown", keyHandler);
7190
+ input.addEventListener("blur", blurHandler);
7191
+ input.addEventListener("focus", () => {
7192
+ if (input.value.length >= minChars) doSearch(input.value);
7193
+ });
7194
+ cleanup.push(
7195
+ () => input.removeEventListener("input", inputHandler),
7196
+ () => input.removeEventListener("keydown", keyHandler),
7197
+ () => input.removeEventListener("blur", blurHandler),
7198
+ () => clearTimeout(debounceTimer),
7199
+ () => {
7200
+ if (list.parentNode) list.parentNode.removeChild(list);
7201
+ }
7202
+ );
7203
+ this.instances.set(input, { cleanup, list, close });
7204
+ },
7205
+ destroy: function(el) {
7206
+ const instance = this.instances.get(el);
7207
+ if (!instance) return;
7208
+ instance.cleanup.forEach((fn) => fn());
7209
+ this.instances.delete(el);
7210
+ },
7211
+ destroyAll: function() {
7212
+ this.instances.forEach((_, el) => this.destroy(el));
7213
+ }
7214
+ };
7215
+ if (typeof window.Vanduo !== "undefined") {
7216
+ window.Vanduo.register("suggest", Suggest);
7217
+ }
7218
+ window.VanduoSuggest = Suggest;
7219
+ })();
7220
+
7221
+ // js/components/validate.js
7222
+ (function() {
7223
+ "use strict";
7224
+ const Validate = {
7225
+ instances: /* @__PURE__ */ new Map(),
7226
+ rules: {
7227
+ required: (value) => value.trim().length > 0,
7228
+ email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
7229
+ url: (value) => {
7230
+ try {
7231
+ new URL(value);
7232
+ return true;
7233
+ } catch (_e) {
7234
+ return false;
7235
+ }
7236
+ },
7237
+ number: (value) => !isNaN(parseFloat(value)) && isFinite(value),
7238
+ min: (value, param) => value.length >= parseInt(param, 10),
7239
+ max: (value, param) => value.length <= parseInt(param, 10),
7240
+ minVal: (value, param) => parseFloat(value) >= parseFloat(param),
7241
+ maxVal: (value, param) => parseFloat(value) <= parseFloat(param),
7242
+ pattern: (value, param) => {
7243
+ try {
7244
+ return new RegExp(param).test(value);
7245
+ } catch (_e) {
7246
+ return false;
7247
+ }
7248
+ },
7249
+ match: (value, param) => {
7250
+ const other = document.querySelector('[name="' + param + '"]');
7251
+ return other ? value === other.value : false;
7252
+ }
7253
+ },
7254
+ messages: {
7255
+ required: "This field is required",
7256
+ email: "Please enter a valid email address",
7257
+ url: "Please enter a valid URL",
7258
+ number: "Please enter a valid number",
7259
+ min: "Minimum {0} characters required",
7260
+ max: "Maximum {0} characters allowed",
7261
+ minVal: "Value must be at least {0}",
7262
+ maxVal: "Value must be at most {0}",
7263
+ pattern: "Invalid format",
7264
+ match: "Fields do not match"
7265
+ },
7266
+ init: function() {
7267
+ const forms = document.querySelectorAll("[data-vd-validate], .vd-validate");
7268
+ forms.forEach((form) => {
7269
+ if (this.instances.has(form)) return;
7270
+ this.initInstance(form);
7271
+ });
7272
+ },
7273
+ initInstance: function(form) {
7274
+ const cleanup = [];
7275
+ const mode = form.getAttribute("data-vd-validate-mode") || "blur";
7276
+ const fields = form.querySelectorAll("[data-vd-rules]");
7277
+ const validateField = (field) => {
7278
+ const rulesStr = field.getAttribute("data-vd-rules") || "";
7279
+ const rules = rulesStr.split("|").map((r) => r.trim()).filter(Boolean);
7280
+ const value = field.value;
7281
+ const errors = [];
7282
+ for (const rule of rules) {
7283
+ const [name, ...params] = rule.split(":");
7284
+ const param = params.join(":");
7285
+ const validator = this.rules[name];
7286
+ if (validator && !validator(value, param)) {
7287
+ const customMsg = field.getAttribute("data-vd-msg-" + name);
7288
+ let msg = customMsg || this.messages[name] || "Invalid";
7289
+ if (param) msg = msg.replace("{0}", param);
7290
+ errors.push(msg);
7291
+ break;
7292
+ }
7293
+ }
7294
+ this.setFieldState(field, errors);
7295
+ return errors.length === 0;
7296
+ };
7297
+ const validateAll = () => {
7298
+ let valid = true;
7299
+ fields.forEach((field) => {
7300
+ if (!validateField(field)) valid = false;
7301
+ });
7302
+ return valid;
7303
+ };
7304
+ fields.forEach((field) => {
7305
+ if (mode === "input" || mode === "blur") {
7306
+ const eventType = mode === "input" ? "input" : "blur";
7307
+ const handler = () => validateField(field);
7308
+ field.addEventListener(eventType, handler);
7309
+ cleanup.push(() => field.removeEventListener(eventType, handler));
7310
+ if (mode === "blur") {
7311
+ const inputClear = () => {
7312
+ if (field.classList.contains("is-invalid") || field.classList.contains("is-valid")) {
7313
+ validateField(field);
7314
+ }
7315
+ };
7316
+ field.addEventListener("input", inputClear);
7317
+ cleanup.push(() => field.removeEventListener("input", inputClear));
7318
+ }
7319
+ }
7320
+ });
7321
+ const submitHandler = (e) => {
7322
+ const valid = validateAll();
7323
+ if (!valid) {
7324
+ e.preventDefault();
7325
+ e.stopPropagation();
7326
+ const firstInvalid = form.querySelector(".is-invalid");
7327
+ if (firstInvalid) firstInvalid.focus();
7328
+ }
7329
+ form.dispatchEvent(new CustomEvent("validate:submit", {
7330
+ detail: { valid },
7331
+ bubbles: true
7332
+ }));
7333
+ };
7334
+ form.addEventListener("submit", submitHandler);
7335
+ cleanup.push(() => form.removeEventListener("submit", submitHandler));
7336
+ this.instances.set(form, { cleanup, validateAll, validateField });
7337
+ },
7338
+ setFieldState: function(field, errors) {
7339
+ const wrapper = field.closest(".vd-form-group") || field.parentElement;
7340
+ let errorEl = wrapper.querySelector(".vd-validate-error");
7341
+ field.classList.remove("is-valid", "is-invalid");
7342
+ if (errors.length > 0) {
7343
+ field.classList.add("is-invalid");
7344
+ field.setAttribute("aria-invalid", "true");
7345
+ if (!errorEl) {
7346
+ errorEl = document.createElement("div");
7347
+ errorEl.className = "vd-validate-error";
7348
+ errorEl.id = "vd-err-" + Math.random().toString(36).slice(2, 9);
7349
+ errorEl.setAttribute("role", "alert");
7350
+ wrapper.appendChild(errorEl);
7351
+ }
7352
+ errorEl.textContent = errors[0];
7353
+ errorEl.style.display = "";
7354
+ field.setAttribute("aria-describedby", errorEl.id);
7355
+ } else if (field.value.trim()) {
7356
+ field.classList.add("is-valid");
7357
+ field.removeAttribute("aria-invalid");
7358
+ if (errorEl) errorEl.style.display = "none";
7359
+ } else {
7360
+ field.removeAttribute("aria-invalid");
7361
+ if (errorEl) errorEl.style.display = "none";
7362
+ }
7363
+ },
7364
+ validateForm: function(form) {
7365
+ const instance = this.instances.get(form);
7366
+ return instance ? instance.validateAll() : false;
7367
+ },
7368
+ addRule: function(name, validator, message) {
7369
+ this.rules[name] = validator;
7370
+ if (message) this.messages[name] = message;
7371
+ },
7372
+ destroy: function(form) {
7373
+ const instance = this.instances.get(form);
7374
+ if (!instance) return;
7375
+ instance.cleanup.forEach((fn) => fn());
7376
+ this.instances.delete(form);
7377
+ },
7378
+ destroyAll: function() {
7379
+ this.instances.forEach((_, form) => this.destroy(form));
7380
+ }
7381
+ };
7382
+ if (typeof window.Vanduo !== "undefined") {
7383
+ window.Vanduo.register("validate", Validate);
7384
+ }
7385
+ window.VanduoValidate = Validate;
7386
+ })();
7387
+
7388
+ // js/components/datepicker.js
7389
+ (function() {
7390
+ "use strict";
7391
+ const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
7392
+ const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
7393
+ const Datepicker = {
7394
+ instances: /* @__PURE__ */ new Map(),
7395
+ init: function() {
7396
+ const inputs = document.querySelectorAll("[data-vd-datepicker]");
7397
+ inputs.forEach((el) => {
7398
+ if (this.instances.has(el)) return;
7399
+ this.initInstance(el);
7400
+ });
7401
+ },
7402
+ initInstance: function(input) {
7403
+ const cleanup = [];
7404
+ const format = input.getAttribute("data-vd-datepicker-format") || "yyyy-mm-dd";
7405
+ const minStr = input.getAttribute("data-vd-datepicker-min");
7406
+ const maxStr = input.getAttribute("data-vd-datepicker-max");
7407
+ const minDate = minStr ? new Date(minStr) : null;
7408
+ const maxDate = maxStr ? new Date(maxStr) : null;
7409
+ const today = /* @__PURE__ */ new Date();
7410
+ let viewYear = today.getFullYear();
7411
+ let viewMonth = today.getMonth();
7412
+ let selectedDate = null;
7413
+ let viewMode = "days";
7414
+ if (input.value) {
7415
+ const parsed = new Date(input.value);
7416
+ if (!isNaN(parsed.getTime())) {
7417
+ selectedDate = parsed;
7418
+ viewYear = parsed.getFullYear();
7419
+ viewMonth = parsed.getMonth();
7420
+ }
7421
+ }
7422
+ const popup = document.createElement("div");
7423
+ popup.className = "vd-datepicker-popup";
7424
+ popup.setAttribute("role", "dialog");
7425
+ popup.setAttribute("aria-label", "Choose date");
7426
+ const wrapper = document.createElement("div");
7427
+ wrapper.className = "vd-suggest-wrapper";
7428
+ wrapper.style.position = "relative";
7429
+ wrapper.style.display = "inline-block";
7430
+ input.parentNode.insertBefore(wrapper, input);
7431
+ wrapper.appendChild(input);
7432
+ wrapper.appendChild(popup);
7433
+ const formatDate = (d) => {
7434
+ const yyyy = d.getFullYear();
7435
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
7436
+ const dd = String(d.getDate()).padStart(2, "0");
7437
+ return format.replace("yyyy", yyyy).replace("mm", mm).replace("dd", dd);
7438
+ };
7439
+ const isDisabled = (d) => {
7440
+ if (minDate && d < minDate) return true;
7441
+ if (maxDate && d > maxDate) return true;
7442
+ return false;
7443
+ };
7444
+ const isSameDay = (a, b) => a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
7445
+ const render = () => {
7446
+ popup.innerHTML = "";
7447
+ const header = document.createElement("div");
7448
+ header.className = "vd-datepicker-header";
7449
+ const prevBtn = document.createElement("button");
7450
+ prevBtn.type = "button";
7451
+ prevBtn.className = "vd-datepicker-prev";
7452
+ prevBtn.innerHTML = "&#8249;";
7453
+ prevBtn.setAttribute("aria-label", "Previous");
7454
+ const nextBtn = document.createElement("button");
7455
+ nextBtn.type = "button";
7456
+ nextBtn.className = "vd-datepicker-next";
7457
+ nextBtn.innerHTML = "&#8250;";
7458
+ nextBtn.setAttribute("aria-label", "Next");
7459
+ const title = document.createElement("span");
7460
+ title.className = "vd-datepicker-title";
7461
+ if (viewMode === "days") {
7462
+ title.textContent = MONTHS[viewMonth] + " " + viewYear;
7463
+ title.addEventListener("click", () => {
7464
+ viewMode = "months";
7465
+ render();
7466
+ });
7467
+ prevBtn.addEventListener("click", () => {
7468
+ viewMonth--;
7469
+ if (viewMonth < 0) {
7470
+ viewMonth = 11;
7471
+ viewYear--;
7472
+ }
7473
+ render();
7474
+ });
7475
+ nextBtn.addEventListener("click", () => {
7476
+ viewMonth++;
7477
+ if (viewMonth > 11) {
7478
+ viewMonth = 0;
7479
+ viewYear++;
7480
+ }
7481
+ render();
7482
+ });
7483
+ } else if (viewMode === "months") {
7484
+ title.textContent = String(viewYear);
7485
+ title.addEventListener("click", () => {
7486
+ viewMode = "years";
7487
+ render();
7488
+ });
7489
+ prevBtn.addEventListener("click", () => {
7490
+ viewYear--;
7491
+ render();
7492
+ });
7493
+ nextBtn.addEventListener("click", () => {
7494
+ viewYear++;
7495
+ render();
7496
+ });
7497
+ } else {
7498
+ const decadeStart = Math.floor(viewYear / 10) * 10;
7499
+ title.textContent = decadeStart + " - " + (decadeStart + 9);
7500
+ prevBtn.addEventListener("click", () => {
7501
+ viewYear -= 10;
7502
+ render();
7503
+ });
7504
+ nextBtn.addEventListener("click", () => {
7505
+ viewYear += 10;
7506
+ render();
7507
+ });
7508
+ }
7509
+ header.appendChild(prevBtn);
7510
+ header.appendChild(title);
7511
+ header.appendChild(nextBtn);
7512
+ popup.appendChild(header);
7513
+ if (viewMode === "days") {
7514
+ const weekdays = document.createElement("div");
7515
+ weekdays.className = "vd-datepicker-weekdays";
7516
+ DAYS.forEach((d) => {
7517
+ const span = document.createElement("span");
7518
+ span.textContent = d;
7519
+ weekdays.appendChild(span);
7520
+ });
7521
+ popup.appendChild(weekdays);
7522
+ const grid = document.createElement("div");
7523
+ grid.className = "vd-datepicker-days";
7524
+ const firstDay = new Date(viewYear, viewMonth, 1).getDay();
7525
+ const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
7526
+ const daysInPrev = new Date(viewYear, viewMonth, 0).getDate();
7527
+ for (let i = firstDay - 1; i >= 0; i--) {
7528
+ const btn = createDayBtn(daysInPrev - i, true);
7529
+ grid.appendChild(btn);
7530
+ }
7531
+ for (let d = 1; d <= daysInMonth; d++) {
7532
+ const date = new Date(viewYear, viewMonth, d);
7533
+ const btn = createDayBtn(d, false, date);
7534
+ grid.appendChild(btn);
7535
+ }
7536
+ const totalCells = firstDay + daysInMonth;
7537
+ const remaining = totalCells % 7 === 0 ? 0 : 7 - totalCells % 7;
7538
+ for (let i = 1; i <= remaining; i++) {
7539
+ const btn = createDayBtn(i, true);
7540
+ grid.appendChild(btn);
7541
+ }
7542
+ popup.appendChild(grid);
7543
+ } else if (viewMode === "months") {
7544
+ const grid = document.createElement("div");
7545
+ grid.className = "vd-datepicker-months";
7546
+ MONTHS.forEach((name, i) => {
7547
+ const btn = document.createElement("button");
7548
+ btn.type = "button";
7549
+ btn.className = "vd-datepicker-month-btn";
7550
+ btn.textContent = name.slice(0, 3);
7551
+ if (selectedDate && selectedDate.getFullYear() === viewYear && selectedDate.getMonth() === i) {
7552
+ btn.classList.add("is-selected");
7553
+ }
7554
+ btn.addEventListener("click", () => {
7555
+ viewMonth = i;
7556
+ viewMode = "days";
7557
+ render();
7558
+ });
7559
+ grid.appendChild(btn);
7560
+ });
7561
+ popup.appendChild(grid);
7562
+ } else {
7563
+ const grid = document.createElement("div");
7564
+ grid.className = "vd-datepicker-years";
7565
+ const decadeStart = Math.floor(viewYear / 10) * 10;
7566
+ for (let y = decadeStart - 1; y <= decadeStart + 10; y++) {
7567
+ const btn = document.createElement("button");
7568
+ btn.type = "button";
7569
+ btn.className = "vd-datepicker-year-btn";
7570
+ btn.textContent = y;
7571
+ if (selectedDate && selectedDate.getFullYear() === y) btn.classList.add("is-selected");
7572
+ if (y < decadeStart || y > decadeStart + 9) btn.style.opacity = "0.4";
7573
+ btn.addEventListener("click", () => {
7574
+ viewYear = y;
7575
+ viewMode = "months";
7576
+ render();
7577
+ });
7578
+ grid.appendChild(btn);
7579
+ }
7580
+ popup.appendChild(grid);
7581
+ }
7582
+ };
7583
+ const createDayBtn = (day, outside, date) => {
7584
+ const btn = document.createElement("button");
7585
+ btn.type = "button";
7586
+ btn.className = "vd-datepicker-day";
7587
+ btn.textContent = day;
7588
+ if (outside) {
7589
+ btn.classList.add("is-outside");
7590
+ btn.tabIndex = -1;
7591
+ return btn;
7592
+ }
7593
+ if (date && isSameDay(date, today)) btn.classList.add("is-today");
7594
+ if (date && isSameDay(date, selectedDate)) btn.classList.add("is-selected");
7595
+ if (date && isDisabled(date)) {
7596
+ btn.classList.add("is-disabled");
7597
+ return btn;
7598
+ }
7599
+ if (date) {
7600
+ btn.addEventListener("click", () => {
7601
+ selectedDate = date;
7602
+ viewYear = date.getFullYear();
7603
+ viewMonth = date.getMonth();
7604
+ input.value = formatDate(date);
7605
+ close();
7606
+ input.dispatchEvent(new CustomEvent("datepicker:select", {
7607
+ detail: { date, formatted: input.value },
7608
+ bubbles: true
7609
+ }));
7610
+ input.dispatchEvent(new Event("change", { bubbles: true }));
7611
+ });
7612
+ }
7613
+ return btn;
7614
+ };
7615
+ const open = () => {
7616
+ render();
7617
+ popup.classList.add("is-open");
7618
+ input.setAttribute("aria-expanded", "true");
7619
+ };
7620
+ const close = () => {
7621
+ popup.classList.remove("is-open");
7622
+ input.setAttribute("aria-expanded", "false");
7623
+ viewMode = "days";
7624
+ };
7625
+ const focusHandler = () => open();
7626
+ const outsideHandler = (e) => {
7627
+ if (!wrapper.contains(e.target)) close();
7628
+ };
7629
+ const escHandler = (e) => {
7630
+ if (e.key === "Escape") close();
7631
+ };
7632
+ input.addEventListener("focus", focusHandler);
7633
+ document.addEventListener("click", outsideHandler, true);
7634
+ document.addEventListener("keydown", escHandler);
7635
+ input.setAttribute("aria-haspopup", "dialog");
7636
+ input.setAttribute("aria-expanded", "false");
7637
+ input.setAttribute("autocomplete", "off");
7638
+ cleanup.push(
7639
+ () => input.removeEventListener("focus", focusHandler),
7640
+ () => document.removeEventListener("click", outsideHandler, true),
7641
+ () => document.removeEventListener("keydown", escHandler)
7642
+ );
7643
+ this.instances.set(input, { cleanup, open, close, popup });
7644
+ },
7645
+ destroy: function(el) {
7646
+ const instance = this.instances.get(el);
7647
+ if (!instance) return;
7648
+ instance.cleanup.forEach((fn) => fn());
7649
+ this.instances.delete(el);
7650
+ },
7651
+ destroyAll: function() {
7652
+ this.instances.forEach((_, el) => this.destroy(el));
7653
+ }
7654
+ };
7655
+ if (typeof window.Vanduo !== "undefined") {
7656
+ window.Vanduo.register("datepicker", Datepicker);
7657
+ }
7658
+ window.VanduoDatepicker = Datepicker;
7659
+ })();
7660
+
7661
+ // js/components/timepicker.js
7662
+ (function() {
7663
+ "use strict";
7664
+ const Timepicker = {
7665
+ instances: /* @__PURE__ */ new Map(),
7666
+ init: function() {
7667
+ const inputs = document.querySelectorAll("[data-vd-timepicker]");
7668
+ inputs.forEach((el) => {
7669
+ if (this.instances.has(el)) return;
7670
+ this.initInstance(el);
7671
+ });
7672
+ },
7673
+ initInstance: function(input) {
7674
+ const cleanup = [];
7675
+ const is24h = input.getAttribute("data-vd-timepicker-format") === "24h";
7676
+ const step = parseInt(input.getAttribute("data-vd-timepicker-step") || "30", 10);
7677
+ let wrapper = input.closest(".vd-suggest-wrapper");
7678
+ if (!wrapper) {
7679
+ wrapper = document.createElement("div");
7680
+ wrapper.style.position = "relative";
7681
+ wrapper.style.display = "inline-block";
7682
+ input.parentNode.insertBefore(wrapper, input);
7683
+ wrapper.appendChild(input);
7684
+ }
7685
+ const popup = document.createElement("div");
7686
+ popup.className = "vd-timepicker-popup";
7687
+ popup.setAttribute("role", "listbox");
7688
+ wrapper.appendChild(popup);
7689
+ const times = [];
7690
+ for (let h = 0; h < 24; h++) {
7691
+ for (let m = 0; m < 60; m += step) {
7692
+ const hh24 = String(h).padStart(2, "0");
7693
+ const mm = String(m).padStart(2, "0");
7694
+ if (is24h) {
7695
+ times.push({ display: hh24 + ":" + mm, value: hh24 + ":" + mm });
7696
+ } else {
7697
+ const period = h < 12 ? "AM" : "PM";
7698
+ const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
7699
+ const display = h12 + ":" + mm + " " + period;
7700
+ times.push({ display, value: hh24 + ":" + mm });
7701
+ }
7702
+ }
7703
+ }
7704
+ const render = () => {
7705
+ popup.innerHTML = "";
7706
+ times.forEach((t) => {
7707
+ const item = document.createElement("div");
7708
+ item.className = "vd-timepicker-item";
7709
+ item.setAttribute("role", "option");
7710
+ item.textContent = t.display;
7711
+ if (input.value === t.value || input.value === t.display) {
7712
+ item.classList.add("is-selected");
7713
+ }
7714
+ item.addEventListener("click", () => {
7715
+ input.value = t.display;
7716
+ popup.querySelectorAll(".vd-timepicker-item").forEach((i) => i.classList.remove("is-selected"));
7717
+ item.classList.add("is-selected");
7718
+ close();
7719
+ input.dispatchEvent(new CustomEvent("timepicker:select", {
7720
+ detail: { display: t.display, value: t.value },
7721
+ bubbles: true
7722
+ }));
7723
+ input.dispatchEvent(new Event("change", { bubbles: true }));
7724
+ });
7725
+ popup.appendChild(item);
7726
+ });
7727
+ };
7728
+ const open = () => {
7729
+ render();
7730
+ popup.classList.add("is-open");
7731
+ input.setAttribute("aria-expanded", "true");
7732
+ const selected = popup.querySelector(".is-selected");
7733
+ if (selected) selected.scrollIntoView({ block: "center" });
7734
+ };
7735
+ const close = () => {
7736
+ popup.classList.remove("is-open");
7737
+ input.setAttribute("aria-expanded", "false");
7738
+ };
7739
+ const focusHandler = () => open();
7740
+ const outsideHandler = (e) => {
7741
+ if (!wrapper.contains(e.target)) close();
7742
+ };
7743
+ const escHandler = (e) => {
7744
+ if (e.key === "Escape") close();
7745
+ };
7746
+ input.addEventListener("focus", focusHandler);
7747
+ document.addEventListener("click", outsideHandler, true);
7748
+ document.addEventListener("keydown", escHandler);
7749
+ input.setAttribute("aria-haspopup", "listbox");
7750
+ input.setAttribute("aria-expanded", "false");
7751
+ input.setAttribute("autocomplete", "off");
7752
+ input.readOnly = true;
7753
+ cleanup.push(
7754
+ () => input.removeEventListener("focus", focusHandler),
7755
+ () => document.removeEventListener("click", outsideHandler, true),
7756
+ () => document.removeEventListener("keydown", escHandler)
7757
+ );
7758
+ this.instances.set(input, { cleanup, open, close });
7759
+ },
7760
+ destroy: function(el) {
7761
+ const instance = this.instances.get(el);
7762
+ if (!instance) return;
7763
+ instance.cleanup.forEach((fn) => fn());
7764
+ this.instances.delete(el);
7765
+ },
7766
+ destroyAll: function() {
7767
+ this.instances.forEach((_, el) => this.destroy(el));
7768
+ }
7769
+ };
7770
+ if (typeof window.Vanduo !== "undefined") {
7771
+ window.Vanduo.register("timepicker", Timepicker);
7772
+ }
7773
+ window.VanduoTimepicker = Timepicker;
7774
+ })();
7775
+
7776
+ // js/components/stepper.js
7777
+ (function() {
7778
+ "use strict";
7779
+ const Stepper = {
7780
+ instances: /* @__PURE__ */ new Map(),
7781
+ init: function() {
7782
+ const steppers = document.querySelectorAll(".vd-stepper");
7783
+ steppers.forEach((el) => {
7784
+ if (this.instances.has(el)) return;
7785
+ this.initInstance(el);
7786
+ });
7787
+ },
7788
+ initInstance: function(el) {
7789
+ const cleanup = [];
7790
+ const items = Array.from(el.querySelectorAll(".vd-stepper-item"));
7791
+ const isClickable = el.classList.contains("vd-stepper-clickable");
7792
+ let currentIndex = items.findIndex((i) => i.classList.contains("is-active"));
7793
+ if (currentIndex === -1) currentIndex = 0;
7794
+ const setStep = (index) => {
7795
+ if (index < 0 || index >= items.length) return;
7796
+ const prev = currentIndex;
7797
+ currentIndex = index;
7798
+ items.forEach((item, i) => {
7799
+ item.classList.remove("is-active", "is-completed");
7800
+ if (i < index) item.classList.add("is-completed");
7801
+ else if (i === index) item.classList.add("is-active");
7802
+ });
7803
+ el.dispatchEvent(new CustomEvent("stepper:change", {
7804
+ detail: { current: index, previous: prev, total: items.length },
7805
+ bubbles: true
7806
+ }));
7807
+ };
7808
+ if (isClickable) {
7809
+ items.forEach((item, i) => {
7810
+ const handler = () => setStep(i);
7811
+ item.addEventListener("click", handler);
7812
+ cleanup.push(() => item.removeEventListener("click", handler));
7813
+ });
7814
+ }
7815
+ setStep(currentIndex);
7816
+ this.instances.set(el, {
7817
+ cleanup,
7818
+ setStep,
7819
+ next: () => setStep(currentIndex + 1),
7820
+ prev: () => setStep(currentIndex - 1),
7821
+ getCurrent: () => currentIndex
7822
+ });
7823
+ },
7824
+ setStep: function(el, index) {
7825
+ const inst = this.instances.get(el);
7826
+ if (inst) inst.setStep(index);
7827
+ },
7828
+ next: function(el) {
7829
+ const inst = this.instances.get(el);
7830
+ if (inst) inst.next();
7831
+ },
7832
+ prev: function(el) {
7833
+ const inst = this.instances.get(el);
7834
+ if (inst) inst.prev();
7835
+ },
7836
+ destroy: function(el) {
7837
+ const inst = this.instances.get(el);
7838
+ if (!inst) return;
7839
+ inst.cleanup.forEach((fn) => fn());
7840
+ this.instances.delete(el);
7841
+ },
7842
+ destroyAll: function() {
7843
+ this.instances.forEach((_, el) => this.destroy(el));
7844
+ }
7845
+ };
7846
+ if (typeof window.Vanduo !== "undefined") {
7847
+ window.Vanduo.register("stepper", Stepper);
7848
+ }
7849
+ window.VanduoStepper = Stepper;
7850
+ })();
7851
+
7852
+ // js/components/rating.js
7853
+ (function() {
7854
+ "use strict";
7855
+ const Rating = {
7856
+ instances: /* @__PURE__ */ new Map(),
7857
+ init: function() {
7858
+ const ratings = document.querySelectorAll("[data-vd-rating]");
7859
+ ratings.forEach((el) => {
7860
+ if (this.instances.has(el)) return;
7861
+ this.initInstance(el);
7862
+ });
7863
+ },
7864
+ initInstance: function(el) {
7865
+ const cleanup = [];
7866
+ const max = parseInt(el.getAttribute("data-vd-rating-max") || "5", 10);
7867
+ const initialValue = parseFloat(el.getAttribute("data-vd-rating-value") || "0");
7868
+ const readonly = el.classList.contains("vd-rating-readonly") || el.hasAttribute("data-vd-rating-readonly");
7869
+ let currentValue = initialValue;
7870
+ el.classList.add("vd-rating");
7871
+ el.setAttribute("role", "radiogroup");
7872
+ el.setAttribute("aria-label", el.getAttribute("aria-label") || "Rating");
7873
+ el.innerHTML = "";
7874
+ const stars = [];
7875
+ for (let i = 1; i <= max; i++) {
7876
+ const star = document.createElement("button");
7877
+ star.type = "button";
7878
+ star.className = "vd-rating-star";
7879
+ star.setAttribute("role", "radio");
7880
+ star.setAttribute("aria-label", i + " star" + (i > 1 ? "s" : ""));
7881
+ star.setAttribute("aria-checked", i <= currentValue ? "true" : "false");
7882
+ if (readonly) star.tabIndex = -1;
7883
+ stars.push(star);
7884
+ el.appendChild(star);
7885
+ }
7886
+ const valueDisplay = document.createElement("span");
7887
+ valueDisplay.className = "vd-rating-value";
7888
+ valueDisplay.textContent = currentValue > 0 ? currentValue.toString() : "";
7889
+ el.appendChild(valueDisplay);
7890
+ const updateStars = (value) => {
7891
+ stars.forEach((star, i) => {
7892
+ star.classList.remove("is-active", "is-half");
7893
+ const starNum = i + 1;
7894
+ if (starNum <= Math.floor(value)) {
7895
+ star.classList.add("is-active");
7896
+ } else if (starNum - 0.5 <= value) {
7897
+ star.classList.add("is-half");
7898
+ }
7899
+ star.setAttribute("aria-checked", starNum <= value ? "true" : "false");
7900
+ });
7901
+ valueDisplay.textContent = value > 0 ? value.toString() : "";
7902
+ };
7903
+ updateStars(currentValue);
7904
+ if (!readonly) {
7905
+ stars.forEach((star, i) => {
7906
+ const enterHandler = () => {
7907
+ stars.forEach((s, j) => {
7908
+ s.classList.toggle("is-hovered", j <= i);
7909
+ });
7910
+ };
7911
+ const leaveHandler = () => {
7912
+ stars.forEach((s) => s.classList.remove("is-hovered"));
7913
+ };
7914
+ const clickHandler = () => {
7915
+ currentValue = i + 1;
7916
+ el.setAttribute("data-vd-rating-value", currentValue);
7917
+ updateStars(currentValue);
7918
+ el.dispatchEvent(new CustomEvent("rating:change", {
7919
+ detail: { value: currentValue, max },
7920
+ bubbles: true
7921
+ }));
7922
+ };
7923
+ star.addEventListener("mouseenter", enterHandler);
7924
+ star.addEventListener("mouseleave", leaveHandler);
7925
+ star.addEventListener("click", clickHandler);
7926
+ cleanup.push(
7927
+ () => star.removeEventListener("mouseenter", enterHandler),
7928
+ () => star.removeEventListener("mouseleave", leaveHandler),
7929
+ () => star.removeEventListener("click", clickHandler)
7930
+ );
7931
+ });
7932
+ const keyHandler = (e) => {
7933
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
7934
+ e.preventDefault();
7935
+ if (currentValue < max) {
7936
+ currentValue++;
7937
+ updateStars(currentValue);
7938
+ stars[currentValue - 1].focus();
7939
+ el.dispatchEvent(new CustomEvent("rating:change", { detail: { value: currentValue, max }, bubbles: true }));
7940
+ }
7941
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
7942
+ e.preventDefault();
7943
+ if (currentValue > 1) {
7944
+ currentValue--;
7945
+ updateStars(currentValue);
7946
+ stars[currentValue - 1].focus();
7947
+ el.dispatchEvent(new CustomEvent("rating:change", { detail: { value: currentValue, max }, bubbles: true }));
7948
+ }
7949
+ }
7950
+ };
7951
+ el.addEventListener("keydown", keyHandler);
7952
+ cleanup.push(() => el.removeEventListener("keydown", keyHandler));
7953
+ }
7954
+ this.instances.set(el, {
7955
+ cleanup,
7956
+ getValue: () => currentValue,
7957
+ setValue: (v) => {
7958
+ currentValue = v;
7959
+ updateStars(v);
7960
+ }
7961
+ });
7962
+ },
7963
+ getValue: function(el) {
7964
+ const inst = this.instances.get(el);
7965
+ return inst ? inst.getValue() : 0;
7966
+ },
7967
+ setValue: function(el, value) {
7968
+ const inst = this.instances.get(el);
7969
+ if (inst) inst.setValue(value);
7970
+ },
7971
+ destroy: function(el) {
7972
+ const inst = this.instances.get(el);
7973
+ if (!inst) return;
7974
+ inst.cleanup.forEach((fn) => fn());
7975
+ this.instances.delete(el);
7976
+ },
7977
+ destroyAll: function() {
7978
+ this.instances.forEach((_, el) => this.destroy(el));
7979
+ }
7980
+ };
7981
+ if (typeof window.Vanduo !== "undefined") {
7982
+ window.Vanduo.register("rating", Rating);
7983
+ }
7984
+ window.VanduoRating = Rating;
7985
+ })();
7986
+
7987
+ // js/components/transfer.js
7988
+ (function() {
7989
+ "use strict";
7990
+ const Transfer = {
7991
+ instances: /* @__PURE__ */ new Map(),
7992
+ init: function() {
7993
+ const transfers = document.querySelectorAll("[data-vd-transfer]");
7994
+ transfers.forEach((el) => {
7995
+ if (this.instances.has(el)) return;
7996
+ this.initInstance(el);
7997
+ });
7998
+ },
7999
+ initInstance: function(el) {
8000
+ const cleanup = [];
8001
+ el.classList.add("vd-transfer");
8002
+ let sourceData, targetData;
8003
+ try {
8004
+ const raw = JSON.parse(el.getAttribute("data-vd-transfer") || "[]");
8005
+ sourceData = raw.map((item, i) => ({
8006
+ id: item.id || "item-" + i,
8007
+ label: item.label || item.text || String(item),
8008
+ selected: false
8009
+ }));
8010
+ } catch (_e) {
8011
+ sourceData = [];
8012
+ }
8013
+ targetData = [];
8014
+ const sourceSelected = /* @__PURE__ */ new Set();
8015
+ const targetSelected = /* @__PURE__ */ new Set();
8016
+ const render = () => {
8017
+ el.innerHTML = "";
8018
+ const sourcePanel = createPanel("Source", sourceData, sourceSelected, "source");
8019
+ const actions = document.createElement("div");
8020
+ actions.className = "vd-transfer-actions";
8021
+ const moveRightBtn = document.createElement("button");
8022
+ moveRightBtn.type = "button";
8023
+ moveRightBtn.className = "vd-transfer-btn";
8024
+ moveRightBtn.innerHTML = "&#8250;";
8025
+ moveRightBtn.setAttribute("aria-label", "Move to target");
8026
+ moveRightBtn.disabled = sourceSelected.size === 0;
8027
+ moveRightBtn.addEventListener("click", () => moveRight());
8028
+ const moveLeftBtn = document.createElement("button");
8029
+ moveLeftBtn.type = "button";
8030
+ moveLeftBtn.className = "vd-transfer-btn";
8031
+ moveLeftBtn.innerHTML = "&#8249;";
8032
+ moveLeftBtn.setAttribute("aria-label", "Move to source");
8033
+ moveLeftBtn.disabled = targetSelected.size === 0;
8034
+ moveLeftBtn.addEventListener("click", () => moveLeft());
8035
+ actions.appendChild(moveRightBtn);
8036
+ actions.appendChild(moveLeftBtn);
8037
+ const targetPanel = createPanel("Target", targetData, targetSelected, "target");
8038
+ el.appendChild(sourcePanel);
8039
+ el.appendChild(actions);
8040
+ el.appendChild(targetPanel);
8041
+ };
8042
+ const createPanel = (title, data, selected, _side) => {
8043
+ const panel = document.createElement("div");
8044
+ panel.className = "vd-transfer-panel";
8045
+ const header = document.createElement("div");
8046
+ header.className = "vd-transfer-header";
8047
+ const titleSpan = document.createElement("span");
8048
+ titleSpan.textContent = title;
8049
+ const count = document.createElement("span");
8050
+ count.className = "vd-transfer-count";
8051
+ count.textContent = selected.size + "/" + data.length;
8052
+ header.appendChild(titleSpan);
8053
+ header.appendChild(count);
8054
+ panel.appendChild(header);
8055
+ const searchDiv = document.createElement("div");
8056
+ searchDiv.className = "vd-transfer-search";
8057
+ const searchInput = document.createElement("input");
8058
+ searchInput.type = "text";
8059
+ searchInput.placeholder = "Search...";
8060
+ searchInput.setAttribute("aria-label", "Search " + title.toLowerCase());
8061
+ searchDiv.appendChild(searchInput);
8062
+ panel.appendChild(searchDiv);
8063
+ const list = document.createElement("ul");
8064
+ list.className = "vd-transfer-list";
8065
+ list.setAttribute("role", "listbox");
8066
+ const renderList = (filter) => {
8067
+ list.innerHTML = "";
8068
+ const filtered = filter ? data.filter((d) => {
8069
+ const label = (d.label || d.text || String(d)).toLowerCase();
8070
+ return label.includes(filter.toLowerCase());
8071
+ }) : data;
8072
+ filtered.forEach((item) => {
8073
+ const li = document.createElement("li");
8074
+ li.className = "vd-transfer-item";
8075
+ li.setAttribute("role", "option");
8076
+ if (selected.has(item.id)) li.classList.add("is-selected");
8077
+ const checkbox = document.createElement("input");
8078
+ checkbox.type = "checkbox";
8079
+ checkbox.checked = selected.has(item.id);
8080
+ checkbox.setAttribute("aria-label", item.label);
8081
+ const label = document.createElement("span");
8082
+ label.textContent = item.label;
8083
+ li.addEventListener("click", () => {
8084
+ if (selected.has(item.id)) selected.delete(item.id);
8085
+ else selected.add(item.id);
8086
+ render();
8087
+ });
8088
+ li.appendChild(checkbox);
8089
+ li.appendChild(label);
8090
+ list.appendChild(li);
8091
+ });
8092
+ };
8093
+ searchInput.addEventListener("input", () => renderList(searchInput.value));
8094
+ renderList("");
8095
+ panel.appendChild(list);
8096
+ return panel;
8097
+ };
8098
+ const moveRight = () => {
8099
+ const toMove = sourceData.filter((d) => sourceSelected.has(d.id));
8100
+ sourceData = sourceData.filter((d) => !sourceSelected.has(d.id));
8101
+ targetData = targetData.concat(toMove);
8102
+ sourceSelected.clear();
8103
+ render();
8104
+ fireChange();
8105
+ };
8106
+ const moveLeft = () => {
8107
+ const toMove = targetData.filter((d) => targetSelected.has(d.id));
8108
+ targetData = targetData.filter((d) => !targetSelected.has(d.id));
8109
+ sourceData = sourceData.concat(toMove);
8110
+ targetSelected.clear();
8111
+ render();
8112
+ fireChange();
8113
+ };
8114
+ const fireChange = () => {
8115
+ el.dispatchEvent(new CustomEvent("transfer:change", {
8116
+ detail: {
8117
+ source: sourceData.map((d) => d.id),
8118
+ target: targetData.map((d) => d.id)
8119
+ },
8120
+ bubbles: true
8121
+ }));
8122
+ };
8123
+ render();
8124
+ this.instances.set(el, {
8125
+ cleanup,
8126
+ getTarget: () => targetData.map((d) => d.id),
8127
+ getSource: () => sourceData.map((d) => d.id)
8128
+ });
8129
+ },
8130
+ getSelected: function(el) {
8131
+ const inst = this.instances.get(el);
8132
+ return inst ? inst.getTarget() : [];
8133
+ },
8134
+ destroy: function(el) {
8135
+ const inst = this.instances.get(el);
8136
+ if (!inst) return;
8137
+ inst.cleanup.forEach((fn) => fn());
8138
+ el.innerHTML = "";
8139
+ this.instances.delete(el);
8140
+ },
8141
+ destroyAll: function() {
8142
+ this.instances.forEach((_, el) => this.destroy(el));
8143
+ }
8144
+ };
8145
+ if (typeof window.Vanduo !== "undefined") {
8146
+ window.Vanduo.register("transfer", Transfer);
8147
+ }
8148
+ window.VanduoTransfer = Transfer;
8149
+ })();
8150
+
8151
+ // js/components/tree.js
8152
+ (function() {
8153
+ "use strict";
8154
+ const Tree = {
8155
+ instances: /* @__PURE__ */ new Map(),
8156
+ init: function() {
8157
+ const trees = document.querySelectorAll("[data-vd-tree]");
8158
+ trees.forEach((el) => {
8159
+ if (this.instances.has(el)) return;
8160
+ this.initInstance(el);
8161
+ });
8162
+ },
8163
+ initInstance: function(el) {
8164
+ const cleanup = [];
8165
+ const cascade = el.getAttribute("data-vd-tree-cascade") !== "false";
8166
+ let data;
8167
+ try {
8168
+ data = JSON.parse(el.getAttribute("data-vd-tree") || "[]");
8169
+ } catch (_e) {
8170
+ data = [];
8171
+ }
8172
+ el.classList.add("vd-tree");
8173
+ el.setAttribute("role", "tree");
8174
+ const render = (items, parent) => {
8175
+ parent.innerHTML = "";
8176
+ items.forEach((item) => {
8177
+ const node = document.createElement("li");
8178
+ node.className = "vd-tree-node";
8179
+ node.setAttribute("role", "treeitem");
8180
+ node.setAttribute("aria-expanded", item.open ? "true" : "false");
8181
+ if (item.open) node.classList.add("is-open");
8182
+ const content = document.createElement("div");
8183
+ content.className = "vd-tree-node-content";
8184
+ if (item.children && item.children.length > 0) {
8185
+ const toggle = document.createElement("button");
8186
+ toggle.type = "button";
8187
+ toggle.className = "vd-tree-toggle";
8188
+ toggle.setAttribute("aria-label", "Toggle");
8189
+ toggle.addEventListener("click", (e) => {
8190
+ e.stopPropagation();
8191
+ item.open = !item.open;
8192
+ node.classList.toggle("is-open");
8193
+ node.setAttribute("aria-expanded", item.open ? "true" : "false");
8194
+ });
8195
+ content.appendChild(toggle);
8196
+ } else {
8197
+ const ph = document.createElement("span");
8198
+ ph.className = "vd-tree-toggle-placeholder";
8199
+ content.appendChild(ph);
8200
+ }
8201
+ if (el.hasAttribute("data-vd-tree-checkbox")) {
8202
+ const cb = document.createElement("input");
8203
+ cb.type = "checkbox";
8204
+ cb.className = "vd-tree-checkbox";
8205
+ cb.checked = !!item.checked;
8206
+ cb.setAttribute("aria-label", item.label);
8207
+ cb.addEventListener("change", (e) => {
8208
+ e.stopPropagation();
8209
+ item.checked = cb.checked;
8210
+ if (cascade && item.children) {
8211
+ setChildChecked(item.children, cb.checked);
8212
+ render(data, el);
8213
+ }
8214
+ el.dispatchEvent(new CustomEvent("tree:check", {
8215
+ detail: { id: item.id, checked: cb.checked, label: item.label },
8216
+ bubbles: true
8217
+ }));
8218
+ });
8219
+ content.appendChild(cb);
8220
+ }
8221
+ if (item.icon) {
8222
+ const icon = document.createElement("span");
8223
+ icon.className = "vd-tree-icon " + item.icon;
8224
+ content.appendChild(icon);
8225
+ }
8226
+ const label = document.createElement("span");
8227
+ label.className = "vd-tree-label";
8228
+ label.textContent = item.label || "";
8229
+ content.appendChild(label);
8230
+ node.appendChild(content);
8231
+ if (item.children && item.children.length > 0) {
8232
+ const childList = document.createElement("ul");
8233
+ childList.className = "vd-tree-children";
8234
+ childList.setAttribute("role", "group");
8235
+ render(item.children, childList);
8236
+ node.appendChild(childList);
8237
+ }
8238
+ parent.appendChild(node);
8239
+ });
8240
+ };
8241
+ const setChildChecked = (items, checked) => {
8242
+ items.forEach((item) => {
8243
+ item.checked = checked;
8244
+ if (item.children) setChildChecked(item.children, checked);
8245
+ });
8246
+ };
8247
+ const keyHandler = (e) => {
8248
+ const focused = document.activeElement;
8249
+ if (!el.contains(focused)) return;
8250
+ const nodes = Array.from(el.querySelectorAll(".vd-tree-node-content"));
8251
+ const idx = nodes.indexOf(focused.closest(".vd-tree-node-content"));
8252
+ if (idx === -1) return;
8253
+ switch (e.key) {
8254
+ case "ArrowDown":
8255
+ e.preventDefault();
8256
+ if (idx < nodes.length - 1) {
8257
+ const next = nodes[idx + 1].querySelector(".vd-tree-toggle, .vd-tree-label");
8258
+ if (next) next.focus();
8259
+ }
8260
+ break;
8261
+ case "ArrowUp":
8262
+ e.preventDefault();
8263
+ if (idx > 0) {
8264
+ const prev = nodes[idx - 1].querySelector(".vd-tree-toggle, .vd-tree-label");
8265
+ if (prev) prev.focus();
8266
+ }
8267
+ break;
8268
+ }
8269
+ };
8270
+ el.addEventListener("keydown", keyHandler);
8271
+ cleanup.push(() => el.removeEventListener("keydown", keyHandler));
8272
+ render(data, el);
8273
+ this.instances.set(el, {
8274
+ cleanup,
8275
+ getData: () => data,
8276
+ getChecked: () => {
8277
+ const checked = [];
8278
+ const collect = (items) => {
8279
+ items.forEach((i) => {
8280
+ if (i.checked) checked.push(i.id || i.label);
8281
+ if (i.children) collect(i.children);
8282
+ });
8283
+ };
8284
+ collect(data);
8285
+ return checked;
8286
+ }
8287
+ });
8288
+ },
8289
+ getChecked: function(el) {
8290
+ const inst = this.instances.get(el);
8291
+ return inst ? inst.getChecked() : [];
8292
+ },
8293
+ destroy: function(el) {
8294
+ const inst = this.instances.get(el);
8295
+ if (!inst) return;
8296
+ inst.cleanup.forEach((fn) => fn());
8297
+ el.innerHTML = "";
8298
+ this.instances.delete(el);
8299
+ },
8300
+ destroyAll: function() {
8301
+ this.instances.forEach((_, el) => this.destroy(el));
8302
+ }
8303
+ };
8304
+ if (typeof window.Vanduo !== "undefined") {
8305
+ window.Vanduo.register("tree", Tree);
8306
+ }
8307
+ window.VanduoTree = Tree;
8308
+ })();
8309
+
8310
+ // js/components/spotlight.js
8311
+ (function() {
8312
+ "use strict";
8313
+ const Spotlight = {
8314
+ _active: false,
8315
+ _steps: [],
8316
+ _currentStep: 0,
8317
+ _elements: {},
8318
+ _cleanup: [],
8319
+ _boundTriggers: /* @__PURE__ */ new WeakMap(),
8320
+ _triggerElement: null,
8321
+ init: function() {
8322
+ const triggers = document.querySelectorAll("[data-vd-spotlight]");
8323
+ triggers.forEach((trigger) => {
8324
+ if (this._boundTriggers.has(trigger)) return;
8325
+ const clickHandler = (event) => {
8326
+ event.preventDefault();
8327
+ const steps = this._parseSteps(trigger.getAttribute("data-vd-spotlight"));
8328
+ if (steps.length === 0) return;
8329
+ this.start(steps, { trigger });
8330
+ };
8331
+ trigger.addEventListener("click", clickHandler);
8332
+ this._boundTriggers.set(trigger, clickHandler);
8333
+ });
8334
+ },
8335
+ _parseSteps: function(raw) {
8336
+ if (typeof raw !== "string" || raw.trim() === "") return [];
8337
+ try {
8338
+ const parsed = JSON.parse(raw);
8339
+ return this._normalizeSteps(parsed);
8340
+ } catch (error) {
8341
+ console.error("VanduoSpotlight: invalid data-vd-spotlight payload.", error);
8342
+ return [];
8343
+ }
8344
+ },
8345
+ _normalizeStep: function(step) {
8346
+ if (!step || typeof step !== "object") return null;
8347
+ const target = step.target;
8348
+ const hasSelectorTarget = typeof target === "string" && target.trim() !== "";
8349
+ const hasElementTarget = typeof Element !== "undefined" && target instanceof Element;
8350
+ if (!hasSelectorTarget && !hasElementTarget) return null;
8351
+ const title = typeof step.title === "string" ? step.title : "";
8352
+ const description = typeof step.description === "string" ? step.description : typeof step.content === "string" ? step.content : "";
8353
+ return {
8354
+ target,
8355
+ title,
8356
+ description
8357
+ };
8358
+ },
8359
+ _normalizeSteps: function(steps) {
8360
+ if (!Array.isArray(steps)) return [];
8361
+ return steps.map((step) => this._normalizeStep(step)).filter(Boolean);
8362
+ },
8363
+ start: function(steps, options) {
8364
+ if (this._active) this.stop();
8365
+ const normalizedSteps = this._normalizeSteps(steps);
8366
+ if (normalizedSteps.length === 0) return;
8367
+ const startOptions = options || {};
8368
+ this._steps = normalizedSteps;
8369
+ this._currentStep = 0;
8370
+ this._active = true;
8371
+ this._triggerElement = startOptions.trigger || (document.activeElement instanceof HTMLElement ? document.activeElement : null);
8372
+ const overlay = document.createElement("div");
8373
+ overlay.className = "vd-spotlight-overlay";
8374
+ overlay.setAttribute("aria-hidden", "true");
8375
+ document.body.appendChild(overlay);
8376
+ const tooltip = document.createElement("div");
8377
+ tooltip.className = "vd-spotlight-tooltip";
8378
+ tooltip.setAttribute("role", "dialog");
8379
+ tooltip.setAttribute("aria-modal", "true");
8380
+ tooltip.tabIndex = -1;
8381
+ document.body.appendChild(tooltip);
8382
+ this._elements = { overlay, tooltip };
8383
+ const escHandler = (e) => {
8384
+ if (e.key === "Escape") this.stop();
8385
+ };
8386
+ document.addEventListener("keydown", escHandler);
8387
+ this._cleanup.push(() => document.removeEventListener("keydown", escHandler));
8388
+ overlay.addEventListener("click", () => this.stop());
8389
+ this._showStep(this._currentStep);
8390
+ },
8391
+ _showStep: function(index) {
8392
+ const step = this._steps[index];
8393
+ if (!step) return;
8394
+ const target = typeof step.target === "string" ? document.querySelector(step.target) : step.target;
8395
+ const { tooltip } = this._elements;
8396
+ document.querySelectorAll(".vd-spotlight-target").forEach((el) => {
8397
+ el.classList.remove("vd-spotlight-target");
8398
+ });
8399
+ if (target) {
8400
+ target.classList.add("vd-spotlight-target");
8401
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
8402
+ }
8403
+ const total = this._steps.length;
8404
+ tooltip.innerHTML = "";
8405
+ tooltip.removeAttribute("aria-labelledby");
8406
+ tooltip.removeAttribute("aria-describedby");
8407
+ if (step.title) {
8408
+ const title = document.createElement("h4");
8409
+ title.className = "vd-spotlight-title";
8410
+ title.id = "vd-spotlight-title-" + index + "-" + Date.now();
8411
+ title.textContent = step.title;
8412
+ tooltip.appendChild(title);
8413
+ tooltip.setAttribute("aria-labelledby", title.id);
8414
+ }
8415
+ if (step.description) {
8416
+ const desc = document.createElement("p");
8417
+ desc.className = "vd-spotlight-description";
8418
+ desc.id = "vd-spotlight-description-" + index + "-" + Date.now();
8419
+ desc.textContent = step.description;
8420
+ tooltip.appendChild(desc);
8421
+ tooltip.setAttribute("aria-describedby", desc.id);
8422
+ }
8423
+ const footer = document.createElement("div");
8424
+ footer.className = "vd-spotlight-footer";
8425
+ footer.setAttribute("aria-label", "Step " + (index + 1) + " of " + total);
8426
+ const counter = document.createElement("span");
8427
+ counter.className = "vd-spotlight-counter";
8428
+ counter.textContent = index + 1 + " / " + total;
8429
+ const actions = document.createElement("div");
8430
+ actions.className = "vd-spotlight-actions";
8431
+ if (index > 0) {
8432
+ const prevBtn = document.createElement("button");
8433
+ prevBtn.type = "button";
8434
+ prevBtn.className = "vd-spotlight-btn";
8435
+ prevBtn.textContent = "Back";
8436
+ prevBtn.addEventListener("click", () => this.prev());
8437
+ actions.appendChild(prevBtn);
8438
+ }
8439
+ const skipBtn = document.createElement("button");
8440
+ skipBtn.type = "button";
8441
+ skipBtn.className = "vd-spotlight-btn";
8442
+ skipBtn.textContent = "Skip";
8443
+ skipBtn.addEventListener("click", () => this.stop());
8444
+ actions.appendChild(skipBtn);
8445
+ if (index < total - 1) {
8446
+ const nextBtn = document.createElement("button");
8447
+ nextBtn.type = "button";
8448
+ nextBtn.className = "vd-spotlight-btn vd-spotlight-btn-primary";
8449
+ nextBtn.textContent = "Next";
8450
+ nextBtn.addEventListener("click", () => this.next());
8451
+ actions.appendChild(nextBtn);
8452
+ } else {
8453
+ const doneBtn = document.createElement("button");
8454
+ doneBtn.type = "button";
8455
+ doneBtn.className = "vd-spotlight-btn vd-spotlight-btn-primary";
8456
+ doneBtn.textContent = "Done";
8457
+ doneBtn.addEventListener("click", () => this.stop());
8458
+ actions.appendChild(doneBtn);
8459
+ }
8460
+ footer.appendChild(counter);
8461
+ footer.appendChild(actions);
8462
+ tooltip.appendChild(footer);
8463
+ if (target) {
8464
+ requestAnimationFrame(() => {
8465
+ const rect = target.getBoundingClientRect();
8466
+ const tRect = tooltip.getBoundingClientRect();
8467
+ let top = rect.bottom + 12 + window.scrollY;
8468
+ let left = rect.left + (rect.width - tRect.width) / 2 + window.scrollX;
8469
+ left = Math.max(8, Math.min(left, window.innerWidth - tRect.width - 8));
8470
+ if (top + tRect.height > window.innerHeight + window.scrollY) {
8471
+ top = rect.top - tRect.height - 12 + window.scrollY;
8472
+ }
8473
+ tooltip.style.top = top + "px";
8474
+ tooltip.style.left = left + "px";
8475
+ });
8476
+ }
8477
+ document.dispatchEvent(new CustomEvent("spotlight:step", {
8478
+ detail: { index, step: index, total, data: step }
8479
+ }));
8480
+ },
8481
+ next: function() {
8482
+ if (this._currentStep < this._steps.length - 1) {
8483
+ this._currentStep++;
8484
+ this._showStep(this._currentStep);
8485
+ }
8486
+ },
8487
+ prev: function() {
8488
+ if (this._currentStep > 0) {
8489
+ this._currentStep--;
8490
+ this._showStep(this._currentStep);
8491
+ }
8492
+ },
8493
+ stop: function() {
8494
+ if (!this._active) return;
8495
+ const total = this._steps.length;
8496
+ const detail = {
8497
+ completedSteps: total === 0 ? 0 : Math.min(this._currentStep + 1, total),
8498
+ total,
8499
+ completed: total > 0 && this._currentStep >= total - 1
8500
+ };
8501
+ this._active = false;
8502
+ document.querySelectorAll(".vd-spotlight-target").forEach((el) => {
8503
+ el.classList.remove("vd-spotlight-target");
8504
+ });
8505
+ if (this._elements.overlay && this._elements.overlay.parentNode) {
8506
+ this._elements.overlay.parentNode.removeChild(this._elements.overlay);
8507
+ }
8508
+ if (this._elements.tooltip && this._elements.tooltip.parentNode) {
8509
+ this._elements.tooltip.parentNode.removeChild(this._elements.tooltip);
8510
+ }
8511
+ this._cleanup.forEach((fn) => fn());
8512
+ this._cleanup = [];
8513
+ this._elements = {};
8514
+ this._steps = [];
8515
+ this._currentStep = 0;
8516
+ if (this._triggerElement && this._triggerElement.isConnected && typeof this._triggerElement.focus === "function") {
8517
+ this._triggerElement.focus();
8518
+ }
8519
+ this._triggerElement = null;
8520
+ document.dispatchEvent(new CustomEvent("spotlight:end", { detail }));
8521
+ },
8522
+ destroyAll: function() {
8523
+ this.stop();
8524
+ }
8525
+ };
8526
+ if (typeof window.Vanduo !== "undefined") {
8527
+ window.Vanduo.register("spotlight", Spotlight);
8528
+ }
8529
+ window.VanduoSpotlight = Spotlight;
8530
+ })();
8531
+
6384
8532
  // js/index.js
6385
8533
  var Vanduo = window.Vanduo;
6386
8534
  var index_default = Vanduo;