From 680b933f68422fac3aa485cc47d160104678dd38 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sat, 17 Sep 2022 18:39:50 +0300 Subject: [PATCH] add more --- js/src/base.js | 34 ++++++++- js/src/toa.js | 192 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 187 insertions(+), 39 deletions(-) diff --git a/js/src/base.js b/js/src/base.js index 3e52b98f10..e85dc9be88 100644 --- a/js/src/base.js +++ b/js/src/base.js @@ -1,3 +1,35 @@ -class Base extends HTMLElement {} +import { executeAfterTransition } from './util/index' +import Manipulator from './dom/manipulator' + +class Base extends HTMLElement { + constructor(config) { + super() + + this._config = this._mergeConfigObj(config, this) + } + + _queueCallback(callback, element, isAnimated = true) { + executeAfterTransition(callback, element, isAnimated) + } + + static get DATA_KEY() { + return `bs.${this.NAME}` + } + + static get EVENT_KEY() { + return `.${this.DATA_KEY}` + } + + _mergeConfigObj(config, element) { + const jsonConfig = Manipulator.getDataAttribute(element, 'config') // try to parse + + return { + ...this.constructor.Default, + ...(typeof jsonConfig === 'object' ? jsonConfig : {}), + ...Manipulator.getDataAttributes(element), + ...(typeof config === 'object' ? config : {}) + } + } +} export default Base diff --git a/js/src/toa.js b/js/src/toa.js index 85c2b90571..19bbe23b45 100644 --- a/js/src/toa.js +++ b/js/src/toa.js @@ -5,71 +5,187 @@ * -------------------------------------------------------------------------- */ import Base from './base' +import EventHandler from './dom/event-handler' + +const NAME = 'toast' +const DATA_KEY = 'bs.toast' +const EVENT_KEY = `.${DATA_KEY}` + +const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}` +const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}` +const EVENT_FOCUSIN = `focusin${EVENT_KEY}` +const EVENT_FOCUSOUT = `focusout${EVENT_KEY}` +const EVENT_HIDE = `hide${EVENT_KEY}` +const EVENT_HIDDEN = `hidden${EVENT_KEY}` +const EVENT_SHOW = `show${EVENT_KEY}` +const EVENT_SHOWN = `shown${EVENT_KEY}` + +const CLASS_NAME_FADE = 'fade' +const CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility +const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_SHOWING = 'showing' // // const TEMPLATE = '' +const DefaultType = { + animation: 'boolean', + autohide: 'boolean', + delay: 'number' +} + +const Default = { + animation: true, + autohide: true, + delay: 5000 +} + export default class Toa extends Base { constructor() { super() - this.autohide = this.getAttribute('autohide') !== 'false' - console.log(this.autohide) // eslint-disable-line no-console - this.classList.add('toast', 'bg-danger', 'p-2') + console.log(this._config) // eslint-disable-line no-console + this.classList.add('toast', 'p-2') this.setAttribute('role', 'alert') - this.ariaLive = 'assertlive' - this.ariaAtomic = 'true' + this.setAttribute('aria-live', 'assertlive') + this.setAttribute('aria-atomic', 'true') + this._timeout = null + this._hasMouseInteraction = false + this._hasKeyboardInteraction = false + } + + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME } show() { - this.classList.add('show') - this.style.display = 'block' - if (this.autohide) { - setTimeout(() => this.hide(), this.constructor.properties.delay.value) - } - } + const showEvent = EventHandler.trigger(this, EVENT_SHOW) - hide() { - if (this._hasMouseInteraction) { - setTimeout(() => this.hide(), this.constructor.properties.delay.value) + if (showEvent.defaultPrevented) { return } - this.style.removeProperty('display') - this.classList.remove('show') - } + this._clearTimeout() - // Button's properties. - static get properties() { - return { - animation: { - type: Boolean, - value: true - }, - autohide: { - type: Boolean, - value: true - }, - delay: { - type: Number, - value: 3000 - } + if (this._config.animation) { + this.classList.add(CLASS_NAME_FADE) } + + const complete = () => { + this.classList.remove(CLASS_NAME_SHOWING) + EventHandler.trigger(this, EVENT_SHOWN) + + this._maybeScheduleHide() + } + + this.classList.remove(CLASS_NAME_HIDE) // @deprecated + // reflow(this) + this.style.display = 'block' + this.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING) + + this._queueCallback(complete, this, this._config.animation) } - connectedCallback() { - this.show() + hide() { + if (!this.isShown()) { + return + } - this.addEventListener('pointerenter', event => this._onInteraction(event, true)) - this.addEventListener('pointerleave', event => this._onInteraction(event, false)) + const hideEvent = EventHandler.trigger(this, EVENT_HIDE) + + if (hideEvent.defaultPrevented) { + return + } + + const complete = () => { + this.classList.add(CLASS_NAME_HIDE) // @deprecated + this.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW) + EventHandler.trigger(this, EVENT_HIDDEN) + this.style.removeProperty('display') + } + + this.classList.add(CLASS_NAME_SHOWING) + this._queueCallback(complete, this, this._config.animation) + } + + isShown() { + return this.classList.contains(CLASS_NAME_SHOW) + } + + // Private + + _maybeScheduleHide() { + if (!this._config.autohide) { + return + } + + if (this._hasMouseInteraction || this._hasKeyboardInteraction) { + return + } + + this._timeout = setTimeout(() => { + this.hide() + }, this._config.delay) } _onInteraction(event, isInteracting) { switch (event.type) { - case 'pointerenter': - case 'pointerleave': + case 'mouseover': + case 'mouseout': this._hasMouseInteraction = isInteracting break + case 'focusin': + case 'focusout': + this._hasKeyboardInteraction = isInteracting + break default: break } + + if (isInteracting) { + this._clearTimeout() + return + } + + const nextElement = event.relatedTarget + if (this === nextElement || this.contains(nextElement)) { + return + } + + this._maybeScheduleHide() } + + _setListeners() { + EventHandler.on(this, EVENT_MOUSEOVER, event => this._onInteraction(event, true)) + EventHandler.on(this, EVENT_MOUSEOUT, event => this._onInteraction(event, false)) + EventHandler.on(this, EVENT_FOCUSIN, event => this._onInteraction(event, true)) + EventHandler.on(this, EVENT_FOCUSOUT, event => this._onInteraction(event, false)) + } + + _clearTimeout() { + clearTimeout(this._timeout) + this._timeout = null + } + + connectedCallback() { + this._setListeners() + this.show() + } + + // + // static get observedAttributes() { + // return ['duration']; + // } + // + // attributeChangedCallback(name, oldValue, newValue) { + // if (name === 'duration') { + // this.duration = newValue; + // } + // } }