import BaseCustomElement from "./BaseCustomElement"
import RichString from "./RichString"
import ConfirmationDialog from "./ConfirmationDialog"
/** Confirms form submissions with the user before allowing the form to be submitted. This can be applied per submit
* button and only affects the behavior of `button` or `input type=submit`, and *only* if no other click handler
* has been put onto those buttons. Note that this does not affect links (`a` elements).
*
* This can ask for confirmation with {@link external:Window#confirm} or a
* `brut-confirmation-dialog`. What it will use depends on several factors, all of which
* are geared toward doing the right thing. Note that setting `show-warnings` will elucidate the reasons
* this component does what it does.
*
* * If `dialog` is set:
* - If that id is on a `<brut-confirmation-dialog>` that is used.
* - If not, `window.confirm` is used.
* * If `dialog` is not set:
* - If there is exactly one `<brut-confirmation-dialog>` on the page, this is used.
* - If there is more than one, or no `<brut-confirmation-dialog>`s, `window.confirm` is used.
*
* @see ConfirmationDialog
*
* @property {string} message - the message to show that asks for confirmation. It should be written such that
* "OK" is grammatically correct for confirmation and "Cancel" is for aborting.
* @property {string} dialog - optional ID of the `brut-confirmation-dialog` to use instead of `window.confirm`.
* If there is no such dialog or the id references the wrong element type,
* `window.confirm` will be used. Setting `show-warnings` will generate a warning for this.
*
* @customElement brut-confirm-submit
*/
class ConfirmSubmit extends BaseCustomElement {
static tagName = "brut-confirm-submit"
static observedAttributes = [
"message",
"dialog",
"show-warnings",
]
#message = new RichString("")
#confirming = false
#dialogId = null
messageChangedCallback({newValue}) {
this.#message = new RichString(newValue || "")
}
dialogChangedCallback({newValue}) {
this.#dialogId = RichString.fromString(newValue)
}
constructor() {
super()
this.onClick = (event) => {
if (this.#confirming) {
this.#confirming = false
return
}
if (this.#message.isBlank()) {
this.logger.warn("No message provided, so cannot confirm")
return
}
const dialog = this.#findDialog()
if (dialog) {
event.preventDefault()
dialog.setAttribute("message",this.#message.toString())
const buttonLabel = event.target.getAttribute("aria-label") || event.target.textContent
dialog.setAttribute("confirm-label",buttonLabel)
this.#confirming = true
dialog.showModal((confirm) => {
if (confirm) {
event.target.click()
}
else {
this.#confirming = false
}
})
}
else {
const result = window.confirm(this.#message)
if (!result) {
event.preventDefault()
}
}
}
}
#findDialog() {
if (this.#dialogId) {
const dialog = document.getElementById(this.#dialogId)
if (dialog) {
if (dialog.tagName.toLowerCase() != ConfirmationDialog.tagName) {
throw `${this.#dialogId} is the id of a '${dialog.tagName}', not '${ConfirmationDialog.tagName}'`
}
return dialog
}
this.logger.warn(`No dialog with id ${this.#dialogId} - using window.confirm as a fallback`)
return null
}
const dialogs = document.querySelectorAll(ConfirmationDialog.tagName)
if (dialogs.length == 1) {
return dialogs[0]
}
if (dialogs.length == 0) {
this.logger.warn(`No '${ConfirmationDialog.tagName}' found in document - using window.confirm as a fallback`)
return null
}
throw `Found ${dialogs.length} '${ConfirmationDialog.tagName}' elements. Not sure which to use. Remove all but one or specify the 'dialog' attribute on this element to specify which one to use`
}
update() {
this.querySelectorAll("button").forEach( (button) => button.addEventListener("click", this.onClick) )
this.querySelectorAll("input[type=submit]").forEach( (button) => button.addEventListener("click", this.onClick) )
}
}
export default ConfirmSubmit