71 lines
2.4 KiB
JavaScript
71 lines
2.4 KiB
JavaScript
|
|
/**
|
||
|
|
* ItemCard web component
|
||
|
|
*
|
||
|
|
* Progressive enhancement for item cards in both outfit and search views.
|
||
|
|
* Replaces baseline Show/Hide/Add buttons with click-to-toggle behavior:
|
||
|
|
*
|
||
|
|
* Outfit view (radio inputs):
|
||
|
|
* - Clicking a closeted item's label selects its radio and submits the Show form
|
||
|
|
* - Clicking a worn item's label submits the Hide form (un-wears it)
|
||
|
|
* - Arrow keys navigate between items via native radio behavior
|
||
|
|
* - Space on a checked radio submits the Hide form (radios don't toggle natively)
|
||
|
|
*
|
||
|
|
* Search view (checkbox inputs):
|
||
|
|
* - Clicking an unworn item's label checks the checkbox and submits the Add form
|
||
|
|
* - Clicking a worn item's label prevents default and submits the Hide form
|
||
|
|
*/
|
||
|
|
class ItemCard extends HTMLElement {
|
||
|
|
connectedCallback() {
|
||
|
|
this.addEventListener("click", this.#handleClick);
|
||
|
|
this.addEventListener("keydown", this.#handleKeydown);
|
||
|
|
this.addEventListener("change", this.#handleChange);
|
||
|
|
}
|
||
|
|
|
||
|
|
#handleClick = (e) => {
|
||
|
|
const label = e.target.closest(".item-card-label");
|
||
|
|
if (!label) return;
|
||
|
|
|
||
|
|
const input = label.querySelector("input[type=radio], input[type=checkbox]");
|
||
|
|
if (!input) return;
|
||
|
|
|
||
|
|
// If this item is worn, un-wear it by submitting the Hide form
|
||
|
|
if (this.dataset.isWorn != null) {
|
||
|
|
e.preventDefault();
|
||
|
|
const hideButton = this.querySelector(".item-hide-button");
|
||
|
|
if (hideButton) hideButton.closest("form").requestSubmit();
|
||
|
|
}
|
||
|
|
// Otherwise, let the default label click proceed—it checks the input,
|
||
|
|
// which fires the `change` event handled below
|
||
|
|
};
|
||
|
|
|
||
|
|
#handleKeydown = (e) => {
|
||
|
|
// Spacebar on an already-checked radio: un-wear the item
|
||
|
|
if (e.key !== " ") return;
|
||
|
|
|
||
|
|
const input = e.target;
|
||
|
|
if (input.type !== "radio" || !input.checked) return;
|
||
|
|
if (this.dataset.isWorn == null) return;
|
||
|
|
|
||
|
|
e.preventDefault();
|
||
|
|
const hideButton = this.querySelector(".item-hide-button");
|
||
|
|
if (hideButton) hideButton.closest("form").requestSubmit();
|
||
|
|
};
|
||
|
|
|
||
|
|
#handleChange = (e) => {
|
||
|
|
const input = e.target;
|
||
|
|
if (!input.checked) return;
|
||
|
|
if (input.type !== "radio" && input.type !== "checkbox") return;
|
||
|
|
|
||
|
|
// Submit the Show form to wear this item, or the Add form if not closeted
|
||
|
|
const showButton = this.querySelector(".item-show-button");
|
||
|
|
if (showButton) {
|
||
|
|
showButton.closest("form").requestSubmit();
|
||
|
|
} else {
|
||
|
|
const addButton = this.querySelector(".item-add-button");
|
||
|
|
if (addButton) addButton.closest("form").requestSubmit();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
customElements.define("item-card", ItemCard);
|