mirror of
https://github.com/twbs/bootstrap.git
synced 2024-12-01 13:24:25 +01:00
Move getElementFromSelector
& getSelectorFromElement
to SelectorEngine (#36027)
* Move `getElementFromSelector` & getSelectorFromElement` inside selector-engine.js, in order to use SelectorEngine methods, avoiding raw querySelector usage * add `getMultipleElementsFromSelector` helper Co-authored-by: Julien Déramond <juderamond@gmail.com>
This commit is contained in:
parent
2b21094074
commit
e81e7cda90
@ -7,7 +7,6 @@
|
||||
|
||||
import {
|
||||
defineJQueryPlugin,
|
||||
getElementFromSelector,
|
||||
getNextActiveElement,
|
||||
isRTL,
|
||||
isVisible,
|
||||
@ -431,7 +430,7 @@ class Carousel extends BaseComponent {
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {
|
||||
const target = getElementFromSelector(this)
|
||||
const target = SelectorEngine.getElementFromSelector(this)
|
||||
|
||||
if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
|
||||
return
|
||||
|
@ -8,8 +8,6 @@
|
||||
import {
|
||||
defineJQueryPlugin,
|
||||
getElement,
|
||||
getElementFromSelector,
|
||||
getSelectorFromElement,
|
||||
reflow
|
||||
} from './util/index.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
@ -68,7 +66,7 @@ class Collapse extends BaseComponent {
|
||||
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)
|
||||
|
||||
for (const elem of toggleList) {
|
||||
const selector = getSelectorFromElement(elem)
|
||||
const selector = SelectorEngine.getSelectorFromElement(elem)
|
||||
const filterElement = SelectorEngine.find(selector)
|
||||
.filter(foundElement => foundElement === this._element)
|
||||
|
||||
@ -185,7 +183,7 @@ class Collapse extends BaseComponent {
|
||||
this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)
|
||||
|
||||
for (const trigger of this._triggerArray) {
|
||||
const element = getElementFromSelector(trigger)
|
||||
const element = SelectorEngine.getElementFromSelector(trigger)
|
||||
|
||||
if (element && !this._isShown(element)) {
|
||||
this._addAriaAndCollapsedClass([trigger], false)
|
||||
@ -229,7 +227,7 @@ class Collapse extends BaseComponent {
|
||||
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)
|
||||
|
||||
for (const element of children) {
|
||||
const selected = getElementFromSelector(element)
|
||||
const selected = SelectorEngine.getElementFromSelector(element)
|
||||
|
||||
if (selected) {
|
||||
this._addAriaAndCollapsedClass([element], this._isShown(selected))
|
||||
@ -285,10 +283,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const selector = getSelectorFromElement(this)
|
||||
const selectorElements = SelectorEngine.find(selector)
|
||||
|
||||
for (const element of selectorElements) {
|
||||
for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {
|
||||
Collapse.getOrCreateInstance(element, { toggle: false }).toggle()
|
||||
}
|
||||
})
|
||||
|
@ -77,6 +77,53 @@ const SelectorEngine = {
|
||||
].map(selector => `${selector}:not([tabindex^="-"])`).join(',')
|
||||
|
||||
return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))
|
||||
},
|
||||
|
||||
getSelector(element) {
|
||||
let selector = element.getAttribute('data-bs-target')
|
||||
|
||||
if (!selector || selector === '#') {
|
||||
let hrefAttribute = element.getAttribute('href')
|
||||
|
||||
// The only valid content that could double as a selector are IDs or classes,
|
||||
// so everything starting with `#` or `.`. If a "real" URL is used as the selector,
|
||||
// `document.querySelector` will rightfully complain it is invalid.
|
||||
// See https://github.com/twbs/bootstrap/issues/32273
|
||||
if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Just in case some CMS puts out a full URL with the anchor appended
|
||||
if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
|
||||
hrefAttribute = `#${hrefAttribute.split('#')[1]}`
|
||||
}
|
||||
|
||||
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
|
||||
}
|
||||
|
||||
return selector
|
||||
},
|
||||
|
||||
getSelectorFromElement(element) {
|
||||
const selector = SelectorEngine.getSelector(element)
|
||||
|
||||
if (selector) {
|
||||
return SelectorEngine.findOne(selector) ? selector : null
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
getElementFromSelector(element) {
|
||||
const selector = SelectorEngine.getSelector(element)
|
||||
|
||||
return selector ? SelectorEngine.findOne(selector) : null
|
||||
},
|
||||
|
||||
getMultipleElementsFromSelector(element) {
|
||||
const selector = SelectorEngine.getSelector(element)
|
||||
|
||||
return selector ? SelectorEngine.find(selector) : []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index.js'
|
||||
import { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import ScrollBarHelper from './util/scrollbar.js'
|
||||
@ -336,7 +336,7 @@ class Modal extends BaseComponent {
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
||||
const target = getElementFromSelector(this)
|
||||
const target = SelectorEngine.getElementFromSelector(this)
|
||||
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault()
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
import {
|
||||
defineJQueryPlugin,
|
||||
getElementFromSelector,
|
||||
isDisabled,
|
||||
isVisible
|
||||
} from './util/index.js'
|
||||
@ -231,7 +230,7 @@ class Offcanvas extends BaseComponent {
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
||||
const target = getElementFromSelector(this)
|
||||
const target = SelectorEngine.getElementFromSelector(this)
|
||||
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault()
|
||||
|
@ -5,7 +5,7 @@
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { defineJQueryPlugin, getElementFromSelector, getNextActiveElement, isDisabled } from './util/index.js'
|
||||
import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import BaseComponent from './base-component.js'
|
||||
@ -106,7 +106,7 @@ class Tab extends BaseComponent {
|
||||
|
||||
element.classList.add(CLASS_NAME_ACTIVE)
|
||||
|
||||
this._activate(getElementFromSelector(element)) // Search and activate/show the proper section
|
||||
this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section
|
||||
|
||||
const complete = () => {
|
||||
if (element.getAttribute('role') !== 'tab') {
|
||||
@ -133,7 +133,7 @@ class Tab extends BaseComponent {
|
||||
element.classList.remove(CLASS_NAME_ACTIVE)
|
||||
element.blur()
|
||||
|
||||
this._deactivate(getElementFromSelector(element)) // Search and deactivate the shown section too
|
||||
this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too
|
||||
|
||||
const complete = () => {
|
||||
if (element.getAttribute('role') !== 'tab') {
|
||||
@ -203,7 +203,7 @@ class Tab extends BaseComponent {
|
||||
}
|
||||
|
||||
_setInitialAttributesOnTargetPanel(child) {
|
||||
const target = getElementFromSelector(child)
|
||||
const target = SelectorEngine.getElementFromSelector(child)
|
||||
|
||||
if (!target) {
|
||||
return
|
||||
|
@ -6,7 +6,8 @@
|
||||
*/
|
||||
|
||||
import EventHandler from '../dom/event-handler.js'
|
||||
import { getElementFromSelector, isDisabled } from './index.js'
|
||||
import { isDisabled } from './index.js'
|
||||
import SelectorEngine from '../dom/selector-engine.js'
|
||||
|
||||
const enableDismissTrigger = (component, method = 'hide') => {
|
||||
const clickEvent = `click.dismiss${component.EVENT_KEY}`
|
||||
@ -21,7 +22,7 @@ const enableDismissTrigger = (component, method = 'hide') => {
|
||||
return
|
||||
}
|
||||
|
||||
const target = getElementFromSelector(this) || this.closest(`.${name}`)
|
||||
const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)
|
||||
const instance = component.getOrCreateInstance(target)
|
||||
|
||||
// Method argument is left, for Alert and only, as it doesn't implement the 'hide' method
|
||||
|
@ -30,47 +30,6 @@ const getUID = prefix => {
|
||||
return prefix
|
||||
}
|
||||
|
||||
const getSelector = element => {
|
||||
let selector = element.getAttribute('data-bs-target')
|
||||
|
||||
if (!selector || selector === '#') {
|
||||
let hrefAttribute = element.getAttribute('href')
|
||||
|
||||
// The only valid content that could double as a selector are IDs or classes,
|
||||
// so everything starting with `#` or `.`. If a "real" URL is used as the selector,
|
||||
// `document.querySelector` will rightfully complain it is invalid.
|
||||
// See https://github.com/twbs/bootstrap/issues/32273
|
||||
if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Just in case some CMS puts out a full URL with the anchor appended
|
||||
if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
|
||||
hrefAttribute = `#${hrefAttribute.split('#')[1]}`
|
||||
}
|
||||
|
||||
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
|
||||
}
|
||||
|
||||
return selector
|
||||
}
|
||||
|
||||
const getSelectorFromElement = element => {
|
||||
const selector = getSelector(element)
|
||||
|
||||
if (selector) {
|
||||
return document.querySelector(selector) ? selector : null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const getElementFromSelector = element => {
|
||||
const selector = getSelector(element)
|
||||
|
||||
return selector ? document.querySelector(selector) : null
|
||||
}
|
||||
|
||||
const getTransitionDurationFromElement = element => {
|
||||
if (!element) {
|
||||
return 0
|
||||
@ -316,10 +275,8 @@ export {
|
||||
executeAfterTransition,
|
||||
findShadowRoot,
|
||||
getElement,
|
||||
getElementFromSelector,
|
||||
getjQuery,
|
||||
getNextActiveElement,
|
||||
getSelectorFromElement,
|
||||
getTransitionDurationFromElement,
|
||||
getUID,
|
||||
isDisabled,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import SelectorEngine from '../../../src/dom/selector-engine'
|
||||
import { getFixture, clearFixture } from '../../helpers/fixture'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
|
||||
describe('SelectorEngine', () => {
|
||||
let fixtureEl
|
||||
@ -232,5 +232,159 @@ describe('SelectorEngine', () => {
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSelectorFromElement', () => {
|
||||
it('should get selector from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if data-bs-target equal to #', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should return null if a selector from a href is a url without an anchor', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return the anchor if a selector from a href is a url', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
|
||||
'<div id="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('#target')
|
||||
})
|
||||
|
||||
it('should return null if selector not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getElementFromSelector', () => {
|
||||
it('should get element from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should get element from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should return null if element not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getMultipleElementsFromSelector', () => {
|
||||
it('should get elements from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should get elements in array, from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should return empty array if elements not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
|
||||
})
|
||||
|
||||
it('should return empty array if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -22,119 +22,6 @@ describe('Util', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSelectorFromElement', () => {
|
||||
it('should get selector from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if data-bs-target equal to #', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should return null if a selector from a href is a url without an anchor', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return the anchor if a selector from a href is a url', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
|
||||
'<div id="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toEqual('#target')
|
||||
})
|
||||
|
||||
it('should return null if selector not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getElementFromSelector', () => {
|
||||
it('should get element from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should get element from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should return null if element not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Util.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTransitionDurationFromElement', () => {
|
||||
it('should get transition from element', () => {
|
||||
fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>'
|
||||
|
Loading…
Reference in New Issue
Block a user