|
|
import { Controller } from "@hotwired/stimulus"
|
|
|
|
|
|
export default class extends Controller {
|
|
|
static targets = ["dialog"]
|
|
|
|
|
|
confirm(event) {
|
|
|
// The second pass -- after the player confirmed -- falls through to Turbo.
|
|
|
if (this.confirmed) return
|
|
|
|
|
|
event.preventDefault()
|
|
|
this.pendingForm = event.target
|
|
|
this.dialogTarget.classList.remove("is-confirming", "is-leaving")
|
|
|
this.leaving = false
|
|
|
this.dialogTarget.showModal()
|
|
|
}
|
|
|
|
|
|
// The dialog leaves upwards before the answer is sent, so the wait is however
|
|
|
// long that slide takes -- and nothing at all where the slide doesn't exist.
|
|
|
async ok() {
|
|
|
if (this.leaving) return
|
|
|
await this.#slideOut("is-confirming")
|
|
|
|
|
|
// requestSubmit(), not submit(): the native call skips the submit event, so
|
|
|
// Turbo would miss it and navigate the whole page out of the game frame.
|
|
|
this.confirmed = true
|
|
|
this.pendingForm?.requestSubmit()
|
|
|
}
|
|
|
|
|
|
async cancel() {
|
|
|
if (this.leaving) return
|
|
|
await this.#slideOut("is-leaving")
|
|
|
this.pendingForm = null
|
|
|
}
|
|
|
|
|
|
backdropClick(event) {
|
|
|
if (event.target === this.dialogTarget) this.cancel()
|
|
|
}
|
|
|
|
|
|
// The slide runs while the dialog is still open, so close() only ever lands
|
|
|
// once it is off screen: nothing is left behind for the browser to lay out,
|
|
|
// which is what Safari does with a dialog that outlives its top layer.
|
|
|
async #slideOut(state) {
|
|
|
this.leaving = true
|
|
|
this.dialogTarget.classList.add(state)
|
|
|
|
|
|
// Reading a style forces the class change to resolve, so the transition
|
|
|
// exists by the time we ask for it.
|
|
|
getComputedStyle(this.dialogTarget).translate
|
|
|
const running = this.dialogTarget.getAnimations({ subtree: false })
|
|
|
await Promise.all(running.map((animation) => animation.finished)).catch(() => {})
|
|
|
|
|
|
this.dialogTarget.close()
|
|
|
}
|
|
|
}
|