@vanduo-oss/framework 1.2.6 → 1.2.8

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