@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.
- package/README.md +31 -5
- package/css/components/affix.css +53 -0
- package/css/components/bubble.css +165 -0
- package/css/components/datepicker.css +216 -0
- package/css/components/fab.css +225 -0
- package/css/components/flow.css +265 -0
- package/css/components/rating.css +112 -0
- package/css/components/ripple.css +63 -0
- package/css/components/sidenav.css +70 -0
- package/css/components/spotlight.css +119 -0
- package/css/components/stepper.css +176 -0
- package/css/components/suggest.css +119 -0
- package/css/components/timeline.css +201 -0
- package/css/components/timepicker.css +80 -0
- package/css/components/transfer.css +165 -0
- package/css/components/tree.css +173 -0
- package/css/components/waypoint.css +59 -0
- package/css/vanduo.css +17 -0
- package/dist/build-info.json +3 -3
- package/dist/vanduo.cjs.js +2152 -4
- package/dist/vanduo.cjs.js.map +4 -4
- package/dist/vanduo.cjs.min.js +5 -5
- package/dist/vanduo.cjs.min.js.map +4 -4
- package/dist/vanduo.css +1943 -1
- package/dist/vanduo.css.map +1 -1
- package/dist/vanduo.esm.js +2152 -4
- package/dist/vanduo.esm.js.map +4 -4
- package/dist/vanduo.esm.min.js +5 -5
- package/dist/vanduo.esm.min.js.map +4 -4
- package/dist/vanduo.js +2152 -4
- package/dist/vanduo.js.map +4 -4
- package/dist/vanduo.min.css +2 -2
- package/dist/vanduo.min.css.map +1 -1
- package/dist/vanduo.min.js +5 -5
- package/dist/vanduo.min.js.map +4 -4
- package/js/components/affix.js +129 -0
- package/js/components/bubble.js +203 -0
- package/js/components/datepicker.js +287 -0
- package/js/components/flow.js +264 -0
- package/js/components/rating.js +160 -0
- package/js/components/ripple.js +74 -0
- package/js/components/sidenav.js +9 -2
- package/js/components/spotlight.js +295 -0
- package/js/components/stepper.js +97 -0
- package/js/components/suggest.js +219 -0
- package/js/components/timepicker.js +142 -0
- package/js/components/transfer.js +206 -0
- package/js/components/tree.js +191 -0
- package/js/components/validate.js +185 -0
- package/js/components/waypoint.js +120 -0
- package/js/index.js +16 -0
- package/package.json +1 -1
package/dist/vanduo.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Vanduo v1.2.
|
|
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.
|
|
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 = "‹";
|
|
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 = "›";
|
|
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 = "›";
|
|
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 = "‹";
|
|
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;
|