diff --git a/components/components.js b/components/components.js new file mode 100644 index 0000000..244851d --- /dev/null +++ b/components/components.js @@ -0,0 +1,5152 @@ +const smButton = document.createElement('template') +smButton.innerHTML = ` + +
+ +
`; +customElements.define('sm-button', + class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smButton.content.cloneNode(true)) + } + static get observedAttributes() { + return ['disabled']; + } + + get disabled() { + return this.hasAttribute('disabled') + } + + set disabled(value) { + if (value) { + this.setAttribute('disabled', '') + }else { + this.removeAttribute('disabled') + } + } + + handleKeyDown(e) { + if (!this.hasAttribute('disabled') && (e.key === 'Enter' || e.code === 'Space')) { + e.preventDefault() + this.click() + } + } + + connectedCallback() { + if (!this.hasAttribute('disabled')) { + this.setAttribute('tabindex', '0') + } + this.setAttribute('role', 'button') + this.addEventListener('keydown', this.handleKeyDown) + } + attributeChangedCallback(name, oldVal, newVal) { + if (name === 'disabled') { + this.removeAttribute('tabindex') + this.setAttribute('aria-disabled', 'true') + } + else { + this.setAttribute('tabindex', '0') + this.setAttribute('aria-disabled', 'false') + } + } + }) +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) + } +}) +const smCheckbox = document.createElement('template') +smCheckbox.innerHTML = ` + +` +customElements.define('sm-checkbox', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smCheckbox.content.cloneNode(true)) + + this.checkbox = this.shadowRoot.querySelector('.checkbox'); + + this.reset = this.reset.bind(this) + this.dispatch = this.dispatch.bind(this) + this.handleKeyDown = this.handleKeyDown.bind(this) + this.handleClick = this.handleClick.bind(this) + } + + static get observedAttributes() { + return ['value', 'disabled', 'checked'] + } + + get disabled() { + return this.hasAttribute('disabled') + } + + set disabled(val) { + if (val) { + this.setAttribute('disabled', '') + } else { + this.removeAttribute('disabled') + } + } + + get checked() { + return this.hasAttribute('checked') + } + + set checked(value) { + if (value) { + this.setAttribute('checked', '') + } + else { + this.removeAttribute('checked') + } + } + + set value(val) { + this.setAttribute('value', val) + } + + get value() { + return this.getAttribute('value') + } + + reset() { + this.removeAttribute('checked') + } + + dispatch(){ + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true + })) + } + handleKeyDown(e){ + if (e.code === "Space") { + e.preventDefault() + this.click() + } + } + handleClick(e){ + this.toggleAttribute('checked') + } + + connectedCallback() { + if (!this.hasAttribute('disabled')) { + this.setAttribute('tabindex', '0') + } + this.setAttribute('role', 'checkbox') + if (!this.hasAttribute('checked')) { + this.setAttribute('aria-checked', 'false') + } + this.addEventListener('keydown', this.handleKeyDown) + this.addEventListener('click', this.handleClick) + } + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + if (name === 'checked') { + this.setAttribute('aria-checked', this.hasAttribute('checked')) + this.dispatch() + } + else if (name === 'disabled') { + if (this.hasAttribute('disabled')) { + this.removeAttribute('tabindex') + } + else { + this.setAttribute('tabindex', '0') + } + } + } + } + disconnectedCallback() { + this.removeEventListener('keydown', this.handleKeyDown) + this.removeEventListener('change', this.handleClick) + } +}) +const smCopy = document.createElement('template') +smCopy.innerHTML = ` + + +
+

+ +
+`; +customElements.define('sm-copy', + class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smCopy.content.cloneNode(true)) + + this.copyContent = this.shadowRoot.querySelector('.copy-content') + this.copyButton = this.shadowRoot.querySelector('.copy-button') + + this.copy = this.copy.bind(this) + } + static get observedAttributes() { + return ['value'] + } + set value(val) { + this.setAttribute('value', val) + } + get value() { + return this.getAttribute('value') + } + fireEvent() { + this.dispatchEvent( + new CustomEvent('copy', { + composed: true, + bubbles: true, + cancelable: true, + }) + ) + } + copy() { + navigator.clipboard.writeText(this.copyContent.textContent) + .then(res => this.fireEvent()) + .catch(err => console.error(err)) + } + connectedCallback() { + this.copyButton.addEventListener('click', this.copy) + } + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'value') { + this.copyContent.textContent = newValue + } + } + disconnectedCallback() { + this.copyButton.removeEventListener('click', this.copy) + } + }) +const fileInput = document.createElement('template') +fileInput.innerHTML = ` + + + +` + +customElements.define('file-input', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(fileInput.content.cloneNode(true)) + this.input = this.shadowRoot.querySelector('input') + this.fileInput = this.shadowRoot.querySelector('.file-input') + this.filesPreviewWraper = this.shadowRoot.querySelector('.files-preview-wrapper') + this.reflectedAttributes = ['accept', 'multiple', 'capture'] + + this.reset = this.reset.bind(this) + this.formatBytes = this.formatBytes.bind(this) + this.createFilePreview = this.createFilePreview.bind(this) + this.handleChange = this.handleChange.bind(this) + this.handleKeyDown = this.handleKeyDown.bind(this) + } + static get observedAttributes() { + return ['accept', 'multiple', 'capture'] + } + get files() { + return this.input.files + } + set accept(val) { + this.setAttribute('accept', val) + } + set multiple(val) { + if (val) { + this.setAttribute('mutiple', '') + } + else { + this.removeAttribute('mutiple') + } + } + set capture(val) { + this.setAttribute('capture', val) + } + set value(val) { + this.input.value = val + } + get isValid() { + return this.input.value !== '' + } + reset(){ + this.input.value = '' + this.filesPreviewWraper.innerHTML = '' + } + formatBytes(a,b=2){if(0===a)return"0 Bytes";const c=0>b?0:b,d=Math.floor(Math.log(a)/Math.log(1024));return parseFloat((a/Math.pow(1024,d)).toFixed(c))+" "+["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][d]} + createFilePreview(file){ + const filePreview = document.createElement('li') + const {name, size} = file + filePreview.className = 'file-preview' + filePreview.innerHTML = ` +
${name}
+
${this.formatBytes(size)}
+ ` + return filePreview + } + handleChange(e){ + this.filesPreviewWraper.innerHTML = '' + const frag = document.createDocumentFragment() + Array.from(e.target.files).forEach(file => { + frag.append( + this.createFilePreview(file) + ) + }); + this.filesPreviewWraper.append(frag) + } + handleKeyDown(e){ + if (e.key === 'Enter' || e.code === 'Space') { + e.preventDefault() + this.input.click() + } + } + connectedCallback() { + this.setAttribute('role', 'button') + this.setAttribute('aria-label', 'File upload') + this.input.addEventListener('change', this.handleChange) + this.fileInput.addEventListener('keydown', this.handleKeyDown) + } + attributeChangedCallback(name) { + if (this.reflectedAttributes.includes(name)){ + if (this.hasAttribute(name)) { + this.input.setAttribute(name, this.getAttribute(name) ? this.getAttribute(name) : '') + } + else { + this.input.removeAttribute(name) + } + } + } + disconnectedCallback() { + this.input.removeEventListener('change', this.handleChange) + this.fileInput.removeEventListener('keydown', this.handleKeyDown) + } +}) +const smForm = document.createElement('template') +smForm.innerHTML = ` + +
+ +
+` + +customElements.define('sm-form', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smForm.content.cloneNode(true)) + + this.form = this.shadowRoot.querySelector('form') + this.formElements + this.requiredElements + this.submitButton + this.resetButton + this.allRequiredValid = false + + this.debounce = this.debounce.bind(this) + this.handleInput = this.handleInput.bind(this) + this.handleKeydown = this.handleKeydown.bind(this) + this.reset = this.reset.bind(this) + } + debounce(callback, wait) { + let timeoutId = null; + return (...args) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + callback.apply(null, args); + }, wait); + }; + } + handleInput(e) { + this.allRequiredValid = this.requiredElements.every(elem => elem.isValid) + if (!this.submitButton) return; + if (this.allRequiredValid) { + this.submitButton.disabled = false; + } + else { + this.submitButton.disabled = true; + } + } + handleKeydown(e) { + if (e.key === 'Enter') { + if (this.allRequiredValid) { + this.submitButton.click() + } +/* else { + this.requiredElements.find(elem => !elem.isValid) + .animate([ + { transform: 'translateX(-1rem)' }, + { transform: 'translateX(1rem)' }, + { transform: 'translateX(-0.5rem)' }, + { transform: 'translateX(0.5rem)' }, + { transform: 'translateX(0)' }, + ], { + duration: 300, + easing: 'ease' + }) + } */ + } + } + reset() { + this.formElements.forEach(elem => elem.reset()) + } + connectedCallback() { + const slot = this.shadowRoot.querySelector('slot') + slot.addEventListener('slotchange', e => { + this.formElements = [...this.querySelectorAll('sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio')] + this.requiredElements = this.formElements.filter(elem => elem.hasAttribute('required')) + this.submitButton = e.target.assignedElements().find(elem => elem.getAttribute('variant') === 'primary' || elem.getAttribute('type') === 'submit'); + this.resetButton = e.target.assignedElements().find(elem => elem.getAttribute('type') === 'reset'); + if (this.resetButton) { + this.resetButton.addEventListener('click', this.reset) + } + }) + this.addEventListener('input', this.debounce(this.handleInput, 100)) + this.addEventListener('keydown', this.debounce(this.handleKeydown, 100)) + } + disconnectedCallback() { + this.removeEventListener('input', this.debounce(this.handleInput, 100)) + this.removeEventListener('keydown', this.debounce(this.handleKeydown, 100)) + } +}) + + +const hamburgerMenu = document.createElement('template') +hamburgerMenu.innerHTML = ` + +
+ +` +class HamburgerMenu extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }).append(hamburgerMenu.content.cloneNode(true)) + + this.resumeScrolling = this.resumeScrolling.bind(this) + this.open = this.open.bind(this) + this.close = this.close.bind(this) + + this.sideNav = this.shadowRoot.querySelector('.side-nav') + this.backdrop = this.shadowRoot.querySelector('.backdrop') + this.isOpen = false + + this.animeOptions = { + duration: 300, + easing: 'ease' + } + } + static get observedAttributes() { + return ['open']; + } + resumeScrolling() { + const scrollY = document.body.style.top; + window.scrollTo(0, parseInt(scrollY || '0') * -1); + setTimeout(() => { + document.body.style.overflow = 'auto'; + document.body.style.top = 'initial' + }, 300); + } + + open() { + if (this.isOpen) return + document.body.style.overflow = 'hidden'; + document.body.style.top = `-${window.scrollY}px` + this.classList.remove('hide') + this.sideNav.classList.add('reveal') + this.backdrop.classList.remove('hide') + this.backdrop.animate([ + { + opacity: 0 + }, + { + opacity: 1 + }, + ], + this.animeOptions) + .onfinish = () => { + this.isOpen = true + this.setAttribute('open', '') + } + + } + close() { + if (!this.isOpen) return + this.sideNav.classList.remove('reveal') + this.backdrop.animate([ + { + opacity: 1 + }, + { + opacity: 0 + }, + ], + this.animeOptions) + .onfinish = () => { + this.backdrop.classList.add('hide') + this.classList.add('hide') + this.isOpen = false + this.removeAttribute('open') + } + } + connectedCallback() { + this.backdrop.addEventListener('click', this.close) + const resizeObserver = new ResizeObserver(entries => { + if (window.innerWidth < 640 && this.isOpen) { + this.classList.remove('hide') + } + else { + this.classList.add('hide') + } + if (window.innerWidth > 640) { + this.classList.remove('hide') + } + }); + resizeObserver.observe(this) + } + + disconnectedCallback() { + this.backdrop.removeEventListener('click', this.close) + } + attributeChangedCallback(name, oldVal, newVal) { + if (name === 'open') { + if (this.hasAttribute('open')) { + this.open() + } + } + } +} + +window.customElements.define('hamburger-menu', HamburgerMenu); +const smInput = document.createElement('template') +smInput.innerHTML = ` + +
+ +

+
+`; +customElements.define('sm-input', + class extends HTMLElement { + + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smInput.content.cloneNode(true)) + + this.inputParent = this.shadowRoot.querySelector('.input') + this.input = this.shadowRoot.querySelector('input') + this.clearBtn = this.shadowRoot.querySelector('.clear') + this.label = this.shadowRoot.querySelector('.label') + this.feedbackText = this.shadowRoot.querySelector('.feedback-text') + this._helperText + this._errorText + this.isRequired = false + this.validationFunction + this.reflectedAttributes = ['value', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step'] + + this.reset = this.reset.bind(this) + this.focusIn = this.focusIn.bind(this) + this.focusOut = this.focusOut.bind(this) + this.fireEvent = this.fireEvent.bind(this) + this.checkInput = this.checkInput.bind(this) + } + + static get observedAttributes() { + return ['value', 'placeholder', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step', 'helper-text', 'error-text'] + } + + get value() { + return this.input.value + } + + set value(val) { + this.input.value = val; + this.checkInput() + this.fireEvent() + } + + get placeholder() { + return this.getAttribute('placeholder') + } + + set placeholder(val) { + this.setAttribute('placeholder', val) + } + + get type() { + return this.getAttribute('type') + } + + set type(val) { + this.setAttribute('type', val) + } + + get isValid() { + const _isValid = this.input.checkValidity() + let _customValid = true + if (this.customValidation) { + _customValid = this.validationFunction(this.input.value) + } + return (_isValid && _customValid) + } + + get validity() { + return this.input.validity + } + + set disabled(value) { + if (value) + this.inputParent.classList.add('disabled') + else + this.inputParent.classList.remove('disabled') + } + set readOnly(value) { + if (value) { + this.setAttribute('readonly', '') + } else { + this.removeAttribute('readonly') + } + } + set customValidation(val) { + this.validationFunction = val + } + set errorText(val) { + this._errorText = val + } + set helperText(val) { + this._helperText = val + } + reset(){ + this.value = '' + } + + focusIn(){ + this.input.focus() + } + + focusOut(){ + this.input.blur() + } + + fireEvent(){ + let event = new Event('input', { + bubbles: true, + cancelable: true, + composed: true + }); + this.dispatchEvent(event); + } + + checkInput(e){ + if (!this.hasAttribute('readonly')) { + if (this.input.value !== '') { + this.clearBtn.classList.remove('hide') + } else { + this.clearBtn.classList.add('hide') + if (this.isRequired) { + this.feedbackText.textContent = '* required' + } + } + if (!this.isValid) { + if (this._errorText) { + this.feedbackText.classList.add('error') + this.feedbackText.classList.remove('success') + this.feedbackText.innerHTML = ` + + ${this._errorText} + ` + } + } else { + this.feedbackText.classList.remove('error') + this.feedbackText.classList.add('success') + this.feedbackText.textContent = '' + } + } + if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder').trim() === '') return; + if (this.input.value !== '') { + if (this.animate) + this.inputParent.classList.add('animate-label') + else + this.label.classList.add('hide') + } else { + if (this.animate) + this.inputParent.classList.remove('animate-label') + else + this.label.classList.remove('hide') + } + } + + + connectedCallback() { + this.animate = this.hasAttribute('animate') + this.input.addEventListener('input', this.checkInput) + this.clearBtn.addEventListener('click', this.reset) + } + + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + if (this.reflectedAttributes.includes(name)) { + if (this.hasAttribute(name)) { + this.input.setAttribute(name, this.getAttribute(name) ? this.getAttribute(name) : '') + } + else { + this.input.removeAttribute(name) + } + } + if (name === 'placeholder') { + this.label.textContent = newValue; + this.setAttribute('aria-label', newValue); + } + else if (this.hasAttribute('value')) { + this.checkInput() + } + else if (name === 'type') { + if (this.hasAttribute('type') && this.getAttribute('type') === 'number') { + this.input.setAttribute('inputmode', 'numeric') + } + } + else if (name === 'helper-text') { + this._helperText = this.getAttribute('helper-text') + } + else if (name === 'error-text') { + this._errorText = this.getAttribute('error-text') + } + else if (name === 'required') { + this.isRequired = this.hasAttribute('required') + this.feedbackText.textContent = '* required' + } + else if (name === 'readonly') { + if (this.hasAttribute('readonly')) { + this.inputParent.classList.add('readonly') + } else { + this.inputParent.classList.remove('readonly') + } + } + else if (name === 'disabled') { + if (this.hasAttribute('disabled')) { + this.inputParent.classList.add('disabled') + } + else { + this.inputParent.classList.remove('disabled') + } + } + } + } + disconnectedCallback() { + this.input.removeEventListener('input', this.checkInput) + this.clearBtn.removeEventListener('click', this.reset) + } + }) +const smMenu = document.createElement('template') +smMenu.innerHTML = ` + +
+ +
+ +
+
`; +customElements.define('sm-menu', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smMenu.content.cloneNode(true)) + + this.isOpen = false; + this.availableOptions + this.containerDimensions + this.animOptions = { + duration: 200, + easing: 'ease' + } + + this.optionList = this.shadowRoot.querySelector('.options') + this.menu = this.shadowRoot.querySelector('.menu') + this.icon = this.shadowRoot.querySelector('.icon') + + this.expand = this.expand.bind(this) + this.collapse = this.collapse.bind(this) + this.toggle = this.toggle.bind(this) + this.handleKeyDown = this.handleKeyDown.bind(this) + this.handleClickoutSide = this.handleClickoutSide.bind(this) + + } + static get observedAttributes() { + return ['value'] + } + get value() { + return this.getAttribute('value') + } + set value(val) { + this.setAttribute('value', val) + } + expand() { + if (!this.isOpen) { + this.optionList.classList.remove('hide') + this.optionList.animate([ + { + transform: window.innerWidth < 640 ? 'translateY(1.5rem)' : 'translateY(-1rem)', + opacity: '0' + }, + { + transform: 'none', + opacity: '1' + }, + ], this.animOptions) + .onfinish = () => { + this.isOpen = true + this.icon.classList.add('focused') + } + } + } + collapse() { + if (this.isOpen) { + this.optionList.animate([ + { + transform: 'none', + opacity: '1' + }, + { + transform: window.innerWidth < 640 ? 'translateY(1.5rem)' : 'translateY(-1rem)', + opacity: '0' + }, + ], this.animOptions) + .onfinish = () => { + this.isOpen = false + this.icon.classList.remove('focused') + this.optionList.classList.add('hide') + } + } + } + toggle() { + if (!this.isOpen) { + this.expand() + } else { + this.collapse() + } + } + handleKeyDown(e) { + // If key is pressed on menu button + if (e.target === this) { + if (e.code === 'ArrowDown') { + e.preventDefault() + this.availableOptions[0].focus() + } + else if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + this.toggle() + } + } else { // If mey is pressed over menu options + if (e.code === 'ArrowUp') { + e.preventDefault() + if (document.activeElement.previousElementSibling) { + document.activeElement.previousElementSibling.focus() + } else { + this.availableOptions[this.availableOptions.length - 1].focus() + } + } + else if (e.code === 'ArrowDown') { + e.preventDefault() + if (document.activeElement.nextElementSibling) { + document.activeElement.nextElementSibling.focus() + } else { + this.availableOptions[0].focus() + } + } + else if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + e.target.click() + } + } + } + handleClickoutSide(e) { + if (!this.contains(e.target) && e.button !== 2) { + this.collapse() + } + } + connectedCallback() { + this.setAttribute('role', 'listbox') + this.setAttribute('aria-label', 'dropdown menu') + const slot = this.shadowRoot.querySelector('.options slot') + slot.addEventListener('slotchange', e => { + this.availableOptions = e.target.assignedElements() + this.containerDimensions = this.optionList.getBoundingClientRect() + }); + this.addEventListener('click', this.toggle) + this.addEventListener('keydown', this.handleKeyDown) + document.addEventListener('mousedown', this.handleClickoutSide) + } + disconnectedCallback() { + this.removeEventListener('click', this.toggle) + this.removeEventListener('keydown', this.handleKeyDown) + document.removeEventListener('mousedown', this.handleClickoutSide) + } +}) + +// option +const menuOption = document.createElement('template') +menuOption.innerHTML = ` + +
+ +
`; +customElements.define('menu-option', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(menuOption.content.cloneNode(true)) + } + + connectedCallback() { + this.setAttribute('role', 'option') + this.addEventListener('keyup', e => { + if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + this.click() + } + }) + } +}) +const smNotifications = document.createElement('template') +smNotifications.innerHTML = ` + +
+` + +customElements.define('sm-notifications', class extends HTMLElement { + constructor() { + super() + this.shadow = this.attachShadow({ + mode: 'open' + }).append(smNotifications.content.cloneNode(true)) + + this.notificationPanel = this.shadowRoot.querySelector('.notification-panel') + this.animationOptions = { + duration: 300, + fill: "forwards", + easing: "cubic-bezier(0.175, 0.885, 0.32, 1.275)" + } + + this.push = this.push.bind(this) + this.createNotification = this.createNotification.bind(this) + this.removeNotification = this.removeNotification.bind(this) + this.clearAll = this.clearAll.bind(this) + + } + + randString(length) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < length; i++) + result += characters.charAt(Math.floor(Math.random() * characters.length)); + return result; + } + + createNotification(message, options) { + const { pinned = false, icon = '' } = options + const notification = document.createElement('div') + notification.id = this.randString(8) + notification.classList.add('notification') + let composition = `` + composition += ` +
${icon}
+

${message}

+ ` + if (pinned) { + notification.classList.add('pinned') + composition += ` + + ` + } + notification.innerHTML = composition + return notification + } + + push(message, options = {}) { + const notification = this.createNotification(message, options) + this.notificationPanel.append(notification) + notification.animate([ + { + transform: `translateY(1rem)`, + opacity: '0' + }, + { + transform: `none`, + opacity: '1' + }, + ], this.animationOptions) + return notification.id + } + + removeNotification(notification) { + notification.animate([ + { + transform: `none`, + opacity: '1' + }, + { + transform: `translateY(0.5rem)`, + opacity: '0' + } + ], this.animationOptions).onfinish = () => { + notification.remove() + } + } + + clearAll() { + Array.from(this.notificationPanel.children).forEach(child => { + this.removeNotification(child) + }) + } + + connectedCallback() { + this.notificationPanel.addEventListener('click', e => { + if (e.target.closest('.close')) ( + this.removeNotification(e.target.closest('.notification')) + ) + }) + + const observer = new MutationObserver(mutationList => { + mutationList.forEach(mutation => { + if (mutation.type === 'childList') { + if (mutation.addedNodes.length && !mutation.addedNodes[0].classList.contains('pinned')) { + setTimeout(() => { + this.removeNotification(mutation.addedNodes[0]) + }, 5000); + } + } + }) + }) + observer.observe(this.notificationPanel, { + childList: true, + }) + } +}) +const smPopup = document.createElement('template') +smPopup.innerHTML = ` + + +`; +customElements.define('sm-popup', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smPopup.content.cloneNode(true)) + + this.allowClosing = false + this.isOpen = false + this.pinned = false + this.popupStack + this.offset + this.touchStartY = 0 + this.touchEndY = 0 + this.touchStartTime = 0 + this.touchEndTime = 0 + this.touchEndAnimataion + + this.popupContainer = this.shadowRoot.querySelector('.popup-container') + this.popup = this.shadowRoot.querySelector('.popup') + this.popupBodySlot = this.shadowRoot.querySelector('.popup-body slot') + this.popupHeader = this.shadowRoot.querySelector('.popup-top') + + this.resumeScrolling = this.resumeScrolling.bind(this) + this.show = this.show.bind(this) + this.hide = this.hide.bind(this) + this.handleTouchStart = this.handleTouchStart.bind(this) + this.handleTouchMove = this.handleTouchMove.bind(this) + this.handleTouchEnd = this.handleTouchEnd.bind(this) + this.movePopup = this.movePopup.bind(this) + } + + static get observedAttributes() { + return ['open']; + } + + get open() { + return this.isOpen + } + + resumeScrolling() { + const scrollY = document.body.style.top; + window.scrollTo(0, parseInt(scrollY || '0') * -1); + setTimeout(() => { + document.body.style.overflow = 'auto'; + document.body.style.top = 'initial' + }, 300); + } + + show(options = {}) { + const {pinned = false, popupStack = undefined} = options + if (popupStack) + this.popupStack = popupStack + if (this.popupStack && !this.hasAttribute('open')) { + this.popupStack.push({ + popup: this, + permission: pinned + }) + if (this.popupStack.items.length > 1) { + this.popupStack.items[this.popupStack.items.length - 2].popup.classList.add('stacked') + } + this.dispatchEvent( + new CustomEvent("popupopened", { + bubbles: true, + detail: { + popup: this, + popupStack: this.popupStack + } + }) + ) + this.setAttribute('open', '') + this.pinned = pinned + this.isOpen = true + } + this.popupContainer.classList.remove('hide') + this.popup.style.transform = 'none'; + document.body.style.overflow = 'hidden'; + document.body.style.top = `-${window.scrollY}px` + return this.popupStack + } + hide() { + if (window.innerWidth < 640) + this.popup.style.transform = 'translateY(100%)'; + else + this.popup.style.transform = 'translateY(3rem)'; + this.popupContainer.classList.add('hide') + this.removeAttribute('open') + if (typeof this.popupStack !== 'undefined') { + this.popupStack.pop() + if (this.popupStack.items.length) { + this.popupStack.items[this.popupStack.items.length - 1].popup.classList.remove('stacked') + } else { + this.resumeScrolling() + } + } else { + this.resumeScrolling() + } + + if (this.forms.length) { + setTimeout(() => { + this.forms.forEach(form => form.reset()) + }, 300); + } + setTimeout(() => { + this.dispatchEvent( + new CustomEvent("popupclosed", { + bubbles: true, + detail: { + popup: this, + popupStack: this.popupStack + } + }) + ) + this.isOpen = false + }, 300); + } + + handleTouchStart(e) { + this.touchStartY = e.changedTouches[0].clientY + this.popup.style.transition = 'transform 0.1s' + this.touchStartTime = e.timeStamp + } + + handleTouchMove(e) { + if (this.touchStartY < e.changedTouches[0].clientY) { + this.offset = e.changedTouches[0].clientY - this.touchStartY; + this.touchEndAnimataion = window.requestAnimationFrame(() => this.movePopup()) + } + } + + handleTouchEnd(e) { + this.touchEndTime = e.timeStamp + cancelAnimationFrame(this.touchEndAnimataion) + this.touchEndY = e.changedTouches[0].clientY + this.popup.style.transition = 'transform 0.3s' + this.threshold = this.popup.getBoundingClientRect().height * 0.3 + if (this.touchEndTime - this.touchStartTime > 200) { + if (this.touchEndY - this.touchStartY > this.threshold) { + if (this.pinned) { + this.show() + return + } else + this.hide() + } else { + this.show() + } + } else { + if (this.touchEndY > this.touchStartY) + if (this.pinned) { + this.show() + return + } + else + this.hide() + } + } + + movePopup() { + this.popup.style.transform = `translateY(${this.offset}px)` + } + + connectedCallback() { + + this.popupBodySlot.addEventListener('slotchange', () => { + this.forms = this.querySelectorAll('sm-form') + }) + this.popupContainer.addEventListener('mousedown', e => { + if (e.target === this.popupContainer && !this.pinned) { + if (this.pinned) { + this.show() + } else + this.hide() + } + }) + + const resizeObserver = new ResizeObserver(entries => { + for (let entry of entries) { + 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.threshold = entry.blockSize.height * 0.3 + } else { + this.threshold = entry.contentRect.height * 0.3 + } + } + }); + resizeObserver.observe(this) + + + this.popupHeader.addEventListener('touchstart', (e) => { this.handleTouchStart(e) }, { passive: true }) + this.popupHeader.addEventListener('touchmove', (e) => { this.handleTouchMove(e) }, { passive: true }) + this.popupHeader.addEventListener('touchend', (e) => { this.handleTouchEnd(e) }, { passive: true }) + } + disconnectedCallback() { + this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, { passive: true }) + this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, { passive: true }) + this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, { passive: true }) + resizeObserver.unobserve() + } + attributeChangedCallback(name, oldVal, newVal) { + if (name === 'open') { + if (this.hasAttribute('open')) { + this.show() + } + } + } +}) + +const smRadio = document.createElement('template') +smRadio.innerHTML = ` + +
+ + + +
+` +window.customElements.define('sm-radio', class extends HTMLElement { + constructor() { + super(); + this.attachShadow({ + mode: 'open' + }).append(smRadio.content.cloneNode(true)) + + this.radio = this.shadowRoot.querySelector('.radio'); + + this.reset = this.reset.bind(this) + this.dispatchChangeEvent = this.dispatchChangeEvent.bind(this) + this.dispatchGroupEvent = this.dispatchGroupEvent.bind(this) + this.handleKeyDown = this.handleKeyDown.bind(this) + this.handleClick = this.handleClick.bind(this) + this.handleRadioGroup = this.handleRadioGroup.bind(this) + + this.uniqueId + this.options + } + static get observedAttributes() { + return ['value', 'disabled', 'checked'] + } + + get disabled() { + return this.hasAttribute('disabled') + } + + set disabled(val) { + if (val) { + this.setAttribute('disabled', '') + } else { + this.removeAttribute('disabled') + } + } + + get checked() { + return this.hasAttribute('checked') + } + + set checked(value) { + if (value) { + this.setAttribute('checked', '') + } + else { + this.removeAttribute('checked') + } + } + + set value(val) { + this.setAttribute('value', val) + } + + get value() { + return this.getAttribute('value') + } + + reset() { + this.removeAttribute('checked') + } + + dispatchChangeEvent() { + this.dispatchEvent(new CustomEvent('change', this.options)) + } + dispatchGroupEvent() { + if (this.hasAttribute('name') && this.getAttribute('name').trim() !== '') { + this.dispatchEvent(new CustomEvent(`changed${this.getAttribute('name')}`, this.options)) + } + } + handleKeyDown(e){ + if (e.code === "Space") { + e.preventDefault() + this.handleClick() + } + } + handleClick() { + if (!this.hasAttribute('checked')) { + this.setAttribute('checked', '') + this.dispatchGroupEvent() + } + + } + handleRadioGroup(e) { + if (e.detail.uid !== this.uniqueId) { + this.reset() + } + } + randString(length) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < length; i++) + result += characters.charAt(Math.floor(Math.random() * characters.length)); + return result; + } + + connectedCallback() { + this.uniqueId = this.randString(8) + this.options = { + bubbles: true, + composed: true, + detail: { + uid: this.uniqueId, + value: this.value, + } + } + if (!this.hasAttribute('disabled')) { + this.setAttribute('tabindex', '0') + } + this.setAttribute('role', 'radio') + if (!this.hasAttribute('checked')) { + this.setAttribute('aria-checked', 'false') + } + this.addEventListener('keydown', this.handleKeyDown) + this.addEventListener('click', this.handleClick) + if (this.hasAttribute('name') && this.getAttribute('name').trim() !== '') { + document.addEventListener(`changed${this.getAttribute('name')}`, this.handleRadioGroup) + } + } + + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + if (name === 'checked') { + this.dispatchChangeEvent() + } + else if (name === 'disabled') { + if (this.hasAttribute('disabled')) { + this.removeAttribute('tabindex') + } + else { + this.setAttribute('tabindex', '0') + } + } + } + } + disconnectedCallback() { + this.removeEventListener('keydown', this.handleKeyDown) + this.removeEventListener('change', this.handleClick) + } +}); +const smSwitch = document.createElement('template') +smSwitch.innerHTML = ` + +` + +customElements.define('sm-switch', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smSwitch.content.cloneNode(true)) + this.switch = this.shadowRoot.querySelector('.switch'); + this.input = this.shadowRoot.querySelector('input') + this.isChecked = false + this.isDisabled = false + + this.dispatch = this.dispatch.bind(this) + } + + static get observedAttributes() { + return ['disabled', 'checked'] + } + + get disabled() { + return this.isDisabled + } + + set disabled(val) { + if (val) { + this.setAttribute('disabled', '') + } else { + this.removeAttribute('disabled') + } + } + + get checked() { + return this.isChecked + } + + set checked(value) { + if (value) { + this.setAttribute('checked', '') + } else { + this.removeAttribute('checked') + } + } + + dispatch(){ + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true, + detail: { + value: this.isChecked + } + })) + } + + connectedCallback() { + this.addEventListener('keydown', e => { + if (e.code === "Space" && !this.isDisabled) { + e.preventDefault() + this.input.click() + } + }) + this.input.addEventListener('click', e => { + if (this.input.checked) + this.checked = true + else + this.checked = false + this.dispatch() + }) + } + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + if (name === 'disabled') { + if (this.hasAttribute('disabled')) { + this.disabled = true + } + else { + this.disabled = false + } + } + else if (name === 'checked') { + if (this.hasAttribute('checked')) { + this.isChecked = true + this.input.checked = true + } + else { + this.isChecked = false + this.input.checked = false + } + } + } + } + +}) +const smSelect = document.createElement('template') +smSelect.innerHTML = ` + +
+
+
+ +
+
+ +
+
`; +customElements.define('sm-select', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smSelect.content.cloneNode(true)) + + this.reset = this.reset.bind(this) + this.open = this.open.bind(this) + this.collapse = this.collapse.bind(this) + this.toggle = this.toggle.bind(this) + this.handleOptionsNavigation = this.handleOptionsNavigation.bind(this) + this.handleOptionSelection = this.handleOptionSelection.bind(this) + this.handleKeydown = this.handleKeydown.bind(this) + this.handleClickOutside = this.handleClickOutside.bind(this) + + this.availableOptions + this.previousOption + this.isOpen = false; + this.slideDown = [{ + transform: `translateY(-0.5rem)`, + opacity: 0 + }, + { + transform: `translateY(0)`, + opacity: 1 + } + ] + this.slideUp = [{ + transform: `translateY(0)`, + opacity: 1 + }, + { + transform: `translateY(-0.5rem)`, + opacity: 0 + } + ] + this.animationOptions = { + duration: 300, + fill: "forwards", + easing: 'ease' + } + + this.optionList = this.shadowRoot.querySelector('.options') + this.chevron = this.shadowRoot.querySelector('.toggle') + this.selection = this.shadowRoot.querySelector('.selection') + this.selectedOptionText = this.shadowRoot.querySelector('.selected-option-text') + } + static get observedAttributes() { + return ['value', 'disabled'] + } + get value() { + return this.getAttribute('value') + } + set value(val) { + this.setAttribute('value', val) + } + + reset() { + + } + + open() { + this.optionList.classList.remove('hide') + this.optionList.animate(this.slideDown, this.animationOptions) + this.chevron.classList.add('rotate') + this.isOpen = true + } + collapse() { + this.chevron.classList.remove('rotate') + this.optionList.animate(this.slideUp, this.animationOptions) + .onfinish = () => { + this.optionList.classList.add('hide') + this.isOpen = false + } + } + toggle() { + if (!this.isOpen && !this.hasAttribute('disabled')) { + this.open() + } else { + this.collapse() + } + } + + fireEvent() { + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true, + detail: { + value: this.value + } + })) + } + + handleOptionsNavigation(e) { + if (e.code === 'ArrowUp') { + e.preventDefault() + if (document.activeElement.previousElementSibling) { + document.activeElement.previousElementSibling.focus() + } else { + this.availableOptions[this.availableOptions.length - 1].focus() + } + } + else if (e.code === 'ArrowDown') { + e.preventDefault() + if (document.activeElement.nextElementSibling) { + document.activeElement.nextElementSibling.focus() + } else { + this.availableOptions[0].focus() + } + } + } + handleOptionSelection(e) { + if (this.previousOption !== document.activeElement) { + this.value = document.activeElement.getAttribute('value') + this.selectedOptionText.textContent = document.activeElement.textContent; + this.fireEvent() + if (this.previousOption) { + this.previousOption.classList.remove('check-selected') + } + document.activeElement.classList.add('check-selected') + this.previousOption = document.activeElement + } + } + handleClick(e) { + if (e.target === this) { + this.toggle() + } + else { + this.handleOptionSelection() + this.collapse() + } + } + handleKeydown(e) { + if (e.target === this) { + if (this.isOpen && e.code === 'ArrowDown') { + e.preventDefault() + this.availableOptions[0].focus() + this.handleOptionSelection(e) + } + else if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + this.toggle() + } + } + else { + this.handleOptionsNavigation(e) + this.handleOptionSelection(e) + if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + this.collapse() + } + } + } + handleClickOutside(e) { + if (this.isOpen && !this.contains(e.target)) { + this.collapse() + } + } + connectedCallback() { + this.setAttribute('role', 'listbox') + if (!this.hasAttribute('disabled')) { + this.selection.setAttribute('tabindex', '0') + } + let slot = this.shadowRoot.querySelector('slot') + slot.addEventListener('slotchange', e => { + this.availableOptions = slot.assignedElements() + if (this.availableOptions[0]) { + let firstElement = this.availableOptions[0]; + this.previousOption = firstElement; + firstElement.classList.add('check-selected') + this.value = firstElement.getAttribute('value') + this.selectedOptionText.textContent = firstElement.textContent + this.availableOptions.forEach((element) => { + element.setAttribute('tabindex', "0"); + }) + } + }); + this.addEventListener('click', this.handleClick) + this.addEventListener('keydown', this.handleKeydown) + document.addEventListener('mousedown', this.handleClickOutside) + } + disconnectedCallback() { + this.removeEventListener('click', this.toggle) + this.removeEventListener('keydown', this.handleKeydown) + document.removeEventListener('mousedown', this.handleClickOutside) + } + attributeChangedCallback(name) { + if (name === "disabled") { + if (this.hasAttribute('disabled')) { + this.selection.removeAttribute('tabindex') + } else { + this.selection.setAttribute('tabindex', '0') + } + } + } +}) + +// option +const smOption = document.createElement('template') +smOption.innerHTML = ` + +
+ + +
`; +customElements.define('sm-option', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smOption.content.cloneNode(true)) + } + + connectedCallback() { + this.setAttribute('role', 'option') + } +}) + +const spinner = document.createElement('template') +spinner.innerHTML = ` + + + +` +class SquareLoader extends HTMLElement { + constructor() { + super(); + this.attachShadow({ + mode: 'open' + }).append(spinner.content.cloneNode(true)) + } +} + +window.customElements.define('sm-spinner', SquareLoader); +const stripSelect = document.createElement('template') +stripSelect.innerHTML = ` + +
+
+ +
+ +
+ +
+
+ +` +customElements.define('strip-select', class extends HTMLElement{ + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(stripSelect.content.cloneNode(true)) + this.stripSelect = this.shadowRoot.querySelector('.strip-select') + this.slottedOptions + this._value + this.scrollDistance + + this.scrollLeft = this.scrollLeft.bind(this) + this.scrollRight = this.scrollRight.bind(this) + this.fireEvent = this.fireEvent.bind(this) + } + get value() { + return this._value + } + scrollLeft(){ + this.stripSelect.scrollBy({ + left: -this.scrollDistance, + behavior: 'smooth' + }) + } + + scrollRight(){ + this.stripSelect.scrollBy({ + left: this.scrollDistance, + behavior: 'smooth' + }) + } + fireEvent(){ + this.dispatchEvent( + new CustomEvent("change", { + bubbles: true, + composed: true, + detail: { + value: this._value + } + }) + ) + } + connectedCallback() { + this.setAttribute('role', 'listbox') + + const slot = this.shadowRoot.querySelector('slot') + const coverLeft = this.shadowRoot.querySelector('.cover--left') + const coverRight = this.shadowRoot.querySelector('.cover--right') + const navButtonLeft = this.shadowRoot.querySelector('.nav-button--left') + const navButtonRight = this.shadowRoot.querySelector('.nav-button--right') + slot.addEventListener('slotchange', e => { + const assignedElements = slot.assignedElements() + assignedElements.forEach(elem => { + if (elem.hasAttribute('selected')) { + elem.setAttribute('active', '') + this._value = elem.value + } + }) + if (!this.hasAttribute('multiline')) { + if (assignedElements.length > 0) { + firstOptionObserver.observe(slot.assignedElements()[0]) + lastOptionObserver.observe(slot.assignedElements()[slot.assignedElements().length - 1]) + } + else { + navButtonLeft.classList.add('hide') + navButtonRight.classList.add('hide') + coverLeft.classList.add('hide') + coverRight.classList.add('hide') + firstOptionObserver.disconnect() + lastOptionObserver.disconnect() + } + } + }) + 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.stripSelect.addEventListener('option-clicked', e => { + if (this._value !== e.target.value) { + this._value = e.target.value + slot.assignedElements().forEach(elem => elem.removeAttribute('active')) + e.target.setAttribute('active', '') + e.target.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }) + this.fireEvent() + } + }) + const firstOptionObserver = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + navButtonLeft.classList.add('hide') + coverLeft.classList.add('hide') + } + else { + navButtonLeft.classList.remove('hide') + coverLeft.classList.remove('hide') + } + }) + }, + { + threshold: 0.9, + root: this + }) + const lastOptionObserver = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + navButtonRight.classList.add('hide') + coverRight.classList.add('hide') + } + else { + navButtonRight.classList.remove('hide') + coverRight.classList.remove('hide') + } + }) + }, + { + threshold: 0.9, + root: this + }) + navButtonLeft.addEventListener('click', this.scrollLeft) + navButtonRight.addEventListener('click', this.scrollRight) + } + disconnectedCallback() { + navButtonLeft.removeEventListener('click', this.scrollLeft) + navButtonRight.removeEventListener('click', this.scrollRight) + } +}) + +//Strip option +const stripOption = document.createElement('template') +stripOption.innerHTML = ` + + +` +customElements.define('strip-option', class extends HTMLElement{ + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(stripOption.content.cloneNode(true)) + this._value + this.radioButton = this.shadowRoot.querySelector('input') + + this.fireEvent = this.fireEvent.bind(this) + this.handleKeyDown = this.handleKeyDown.bind(this) + } + get value() { + return this._value + } + fireEvent(){ + this.dispatchEvent( + new CustomEvent("option-clicked", { + bubbles: true, + composed: true, + detail: { + value: this._value + } + }) + ) + } + handleKeyDown(e){ + if (e.key === 'Enter' || e.key === 'Space') { + this.fireEvent() + } + } + connectedCallback() { + this.setAttribute('role', 'option') + this.setAttribute('tabindex', '0') + this._value = this.getAttribute('value') + this.addEventListener('click', this.fireEvent) + this.addEventListener('keydown', this.handleKeyDown) + } + disconnectedCallback() { + this.removeEventListener('click', this.fireEvent) + this.removeEventListener('keydown', this.handleKeyDown) + } +}) +const smTabHeader = document.createElement('template') +smTabHeader.innerHTML = ` + +
+
+ +
+
+
+`; + +customElements.define('sm-tab-header', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smTabHeader.content.cloneNode(true)) + + this.prevTab + this.allTabs + this.activeTab + + this.indicator = this.shadowRoot.querySelector('.indicator'); + this.tabSlot = this.shadowRoot.querySelector('slot'); + this.tabHeader = this.shadowRoot.querySelector('.tab-header'); + + this.changeTab = this.changeTab.bind(this) + this.handleClick = this.handleClick.bind(this) + this.handlePanelChange = this.handlePanelChange.bind(this) + } + + fireEvent(index) { + this.dispatchEvent( + new CustomEvent(`switchedtab${this.target}`, { + bubbles: true, + detail: { + index: parseInt(index) + } + }) + ) + } + + moveIndiactor(tabDimensions) { + this.indicator.setAttribute('style', `width: ${tabDimensions.width}px; transform: translateX(${tabDimensions.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px)`) + } + + + changeTab(target) { + if (target === this.prevTab || !target.closest('sm-tab')) + return + if (this.prevTab) + this.prevTab.classList.remove('active') + target.classList.add('active') + + target.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }) + this.moveIndiactor(target.getBoundingClientRect()) + this.prevTab = target; + this.activeTab = target; + } + handleClick(e) { + if (e.target.closest('sm-tab')) { + this.changeTab(e.target) + this.fireEvent(e.target.dataset.index) + } + } + + handlePanelChange(e) { + console.log(this.allTabs) + this.changeTab(this.allTabs[e.detail.index]) + } + + connectedCallback() { + if (!this.hasAttribute('target') || this.getAttribute('target').value === '') return; + this.target = this.getAttribute('target') + + this.tabSlot.addEventListener('slotchange', () => { + this.allTabs = this.tabSlot.assignedElements(); + this.allTabs.forEach((tab, index) => { + tab.dataset.index = index + }) + }) + + this.addEventListener('click', this.handleClick) + document.addEventListener(`switchedpanel${this.target}`, this.handlePanelChange) + + let resizeObserver = new ResizeObserver(entries => { + entries.forEach((entry) => { + if (this.prevTab) { + let tabDimensions = this.activeTab.getBoundingClientRect(); + this.moveIndiactor(tabDimensions) + } + }) + }) + resizeObserver.observe(this) + let observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + this.indicator.style.transition = 'none' + if (this.activeTab) { + let tabDimensions = this.activeTab.getBoundingClientRect(); + this.moveIndiactor(tabDimensions) + } else { + this.allTabs[0].classList.add('active') + let tabDimensions = this.allTabs[0].getBoundingClientRect(); + this.moveIndiactor(tabDimensions) + this.fireEvent(0) + this.prevTab = this.tabSlot.assignedElements()[0]; + this.activeTab = this.prevTab; + } + } + }) + }, { + threshold: 1.0 + }) + observer.observe(this) + } + disconnectedCallback() { + this.removeEventListener('click', this.handleClick) + document.removeEventListener(`switchedpanel${this.target}`, this.handlePanelChange) + } +}) + +// tab +const smTab = document.createElement('template') +smTab.innerHTML = ` + +
+ +
+`; + +customElements.define('sm-tab', class extends HTMLElement { + constructor() { + super() + this.shadow = this.attachShadow({ + mode: 'open' + }).append(smTab.content.cloneNode(true)) + } +}) + +// tab-panels + +const smTabPanels = document.createElement('template') +smTabPanels.innerHTML = ` + +
+ Nothing to see here. +
+`; + +customElements.define('sm-tab-panels', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smTabPanels.content.cloneNode(true)) + + this.isTransitioning = false + + this.panelContainer = this.shadowRoot.querySelector('.panel-container'); + this.panelSlot = this.shadowRoot.querySelector('slot'); + this.handleTabChange = this.handleTabChange.bind(this) + } + handleTabChange(e) { + this.isTransitioning = true + this.panelContainer.scrollTo({ + left: this.allPanels[e.detail.index].getBoundingClientRect().left - this.panelContainer.getBoundingClientRect().left + this.panelContainer.scrollLeft, + behavior: 'smooth' + }) + setTimeout(() => { + this.isTransitioning = false + }, 300); + } + fireEvent(index) { + this.dispatchEvent( + new CustomEvent(`switchedpanel${this.id}`, { + bubbles: true, + detail: { + index: parseInt(index) + } + }) + ) + } + connectedCallback() { + this.panelSlot.addEventListener('slotchange', () => { + this.allPanels = this.panelSlot.assignedElements() + this.allPanels.forEach((panel, index) => { + panel.dataset.index = index + intersectionObserver.observe(panel) + }) + }) + document.addEventListener(`switchedtab${this.id}`, this.handleTabChange) + + const intersectionObserver = new IntersectionObserver(entries => { + + entries.forEach(entry => { + if (!this.isTransitioning && entry.isIntersecting) { + this.fireEvent(entry.target.dataset.index), 3000 + } + }) + }, { + threshold: 0.6 + }) + } + disconnectedCallback() { + intersectionObserver.disconnect() + document.removeEventListener(`switchedtab${this.id}`, this.handleTabChange) + } +}) + +const tagsInput = document.createElement('template') +tagsInput.innerHTML = ` + +
+ +

+
+` + +customElements.define('tags-input', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(tagsInput.content.cloneNode(true)) + + this.input = this.shadowRoot.querySelector('input') + this.tagsWrapper = this.shadowRoot.querySelector('.tags-wrapper') + this.placeholder = this.shadowRoot.querySelector('.placeholder') + this.reflectedAttributes = ['placeholder', 'limit'] + this.limit = undefined + this.tags = new Set() + + this.reset = this.reset.bind(this) + this.handleInput = this.handleInput.bind(this) + this.handleKeydown = this.handleKeydown.bind(this) + this.handleClick = this.handleClick.bind(this) + this.removeTag = this.removeTag.bind(this) + } + static get observedAttributes() { + return ['placeholder', 'limit'] + } + get value() { + return [...this.tags].join() + } + focusIn() { + this.input.focus() + } + reset(){ + this.input.value = '' + this.tags.clear() + while (this.input.previousElementSibling) { + this.input.previousElementSibling.remove() + } + } + handleInput(e){ + const inputValueLength = e.target.value.trim().length + e.target.setAttribute('size', inputValueLength ? inputValueLength : '3') + if (inputValueLength) { + this.placeholder.classList.add('hide') + } + else if (!inputValueLength && !this.tags.size) { + this.placeholder.classList.remove('hide') + } + } + handleKeydown(e){ + if (e.key === ',' || e.key === '/') { + e.preventDefault() + } + if (e.target.value.trim() !== '') { + if (e.key === 'Enter' || e.key === ',' || e.key === '/' || e.code === 'Space') { + const tagValue = e.target.value.trim() + if (this.tags.has(tagValue)) { + this.tagsWrapper.querySelector(`[data-value="${tagValue}"]`).animate([ + { + backgroundColor: 'initial' + }, + { + backgroundColor: 'var(--accent-color)' + }, + { + backgroundColor: 'initial' + }, + ], { + duration: 300, + easing: 'ease' + }) + } + else { + const tag = document.createElement('span') + tag.dataset.value = tagValue + tag.className = 'tag' + tag.innerHTML = ` + ${tagValue} + + ` + this.input.before(tag) + this.tags.add(tagValue) + } + e.target.value = '' + e.target.setAttribute('size', '3') + if (this.limit && this.limit < this.tags.size + 1) { + this.input.readOnly = true + return + } + } + } + else { + if (e.key === 'Backspace' && this.input.previousElementSibling) { + this.removeTag(this.input.previousElementSibling) + } + if (this.limit && this.limit > this.tags.size) { + this.input.readOnly = false + } + } + } + handleClick(e){ + if (e.target.closest('.tag')) { + this.removeTag(e.target.closest('.tag')) + } + else { + this.input.focus() + } + } + removeTag(tag){ + this.tags.delete(tag.dataset.value) + tag.remove() + if (!this.tags.size) { + this.placeholder.classList.remove('hide') + } + } + connectedCallback() { + this.input.addEventListener('input', this.handleInput) + this.input.addEventListener('keydown', this.handleKeydown) + this.tagsWrapper.addEventListener('click', this.handleClick) + } + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'placeholder') { + this.placeholder.textContent = newValue + } + if (name === 'limit') { + this.limit = parseInt(newValue) + } + } + disconnectedCallback() { + this.input.removeEventListener('input', this.handleInput) + this.input.removeEventListener('keydown', this.handleKeydown) + this.tagsWrapper.removeEventListener('click', this.handleClick) + } +}) +const smTextarea = document.createElement('template') +smTextarea.innerHTML = ` + + +`; +customElements.define('sm-textarea', + class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smTextarea.content.cloneNode(true)) + + this.textarea = this.shadowRoot.querySelector('textarea') + this.textareaBox = this.shadowRoot.querySelector('.textarea') + this.placeholder = this.shadowRoot.querySelector('.placeholder') + this.reflectedAttributes = ['disabled', 'required', 'readonly', 'rows', 'minlength', 'maxlength'] + + this.reset = this.reset.bind(this) + this.focusIn = this.focusIn.bind(this) + this.fireEvent = this.fireEvent.bind(this) + this.checkInput = this.checkInput.bind(this) + } + static get observedAttributes() { + return ['disabled', 'value', 'placeholder', 'required', 'readonly', 'rows', 'minlength', 'maxlength'] + } + get value() { + return this.textarea.value + } + set value(val) { + this.setAttribute('value', val) + this.fireEvent() + } + get disabled() { + return this.hasAttribute('disabled') + } + set disabled(val) { + if (val) { + this.setAttribute('disabled', '') + } else { + this.removeAttribute('disabled') + } + } + get isValid() { + return this.textarea.checkValidity() + } + reset(){ + this.setAttribute('value', '') + } + focusIn(){ + this.textarea.focus() + } + fireEvent(){ + let event = new Event('input', { + bubbles: true, + cancelable: true, + composed: true + }); + this.dispatchEvent(event); + } + checkInput(){ + if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder') === '') + return; + if (this.textarea.value !== '') { + this.placeholder.classList.add('hide') + } else { + this.placeholder.classList.remove('hide') + } + } + connectedCallback() { + this.textarea.addEventListener('input', e => { + this.textareaBox.dataset.value = this.textarea.value + this.checkInput() + }) + } + attributeChangedCallback(name, oldValue, newValue) { + if (this.reflectedAttributes.includes(name)) { + if (this.hasAttribute(name)) { + this.textarea.setAttribute(name, this.getAttribute(name) ? this.getAttribute(name) : '') + } + else { + this.textContent.removeAttribute(name) + } + } + else if (name === 'placeholder') { + this.placeholder.textContent = this.getAttribute('placeholder') + } + else if (name === 'value') { + this.textarea.value = newValue; + this.textareaBox.dataset.value = newValue + this.checkInput() + } + } + }) +const textField = document.createElement('template') +textField.innerHTML = ` + +
+
+
+ + Edit + + + + Save + + +
+
+` + +customElements.define('text-field', class extends HTMLElement{ + constructor(){ + super() + this.attachShadow({ + mode: 'open' + }).append(textField.content.cloneNode(true)) + + this.textField = this.shadowRoot.querySelector('.text-field') + this.textContainer = this.textField.children[0] + this.iconsContainer = this.textField.children[1] + this.editButton = this.textField.querySelector('.edit-button') + this.saveButton = this.textField.querySelector('.save-button') + this.isTextEditable = false + this.isDisabled = false + + this.fireEvent = this.fireEvent.bind(this) + this.setEditable = this.setEditable.bind(this) + this.setNonEditable = this.setNonEditable.bind(this) + this.revert = this.revert.bind(this) + } + + static get observedAttributes(){ + return ['disabled'] + } + + get value(){ + return this.text + } + set value(val) { + this.text = val + this.textContainer.textContent = val + this.setAttribute('value', val) + } + set disabled(val) { + this.isDisabled = val + if(this.isDisabled) + this.setAttribute('disabled', '') + else + this.removeAttribute('disabled') + } + fireEvent(value){ + let event = new CustomEvent('change', { + bubbles: true, + cancelable: true, + composed: true, + detail: { + value + } + }); + this.dispatchEvent(event); + } + + setEditable(){ + if(this.isTextEditable) return + this.textContainer.contentEditable = true + this.textContainer.classList.add('editable') + this.textContainer.focus() + document.execCommand('selectAll', false, null); + this.editButton.animate(this.rotateOut, this.animOptions).onfinish = () => { + this.editButton.classList.add('hide') + } + setTimeout(() => { + this.saveButton.classList.remove('hide') + this.saveButton.animate(this.rotateIn, this.animOptions) + }, 100); + this.isTextEditable = true + } + setNonEditable(){ + if (!this.isTextEditable) return + this.textContainer.contentEditable = false + this.textContainer.classList.remove('editable') + + if (this.text !== this.textContainer.textContent.trim()) { + this.setAttribute('value', this.textContainer.textContent) + this.text = this.textContainer.textContent.trim() + this.fireEvent(this.text) + } + this.saveButton.animate(this.rotateOut, this.animOptions).onfinish = () => { + this.saveButton.classList.add('hide') + } + setTimeout(() => { + this.editButton.classList.remove('hide') + this.editButton.animate(this.rotateIn, this.animOptions) + }, 100); + this.isTextEditable = false + } + + revert(){ + if (this.textContainer.isContentEditable) { + this.value = this.text + this.setNonEditable() + } + } + + connectedCallback(){ + this.text + if (this.hasAttribute('value')) { + this.text = this.getAttribute('value') + this.textContainer.textContent = this.text + } + if(this.hasAttribute('disable')) + this.isDisabled = true + else + this.isDisabled = false + + this.rotateOut = [ + { + transform: 'rotate(0)', + opacity: 1 + }, + { + transform: 'rotate(90deg)', + opacity: 0 + }, + ] + this.rotateIn = [ + { + transform: 'rotate(-90deg)', + opacity: 0 + }, + { + transform: 'rotate(0)', + opacity: 1 + }, + ] + this.animOptions = { + duration: 300, + easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', + fill: 'forwards' + } + if (!this.isDisabled) { + this.iconsContainer.classList.remove('hide') + this.textContainer.addEventListener('dblclick', this.setEditable) + this.editButton.addEventListener('click', this.setEditable) + this.saveButton.addEventListener('click', this.setNonEditable) + } + } + attributeChangedCallback(name) { + if (name === 'disabled') { + if (this.hasAttribute('disabled')) { + this.textContainer.removeEventListener('dblclick', this.setEditable) + this.editButton.removeEventListener('click', this.setEditable) + this.saveButton.removeEventListener('click', this.setNonEditable) + this.revert() + } + else { + this.textContainer.addEventListener('dblclick', this.setEditable) + this.editButton.addEventListener('click', this.setEditable) + this.saveButton.addEventListener('click', this.setNonEditable) + } + } + } + disconnectedCallback() { + this.textContainer.removeEventListener('dblclick', this.setEditable) + this.editButton.removeEventListener('click', this.setEditable) + this.saveButton.removeEventListener('click', this.setNonEditable) + } +}) +const themeToggle = document.createElement('template') +themeToggle.innerHTML = ` + + +` + +class ThemeToggle extends HTMLElement { + constructor() { + super(); + + this.attachShadow({ + mode: 'open' + }).append(themeToggle.content.cloneNode(true)) + + this.isChecked = false + this.hasTheme = 'light' + + this.toggleState = this.toggleState.bind(this) + this.fireEvent = this.fireEvent.bind(this) + this.handleThemeChange = this.handleThemeChange.bind(this) + } + static get observedAttributes() { + return ['checked']; + } + + daylight() { + this.hasTheme = 'light' + document.body.dataset.theme = 'light' + this.setAttribute('aria-checked', 'false') + } + + nightlight() { + this.hasTheme = 'dark' + document.body.dataset.theme = 'dark' + this.setAttribute('aria-checked', 'true') + } + + toggleState() { + this.toggleAttribute('checked') + this.fireEvent() + } + handleKeyDown(e) { + if (e.code === 'Space') { + this.toggleState() + } + } + handleThemeChange(e) { + if (e.detail.theme !== this.hasTheme) { + if (e.detail.theme === 'dark') { + this.setAttribute('checked', '') + } + else { + this.removeAttribute('checked') + } + } + } + + fireEvent() { + this.dispatchEvent( + new CustomEvent('themechange', { + bubbles: true, + composed: true, + detail: { + theme: this.hasTheme + } + }) + ) + } + + connectedCallback() { + this.setAttribute('role', 'switch') + this.setAttribute('aria-label', 'theme toggle') + if (localStorage.theme === "dark") { + this.nightlight(); + this.setAttribute('checked', '') + } else if (localStorage.theme === "light") { + this.daylight(); + this.removeAttribute('checked') + } + else { + if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) { + this.nightlight(); + this.setAttribute('checked', '') + } else { + this.daylight(); + this.removeAttribute('checked') + } + } + this.addEventListener("click", this.toggleState); + this.addEventListener("keydown", this.handleKeyDown); + document.addEventListener('themechange', this.handleThemeChange) + } + + disconnectedCallback() { + this.removeEventListener("click", this.toggleState); + this.removeEventListener("keydown", this.handleKeyDown); + document.removeEventListener('themechange', this.handleThemeChange) + } + + attributeChangedCallback(name, oldVal, newVal) { + if (name === 'checked') { + if (this.hasAttribute('checked')) { + this.nightlight(); + localStorage.setItem("theme", "dark"); + } else { + this.daylight(); + localStorage.setItem("theme", "light"); + } + } + } +} + +window.customElements.define('theme-toggle', ThemeToggle); \ No newline at end of file diff --git a/components/dist/tabs.js b/components/dist/tabs.js index a712daf..74f4072 100644 --- a/components/dist/tabs.js +++ b/components/dist/tabs.js @@ -152,7 +152,6 @@ customElements.define('sm-tab-header', class extends HTMLElement { } handlePanelChange(e) { - console.log(this.allTabs) this.changeTab(this.allTabs[e.detail.index]) } diff --git a/components/dist/tabs.min.js b/components/dist/tabs.min.js index 82d6ba2..1850cc4 100644 --- a/components/dist/tabs.min.js +++ b/components/dist/tabs.min.js @@ -1 +1 @@ -const smTabHeader=document.createElement("template");smTabHeader.innerHTML='\n\n
\n
\n \n
\n
\n
\n',customElements.define("sm-tab-header",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smTabHeader.content.cloneNode(!0)),this.prevTab,this.allTabs,this.activeTab,this.indicator=this.shadowRoot.querySelector(".indicator"),this.tabSlot=this.shadowRoot.querySelector("slot"),this.tabHeader=this.shadowRoot.querySelector(".tab-header"),this.changeTab=this.changeTab.bind(this),this.handleClick=this.handleClick.bind(this),this.handlePanelChange=this.handlePanelChange.bind(this)}fireEvent(t){this.dispatchEvent(new CustomEvent(`switchedtab${this.target}`,{bubbles:!0,detail:{index:parseInt(t)}}))}moveIndiactor(t){this.indicator.setAttribute("style",`width: ${t.width}px; transform: translateX(${t.left-this.tabHeader.getBoundingClientRect().left+this.tabHeader.scrollLeft}px)`)}changeTab(t){t!==this.prevTab&&t.closest("sm-tab")&&(this.prevTab&&this.prevTab.classList.remove("active"),t.classList.add("active"),t.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"}),this.moveIndiactor(t.getBoundingClientRect()),this.prevTab=t,this.activeTab=t)}handleClick(t){t.target.closest("sm-tab")&&(this.changeTab(t.target),this.fireEvent(t.target.dataset.index))}handlePanelChange(t){console.log(this.allTabs),this.changeTab(this.allTabs[t.detail.index])}connectedCallback(){if(!this.hasAttribute("target")||""===this.getAttribute("target").value)return;this.target=this.getAttribute("target"),this.tabSlot.addEventListener("slotchange",()=>{this.allTabs=this.tabSlot.assignedElements(),this.allTabs.forEach((t,n)=>{t.dataset.index=n})}),this.addEventListener("click",this.handleClick),document.addEventListener(`switchedpanel${this.target}`,this.handlePanelChange);let t=new ResizeObserver(t=>{t.forEach(t=>{if(this.prevTab){let t=this.activeTab.getBoundingClientRect();this.moveIndiactor(t)}})});t.observe(this);let n=new IntersectionObserver(t=>{t.forEach(t=>{if(t.isIntersecting)if(this.indicator.style.transition="none",this.activeTab){let t=this.activeTab.getBoundingClientRect();this.moveIndiactor(t)}else{this.allTabs[0].classList.add("active");let t=this.allTabs[0].getBoundingClientRect();this.moveIndiactor(t),this.fireEvent(0),this.prevTab=this.tabSlot.assignedElements()[0],this.activeTab=this.prevTab}})},{threshold:1});n.observe(this)}disconnectedCallback(){this.removeEventListener("click",this.handleClick),document.removeEventListener(`switchedpanel${this.target}`,this.handlePanelChange)}});const smTab=document.createElement("template");smTab.innerHTML='\n\n
\n\n
\n',customElements.define("sm-tab",class extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"}).append(smTab.content.cloneNode(!0))}});const smTabPanels=document.createElement("template");smTabPanels.innerHTML='\n\n
\n Nothing to see here.\n
\n',customElements.define("sm-tab-panels",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smTabPanels.content.cloneNode(!0)),this.isTransitioning=!1,this.panelContainer=this.shadowRoot.querySelector(".panel-container"),this.panelSlot=this.shadowRoot.querySelector("slot"),this.handleTabChange=this.handleTabChange.bind(this)}handleTabChange(t){this.isTransitioning=!0,this.panelContainer.scrollTo({left:this.allPanels[t.detail.index].getBoundingClientRect().left-this.panelContainer.getBoundingClientRect().left+this.panelContainer.scrollLeft,behavior:"smooth"}),setTimeout(()=>{this.isTransitioning=!1},300)}fireEvent(t){this.dispatchEvent(new CustomEvent(`switchedpanel${this.id}`,{bubbles:!0,detail:{index:parseInt(t)}}))}connectedCallback(){this.panelSlot.addEventListener("slotchange",()=>{this.allPanels=this.panelSlot.assignedElements(),this.allPanels.forEach((n,e)=>{n.dataset.index=e,t.observe(n)})}),document.addEventListener(`switchedtab${this.id}`,this.handleTabChange);const t=new IntersectionObserver(t=>{t.forEach(t=>{!this.isTransitioning&&t.isIntersecting&&this.fireEvent(t.target.dataset.index)})},{threshold:.6})}disconnectedCallback(){intersectionObserver.disconnect(),document.removeEventListener(`switchedtab${this.id}`,this.handleTabChange)}}); \ No newline at end of file +const smTabHeader=document.createElement("template");smTabHeader.innerHTML='\n\n
\n
\n \n
\n
\n
\n',customElements.define("sm-tab-header",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smTabHeader.content.cloneNode(!0)),this.prevTab,this.allTabs,this.activeTab,this.indicator=this.shadowRoot.querySelector(".indicator"),this.tabSlot=this.shadowRoot.querySelector("slot"),this.tabHeader=this.shadowRoot.querySelector(".tab-header"),this.changeTab=this.changeTab.bind(this),this.handleClick=this.handleClick.bind(this),this.handlePanelChange=this.handlePanelChange.bind(this)}fireEvent(t){this.dispatchEvent(new CustomEvent(`switchedtab${this.target}`,{bubbles:!0,detail:{index:parseInt(t)}}))}moveIndiactor(t){this.indicator.setAttribute("style",`width: ${t.width}px; transform: translateX(${t.left-this.tabHeader.getBoundingClientRect().left+this.tabHeader.scrollLeft}px)`)}changeTab(t){t!==this.prevTab&&t.closest("sm-tab")&&(this.prevTab&&this.prevTab.classList.remove("active"),t.classList.add("active"),t.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"}),this.moveIndiactor(t.getBoundingClientRect()),this.prevTab=t,this.activeTab=t)}handleClick(t){t.target.closest("sm-tab")&&(this.changeTab(t.target),this.fireEvent(t.target.dataset.index))}handlePanelChange(t){this.changeTab(this.allTabs[t.detail.index])}connectedCallback(){if(!this.hasAttribute("target")||""===this.getAttribute("target").value)return;this.target=this.getAttribute("target"),this.tabSlot.addEventListener("slotchange",()=>{this.allTabs=this.tabSlot.assignedElements(),this.allTabs.forEach((t,n)=>{t.dataset.index=n})}),this.addEventListener("click",this.handleClick),document.addEventListener(`switchedpanel${this.target}`,this.handlePanelChange);let t=new ResizeObserver(t=>{t.forEach(t=>{if(this.prevTab){let t=this.activeTab.getBoundingClientRect();this.moveIndiactor(t)}})});t.observe(this);let n=new IntersectionObserver(t=>{t.forEach(t=>{if(t.isIntersecting)if(this.indicator.style.transition="none",this.activeTab){let t=this.activeTab.getBoundingClientRect();this.moveIndiactor(t)}else{this.allTabs[0].classList.add("active");let t=this.allTabs[0].getBoundingClientRect();this.moveIndiactor(t),this.fireEvent(0),this.prevTab=this.tabSlot.assignedElements()[0],this.activeTab=this.prevTab}})},{threshold:1});n.observe(this)}disconnectedCallback(){this.removeEventListener("click",this.handleClick),document.removeEventListener(`switchedpanel${this.target}`,this.handlePanelChange)}});const smTab=document.createElement("template");smTab.innerHTML='\n\n
\n\n
\n',customElements.define("sm-tab",class extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"}).append(smTab.content.cloneNode(!0))}});const smTabPanels=document.createElement("template");smTabPanels.innerHTML='\n\n
\n Nothing to see here.\n
\n',customElements.define("sm-tab-panels",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smTabPanels.content.cloneNode(!0)),this.isTransitioning=!1,this.panelContainer=this.shadowRoot.querySelector(".panel-container"),this.panelSlot=this.shadowRoot.querySelector("slot"),this.handleTabChange=this.handleTabChange.bind(this)}handleTabChange(t){this.isTransitioning=!0,this.panelContainer.scrollTo({left:this.allPanels[t.detail.index].getBoundingClientRect().left-this.panelContainer.getBoundingClientRect().left+this.panelContainer.scrollLeft,behavior:"smooth"}),setTimeout(()=>{this.isTransitioning=!1},300)}fireEvent(t){this.dispatchEvent(new CustomEvent(`switchedpanel${this.id}`,{bubbles:!0,detail:{index:parseInt(t)}}))}connectedCallback(){this.panelSlot.addEventListener("slotchange",()=>{this.allPanels=this.panelSlot.assignedElements(),this.allPanels.forEach((n,e)=>{n.dataset.index=e,t.observe(n)})}),document.addEventListener(`switchedtab${this.id}`,this.handleTabChange);const t=new IntersectionObserver(t=>{t.forEach(t=>{!this.isTransitioning&&t.isIntersecting&&this.fireEvent(t.target.dataset.index)})},{threshold:.6})}disconnectedCallback(){intersectionObserver.disconnect(),document.removeEventListener(`switchedtab${this.id}`,this.handleTabChange)}}); \ No newline at end of file diff --git a/components/index.html b/components/index.html index 33c6a33..758e628 100644 --- a/components/index.html +++ b/components/index.html @@ -1433,6 +1433,23 @@

This component can be used sub-page navigation.

+

Interactive demo

+ + Audio + Video + + +
+

Audio

+

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Exercitationem esse quod quae + repellat qui. Expedita fugiat voluptates beatae itaque corporis!

+
+
+

Video

+

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Exercitationem esse quod quae + repellat qui. Expedita fugiat voluptates beatae itaque corporis!

+
+

Usage

 
@@ -1493,23 +1510,6 @@
                         

-

Interactive demo

- - Audio - Video - - -
-

Audio

-

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Exercitationem esse quod quae - repellat qui. Expedita fugiat voluptates beatae itaque corporis!

-
-
-

Video

-

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Exercitationem esse quod quae - repellat qui. Expedita fugiat voluptates beatae itaque corporis!

-
-

Variants

tab is the only other variant than default style.

@@ -1923,27 +1923,7 @@ - - - - - - - - - - - - - - - - - - - - - +