(function (root, factory) { "use strict"; const api = factory(); if (typeof module === "object" && module.exports) { module.exports = api; } if (root && root.document) { root.AcademicArcadeParentFeedback = api; api.bootstrap({ globalObject: root }); } })(typeof globalThis !== "undefined" ? globalThis : this, function () { "use strict"; const AUTO_ADVANCE_DELAY = 6000; const MANUAL_RESTART_DELAY = 10000; function calculatePositions(maximumScroll, step) { const maximum = Math.max(0, Number(maximumScroll) || 0); const increment = Math.max(0, Number(step) || 0); if (maximum <= 1 || increment <= 0) return [0]; const positions = [0]; for (let position = increment; position < maximum - 1; position += increment) { positions.push(position); } if (Math.abs(positions[positions.length - 1] - maximum) > 1) { positions.push(maximum); } return positions; } function nearestPositionIndex(positions, currentPosition) { if (!positions || !positions.length) return 0; const current = Number(currentPosition) || 0; let nearestIndex = 0; let nearestDistance = Math.abs(positions[0] - current); positions.forEach(function (position, index) { const distance = Math.abs(position - current); if (distance < nearestDistance) { nearestDistance = distance; nearestIndex = index; } }); return nearestIndex; } function createCarousel(options) { const settings = options || {}; const runtime = settings.globalObject || (typeof window !== "undefined" ? window : null); const documentObject = settings.document || (runtime && runtime.document); if (!runtime || !documentObject) return null; const section = settings.section || documentObject.querySelector(".parent-feedback-section"); const track = section && section.querySelector("[data-parent-feedback-track]"); const previousButton = section && section.querySelector("[data-parent-feedback-previous]"); const nextButton = section && section.querySelector("[data-parent-feedback-next]"); if (!section || !track || !previousButton || !nextButton) return null; const indicatorContainer = section.querySelector( "[data-parent-feedback-indicators]" ); const reducedMotion = runtime.matchMedia("(prefers-reduced-motion: reduce)"); let autoAdvanceTimer = null; let restartTimer = null; let pointerIsInside = false; let positions = []; let indicatorDots = []; function cardStep() { const card = track.querySelector(".parent-feedback-card"); if (!card) return track.clientWidth; const trackStyle = runtime.getComputedStyle(track); const gap = parseFloat(trackStyle.columnGap || trackStyle.gap) || 0; return card.getBoundingClientRect().width + gap; } function updateIndicators(position) { if (!indicatorDots.length) return; const activeIndex = nearestPositionIndex( positions, position === undefined ? track.scrollLeft : position ); indicatorDots.forEach(function (dot, index) { dot.classList.toggle("is-active", index === activeIndex); }); } function rebuildIndicators() { const previousIndex = positions.length ? nearestPositionIndex(positions, track.scrollLeft) : 0; const nextPositions = calculatePositions( Math.max(0, track.scrollWidth - track.clientWidth), cardStep() ); const nextIndex = Math.min(previousIndex, nextPositions.length - 1); const nextPosition = nextPositions[nextIndex] || 0; const shouldRealign = positions.length > 0 && Math.abs(track.scrollLeft - nextPosition) > 1; positions = nextPositions; if (shouldRealign) { track.scrollTo({ left: nextPosition, behavior: "auto" }); } if (!indicatorContainer) return; indicatorDots = positions.map(function () { const dot = documentObject.createElement("span"); dot.className = "parent-feedback-indicator"; return dot; }); indicatorContainer.replaceChildren.apply( indicatorContainer, indicatorDots ); indicatorContainer.hidden = positions.length <= 1; updateIndicators(nextPosition); } function scrollByOneCard(direction) { if (!positions.length) rebuildIndicators(); const currentIndex = nearestPositionIndex(positions, track.scrollLeft); const positionCount = positions.length; const nextIndex = ( currentIndex + (direction < 0 ? -1 : 1) + positionCount ) % positionCount; const nextPosition = positions[nextIndex] || 0; const behavior = reducedMotion.matches ? "auto" : "smooth"; track.scrollTo({ left: nextPosition, behavior: behavior }); if (behavior === "auto") updateIndicators(nextPosition); } function stopAutoAdvance() { runtime.clearInterval(autoAdvanceTimer); autoAdvanceTimer = null; } function canAutoAdvance() { return !reducedMotion.matches && !pointerIsInside && !section.contains(documentObject.activeElement) && documentObject.visibilityState === "visible"; } function startAutoAdvance() { stopAutoAdvance(); if (!canAutoAdvance()) return; autoAdvanceTimer = runtime.setInterval(function () { if (canAutoAdvance()) { scrollByOneCard(1); } else { stopAutoAdvance(); } }, AUTO_ADVANCE_DELAY); } function scheduleAutoAdvance() { stopAutoAdvance(); runtime.clearTimeout(restartTimer); restartTimer = runtime.setTimeout(startAutoAdvance, MANUAL_RESTART_DELAY); } function handleManualNavigation(direction) { stopAutoAdvance(); runtime.clearTimeout(restartTimer); scrollByOneCard(direction); scheduleAutoAdvance(); } previousButton.addEventListener("click", function () { handleManualNavigation(-1); }); nextButton.addEventListener("click", function () { handleManualNavigation(1); }); section.addEventListener("pointerenter", function () { pointerIsInside = true; stopAutoAdvance(); runtime.clearTimeout(restartTimer); }); section.addEventListener("pointerleave", function () { pointerIsInside = false; scheduleAutoAdvance(); }); section.addEventListener("focusin", function () { stopAutoAdvance(); runtime.clearTimeout(restartTimer); }); section.addEventListener("focusout", function (event) { if (!section.contains(event.relatedTarget)) { scheduleAutoAdvance(); } }); track.addEventListener("pointerdown", function () { stopAutoAdvance(); runtime.clearTimeout(restartTimer); }); track.addEventListener("pointerup", scheduleAutoAdvance); track.addEventListener("pointercancel", scheduleAutoAdvance); track.addEventListener("wheel", scheduleAutoAdvance, { passive: true }); track.addEventListener("scroll", function () { updateIndicators(); }, { passive: true }); documentObject.addEventListener("visibilitychange", function () { if (documentObject.visibilityState === "visible") { scheduleAutoAdvance(); } else { stopAutoAdvance(); } }); function handleMotionPreferenceChange() { runtime.clearTimeout(restartTimer); if (reducedMotion.matches) { stopAutoAdvance(); } else { startAutoAdvance(); } } if (typeof reducedMotion.addEventListener === "function") { reducedMotion.addEventListener("change", handleMotionPreferenceChange); } else { reducedMotion.addListener(handleMotionPreferenceChange); } rebuildIndicators(); if (typeof runtime.ResizeObserver === "function") { const resizeObserver = new runtime.ResizeObserver(rebuildIndicators); resizeObserver.observe(track); } else if (typeof runtime.addEventListener === "function") { runtime.addEventListener("resize", rebuildIndicators); } startAutoAdvance(); return { getPositions: function () { return positions.slice(); }, rebuildIndicators: rebuildIndicators, scrollByOneCard: scrollByOneCard, startAutoAdvance: startAutoAdvance, stopAutoAdvance: stopAutoAdvance }; } function bootstrap(options) { return createCarousel(options); } return { AUTO_ADVANCE_DELAY: AUTO_ADVANCE_DELAY, MANUAL_RESTART_DELAY: MANUAL_RESTART_DELAY, calculatePositions: calculatePositions, nearestPositionIndex: nearestPositionIndex, createCarousel: createCarousel, bootstrap: bootstrap }; });