const smCarousel = document.createElement('template')
smCarousel.innerHTML = `
`;
customElements.define('sm-carousel', class extends HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
}).append(smCarousel.content.cloneNode(true))
this.isAutoPlaying = false
this.autoPlayInterval = 5000
this.autoPlayTimeout
this.initialTimeout
this.activeSlideNum = 0
this.carouselItems
this.indicators
this.showIndicator = false
this.carousel = this.shadowRoot.querySelector('.carousel')
this.carouselContainer = this.shadowRoot.querySelector('.carousel-container')
this.carouselSlot = this.shadowRoot.querySelector('slot')
this.navButtonRight = this.shadowRoot.querySelector('.carousel__button--right')
this.navButtonLeft = this.shadowRoot.querySelector('.carousel__button--left')
this.indicatorsContainer = this.shadowRoot.querySelector('.indicators')
this.scrollLeft = this.scrollLeft.bind(this)
this.scrollRight = this.scrollRight.bind(this)
this.handleIndicatorClick = this.handleIndicatorClick.bind(this)
this.showSlide = this.showSlide.bind(this)
this.nextSlide = this.nextSlide.bind(this)
this.autoPlay = this.autoPlay.bind(this)
this.startAutoPlay = this.startAutoPlay.bind(this)
this.stopAutoPlay = this.stopAutoPlay.bind(this)
}
static get observedAttributes() {
return ['indicator', 'autoplay', 'interval']
}
scrollLeft() {
this.carousel.scrollBy({
left: -this.scrollDistance,
behavior: 'smooth'
})
}
scrollRight() {
this.carousel.scrollBy({
left: this.scrollDistance,
behavior: 'smooth'
})
}
showSlide(slideNum) {
this.carousel.scrollTo({
left: (this.carouselItems[slideNum].getBoundingClientRect().left - this.carousel.getBoundingClientRect().left + this.carousel.scrollLeft),
behavior: 'smooth'
})
}
nextSlide() {
if (!this.carouselItems) return
let showSlideNo = (this.activeSlideNum + 1) < this.carouselItems.length ? this.activeSlideNum + 1 : 0
this.showSlide(showSlideNo)
}
autoPlay() {
this.nextSlide()
if (this.isAutoPlaying) {
this.autoPlayTimeout = setTimeout(() => {
this.autoPlay()
}, this.autoPlayInterval);
}
}
startAutoPlay() {
this.setAttribute('autoplay', '')
}
stopAutoPlay() {
this.removeAttribute('autoplay')
}
createIndicator(index) {
let indicator = document.createElement('div')
indicator.classList.add('indicator')
indicator.dataset.rank = index
return indicator
}
handleIndicatorClick(e) {
if (e.target.closest('.indicator')) {
const slideNum = parseInt(e.target.closest('.indicator').dataset.rank)
if (this.activeSlideNum !== slideNum) {
this.showSlide(slideNum)
}
}
}
handleKeyDown(e) {
if (e.code === 'ArrowLeft')
this.scrollRight()
else if (e.code === 'ArrowRight')
this.scrollRight()
}
connectedCallback() {
let frag = document.createDocumentFragment();
this.carouselSlot.addEventListener('slotchange', e => {
this.carouselItems = this.carouselSlot.assignedElements()
this.carouselItems.forEach(item => allElementsObserver.observe(item))
if (this.carouselItems.length > 0) {
firstOptionObserver.observe(this.carouselItems[0])
lastOptionObserver.observe(this.carouselItems[this.carouselItems.length - 1])
}
else {
navButtonLeft.classList.add('hide')
navButtonRight.classList.add('hide')
firstOptionObserver.disconnect()
lastOptionObserver.disconnect()
}
if (this.showIndicator) {
this.indicatorsContainer.innerHTML = ``
this.carouselItems.forEach((item, index) => {
frag.append(
this.createIndicator(index)
)
item.dataset.rank = index
})
this.indicatorsContainer.append(frag)
this.indicators = this.indicatorsContainer.children
}
})
const IOOoptions = {
threshold: 0.9,
root: this
}
const allElementsObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (this.showIndicator) {
const activeRank = parseInt(entry.target.dataset.rank)
if (entry.isIntersecting) {
this.indicators[activeRank].classList.add('active')
this.activeSlideNum = activeRank
}
else
this.indicators[activeRank].classList.remove('active')
}
})
}, IOOoptions)
const firstOptionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.navButtonLeft.classList.add('hide')
}
else {
this.navButtonLeft.classList.remove('hide')
}
})
},
IOOoptions
)
const lastOptionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.navButtonRight.classList.add('hide')
}
else {
this.navButtonRight.classList.remove('hide')
}
})
},
IOOoptions
)
const resObs = new ResizeObserver(entries => {
entries.forEach(entry => {
if (entry.contentBoxSize) {
// Firefox implements `contentBoxSize` as a single content rect, rather than an array
const contentBoxSize = Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize;
this.scrollDistance = contentBoxSize.inlineSize * 0.6
} else {
this.scrollDistance = entry.contentRect.width * 0.6
}
})
})
resObs.observe(this)
this.addEventListener('keydown', this.handleKeyDown)
this.navButtonRight.addEventListener('click', this.scrollRight)
this.navButtonLeft.addEventListener('click', this.scrollLeft)
this.indicatorsContainer.addEventListener('click', this.handleIndicatorClick)
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
if (name === 'indicator') {
this.showIndicator = this.hasAttribute('indicator')
}
if (name === 'autoplay') {
if (this.hasAttribute('autoplay')) {
this.initialTimeout = setTimeout(() => {
this.isAutoPlaying = true
this.autoPlay()
}, this.autoPlayInterval);
}
else {
this.isAutoPlaying = false
clearTimeout(this.autoPlayTimeout)
clearTimeout(this.initialTimeout)
}
}
if (name === 'interval') {
if (this.hasAttribute('interval') && this.getAttribute('interval').trim() !== '') {
this.autoPlayInterval = Math.abs(parseInt(this.getAttribute('interval').trim()))
}
else {
this.autoPlayInterval = 5000
}
}
}
}
disconnectedCallback() {
this.navButtonRight.removeEventListener('click', this.scrollRight)
this.navButtonLeft.removeEventListener('click', this.scrollLeft)
this.indicatorsContainer.removeEventListener('click', this.handleIndicatorClick)
}
})