product category page: front-end sorting and filtering using turboframes

This commit is contained in:
elvira
2026-08-31 22:56:40 +03:00
parent b070a7d1e6
commit 8a07c772f8
20 changed files with 428 additions and 148 deletions
@@ -0,0 +1,27 @@
import { Controller } from '@hotwired/stimulus'
// Submits the host <form> a short beat after a control inside it changes,
// coalescing a burst — rapid slider nudges, or holding an arrow key on a range
// input — into a single submit. Wire it on the <form>:
//
// <form data-controller="auto-submit"
// data-action="change->auto-submit#submit range-slider:change->auto-submit#submit"
// data-auto-submit-delay-value="300"> (delay optional, ms)
//
// `change` covers native inputs (checkbox, select); the range slider emits its
// own `range-slider:change` on commit. Uses requestSubmit() (not submit()) so a
// <turbo-frame> around the form still captures the navigation and validation runs.
export default class extends Controller {
static values = { delay: { type: Number, default: 300 } }
submit() {
clearTimeout(this.#timer)
this.#timer = setTimeout(() => this.element.requestSubmit(), this.delayValue)
}
disconnect() {
clearTimeout(this.#timer)
}
#timer
}
+2
View File
@@ -4,6 +4,7 @@
// application.register('hello', HelloController);
import AppearController from './appear-controller'
import AutoSubmitController from './auto-submit-controller'
import BackToTopController from './back-to-top-controller'
import CarouselController from './carousel-controller'
import DropdownController from './dropdown-controller'
@@ -16,6 +17,7 @@ import TabsController from './tabs-controller'
export function registerControllers(application) {
application.register('appear', AppearController)
application.register('auto-submit', AutoSubmitController)
application.register('back-to-top', BackToTopController)
application.register('carousel', CarouselController)
application.register('dropdown', DropdownController)
@@ -14,7 +14,7 @@ import { Controller } from '@hotwired/stimulus'
// max and vice versa. Emits `range-slider:input` while dragging and
// `range-slider:change` on commit, both with { min, max }.
export default class extends Controller {
static targets = ['minInput', 'maxInput', 'field', 'track', 'minThumb', 'maxThumb', 'output', 'reset']
static targets = ['minInput', 'maxInput', 'field', 'track', 'minThumb', 'maxThumb', 'output']
static values = {
min: Number,
max: Number,
@@ -26,7 +26,6 @@ export default class extends Controller {
connect() {
this.#clamp()
this.defaults = { min: this.#lo, max: this.#hi }
this.fieldTargets.forEach((field) => field.classList.add('sr-only'))
this.#render()
}
@@ -78,13 +77,6 @@ export default class extends Controller {
this.#startDrag(event, input)
}
reset() {
this.minInputTarget.value = this.defaults.min
this.maxInputTarget.value = this.defaults.max
this.#render()
this.#emit('change')
}
// ── internals ──────────────────────────────────────────────────────
#startDrag(event, input) {
@@ -158,10 +150,6 @@ export default class extends Controller {
const fmt = (v) => `${this.prefixValue}${v}${this.suffixValue}`
this.outputTarget.textContent = fmt(lo) + this.separatorValue + fmt(hi)
}
if (this.hasResetTarget) {
this.resetTarget.hidden = lo === this.defaults.min && hi === this.defaults.max
}
}
#emit(name) {