From f945caa2bbb36bbbbd99d043b20157828983838e Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Tue, 14 Feb 2023 19:34:56 +0530 Subject: [PATCH] Updating std UI utils --- main_UI.js | 888 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 658 insertions(+), 230 deletions(-) diff --git a/main_UI.js b/main_UI.js index 9057041..2e103e6 100644 --- a/main_UI.js +++ b/main_UI.js @@ -1,239 +1,316 @@ -// Global variables -const appPages = ['dashboard', 'settings']; +/*jshint esversion: 8 */ +/** + * @yaireo/relative-time - javascript function to transform timestamp or date to local relative-time + * + * @version v1.0.0 + * @homepage https://github.com/yairEO/relative-time + */ + +!function (e, t) { var o = o || {}; "function" == typeof o && o.amd ? o([], t) : "object" == typeof exports && "object" == typeof module ? module.exports = t() : "object" == typeof exports ? exports.RelativeTime = t() : e.RelativeTime = t() }(this, (function () { const e = { year: 31536e6, month: 2628e6, day: 864e5, hour: 36e5, minute: 6e4, second: 1e3 }, t = "en", o = { numeric: "auto" }; function n(e) { e = { locale: (e = e || {}).locale || t, options: { ...o, ...e.options } }, this.rtf = new Intl.RelativeTimeFormat(e.locale, e.options) } return n.prototype = { from(t, o) { const n = t - (o || new Date); for (let t in e) if (Math.abs(n) > e[t] || "second" == t) return this.rtf.format(Math.round(n / e[t]), t) } }, n })); + +const relativeTime = new RelativeTime({ style: 'narrow' }); + const domRefs = {}; +const uiGlobals = { + // Use to store global variables +} let timerId; -const currentYear = new Date().getFullYear(); - -//Checks for internet connection status -if (!navigator.onLine) - notify( - "There seems to be a problem connecting to the internet, Please check you internet connection.", - "error", - { sound: true } - ); -window.addEventListener("offline", () => { - notify( - "There seems to be a problem connecting to the internet, Please check you internet connection.", - "error", - { pinned: true, sound: true } - ); -}); -window.addEventListener("online", () => { - getRef("notification_drawer").clearAll(); - notify("We are back online.", "success"); -}); - -// Use instead of document.getElementById -function getRef(elementId) { - if (!domRefs.hasOwnProperty(elementId)) { - domRefs[elementId] = { - count: 1, - ref: null, - }; - return document.getElementById(elementId); - } else { - if (domRefs[elementId].count < 3) { - domRefs[elementId].count = domRefs[elementId].count + 1; +const uiUtils = { + // Use instead of document.getElementById + memoRef(elementId) { + if (!domRefs.hasOwnProperty(elementId)) { + domRefs[elementId] = { + count: 1, + ref: null, + }; return document.getElementById(elementId); } else { - if (!domRefs[elementId].ref) - domRefs[elementId].ref = document.getElementById(elementId); - return domRefs[elementId].ref; + if (domRefs[elementId].count < 3) { + domRefs[elementId].count = domRefs[elementId].count + 1; + return document.getElementById(elementId); + } else { + if (!domRefs[elementId].ref) + domRefs[elementId].ref = document.getElementById(elementId); + return domRefs[elementId].ref; + } } - } -} + }, -// returns dom with specified element -function createElement(tagName, options) { - const { className, textContent, innerHTML, attributes = {} } = options - const elem = document.createElement(tagName) - for (let attribute in attributes) { - elem.setAttribute(attribute, attributes[attribute]) - } - if (className) - elem.className = className - if (textContent) - elem.textContent = textContent - if (innerHTML) - elem.innerHTML = innerHTML - return elem -} - -// Use when a function needs to be executed after user finishes changes -const debounce = (callback, wait) => { - let timeoutId = null; - return (...args) => { - window.clearTimeout(timeoutId); - timeoutId = window.setTimeout(() => { - callback.apply(null, args); - }, wait); - }; -} - -// Limits the rate of function execution -function throttle(func, delay) { - // If setTimeout is already scheduled, no need to do anything - if (timerId) { - return; - } - - // Schedule a setTimeout after delay seconds - timerId = setTimeout(function () { - func(); - - // Once setTimeout function execution is finished, timerId = undefined so that in - // the next scroll event function execution can be scheduled by the setTimeout - timerId = undefined; - }, delay); -} - -class Stack { - constructor() { - this.items = []; - } - push(element) { - this.items.push(element); - } - pop() { - if (this.items.length == 0) - return "Underflow"; - return this.items.pop(); - } - peek() { - return this.items[this.items.length - 1]; - } -} - -// function required for popups or modals to appear -function showPopup(popupId, pinned) { - zIndex++ - getRef(popupId).setAttribute('style', `z-index: ${zIndex}`) - popupStack = getRef(popupId).show({ pinned, popupStack }) - return getRef(popupId); -} - -// hides the popup or modal -function hidePopup() { - if (popupStack.peek() === undefined) - return; - popupStack.peek().popup.hide() -} - -// displays a popup for asking permission. Use this instead of JS confirm -const getConfirmation = (title, message, cancelText = 'Cancel', confirmText = 'OK') => { - return new Promise(resolve => { - showPopup('confirmation_popup', true) - getRef('confirm_title').textContent = title; - getRef('confirm_message').textContent = message; - let cancelButton = getRef('confirmation_popup').children[2].children[0], - submitButton = getRef('confirmation_popup').children[2].children[1] - submitButton.textContent = confirmText - cancelButton.textContent = cancelText - submitButton.onclick = () => { - hidePopup() - resolve(true); + // returns dom with specified element + createElement(tagName, options) { + const { className, textContent, innerHTML, attributes = {} } = options + const elem = document.createElement(tagName) + for (let attribute in attributes) { + elem.setAttribute(attribute, attributes[attribute]) } - cancelButton.onclick = () => { - hidePopup() - resolve(false); - } - }) -} + if (className) + elem.className = className + if (textContent) + elem.textContent = textContent + if (innerHTML) + elem.innerHTML = innerHTML + return elem + }, -// displays a popup for asking user input. Use this instead of JS prompt -async function getPromptInput(title, message = '', isPassword = true, cancelText = 'Cancel', confirmText = 'OK') { - showPopup('prompt_popup', true) - getRef('prompt_title').textContent = title; - let input = getRef('prompt_input'); - input.setAttribute("placeholder", message) - let buttons = getRef('prompt_popup').querySelectorAll("sm-button"); - if (isPassword) - input.setAttribute("type", "text") - else - input.setAttribute("type", "password") - input.focusIn() - buttons[0].textContent = cancelText; - buttons[1].textContent = confirmText; - return new Promise((resolve, reject) => { - buttons[0].onclick = () => { - hidePopup() + // Use when a function needs to be executed after user finishes changes + debounce: (callback, wait) => { + let timeoutId = null; + return (...args) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + callback.apply(null, args); + }, wait); + }; + }, + // Limits the rate of function execution + throttle(func, delay) { + // If setTimeout is already scheduled, no need to do anything + if (timerId) { return; } - buttons[1].onclick = () => { - let value = input.value; - hidePopup() - resolve(value) + + // Schedule a setTimeout after delay seconds + timerId = setTimeout(function () { + func(); + + // Once setTimeout function execution is finished, timerId = undefined so that in + // the next scroll event function execution can be scheduled by the setTimeout + timerId = undefined; + }, delay); + }, + // implements event delegation + delegate(el, event, selector, fn) { + el.addEventListener(event, function (e) { + const potentialTarget = e.target.closest(selector) + if (potentialTarget) { + e.delegateTarget = potentialTarget + fn.call(this, e) + } + }) + }, + formatTime(timestamp, format) { + try { + if (String(timestamp).length < 13) + timestamp *= 1000 + let [day, month, date, year] = new Date(timestamp).toString().split(' '), + minutes = new Date(timestamp).getMinutes(), + hours = new Date(timestamp).getHours(), + currentTime = new Date().toString().split(' ') + + minutes = minutes < 10 ? `0${minutes}` : minutes + let finalHours = ``; + if (hours > 12) + finalHours = `${hours - 12}:${minutes}` + else if (hours === 0) + finalHours = `12:${minutes}` + else + finalHours = `${hours}:${minutes}` + + finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM` + switch (format) { + case 'date-only': + return `${month} ${date}, ${year}`; + break; + case 'time-only': + return finalHours; + case 'relative': + // check if timestamp is older than a day + if (Date.now() - new Date(timestamp) < 60 * 60 * 24 * 1000) + return `${finalHours}`; + else + return relativeTime.from(timestamp) + default: + return `${month} ${date}, ${year} at ${finalHours}`; + } + } catch (e) { + console.error(e); + return timestamp; } + } +}; +//Checks for internet connection status +if (!navigator.onLine) + uiGlobals.connectionErrorNotification = notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error') +window.addEventListener('offline', () => { + uiGlobals.connectionErrorNotification = notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error') +}) +window.addEventListener('online', () => { + getRef('notification_drawer').remove(uiGlobals.connectionErrorNotification) + notify('We are back online.', 'success') + location.reload() +}) +let zIndex = 50 +// function required for popups or modals to appear +function openPopup(popupId, pinned) { + zIndex++ + getRef(popupId).setAttribute('style', `z-index: ${zIndex}`) + return getRef(popupId).show({ pinned }) +} + +// hides the popup or modal +function closePopup(options = {}) { + if (popupStack.peek() === undefined) + return; + popupStack.peek().popup.hide(options) +} +document.addEventListener('popupopened', async e => { + //pushes popup as septate entry in history + history.pushState({ type: 'popup' }, null, null) + switch (e.target.id) { + case '': + break; + } +}) + +document.addEventListener('popupclosed', e => { + switch (e.target.id) { + case '': + break; + } + if (popupStack.items.length === 0) { + // if there are no more popups, do something + } + zIndex--; +}) +window.addEventListener('popstate', e => { + if (!e.state) return + switch (e.state.type) { + case 'popup': + closePopup() + break; + } +}) + +// displays a popup for asking permission. Use this instead of JS confirm +const getConfirmation = (title, options = {}) => { + return new Promise(resolve => { + const { message = '', cancelText = 'Cancel', confirmText = 'OK', danger = false } = options + getRef('confirm_title').innerText = title; + getRef('confirm_message').innerText = message; + const cancelButton = getRef('confirmation_popup').querySelector('.cancel-button'); + const confirmButton = getRef('confirmation_popup').querySelector('.confirm-button') + confirmButton.textContent = confirmText + cancelButton.textContent = cancelText + if (danger) + confirmButton.classList.add('button--danger') + else + confirmButton.classList.remove('button--danger') + const { opened, closed } = openPopup('confirmation_popup') + confirmButton.onclick = () => { + closePopup({ payload: true }) + } + cancelButton.onclick = () => { + closePopup() + } + closed.then((payload) => { + confirmButton.onclick = null + cancelButton.onclick = null + if (payload) + resolve(true) + else + resolve(false) + }) + }) +} +// displays a popup for asking user input. Use this instead of JS prompt +function getPromptInput(title, message = '', options = {}) { + let { placeholder = '', isPassword = false, cancelText = 'Cancel', confirmText = 'OK' } = options + getRef('prompt_title').innerText = title; + getRef('prompt_message').innerText = message; + const cancelButton = getRef('prompt_popup').querySelector('.cancel-button'); + const confirmButton = getRef('prompt_popup').querySelector('.confirm-button') + if (isPassword) { + placeholder = 'Password' + getRef('prompt_input').setAttribute("type", "password") + } + getRef('prompt_input').setAttribute("placeholder", placeholder) + getRef('prompt_input').focusIn() + cancelButton.textContent = cancelText; + confirmButton.textContent = confirmText; + openPopup('prompt_popup', true) + return new Promise((resolve, reject) => { + cancelButton.addEventListener('click', () => { + closePopup() + return null + }, { once: true }) + confirmButton.addEventListener('click', () => { + closePopup() + resolve(getRef('prompt_input').value) + }, { once: true }) }) } //Function for displaying toast notifications. pass in error for mode param if you want to show an error. function notify(message, mode, options = {}) { - const { pinned = false, sound = false } = options let icon switch (mode) { case 'success': - icon = `` + icon = `` break; case 'error': - icon = `` + icon = `` + options.pinned = true break; } - getRef("notification_drawer").push(message, { pinned, icon }); - if (navigator.onLine && sound) { - getRef("notification_sound").currentTime = 0; - getRef("notification_sound").play(); + if (mode === 'error') { + console.error(message) } + return getRef("notification_drawer").push(message, { icon, ...options }); } -function getFormatedTime(time, relative) { - try { - if (String(time).indexOf("_")) time = String(time).split("_")[0]; - const intTime = parseInt(time); - if (String(intTime).length < 13) time *= 1000; - let timeFrag = new Date(intTime).toString().split(" "), - day = timeFrag[0], - month = timeFrag[1], - date = timeFrag[2], - year = timeFrag[3], - minutes = new Date(intTime).getMinutes(), - hours = new Date(intTime).getHours(), - currentTime = new Date().toString().split(" "); - - minutes = minutes < 10 ? `0${minutes}` : minutes; - let finalHours = ``; - if (hours > 12) finalHours = `${hours - 12}:${minutes}`; - else if (hours === 0) finalHours = `12:${minutes}`; - else finalHours = `${hours}:${minutes}`; - - finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`; - if (relative) { - return `${date} ${month} ${year}`; - } else return `${finalHours} ${month} ${date} ${year}`; - } catch (e) { - console.error(e); - return time; +// detect browser version +function detectBrowser() { + let ua = navigator.userAgent, + tem, + M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; + if (/trident/i.test(M[1])) { + tem = /\brv[ :]+(\d+)/g.exec(ua) || []; + return 'IE ' + (tem[1] || ''); } + if (M[1] === 'Chrome') { + tem = ua.match(/\b(OPR|Edge)\/(\d+)/); + if (tem != null) return tem.slice(1).join(' ').replace('OPR', 'Opera'); + } + M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; + if ((tem = ua.match(/version\/(\d+)/i)) != null) M.splice(1, 1, tem[1]); + return M.join(' '); } -window.addEventListener('hashchange', e => showPage(window.location.hash)) +window.addEventListener('hashchange', e => routeTo(window.location.hash)) window.addEventListener("load", () => { - document.body.classList.remove('hide-completely') - showPage(window.location.hash) - // document.querySelectorAll('sm-input[data-flo-id]').forEach(input => input.customValidation = validateAddr) + const [browserName, browserVersion] = detectBrowser().split(' '); + const supportedVersions = { + Chrome: 85, + Firefox: 75, + Safari: 13, + } + if (browserName in supportedVersions) { + if (parseInt(browserVersion) < supportedVersions[browserName]) { + notify(`${browserName} ${browserVersion} is not fully supported, some features may not work properly. Please update to ${supportedVersions[browserName]} or higher.`, 'error') + } + } else { + notify('Browser is not fully compatible, some features may not work. for best experience please use Chrome, Edge, Firefox or Safari', 'error') + } + routeTo(window.location.hash) + document.body.classList.remove('hidden') document.addEventListener('keyup', (e) => { - if (e.code === 'Escape') { - hidePopup() + if (e.key === 'Escape') { + closePopup() } }) - document.addEventListener("pointerdown", (e) => { - if (e.target.closest("button, sm-button:not([disabled]), .interact")) { - createRipple(e, e.target.closest("button, sm-button, .interact")); - } - }); document.addEventListener('copy', () => { notify('copied', 'success') }) + document.addEventListener("pointerdown", (e) => { + if (e.target.closest("button:not([disabled]), .interactive")) { + createRipple(e, e.target.closest("button, .interactive")); + } + }); + document.querySelectorAll('.popup__header__close, .close-popup-on-click').forEach(elem => { + elem.addEventListener('click', () => { + closePopup() + }) + }) }); function createRipple(event, target) { @@ -264,42 +341,393 @@ function createRipple(event, target) { }; } -function showPage(targetPage, options = {}) { - const { firstLoad, hashChange } = options +class Router { + constructor(options = {}) { + const { routes = {}, state = {}, routingStart, routingEnd } = options + this.routes = routes + this.state = state + this.routingStart = routingStart + this.routingEnd = routingEnd + window.addEventListener('hashchange', e => this.routeTo(window.location.hash)) + } + addRoute(route, callback) { + this.routes[route] = callback + } + set state(state) { + this._state = state + } + async routeTo(path) { + let page + let wildcards = [] + let queryString + let params + [path, queryString] = path.split('?'); + if (path.includes('#')) + path = path.split('#')[1]; + if (path.includes('/')) + [, page, ...wildcards] = path.split('/') + else + page = path + this.state = { page, wildcards } + if (queryString) { + params = new URLSearchParams(queryString) + this.state.params = Object.fromEntries(params) + } + if (this.routingStart) { + this.routingStart(this.state) + } + if (this.routes[page]) { + await this.routes[page](this.state) + this.state.lastPage = page + } else { + this.routes['404'](this.state) + } + if (this.routingEnd) { + this.routingEnd(this.state) + } + } +} +const router = new Router({ + routingStart(state) { + loading() + if ("scrollRestoration" in history) { + history.scrollRestoration = "manual"; + } + window.scrollTo(0, 0); + if (state.page !== 'home') + getRef("page_header").classList.remove("hidden"); + }, + routingEnd() { + loading(false) + } +}) + +const appState = { + params: {}, + openedPages: new Set(), +} +const generalPages = ['sign_up', 'sign_in', 'loading', 'landing'] +async function routeTo(targetPage, options = {}) { + const { firstLoad } = options let pageId + let subPageId1 + let subPageId2 + let searchParams + let params if (targetPage === '') { - pageId = 'overview_page' + try { + if (floDapps.user.id) + pageId = 'chat_page' + } catch (e) { + pageId = 'landing' + } + } else { + if (targetPage.includes('/')) { + let path; + [path, searchParams] = targetPage.split('?'); + [, pageId, subPageId1, subPageId2] = path.split('/') + } else { + pageId = targetPage + } } - else { - pageId = targetPage.includes('#') ? targetPage.split('#')[1] : targetPage + + if (!document.querySelector(`#${pageId}`)?.classList.contains('inner-page')) return + try { + if (floDapps.user.id && (generalPages.includes(pageId))) { + history.replaceState(null, null, '#/chat_page'); + pageId = 'chat_page' + } + } catch (e) { + if (!(generalPages.includes(pageId))) return } - if(!appPages.includes(pageId)) return - document.querySelector('.page:not(.hide-completely)').classList.add('hide-completely') - document.querySelector('.nav-list__item--active').classList.remove('nav-list__item--active') - getRef(pageId).classList.remove('hide-completely') - getRef(pageId).animate([ - { - opacity: 0, - transform: 'translateX(-1rem)' - }, - { - opacity: 1, - transform: 'none' - }, - ], - { - duration: 300, - easing: 'ease' + appState.currentPage = pageId + + if (searchParams) { + const urlSearchParams = new URLSearchParams('?' + searchParams); + params = Object.fromEntries(urlSearchParams.entries()); + } + if (params) + appState.params = params + switch (pageId) { + case 'sign_in': + setTimeout(() => { + getRef('private_key_field').focusIn() + }, 0); + break; + case 'sign_up': + getRef('keys_generator').generateKeys() + break; + default: + break; + } + + if (appState.lastPage !== pageId) { + const animOptions = { + duration: 100, + fill: 'forwards', + easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' + } + document.querySelectorAll('.page').forEach(page => page.classList.add('hidden')) + getRef(pageId).closest('.page').classList.remove('hidden') + document.querySelectorAll('.inner-page').forEach(page => page.classList.add('hidden')) + getRef(pageId).classList.remove('hidden') + getRef(pageId).animate([ + { + opacity: 0, + transform: 'translateY(1rem)' + }, + { + opacity: 1, + transform: 'translateY(0)' + }, + ], + { + duration: 300, + easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' + }).onfinish = () => { + } + appState.lastPage = pageId + } + if (params) + appState.params = params + appState.openedPages.add(pageId) + +} +// class based lazy loading +class LazyLoader { + constructor(container, elementsToRender, renderFn, options = {}) { + const { batchSize = 10, freshRender, bottomFirst = false, domUpdated } = options + + this.elementsToRender = elementsToRender + this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || [] + this.renderFn = renderFn + this.intersectionObserver + + this.batchSize = batchSize + this.freshRender = freshRender + this.domUpdated = domUpdated + this.bottomFirst = bottomFirst + + this.shouldLazyLoad = false + this.lastScrollTop = 0 + this.lastScrollHeight = 0 + + this.lazyContainer = document.querySelector(container) + + this.update = this.update.bind(this) + this.render = this.render.bind(this) + this.init = this.init.bind(this) + this.clear = this.clear.bind(this) + } + get elements() { + return this.arrayOfElements + } + init() { + this.intersectionObserver = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + observer.disconnect() + this.render({ lazyLoad: true }) + } + }) + }, { + root: this.lazyContainer }) - const targetListItem = document.querySelector(`.nav-list__item[href="#${pageId}"]`) - targetListItem.classList.add('nav-list__item--active') - if (firstLoad && window.innerWidth > 640 && targetListItem.getBoundingClientRect().top > getRef('side_nav').getBoundingClientRect().height) { - getRef('side_nav').scrollTo({ - top: (targetListItem.getBoundingClientRect().top - getRef('side_nav').getBoundingClientRect().top + getRef('side_nav').scrollTop), - behavior: 'smooth' + this.mutationObserver = new MutationObserver(mutationList => { + mutationList.forEach(mutation => { + if (mutation.type === 'childList') { + if (mutation.addedNodes.length) { + if (this.bottomFirst) { + if (this.lazyContainer.firstElementChild) + this.intersectionObserver.observe(this.lazyContainer.firstElementChild) + } else { + if (this.lazyContainer.lastElementChild) + this.intersectionObserver.observe(this.lazyContainer.lastElementChild) + } + } + } + }) }) + this.mutationObserver.observe(this.lazyContainer, { + childList: true, + }) + this.render() } - if (hashChange && window.innerWidth < 640) { - getRef('side_nav').close() + update(elementsToRender) { + this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || [] + } + render(options = {}) { + let { lazyLoad = false } = options + this.shouldLazyLoad = lazyLoad + const frag = document.createDocumentFragment(); + if (lazyLoad) { + if (this.bottomFirst) { + this.updateEndIndex = this.updateStartIndex + this.updateStartIndex = this.updateEndIndex - this.batchSize + } else { + this.updateStartIndex = this.updateEndIndex + this.updateEndIndex = this.updateEndIndex + this.batchSize + } + } else { + this.intersectionObserver.disconnect() + if (this.bottomFirst) { + this.updateEndIndex = this.arrayOfElements.length + this.updateStartIndex = this.updateEndIndex - this.batchSize - 1 + } else { + this.updateStartIndex = 0 + this.updateEndIndex = this.batchSize + } + this.lazyContainer.innerHTML = ``; + } + this.lastScrollHeight = this.lazyContainer.scrollHeight + this.lastScrollTop = this.lazyContainer.scrollTop + this.arrayOfElements.slice(this.updateStartIndex, this.updateEndIndex).forEach((element, index) => { + frag.append(this.renderFn(element)) + }) + if (this.bottomFirst) { + this.lazyContainer.prepend(frag) + // scroll anchoring for reverse scrolling + this.lastScrollTop += this.lazyContainer.scrollHeight - this.lastScrollHeight + this.lazyContainer.scrollTo({ top: this.lastScrollTop }) + this.lastScrollHeight = this.lazyContainer.scrollHeight + } else { + this.lazyContainer.append(frag) + } + if (!lazyLoad && this.bottomFirst) { + this.lazyContainer.scrollTop = this.lazyContainer.scrollHeight + } + // Callback to be called if elements are updated or rendered for first time + if (!lazyLoad && this.freshRender) + this.freshRender() + } + clear() { + this.intersectionObserver.disconnect() + this.mutationObserver.disconnect() + this.lazyContainer.innerHTML = ``; + } + reset() { + this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || [] + this.render() + } +} + +const slideInLeft = [ + { + opacity: 0, + transform: 'translateX(1.5rem)' + }, + { + opacity: 1, + transform: 'translateX(0)' + } +] +const slideOutLeft = [ + { + opacity: 1, + transform: 'translateX(0)' + }, + { + opacity: 0, + transform: 'translateX(-1.5rem)' + }, +] +const slideInRight = [ + { + opacity: 0, + transform: 'translateX(-1.5rem)' + }, + { + opacity: 1, + transform: 'translateX(0)' + } +] +const slideOutRight = [ + { + opacity: 1, + transform: 'translateX(0)' + }, + { + opacity: 0, + transform: 'translateX(1.5rem)' + }, +] +const slideInDown = [ + { + opacity: 0, + transform: 'translateY(-1.5rem)' + }, + { + opacity: 1, + transform: 'translateY(0)' + }, +] +const slideOutUp = [ + { + opacity: 1, + transform: 'translateY(0)' + }, + { + opacity: 0, + transform: 'translateY(-1.5rem)' + }, +] + +function showChildElement(id, index, options = {}) { + return new Promise((resolve) => { + const { mobileView = false, entry, exit } = options + const animOptions = { + duration: 150, + easing: 'ease', + fill: 'forwards' + } + const parent = typeof id === 'string' ? document.getElementById(id) : id; + const visibleElement = [...parent.children].find(elem => !elem.classList.contains(mobileView ? 'hide-on-mobile' : 'hidden')); + if (visibleElement === parent.children[index]) return; + visibleElement.getAnimations().forEach(anim => anim.cancel()) + parent.children[index].getAnimations().forEach(anim => anim.cancel()) + if (visibleElement) { + if (exit) { + visibleElement.animate(exit, animOptions).onfinish = () => { + visibleElement.classList.add(mobileView ? 'hide-on-mobile' : 'hidden') + parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden') + if (entry) + parent.children[index].animate(entry, animOptions).onfinish = () => resolve() + } + } else { + visibleElement.classList.add(mobileView ? 'hide-on-mobile' : 'hidden') + parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden') + resolve() + } + } else { + parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden') + parent.children[index].animate(entry, animOptions).onfinish = () => resolve() + } + }) +} +function buttonLoader(id, show) { + const button = typeof id === 'string' ? getRef(id) : id; + button.disabled = show; + const animOptions = { + duration: 200, + fill: 'forwards', + easing: 'ease' + } + if (show) { + button.animate([ + { + clipPath: 'circle(100%)', + }, + { + clipPath: 'circle(0)', + }, + ], animOptions).onfinish = e => { + e.target.commitStyles() + e.target.cancel() + } + button.parentNode.append(createElement('sm-spinner')) + } else { + button.style = '' + const potentialTarget = button.parentNode.querySelector('sm-spinner') + if (potentialTarget) potentialTarget.remove(); } } \ No newline at end of file