UNPKG

xtendui

Version:

Xtend UI is a powerful frontend library of tailwind components enhanced by vanilla js. It helps you build interfaces with advanced interactions and animations.

303 lines (275 loc) 7.1 kB
/*! * Xtend UI (https://xtendui.com/) * @copyright (c) 2017 - 2021 Riccardo Caroli * @license MIT (https://github.com/xtendui/xtendui/blob/beta/LICENSE.txt) */ import { Xt } from './xt' import RJSON from 'relaxed-json' Xt.RJSON = RJSON /** * Stickyflow */ class Stickyflow { /** * fields */ _optionsCustom _optionsDefault _optionsInitial _componentNs _scrollTopOld componentName ns options initial disabled = false container element filler /** * constructor * @param {Node|HTMLElement|EventTarget|Window} object Base node * @param {Object} optionsCustom User options * @constructor */ constructor(object, optionsCustom = {}) { const self = this self.container = object self._optionsCustom = optionsCustom self.componentName = self.constructor.componentName self._componentNs = self.componentName.replace('-', '.') // init self._initVars() self._initLogic() } // // init // /** * init vars */ _initVars() { const self = this // options self._optionsDefault = Xt.merge([self.constructor.optionsDefault, Xt.options[self.componentName]]) self._optionsInitial = self.options = Xt.merge([self._optionsDefault, self._optionsCustom]) } /** * init logic */ _initLogic() { const self = this const options = self.options // set self Xt._set({ name: self.componentName, el: self.container, self }) // namespace self.ns = self.ns ?? Xt.uniqueId() // enable first for proper initial activation self.enable() // matches Xt._initMatches({ self, optionsInitial: self._optionsInitial }) // vars self.initial = true // elements self.element = self.container.querySelector(options.element) self.filler = self.container.querySelector(options.filler) // vars self._scrollTopOld = 0 // events const changeHandler = Xt.dataStorage.put(window, `scroll resize/${self.ns}`, self._eventChange.bind(self)) addEventListener('scroll', changeHandler) addEventListener('resize', changeHandler) // initial self._initStart() // init Xt.frame({ el: self.container, ns: `${self.ns}Init`, func: () => { // initialized class self.container.setAttribute(`data-${self.componentName}-init`, '') // dispatch event self.container.dispatchEvent(new CustomEvent(`init.${self._componentNs}`)) self.initial = false // debug if (options.debug) { // eslint-disable-next-line no-console console.debug(`${self.componentName} init`, self) } }, }) // disable last for proper options.disableDeactivate if (self.options.disabled) { self.disable() } } /** * init start */ _initStart() { const self = this // disabled if (self.disabled) { return } // initial self._eventChange() } // // methods // /** * eventChange * @param {Node|HTMLElement|EventTarget|Window} step * @param {Event} e */ _eventChange() { const self = this // disabled if (self.disabled) { return } // position const scrollTop = document.scrollingElement.scrollTop const windowHeight = window.innerHeight const objectHeight = self.element.offsetHeight if (objectHeight < windowHeight) { self.filler.style.height = '' self.element.style.top = '0' self.element.style.bottom = '' } else { if (scrollTop > self._scrollTopOld) { if (!self.element.classList.contains('xt-stickyflow-top')) { const pos = windowHeight - objectHeight const height = Math.max(0, self.element.offsetTop - self.filler.offsetTop) self.filler.style.height = `${height}px` self.element.style.top = `${pos}px` self.element.style.bottom = '' self.element.classList.remove('xt-stickyflow-bottom') self.element.classList.add('xt-stickyflow-top') } } else { if (!self.element.classList.contains('xt-stickyflow-bottom')) { const pos = windowHeight - objectHeight const height = Math.max(0, self.element.offsetTop - self.filler.offsetTop) self.filler.style.height = `${height}px` self.element.style.top = '' self.element.style.bottom = `${pos}px` self.element.classList.add('xt-stickyflow-bottom') self.element.classList.remove('xt-stickyflow-top') } } } // change Xt.frame({ el: self.container, ns: `${self.ns}Change`, func: () => { // dispatch event self.container.dispatchEvent(new CustomEvent(`change.${self._componentNs}`)) }, }) self._scrollTopOld = scrollTop } // // status // /** * enable */ enable() { const self = this if (self.disabled) { // enable self.disabled = false // dispatch event self.container.dispatchEvent(new CustomEvent(`status.${self._componentNs}`)) } } /** * disable * @param {Object} params * @param {Boolean} params.skipEvent Skip dispatch event */ disable({ skipEvent = false } = {}) { const self = this if (!self.disabled) { // disable self.disabled = true // position self.filler.style.height = '' self.element.style.top = '' self.element.style.bottom = '' self.element.classList.remove('xt-stickyflow-bottom') self.element.classList.remove('xt-stickyflow-top') // dispatch event if (!skipEvent) { self.container.dispatchEvent(new CustomEvent(`status.${self._componentNs}`)) } } } // // util // /** * reinit */ reinit() { const self = this // reinit self._initLogic() } /** * destroy */ destroy() { const self = this // remove events const changeHandler = Xt.dataStorage.get(window, `scroll resize/${self.ns}`) removeEventListener('scroll', changeHandler) removeEventListener('resize', changeHandler) // initialized class self.container.removeAttribute(`data-${self.componentName}-init`) // set self Xt._remove({ name: self.componentName, el: self.container }) // dispatch event self.container.dispatchEvent(new CustomEvent(`destroy.${self._componentNs}`)) // delete delete this } // } // // options // Stickyflow.componentName = 'xt-stickyflow' Stickyflow.optionsDefault = { debug: false, // elements element: false, filler: false, } // // export // Xt.Stickyflow = Stickyflow // // observe // if (typeof window !== 'undefined') { Xt.mount({ matches: `[data-${Xt.Stickyflow.componentName}]`, mount: ({ ref }) => { // vars const optionsMarkup = ref.getAttribute(`data-${Xt.Stickyflow.componentName}`) const options = optionsMarkup ? RJSON.parse(optionsMarkup) : {} // init let self = new Xt.Stickyflow(ref, options) // unmount return () => { self.destroy() self = null } }, }) }