Packages
moon
2.79.12
2.90.5
retired
2.90.4
2.90.3
2.90.2
2.90.1
2.90.0
2.89.4
2.89.1
2.89.0
2.88.0
2.87.11
2.87.10
2.87.9
2.87.8
2.87.7
2.87.6
2.87.5
2.87.4
2.87.3
2.87.2
2.87.1
2.87.0
2.86.1
2.86.0
2.85.1
2.85.0
2.84.0
2.83.0
2.82.0
2.81.4
2.81.3
2.81.2
2.81.1
2.81.0
2.80.2
2.80.1
2.80.0
2.79.13
2.79.12
2.79.11
2.79.10
2.79.9
2.79.8
2.79.7
2.79.6
2.79.5
2.79.4
2.79.3
2.79.2
2.79.1
2.79.0
2.78.4
2.78.3
2.78.2
2.78.1
2.78.0
2.77.0
2.76.5
2.76.4
2.76.3
2.76.2
2.76.1
2.76.0
2.75.0
2.74.1
2.74.0
2.73.8
2.73.7
2.73.6
2.73.5
2.73.4
2.73.3
2.73.2
2.73.1
2.73.0
2.72.5
2.72.4
2.72.3
2.72.2
2.72.1
2.72.0
2.71.1
2.71.0
2.70.0
2.69.2
2.69.1
2.69.0
2.68.11
2.68.10
Components-based design system written in elixir
Current section
Files
Jump to
Current section
Files
lib/moon/design/carousel.hooks.js
import scrollIntoView from "scroll-into-view-if-needed";
import smoothScrollIntoView from "smooth-scroll-into-view-if-needed";
export const THRESHOLD = 0.75;
export default {
mounted() {
this.reel = this.el.querySelector(".moon-reel");
this.items = this.reel.querySelectorAll("li");
this.indicators = this.el.querySelectorAll(".moon-indicator");
this.itemCount = this.items.length;
this.leftArrow = this.el.querySelector(".moon-left-arrow");
this.rightArrow = this.el.querySelector(".moon-right-arrow");
this.itemsArray = this.getArray("li", this.reel);
this.indicatorsArray = this.getArray(".moon-indicator", this.el);
this.initScrolls();
this.setActiveItem();
this.initIntersectionObserver();
this.el.addEventListener("triggerLeftArrowClick", () => {
this.handleArrowClick(true);
});
this.el.addEventListener("triggerRightArrowClick", () => {
this.handleArrowClick(false);
});
this.delay = parseInt(this.el.dataset.autoslide_delay, 10);
if (!isNaN(this.delay)) {
this.startAutoSlide(this.delay);
}
},
updated() {
this.scrollToIndex();
},
setActiveItem() {
const value = parseInt(this.el.dataset.value, 10);
const lastIndex = this.itemsArray.length - 1;
const scrollToIndex = this.itemsArray.findIndex(
(item, index) => index === value
);
if (scrollToIndex < lastIndex && scrollToIndex !== -1) {
this.scrollIntoViewSmoothly(this.itemsArray[scrollToIndex], {
block: "nearest",
behavior: "smooth",
inline: "nearest",
boundary: this.reel,
});
}
},
initScrolls() {
if (document && "scrollBehavior" in document.documentElement.style) {
this.scrollIntoViewSmoothly = scrollIntoView;
} else {
this.scrollIntoViewSmoothly = smoothScrollIntoView;
}
},
handleArrowClick(isLeftArrow) {
const visibleIndex = isLeftArrow
? this.findFirstVisibleIndex()
: this.findLastVisibleIndex();
const step = parseInt(this.el.dataset.step, 10);
const lastIndex = this.itemsArray.length - 1;
let actualScrollForIndex;
if (isLeftArrow) {
actualScrollForIndex = Math.max(0, visibleIndex - step);
} else {
actualScrollForIndex = Math.min(lastIndex, visibleIndex + step);
}
const scrollToIndex = this.itemsArray.findIndex(
(item, index) => index === actualScrollForIndex
);
if (scrollToIndex !== -1) {
this.scrollIntoViewSmoothly(this.itemsArray[scrollToIndex], {
block: "nearest",
inline: step === 1 ? "center" : "nearest",
behavior: "smooth",
boundary: this.reel,
});
}
},
scrollToIndex() {
const item = this.el.querySelector(".active");
const step = parseInt(this.el.dataset.step, 10);
if (item) {
this.scrollIntoViewSmoothly(item, {
block: "nearest",
inline: step === 1 ? "center" : "nearest",
behavior: "smooth",
boundary: this.reel,
});
this.processVisibleEntry({target: item});
}
},
getArray(selector, element) {
const elements = Array.from(element.querySelectorAll(selector));
return elements;
},
initIntersectionObserver() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
entry.intersectionRatio >= THRESHOLD
? this.processVisibleEntry(entry)
: this.processNotVisibleEntry(entry);
});
this.disableArrows();
},
{
root: this.reel,
threshold: THRESHOLD,
}
);
this.items.forEach((item) => {
observer.observe(item);
});
return () =>
this.items.forEach((item) => {
observer.unobserve(item);
});
},
processVisibleEntry(entry, pushToServer = false) {
entry.target.setAttribute("visible", "true");
const itemIndex = this.itemsArray.indexOf(entry.target);
const indicator = this.indicatorsArray[itemIndex];
if (entry.target.getAttribute("visible") === "true" && indicator) {
indicator.classList.remove(indicator.dataset.default_bg_color);
indicator.classList.add(indicator.dataset.selected_bg_color);
entry.target.classList.add("active");
}
if (pushToServer && entry.target.getAttribute("visible") === "true")
this.pushEventTo(this.el, "set_selected_index", {value: `${itemIndex}`});
},
processNotVisibleEntry(entry) {
entry.target.removeAttribute("visible");
const itemIndex = this.itemsArray.indexOf(entry.target);
const indicator = this.indicatorsArray[itemIndex];
if (entry.target.getAttribute("visible") !== "true" && indicator) {
entry.target.classList.remove("active");
indicator.classList.add(indicator.dataset.default_bg_color);
indicator.classList.remove(indicator.dataset.selected_bg_color);
}
},
findFirstVisibleIndex() {
return this.itemsArray.findIndex(
(item) => item.getAttribute("visible") === "true"
);
},
findLastVisibleIndex() {
const reversedIndex = this.itemsArray
// "reverse" mutates existing array, that"s why we copy it via "slice"
.slice()
.reverse()
.findIndex((item) => item.getAttribute("visible") === "true");
const count = this.itemsArray.length - 1;
const finalIndex =
reversedIndex >= 0 ? count - reversedIndex : reversedIndex;
return finalIndex;
},
disableArrows() {
const firstVisibleIndex = this.findFirstVisibleIndex();
const lastVisibleIndex = this.findLastVisibleIndex();
if (firstVisibleIndex === 0 && this.leftArrow) {
this.leftArrow.disabled = true;
} else if (this.leftArrow) {
this.leftArrow.disabled = false;
}
if (lastVisibleIndex === this.itemCount - 1 && this.rightArrow) {
this.rightArrow.disabled = true;
} else if (this.rightArrow) {
this.rightArrow.disabled = false;
}
},
startAutoSlide() {
this.autoSlideTimeout = setInterval(() => {
const lastVisibleIndex = this.findLastVisibleIndex();
const step = parseInt(this.el.dataset.step, 10);
const lastIndex = this.itemsArray.length - 1;
const actualScrollForIndex =
(lastVisibleIndex + step) % (lastIndex + 1);
let scrollToIndex = this.itemsArray.findIndex(
(item, index) => index === actualScrollForIndex
);
if (
scrollToIndex === lastIndex &&
this.itemsArray[lastVisibleIndex] ===
this.itemsArray[scrollToIndex]
) {
scrollToIndex = 0;
}
this.scrollIntoViewSmoothly(this.itemsArray[scrollToIndex], {
block: "nearest",
inline: step === 1 ? "center" : "nearest",
behavior: "smooth",
boundary: this.reel,
});
}, this.delay);
},
};