Files
fixin.me/app/javascript/turbo_actions.js
T
cryptogopher f1578fad75 Update and move turbo_stream actions to separate file
Use CSS `visibility` instead of `display` for #show/#hide.
Access data attributes through `dataset` property.
2026-10-17 03:14:35 +02:00

98 lines
3.4 KiB
JavaScript

// Turbo Streams actions.
const FORM_CONTROLS = ["BUTTON", "FIELDSET", "INPUT", "OPTGROUP", "OPTION", "SELECT",
"TEXTAREA"]
Object.assign(Turbo.StreamElement.prototype, {
disableElement(element) {
if (FORM_CONTROLS.includes(element.tagName)) {
element.setAttribute("disabled", "disabled")
} else {
element.setAttribute("aria-disabled", "true")
// NOTE: tabindex should not be disabled?
element.setAttribute("tabindex", "-1")
}
},
enableElement(element) {
if (FORM_CONTROLS.includes(element.tagName)) {
element.removeAttribute("disabled")
} else {
element.removeAttribute("aria-disabled")
// Assume 'tabindex' is not used explicitly, so removing it is safe.
element.removeAttribute("tabindex")
}
},
showElement(element) { element.style.visibility = "visible" }
})
Object.assign(Turbo.StreamActions, {
disable() {
this.targetElements.forEach((e) => { this.disableElement(e) })
},
enable() {
this.targetElements.forEach((e) => { this.enableElement(e) })
},
hide() {
this.targetElements.forEach((e) => { e.style.visibility = "collapse" })
},
show() {
this.targetElements.forEach((e) => { this.showElement(e) })
},
unselect() {
this.targetElements.forEach((e) => { e.checked = false; this.enableElement(e) })
},
validate_form() {
this.targetElements.forEach((form) => {
this.templateContent.querySelectorAll("span").forEach((span) => {
var control = document.querySelector(
`[form="${form.id}"][name="${span.dataset.name}"]`
)
control.setCustomValidity(span.textContent)
// Add event listener in capture phase to clear custom validity before
// `[oninput]` or any other bubble phase handler is invoked.
// https://www.quirksmode.org/js/events_order.html
// TODO: keep invalidating current value whenever user re-enters it. Store
// invalid value and validation message in `[data-invalid-message/value]`
// and check input value on "input" event (below) (using `[pattern]` may not
// be suitable due to inability to customize validtion message; also
// pattern may be used for other, conflicting purposes).
// After user executes any other action (while form is open), remove `[data-]`
// attributes and custom validation, as value may no longer be invalid.
control.addEventListener(
"input",
function(event) { event.target.setCustomValidity("") },
{capture: true, once: true}
)
})
form.reportValidity()
})
},
close_form() {
this.targetElements.forEach((e) => {
// Move focus if there's no focus or focus inside form being closed.
const focused = document.activeElement
if (!focused || (focused == document.body) || e.contains(focused)) {
let nextForm = e.parentElement.querySelector(`#${e.id} ~ tr:has([autofocus])`)
nextForm ??= e.parentElement.querySelector("tr:has([autofocus])")
nextForm?.querySelector("[autofocus]").focus()
}
document.getElementById(e.dataset.form).remove()
if ('link' in e.dataset) {
this.enableElement(document.getElementById(e.dataset.link))
}
if ('hiddenRow' in e.dataset) {
this.showElement(document.getElementById(e.dataset.hiddenRow))
}
e.remove()
})
}
})