// Improve feedback UX for async interactions with HTMX buttons by showing a spinner on user click.
const findHTMXButtons = () =>
  document.querySelectorAll(`
    .button[hx-delete],
    .button[hx-get],
    .button[hx-patch],
    .button[hx-post],
    .button[hx-put],
    form[hx-delete] .button[type="submit"],
    form[hx-get] .button[type="submit"],
    form[hx-patch] .button[type="submit"],
    form[hx-post] .button[type="submit"],
    form[hx-put] .button[type="submit"],
    form[action] button[type="submit"]
`);

const attachClickHandler = (button) => {
  button.addEventListener("click", () => {
    button.classList.add("is-loading");
  });
};

export const enrichHTMXButtons = () => {
  findHTMXButtons().forEach(attachClickHandler);

  // Remove `loading` state from buttons after a request has been completed.
  htmx.on("htmx:afterRequest", (e) => {
    if (e.target instanceof HTMLButtonElement) {
      e.target.classList.remove("is-loading");
    } else if (e.target instanceof HTMLFormElement) {
      const button = document.querySelector("button.is-loading")
      if (button) button.classList.remove("is-loading");
    }
  });
};

@@ -266,238 +266,557 @@
 put%5D
-,%0A    form%5Bhx-delete%5D .button%5Btype=%22submit%22%5D,%0A    form%5Bhx-get%5D .button%5Btype=%22submit%22%5D,%0A    form%5Bhx-patch%5D .button%5Btype=%22submit%22%5D,%0A    form%5Bhx-post%5D .button%5Btype=%22submit%22%5D,%0A    form%5Bhx-put%5D .button%5Btype=%22submit%22%5D,%0A    form%5Baction%5D 
+%0A%60);%0A%0Aconst attachClickHandler = (button) =%3E %7B%0A  button.addEventListener(%22click%22, () =%3E %7B%0A    button.classList.add(%22is-loading%22);%0A  %7D);%0A%7D;%0A%0A// Form event handlers need to account for native browser validation which short-circuits the event logic,%0A// preventing HTMX events from ever firing.%0Aconst findForms = () =%3E%0A  document.querySelectorAll(%60%0A    form%5Bhx-delete%5D,%0A    form%5Bhx-get%5D,%0A    form%5Bhx-patch%5D,%0A    form%5Bhx-post%5D,%0A    form%5Bhx-put%5D,%0A    form%5Baction%5D%0A%60);%0A%0Aconst attachFormHandlers = (formEl) =%3E %7B%0A  const submitButton = formEl.querySelector('
 butt
@@ -836,103 +836,243 @@
 it%22%5D
-%0A%60);%0A%0Aconst attachClickHandler = (button) =%3E %7B%0A  button.addEventListener(%22click%22, () =%3E %7B%0A    b
+');%0A  formEl.querySelectorAll(%22input%22).forEach((input) =%3E %7B%0A    input.oninvalid = () =%3E %7B%0A      // Add a delay before resetting the button to communicate to the user that the form is indeed working%0A      window.setTimeout(() =%3E submitB
 utto
@@ -1075,35 +1075,38 @@
 utton.classList.
-add
+remove
 (%22is-loading%22);%0A
@@ -1103,27 +1103,216 @@
 oading%22)
-;%0A  %7D);%0A%7D;%0A
+, 500);%0A    %7D;%0A  %7D);%0A%0A  attachClickHandler(submitButton);%0A%7D;%0A%0A/**%0A * Find all buttons on the current page and attach handlers to show a spinner when clicking on the button or submitting%0A * a form.%0A */
 %0Aexport 
@@ -1393,16 +1393,59 @@
 andler);
+%0A  findForms().forEach(attachFormHandlers);
 %0A%0A  // R
@@ -1769,16 +1769,17 @@
 oading%22)
+;
 %0A      i

Reset spinner buttons if Form contains an error