Skip to content

Instantly share code, notes, and snippets.

@robinvdvleuten
Created May 1, 2020 14:14
Show Gist options
  • Save robinvdvleuten/4ca0a80ab3024cd2e8c2becbc68e66a5 to your computer and use it in GitHub Desktop.
Save robinvdvleuten/4ca0a80ab3024cd2e8c2becbc68e66a5 to your computer and use it in GitHub Desktop.
Add support for Tailwind transitions in Unpoly
import { getUpAttributes } from "./utils"
function transition(element, stages) {
stages.start()
stages.during()
requestAnimationFrame(() => {
// Note: Safari's transitionDuration property will list out comma separated transition durations
// for every single transition property. Let's grab the first one and call it a day.
let duration = Number(getComputedStyle(element).transitionDuration.replace(/,.*/, '').replace('s', '')) * 1000
stages.show()
requestAnimationFrame(() => {
stages.end()
setTimeout(() => {
stages.hide()
stages.cleanup()
}, duration);
})
})
}
function transitionClasses(element, classesDuring, classesStart, classesEnd, hook1, hook2) {
const originalClasses = [...element.classList]
const stages = {
start() {
console.log(element, classesStart)
element.classList.add(...classesStart)
},
during() {
element.classList.add(...classesDuring)
},
show() {
hook1()
},
end() {
// Don't remove classes that were in the original class attribute.
element.classList.remove(...classesStart.filter(i => !originalClasses.includes(i)))
element.classList.add(...classesEnd)
},
hide() {
hook2()
},
cleanup() {
element.classList.remove(...classesDuring.filter(i => !originalClasses.includes(i)))
element.classList.remove(...classesEnd.filter(i => !originalClasses.includes(i)))
}
}
transition(element, stages)
}
function transitionClassesIn(element, directives, showCallback) {
const enter = (directives.find(i => i.value === 'enter') || { expression: '' }).expression.split(' ').filter(i => i !== '')
const enterStart = (directives.find(i => i.value === 'enter-start') || { expression: '' }).expression.split(' ').filter(i => i !== '')
const enterEnd = (directives.find(i => i.value === 'enter-end') || { expression: '' }).expression.split(' ').filter(i => i !== '')
transitionClasses(element, enter, enterStart, enterEnd, showCallback, () => {})
}
function transitionClassesOut(element, directives, hideCallback) {
const leave = (directives.find(i => i.value === 'leave') || { expression: '' }).expression.split(' ').filter(i => i !== '')
const leaveStart = (directives.find(i => i.value === 'leave-start') || { expression: '' }).expression.split(' ').filter(i => i !== '')
const leaveEnd = (directives.find(i => i.value === 'leave-end') || { expression: '' }).expression.split(' ').filter(i => i !== '')
transitionClasses(element, leave, leaveStart, leaveEnd, () => {}, hideCallback)
}
up.animation('tailwind:enter', function (element, options) {
const directives = getUpAttributes(element, 'transition')
return new Promise(resolve => {
transitionClassesIn(element, directives, resolve)
})
})
up.animation('tailwind:leave', function (element, options) {
const directives = getUpAttributes(element, 'transition')
return new Promise(resolve => {
transitionClassesOut(element, directives, resolve)
})
})
const upAttributeRegex = /^up-(transition)\b/
export function isUpAttribute(attribute) {
return upAttributeRegex.test(attribute.name)
}
export function getUpAttributes(element, type) {
return Array.from(element.attributes)
.filter(isUpAttribute)
.map(attribute => {
const name = attribute.name
const typeMatch = name.match(upAttributeRegex)
const valueMatch = name.match(/:([a-zA-Z\-:]+)/)
const modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []
return {
type: typeMatch ? typeMatch[1] : null,
value: valueMatch ? valueMatch[1] : null,
modifiers: modifiers.map(i => i.replace('.', '')),
expression: attribute.value,
}
})
.filter(i => {
// If no type is passed in for filtering, bypass filter
if (!type) return true
return i.type === type
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment