You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

92 lines
3.1 KiB

import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["dialog"]
connect() {
this.#openWhenSettled()
}
close() {
this.dialogTarget.close()
}
// A click whose target is the <dialog> itself landed on the backdrop
// (the panel fills the content box), so dismiss.
backdropClose(event) {
if (event.target === this.dialogTarget) this.close()
}
remove() {
this.dispatch("closed", { target: window })
this.element.remove()
}
// The screen underneath may still be animating out. The wave is the last
// thing to leave, so waiting on it holds the modal until the exit is done.
async #openWhenSettled() {
const wave = document.querySelector(".is-exiting .hero-container svg.ico-wave")
if (wave) await this.#settle(wave, "animationend")
if (!this.element.isConnected) return
this.dialogTarget.showModal()
this.dispatch("opened", { target: window })
this.#playEntrance()
}
// The hero opens full-bleed, pushing the copy and the markers off the bottom.
// Once the wave has landed, the carousel and the hero shrink back to their
// measured resting heights, which carries everything below up into view.
// Sizing both is what hides the slide's own copy: the li clips whatever the
// hero pushes past it.
async #playEntrance() {
const panel = this.element.querySelector(".modal-panel")
const carousel = panel?.querySelector(".carousel")
if (!carousel) return
const heroes = Array.from(carousel.querySelectorAll(":scope > li > .hero-container"))
const boxes = [carousel, ...heroes]
const rest = new Map(boxes.map((box) => [box, box.offsetHeight]))
const full = this.dialogTarget.clientHeight
panel.classList.add("is-entering")
boxes.forEach((box) => {
box.style.transition = "none"
box.style.height = `${full}px`
})
getComputedStyle(carousel).height
boxes.forEach((box) => (box.style.transition = ""))
const wave = carousel.querySelector(".hero-container svg.ico-wave")
if (wave) await this.#settle(wave, "animationend")
boxes.forEach((box) => (box.style.height = `${rest.get(box)}px`))
await this.#settle(carousel, "transitionend")
panel.classList.remove("is-entering")
boxes.forEach((box) => (box.style.height = ""))
}
// Resolves when the element's animation or transition ends. Safari doesn't
// reliably have one registered at the moment we ask for it, so the declared
// timing is used as a fallback rather than getAnimations().
#settle(element, eventName) {
return new Promise((resolve) => {
const style = getComputedStyle(element)
const seconds =
eventName === "animationend"
? parseFloat(style.animationDuration) + parseFloat(style.animationDelay)
: parseFloat(style.transitionDuration) + parseFloat(style.transitionDelay)
const finish = () => {
element.removeEventListener(eventName, finish)
clearTimeout(timer)
resolve()
}
const timer = setTimeout(finish, (seconds || 0) * 1000 + 50)
element.addEventListener(eventName, finish)
})
}
}