Last active
December 8, 2024 16:27
-
-
Save fsubal/037a8c9cd7067e634a5ab016d7a089be to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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