Skip to content

Instantly share code, notes, and snippets.

@fsubal
Last active December 8, 2024 16:27
Show Gist options
  • Save fsubal/037a8c9cd7067e634a5ab016d7a089be to your computer and use it in GitHub Desktop.
Save fsubal/037a8c9cd7067e634a5ab016d7a089be to your computer and use it in GitHub Desktop.
export class AnswerEvent extends CustomEvent<FormData> {
static readonly type = 'async-dialog:answer'
constructor(detail: FormData) {
super(AnswerEvent.type, { detail })
}
get answer() {
return this.detail.get('answer')?.toString()
}
get isOk() {
return this.answer === '1'
}
get isCancel() {
return this.answer === '0'
}
}
declare global {
interface HTMLElement {
addEventListener(type: typeof AnswerEvent.type, listener: (e: AnswerEvent) => void): void
removeEventListener(type: typeof AnswerEvent.type, listener: (e: AnswerEvent) => void): void
}
}
interface Buttons {
ok?: string
cancel?: string
}
function h<K extends keyof HTMLElementTagNameMap>(
tagName: K,
attributes: Partial<HTMLElementTagNameMap[K]>,
children: (Node | null | undefined | string | number | false)[]
): HTMLElementTagNameMap[K] {
const element = document.createElement(tagName)
for (const [key, value] of Object.entries(attributes)) {
element[key] = value
}
for (const node of children) {
if (node == null) {
continue
}
if (node == false) {
continue
}
if (node instanceof Node) {
element.appendChild(node)
} else {
element.innerText = node.toString()
}
}
return element
}
function waitForAnswer(message: string, buttons: Buttons) {
return new Promise<AnswerEvent>(resolve => {
const dialog = Object.assign(document.createElement(AsyncDialog.tagName), {
innerText: message,
okButton: buttons.ok,
cancelButton: buttons.cancel
})
dialog.addEventListener(AnswerEvent.type, function onAnswer(e) {
resolve(e)
dialog.removeEventListener(AnswerEvent.type, onAnswer)
document.body.removeChild(dialog)
})
document.body.appendChild(dialog)
})
}
export class AsyncDialog extends HTMLDialogElement {
static readonly tagName = 'async-dialog'
static async alert(message: string, buttons: Pick<Buttons, 'ok'> = { ok: 'OK' }): Promise<void> {
return waitForAnswer(message, buttons).then(e => void e)
}
static async confirm(message: string, buttons: Buttons = { ok: 'OK', cancel: 'Cancel' }): Promise<boolean> {
return waitForAnswer(message, buttons).then(e => e.isOk)
}
shadowRoot!: ShadowRoot
root?: HTMLFormElement
constructor() {
super()
this.attachShadow({ mode: 'closed' })
}
connectedCallback() {
this.shadowRoot.innerHTML = ''
this.root = this.#render()
this.shadowRoot.appendChild(this.root)
this.root.addEventListener('submit', this.#handleSubmit)
}
disconnectedCallback() {
this.root?.removeEventListener('submit', this.#handleSubmit)
this.shadowRoot.innerHTML = ''
}
get header(): string | null {
return this.getAttribute('header')
}
set header(value: string | undefined) {
if (value != null) {
this.setAttribute('header', value)
}
}
get okButton(): string | null {
return this.getAttribute('ok-button')
}
set okButton(value: string | undefined) {
if (value != null) {
this.setAttribute('ok-button', value)
}
}
get cancelButton(): string | null {
return this.getAttribute('cancel-button')
}
set cancelButton(value: string | undefined) {
if (value != null) {
this.setAttribute('cancel-button', value)
}
}
#render() {
return h('form', { method: 'dialog' }, [
this.header && h('header', {}, [this.header]),
h('div', {}, [this.innerHTML]),
h('footer', {}, [
this.okButton && h('button', { value: '1' }, [this.okButton]),
this.cancelButton && h('button', { value: '0' }, [this.cancelButton]),
])
])
}
#handleSubmit = (e: SubmitEvent): void => {
e.preventDefault()
if (e.currentTarget instanceof HTMLFormElement) {
const formData = new FormData(e.currentTarget)
this.dispatchEvent(new AnswerEvent(formData))
}
}
}
const ok = await AsyncDialog.confirm('本当によろしいですか?')
if (ok) {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment