Conversation
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
Code Coverage Summary3 files are newly checked for coverage.
Full summary · PHP report · JS report If appropriate, add one of these labels to override the failing coverage check:
Covered by non-unit tests
|
3d6ffae to
7eaae5c
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…itize symbol attr (comment #4393978666) - render.php: drop the `if ( '' !== $label )` wrapper around the h3 output. `$label` is forced to `__( 'Price', ... )` a few lines above when empty, so the guard is dead code — the heading always renders in practice. - render.php: route `currencySymbol` through `sanitize_text_field` for parity with `label`. Output was already escaped via `esc_html` so this is consistency / future-proofing rather than a fix for a live exploit, but inconsistent input handling between two adjacent attributes is a code smell. - style.scss: replace `#757575` (symbol/separator text) and `#c8c8c8` (input border) with `color-mix(in sRGB, currentColor X%, transparent)` to match the theme-aware pattern every other block in this directory uses (filter-checkbox, filters-popover, results-list). Hardcoded hex values don't follow theme palette changes; the `color-mix` form derives from `currentColor` and adapts automatically. - style.scss: while in the file, swap `left:` / `padding: ... 1.4rem` for the logical-property equivalents (`inset-inline-start`, `padding-block` / `padding-inline`) so the input adornment stays on the start edge under RTL — AGENTS.md calls for logical properties for new CSS. Refs claude[bot] review on PR #48449.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…itize symbol attr (comment #4393978666) - render.php: drop the `if ( '' !== $label )` wrapper around the h3 output. `$label` is forced to `__( 'Price', ... )` a few lines above when empty, so the guard is dead code — the heading always renders in practice. - render.php: route `currencySymbol` through `sanitize_text_field` for parity with `label`. Output was already escaped via `esc_html` so this is consistency / future-proofing rather than a fix for a live exploit, but inconsistent input handling between two adjacent attributes is a code smell. - style.scss: replace `#757575` (symbol/separator text) and `#c8c8c8` (input border) with `color-mix(in sRGB, currentColor X%, transparent)` to match the theme-aware pattern every other block in this directory uses (filter-checkbox, filters-popover, results-list). Hardcoded hex values don't follow theme palette changes; the `color-mix` form derives from `currentColor` and adapts automatically. - style.scss: while in the file, swap `left:` / `padding: ... 1.4rem` for the logical-property equivalents (`inset-inline-start`, `padding-block` / `padding-inline`) so the input adornment stays on the start edge under RTL — AGENTS.md calls for logical properties for new CSS. Refs claude[bot] review on PR #48449.
a61e1ca to
3316ed0
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
We should use WooCommerce currency formatting options as default |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…itize symbol attr (comment #4393978666) - render.php: drop the `if ( '' !== $label )` wrapper around the h3 output. `$label` is forced to `__( 'Price', ... )` a few lines above when empty, so the guard is dead code — the heading always renders in practice. - render.php: route `currencySymbol` through `sanitize_text_field` for parity with `label`. Output was already escaped via `esc_html` so this is consistency / future-proofing rather than a fix for a live exploit, but inconsistent input handling between two adjacent attributes is a code smell. - style.scss: replace `#757575` (symbol/separator text) and `#c8c8c8` (input border) with `color-mix(in sRGB, currentColor X%, transparent)` to match the theme-aware pattern every other block in this directory uses (filter-checkbox, filters-popover, results-list). Hardcoded hex values don't follow theme palette changes; the `color-mix` form derives from `currentColor` and adapts automatically. - style.scss: while in the file, swap `left:` / `padding: ... 1.4rem` for the logical-property equivalents (`inset-inline-start`, `padding-block` / `padding-inline`) so the input adornment stays on the start edge under RTL — AGENTS.md calls for logical properties for new CSS. Refs claude[bot] review on PR #48449.
f63b1ad to
6d0f108
Compare
This comment has been minimized.
This comment has been minimized.
6d0f108 to
38ffc44
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
🤖 Review-cycle summary —
|
| Source | Comment | Resolution |
|---|---|---|
| claude[bot] (round-1 review) | Verdict: no new issues, ready to merge. Three minor observations (edit.js whitespace-trim parity, view.js store() per-access in getters, render.php mb_substr guard) explicitly flagged non-blocking. |
Acknowledged; no commit needed. |
Unaddressed (flagged for owner): None.
CI: all required checks passing on 38ffc443508 (full Jetpack matrix + lints). mergeStateStatus: UNSTABLE is the non-required code-coverage soft-fail only.
Two number inputs (min, max) bound to the shared store's priceRange slice. Drives actions.setPriceRange on change / Enter. URL contract uses the reserved min_price / max_price query params. The block seeds priceCurrencySymbol and priceLabel into the shared store so the active-filters chip block can render budget-filtered URLs correctly. Tracked under epic [RSM-1929].
38ffc44 to
b694185
Compare
WooCommerce's product-filter-price-slider exposes "show inputs" and "inline inputs" as attributes on a single block — no separate inputs- only block. Mirror that: keep `jetpack-search/filter-wc-price` (already shipped on trunk via #48449) as the one canonical price filter, and add a `showSlider` attribute that flips the layout. Two inserter variations sit on top: - Filter by Price → showSlider: false (default, isDefault) - Filter by Price (Slider) → showSlider: true, autoBounds: true `showSlider: false` is the default, so existing trunk instances saved as `<!-- wp:jetpack-search/filter-wc-price /-->` keep rendering exactly as today — they're now "filter" variation instances. BEM under one root: number inputs stay on `__input--min/--max` (no churn for existing CSS), slider thumbs get the new `__slider-input --min/--max` so they don't collide. Wrapper gains a `--with-slider` modifier in slider mode for the inputs-row margin tweak. The watcher attribute (`data-wp-watch`) only emits in slider mode — filter mode doesn't pay the watcher cost. Removed entirely: - The standalone `jetpack-search/filter-wc-price-slider` block dir (never shipped — slider work was all on this branch). - Its tests, replaced by Filter_Wc_Price_Render_Test + filter-wc-price-view.test.js covering both layouts in one place. - `filter-wc-price-slider` import + entry in register-blocks.js. Verified in browser against the raven docker for both variations: - Filter variation: no slider DOM, no watch attribute, number inputs render and commit correctly. - Slider variation: slider thumbs + number inputs both present; slider drag → number input value, number-input commit → slider thumb + --low/--high gradient + aria-valuetext + URL min/max_price.
WooCommerce's product-filter-price-slider exposes "show inputs" and "inline inputs" as attributes on a single block — no separate inputs- only block. Mirror that: keep `jetpack-search/filter-wc-price` (already shipped on trunk via #48449) as the one canonical price filter, and add a `showSlider` attribute that flips the layout. Two inserter variations sit on top: - Filter by Price → showSlider: false (default, isDefault) - Filter by Price (Slider) → showSlider: true, autoBounds: true `showSlider: false` is the default, so existing trunk instances saved as `<!-- wp:jetpack-search/filter-wc-price /-->` keep rendering exactly as today — they're now "filter" variation instances. BEM under one root: number inputs stay on `__input--min/--max` (no churn for existing CSS), slider thumbs get the new `__slider-input --min/--max` so they don't collide. Wrapper gains a `--with-slider` modifier in slider mode for the inputs-row margin tweak. The watcher attribute (`data-wp-watch`) only emits in slider mode — filter mode doesn't pay the watcher cost. Removed entirely: - The standalone `jetpack-search/filter-wc-price-slider` block dir (never shipped — slider work was all on this branch). - Its tests, replaced by Filter_Wc_Price_Render_Test + filter-wc-price-view.test.js covering both layouts in one place. - `filter-wc-price-slider` import + entry in register-blocks.js. Verified in browser against the raven docker for both variations: - Filter variation: no slider DOM, no watch attribute, number inputs render and commit correctly. - Slider variation: slider thumbs + number inputs both present; slider drag → number input value, number-input commit → slider thumb + --low/--high gradient + aria-valuetext + URL min/max_price.
…48670) * Search 3.0: add jetpack-search/filter-wc-price-slider block (RSM-1948) * Search 3.0: rework price slider as dual-thumb single-track (RSM-1948) Mirror WooCommerce Blocks' price-slider pattern — one shared track with two overlaid <input type=range> thumbs (lower = min, upper = max), CSS gradient driven by --low / --high, and a drag-vs-release split: input events update state for live visual feedback, change events commit via setPriceRange to push the URL and refetch results. * Address review: editor warning for inverted bounds + symmetric clampPair test (comment #IC_kwDOAOho7M8AAAABBvjpGQ) * Address review: center thumbs on track + auto-detect bounds from store * Position the input at `top: 50%` of the 4px track wrapper so the native thumb pseudo-element centers on the track midline instead of sitting above it (the previous `inset: 0; top: 0` placed the thumb at y=0). * Add an `autoBounds` block attribute (default true) that derives the slider's min/max from the store's actual product price range via a transient-cached SQL on `wp_postmeta._price`. WPCOM v1.3 doesn't whitelist range/stats aggs, so the search-API path isn't viable; the local DB query with a 5-minute transient gives reliable bounds without a second request. Author can opt out via the inspector toggle to set bounds manually. * Address review: give the input a real height so thumb centers on track Chromium's <input type="range"> thumb pseudo-element doesn't anchor to a height-0 input the way the spec implies — empirically the thumb floats above the track. Giving the input a height matching the thumb's visual size (18px = 14px thumb + 2*2px border) and positioning it at `top: calc(50% - 9px)` so its midline aligns with the track's puts the thumb on the standard centered-on-input rendering path. Click area expands to the thumb's full vertical extent as a side benefit. * Address review: thumbs centered on track + bounds derived from search response Visual fix: * Thumbs were rendering above the track because the runnable-track inside a taller `<input type="range">` defaults to anchoring at the bottom in Chromium, not the input's vertical center. Force the runnable-track to match the input's full height (`18px`) so the thumb (auto-centered on the runnable-track) lands on the input's vertical midline, which is the wrapper's midline, which is where the visual track is drawn. No margin tweaks; the browser does the centering once the runnable-track fills the input. * Wrapper height = thumb visual size (`18px`) so thumbs don't clip; visible 4px track and colored fill are absolutely-positioned overlays vertically centered on the wrapper. Bounds-from-search-response: * Slider bounds now reflect the `MIN/MAX` of `wc.price` across the products in the current search response, with a 5% buffer (rounded outward to whole numbers, minimum 1-unit) so the cheapest / most expensive products don't sit flush at the slider's extremes. Bounds rebase as the result set narrows, mirroring WC Blocks' price-slider behaviour. * The author-set / DB-derived auto-bounds remain as the first-paint default before any search has run. Plumbing: * Add `wc.price` to `SEARCH_FIELDS` and surface `price` (numeric, nullable) on the normalized result so the slider's view can read it without re-parsing the WC-formatted HTML price. * Address review: stable slider bounds + published-only catalog SQL (comment #IC_kwDOAOho7M8AAAABBvvDQQ) Bounds now stay stable across filters — applying other filters narrows products without shrinking the slider track, mirroring WooCommerce's price slider so the user can always drag back out. The catalog SQL is the sole bound source and now joins wp_posts to restrict to published products / product_variation rows (excludes drafts and trashed). Also fixes a latent double-search on the keyboard-only release path (capture the no-op status before setPriceRange runs so the explicit fallthrough search() doesn't fire when setPriceRange already searched internally), drops the now-unused wc.price SEARCH_FIELDS entry + result.price (no consumers left now that bounds are server-derived), and corrects the stale updatePriceSliderRangeFill references. * Address review: dead-code cleanup, RTL/logical properties, aria-valuetext (comments #IC_kwDOAOho7M8AAAABBvxcSw, #IC_kwDOAOho7M8AAAABBvyNRg) * api.test.js: restore stricter `not.toContain('wc.price')` assertion + drop the now-stale comment (left behind from the dropped result-derived bounds round-trip). * view.js: remove dead `priceSliderMinValue` / `priceSliderMaxValue` getters — they were written for a `data-wp-bind--value` directive that never landed, and the watcher syncs input values directly anyway. * style.scss: force `direction: ltr` on the slider track so the thumbs and the gradient fill share an axis on RTL pages (without it, browsers mirror the native range thumbs visually but `--low` / `--high` and the gradient stay LTR, producing a track where the thumbs and the fill disagree on direction). Convert physical `left: 0; right: 0` / `top: 0; left: 0` to logical `inset-inline` / `inset-block-start` / `inset-inline-start` to match the rest of the package. * render.php / view.js: add `aria-valuetext` to both range inputs so screen readers announce the currency-formatted label ("$25") instead of the bare numeric `value` ("25"). Seeded server-side with an unescaped helper (so a symbol like `€` doesn't land as `&euro;`) and refreshed reactively by `updatePriceSliderUi`. * render.php / edit.js: update stale "deferred to follow-up" comments now that auto-bounds shipped in this PR. * Fix CI: drop redundant float casts (phan) + add edit.js / render.php tests (coverage) * render.php: drop `(float)` casts on `floor()`/`ceil()` — both already return float, and phan flags the extra cast as `PhanRedundantConditionInGlobalScope`. * Add `Filter_Wc_Price_Slider_Render_Test.php` mirroring `Search_Input_Render_Test`: registers the block inline so `do_blocks()` resolves render.php directly, then asserts on the rendered markup (label fallback, currency symbol + position, oversized-symbol trim, inverted-bounds coercion, aria-valuetext pre-hydration seed, screen-reader labels, --low/--high seeded inline, Interactivity context payload, action wiring, label escaping). The `function_exists('wc_get_product')`-gated auto-bounds branch is not exercised here — that path runs on the front end with WC loaded. * Add `filter-wc-price-slider-edit.test.js`: renders the editor component via @testing-library/react with thin shims around `@wordpress/components` so each inspector control is drivable with plain DOM events. Covers default render, label / symbol / position overrides, wcSettings fallback, inverted-bounds Notice gating on `autoBounds`, Min/Max disabled state, every setAttributes wire, and the NaN-attr defensive defaults. * Trim filter-wc-price-slider tests to essentials The previous round added breadth tests aimed at the coverage check; keep only the cases that pin non-obvious behavior. * edit.test.js (13 → 4): default render (label fallback + endpoints + toggle default + Min/Max disabled), wcSettings symbol/position read, inverted-bounds Notice + Min/Max enabled when autoBounds=false, and setAttributes wiring across every inspector control. * Render_Test.php (13 → 4): inverted-bounds swap, oversized-symbol trim, aria-valuetext currency-formatted seed, label HTML escaping. Drop the trivial-markup assertions the basic smoke render would catch anyway. * Drop SSR pre-hydration seeding; tighten comments The slider's first-paint optimizations were carrying complexity with little payoff — JS hydration runs in milliseconds, so polishing the no-JS view isn't worth the surface area. Removed from render.php: * `--low` / `--high` inline `style` on the `__range` element (and the `$low_pct` / `$high_pct` / `$track_style` / `$fmt_pct` machinery) * `aria-valuetext` server-side seed on both inputs (and the `$plain_value_text` helper); the watcher sets it on hydration * Inline value-label text content (the `$format_value` helper); spans start empty, watcher fills them * `data-wp-context` `sliderMin`/`sliderMax` payload on the wrapper * URL-state seeding (`$seeded_min` / `$seeded_max` from `wp_interactivity_state['priceRange']`); inputs `value` defaults to `$min_attr` / `$max_attr`, JS hydration writes the URL-derived value view.js follows: drop the `getContext()` branch from `readSliderBounds`, read bounds straight from the inputs' `min` / `max` HTML attributes. Tests updated to match the new flow and trimmed: edit.test.js stays at 4 essentials, view.test.js drops the contextRef machinery, the PHP render test keeps just the inverted-bounds swap and label HTML escape. Also tightened verbose JSDoc and inline comments throughout view.js and render.php. * Address review: clamp negative author bounds, clamp displayed values to slider range, fix stale docblock (PRRC_kwDOAOho7M6_gnwT/wX/wa/we/wi) * render.php: docblock said `change` commits via `actions.search`; the actual path is `actions.setPriceRange` (with a fallthrough `search` call only when setPriceRange would no-op). Fixed. * render.php / edit.js: clamp author-supplied `min` / `max` to >= 0. The JS `parseBound()` and store `setPriceRange()` reject negatives, so a negative attr would produce a slider that visually allowed a range it could never commit. * view.js (watcher): clamp `state.priceRange.min` / `max` to the slider's own bounds before driving labels / aria-valuetext / input values. A deep-linked URL with `max_price` above the catalog's max was getting clamped by the native range input but the label and screen-reader announcement still showed the unclamped figure. * Address review: clamp Step setter to >= 0 for consistency claude[bot]'s round-7 re-review noted the `step` inspector setter guards against `0`/`NaN` (`Number(value) || 1`) but lets a negative slip through, while `min` and `max` already clamp to >= 0. `render.php` normalises a negative step server-side (`(float) $step > 0 ? : 1.0`), so this is editor-cosmetic only — but the inconsistency was the finding. Now wraps the Step setter in `Math.max( 0, … )` matching min/max. * Auto-bounds: query wc_product_meta_lookup, not postmeta The previous extents query scanned wp_postmeta with REGEXP + CAST on `_price`, which is unindexable and grows linearly with `products × meta keys`. Switch to `wc_product_meta_lookup` — WC's denormalized table with indexed DECIMAL `min_price` / `max_price` columns, and the same source WC's own price-slider, classic widget, and Store API hit. * Extract shared WC block helpers into Wc_Block_Helpers The filter-wc-price and filter-wc-price-slider render.php files both duplicated the ~20-line currency resolution block (symbol + position with WC fallbacks and 2-char trim). The slider also hosted the catalog price-extents query inline despite being a self-contained WC-data utility, not template glue. Lift both into a new Wc_Block_Helpers class: - get_currency_display( symbol, position ): array{symbol,position} - get_catalog_price_extents(): array{min,max} Both render files shrink to a single helper call. Behavior is preserved (same transient key/TTL, same fallback order, same null-extents caching). Adds focused Wc_Block_Helpers_Test covering the currency fallbacks and the null-extents cache path that runs without WC loaded. * Slider: add [min] – [max] number-input row, matching WC's pattern The slider on its own forces ~473 arrow-key presses to dial in a precise $473 bound, and screen-reader users have no easy path to enter an exact value — both major a11y gaps. Pair the slider with a number-input row sitting below the track (mirroring WooCommerce's `product-filters/price- slider` block), bidirectionally synced through the shared `priceRange` state. - render.php: emit two number inputs under the slider with currency adornments, screen-reader labels, and the same store bindings the inputs-only `filter-wc-price` block uses. Drop the flank value-display spans (the inputs now show the live values). - view.js: register `priceRangeMin/MaxInputValue` getters and add `onPriceSliderNumberInputChange` / `Keydown` actions that read both number inputs and commit via `setPriceRange`. Slim `updatePriceSliderUi` to track-painting + range-thumb sync + aria-valuetext only; the number inputs sync automatically via `data-wp-bind--value`. - style.scss: mirror filter-wc-price's input field shape (symbol adornment, no spinners, position-aware padding) under the slider's BEM block, and switch from the grid wrapping the flank labels to a simple stack now that the value displays are gone. Sync paths verified in browser: - Slider drag → number input value (via the bind getter) - Number input commit → slider thumb + --low/--high + URL min_price/max_price * Collapse filter-wc-price-slider into a variation of filter-wc-price WooCommerce's product-filter-price-slider exposes "show inputs" and "inline inputs" as attributes on a single block — no separate inputs- only block. Mirror that: keep `jetpack-search/filter-wc-price` (already shipped on trunk via #48449) as the one canonical price filter, and add a `showSlider` attribute that flips the layout. Two inserter variations sit on top: - Filter by Price → showSlider: false (default, isDefault) - Filter by Price (Slider) → showSlider: true, autoBounds: true `showSlider: false` is the default, so existing trunk instances saved as `<!-- wp:jetpack-search/filter-wc-price /-->` keep rendering exactly as today — they're now "filter" variation instances. BEM under one root: number inputs stay on `__input--min/--max` (no churn for existing CSS), slider thumbs get the new `__slider-input --min/--max` so they don't collide. Wrapper gains a `--with-slider` modifier in slider mode for the inputs-row margin tweak. The watcher attribute (`data-wp-watch`) only emits in slider mode — filter mode doesn't pay the watcher cost. Removed entirely: - The standalone `jetpack-search/filter-wc-price-slider` block dir (never shipped — slider work was all on this branch). - Its tests, replaced by Filter_Wc_Price_Render_Test + filter-wc-price-view.test.js covering both layouts in one place. - `filter-wc-price-slider` import + entry in register-blocks.js. Verified in browser against the raven docker for both variations: - Filter variation: no slider DOM, no watch attribute, number inputs render and commit correctly. - Slider variation: slider thumbs + number inputs both present; slider drag → number input value, number-input commit → slider thumb + --low/--high gradient + aria-valuetext + URL min/max_price. * Address review: re-add publish-status JOIN in catalog extents query claude[bot] flagged that the extraction into Wc_Block_Helpers dropped the wp_posts JOIN the prior round added to the slider's render.php. `wc_product_meta_lookup` is populated on every product save (not only on publish), so a store with many drafts or pending-review products would get inflated slider bounds shoppers can never reach. Re-add the JOIN so the extents reflect the published catalog, matching what WC's own Store API does. * Address review: only allocate slider IDs when slider renders Copilot observation on the consolidation commit: `$slider_min_id` and `$slider_max_id` were created via `wp_unique_id()` unconditionally, including in filter mode where they're never emitted — two wasted ID counter increments per render. Move the calls inside the `$show_slider` ternary so filter-mode renders skip them entirely.
Why
Lets shoppers narrow products by budget using min/max number inputs. Respects WooCommerce's native
?min_price=/?max_price=URL contract — share-friendly, back-button works, half-open ranges supported (min only or max only). The currency symbol adornment on each input gives immediate context without screen clutter.Proposed changes
jetpack-search/filter-wc-priceblock (block.json, edit.js, render.php, view.js, style.scss).get_woocommerce_currency_symbol()andwoocommerce_currency_pos. Authors can override per-block; an empty value falls through to WC. Falls back to$/lefton a plain WP install.priceCurrencySymbol+priceCurrencySymbolPosition+priceLabelinto the shared store (forward-looking; consumed by the active-filters chip block — until then unused but harmless).How
The price block doesn't register a filterConfig.
priceRangeis its own state branch;setPriceRange/clearFilters(which also clearspriceRange) landed in #48446. The URL contract uses themin_price/max_pricereserved query params already inRESERVED_QUERY_PARAMS.Screenshots
Captured at 1440×900 against a local docker WP env with WooCommerce active (currency: AUD, position: left) and 30 priced products. Frontend search results render "Searching…" indefinitely because the dev env has no Elasticsearch backend wired up — the block UI itself still seeds, hydrates, and writes URL params correctly.
?min_price=50&max_price=500(values seeded from URL)currencySymbol="kr"+currencySymbolPosition="right"(suffix locale)Stack
PR-4 of 9 closing RSM-1929. Stacks on PR-1 #48446 (foundation). Independent of PR-2 (#48447) and PR-3 (#48448).
Does this pull request change what data or activity we track or use?
No.
Testing instructions
jetpack-search/filter-wc-priceon a search page with WooCommerce active. Leave the Inspector's Currency Symbol blank — the rendered input should adopt your WC currency's symbol (e.g.$for AUD,€for EUR,krfor SEK). Set Symbol Position to "Default (WooCommerce)" — the rendered input should put the symbol on the side WC'swoocommerce_currency_posoption says (left for AUD, right for SEK).€in Currency Symbol, or pick "After amount" — and confirm the block honors the explicit override on the next render./?s=shirt: type10in the min input, blur or press Enter → URL gains?min_price=10. Reload — value persists.50in max — URL becomes?max_price=50(half-open range; min omitted).setPriceRange).setPriceRangevalidation).