Fix species face picker going inert again after Turbo frame load

Here, I remember the trick I learned when building the outfit viewer:
web components are great for making sure stuff stays initialized well
in a Turbo environment!

The problem was, after submitting the form and getting a new preview
loaded via Turbo, the part where we remove `inert` would get undone.
Additionally, this script only loads *once* per session, so if you
Turbo-nav to a different item then that part of the page never ran.

Instead, we use web components to remove the attributes on mount, then
again if they're ever reapplied by Idiomorph.
This commit is contained in:
Emi Matchu 2024-09-03 17:07:53 -07:00
parent 2b2bffd9da
commit 4c44f8d6a4
3 changed files with 49 additions and 12 deletions

View file

@ -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);

View file

@ -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

View file

@ -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|