diff --git a/app/assets/javascripts/items/show.js b/app/assets/javascripts/items/show.js index ca83954c..ff0e8eb7 100644 --- a/app/assets/javascripts/items/show.js +++ b/app/assets/javascripts/items/show.js @@ -1,6 +1,6 @@ -// When the species *face* picker changes, update and submit the main picker form. -document.addEventListener("click", (e) => { - if (!e.target.matches(".species-face-picker input[type=radio]")) return; +// When the species face picker changes, update and submit the main picker form. +document.addEventListener("change", (e) => { + if (!e.target.matches("species-face-picker")) return; try { const mainPicker = document.querySelector("#item-preview .species-color-picker"); @@ -14,8 +14,43 @@ document.addEventListener("click", (e) => { } }); -// Now that the face picker is ready to go, mark it as usable. -for (const options of document.querySelectorAll(".species-face-picker-options")) { - options.removeAttribute("inert"); - options.removeAttribute("aria-hidden"); +class SpeciesFacePicker extends HTMLElement { + connectedCallback() { + this.addEventListener("click", this.#handleClick); + } + + get value() { + return this.querySelector("input[type=radio]:checked")?.value; + } + + #handleClick(e) { + if (e.target.matches("input[type=radio]")) { + this.dispatchEvent(new Event("change", {bubbles: true})); + } + } } + +class SpeciesFacePickerOptions extends HTMLElement { + static observedAttributes = ["inert", "aria-hidden"]; + + connectedCallback() { + // Once this component is loaded, we stop being inert and aria-hidden. We're ready! + this.#activate(); + } + + attributeChangedCallback() { + // If a Turbo Frame tries to morph us into being inert again, activate again! + // (It's important that the server's HTML always return `inert`, for progressive + // enhancement; and it's important to morph this element, so radio focus state + // is preserved. To thread that needle, we have to monitor and remove!) + this.#activate(); + } + + #activate() { + this.removeAttribute("inert"); + this.removeAttribute("aria-hidden"); + } +} + +customElements.define("species-face-picker", SpeciesFacePicker); +customElements.define("species-face-picker-options", SpeciesFacePickerOptions); diff --git a/app/assets/stylesheets/items/_show.sass b/app/assets/stylesheets/items/_show.sass index 2899c2c6..28100f0a 100644 --- a/app/assets/stylesheets/items/_show.sass +++ b/app/assets/stylesheets/items/_show.sass @@ -161,7 +161,8 @@ body.items-show border-color: $error-border-color color: $error-color - .species-face-picker + species-face-picker + display: block position: relative input[type=radio] @@ -186,13 +187,14 @@ body.items-show inset: 0 background: rgba(white, .75) z-index: 1 + cursor: auto display: flex align-items: center justify-content: center text-align: center - &:has(.species-face-picker-options[inert]) + &:has(species-face-picker-options[inert]) cursor: wait .item-zones-info diff --git a/app/views/items/show.html.haml b/app/views/items/show.html.haml index 07fe0caa..0c80a79c 100644 --- a/app/views/items/show.html.haml +++ b/app/views/items/show.html.haml @@ -33,12 +33,12 @@ "id", "human_name", @selected_preview_pet_type.species_id) = submit_tag "Go", name: nil - %form.species-face-picker + %species-face-picker %noscript This fancy species picker requires Javascript, but you can still use the dropdowns instead! - .species-face-picker-options{ - "inert": true, # waits for JS to remove + %species-face-picker-options{ + inert: true, # waits for JS to remove "aria-hidden": true, # waits for JS to remove } - @preview_pet_type_options.each do |pet_type|