Skip to content

Search 3.0: WC price filter block#48449

Merged
kangzj merged 1 commit intotrunkfrom
search/product-filter-price
May 8, 2026
Merged

Search 3.0: WC price filter block#48449
kangzj merged 1 commit intotrunkfrom
search/product-filter-price

Conversation

@kangzj
Copy link
Copy Markdown
Contributor

@kangzj kangzj commented May 1, 2026

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-price block (block.json, edit.js, render.php, view.js, style.scss).
  • Editor registration (Inspector exposes label, currency symbol, symbol position).
  • Symbol + position default to the active WooCommerce settings — get_woocommerce_currency_symbol() and woocommerce_currency_pos. Authors can override per-block; an empty value falls through to WC. Falls back to $ / left on a plain WP install.
  • Render seeds priceCurrencySymbol + priceCurrencySymbolPosition + priceLabel into 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. priceRange is its own state branch; setPriceRange / clearFilters (which also clears priceRange) landed in #48446. The URL contract uses the min_price / max_price reserved query params already in RESERVED_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.

Editor — block selected (Inspector: Label, Currency Symbol, Symbol Position) Frontend — empty inputs (symbol auto-detected from WC)
editor empty
Frontend — ?min_price=50&max_price=500 (values seeded from URL) Frontend — currencySymbol="kr" + currencySymbolPosition="right" (suffix locale)
filtered suffix

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

  1. Stack PR-1 (Search 3.0: foundation data-plane additions for product filter blocks #48446) + this PR.
  2. Insert jetpack-search/filter-wc-price on 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, kr for SEK). Set Symbol Position to "Default (WooCommerce)" — the rendered input should put the symbol on the side WC's woocommerce_currency_pos option says (left for AUD, right for SEK).
  3. Override either control — type in Currency Symbol, or pick "After amount" — and confirm the block honors the explicit override on the next render.
  4. With priced products + /?s=shirt: type 10 in the min input, blur or press Enter → URL gains ?min_price=10. Reload — value persists.
  5. Clear min, type 50 in max — URL becomes ?max_price=50 (half-open range; min omitted).
  6. Type a negative value or non-numeric input — search doesn't fire (silently dropped by setPriceRange).
  7. Type min=100, max=10 (inverted) — search doesn't fire (also dropped by setPriceRange validation).
  8. Click "Clear all" active-filters pill (once PR-5 lands) — both inputs reset to empty.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the search/product-filter-price branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack search/product-filter-price

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions Bot added the [Package] Search Contains core Search functionality for Jetpack and Search plugins label May 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

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:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 1, 2026
@jp-launch-control
Copy link
Copy Markdown

jp-launch-control Bot commented May 1, 2026

Code Coverage Summary

3 files are newly checked for coverage.

File Coverage
projects/packages/search/src/search-blocks/blocks/filter-wc-price/edit.js 0/15 (0.00%) 💔
projects/packages/search/src/search-blocks/blocks/filter-wc-price/render.php 0/66 (0.00%) 💔
projects/packages/search/src/search-blocks/blocks/filter-wc-price/view.js 0/23 (0.00%) 💔

Full summary · PHP report · JS report

If appropriate, add one of these labels to override the failing coverage check: Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage.

@kangzj kangzj self-assigned this May 1, 2026
@kangzj kangzj force-pushed the search/foundation branch from 7ac476d to b1da7e2 Compare May 7, 2026 03:15
@kangzj kangzj force-pushed the search/product-filter-price branch from 3d6ffae to 7eaae5c Compare May 7, 2026 03:29
@kangzj

This comment has been minimized.

@claude

This comment has been minimized.

kangzj added a commit that referenced this pull request May 7, 2026
…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.
@kangzj

This comment has been minimized.

@kangzj

This comment has been minimized.

@claude

This comment has been minimized.

kangzj added a commit that referenced this pull request May 7, 2026
…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.
@kangzj kangzj force-pushed the search/product-filter-price branch from a61e1ca to 3316ed0 Compare May 7, 2026 21:16
@kangzj

This comment has been minimized.

@claude

This comment has been minimized.

@kangzj

This comment has been minimized.

@kangzj kangzj removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 7, 2026
@kangzj

This comment has been minimized.

@claude

This comment has been minimized.

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 7, 2026
@kangzj kangzj removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 7, 2026
@kangzj
Copy link
Copy Markdown
Contributor Author

kangzj commented May 7, 2026

We should use WooCommerce currency formatting options as default

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 7, 2026
@kangzj

This comment has been minimized.

@kangzj kangzj removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 7, 2026
@kangzj

This comment has been minimized.

Copilot finished work on behalf of kangzj May 7, 2026 23:01
@kangzj kangzj force-pushed the search/foundation branch from 4b81e6e to 6a9b4d0 Compare May 7, 2026 23:02
kangzj added a commit that referenced this pull request May 7, 2026
…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.
@kangzj kangzj force-pushed the search/product-filter-price branch from f63b1ad to 6d0f108 Compare May 7, 2026 23:07
@kangzj kangzj removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 7, 2026
@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 7, 2026
Base automatically changed from search/foundation to trunk May 7, 2026 23:19
@kangzj

This comment has been minimized.

@kangzj kangzj added [Status] Needs Team Review Obsolete. Use Needs Review instead. and removed [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. labels May 7, 2026
@kangzj kangzj force-pushed the search/product-filter-price branch from 6d0f108 to 38ffc44 Compare May 7, 2026 23:37
@kangzj
Copy link
Copy Markdown
Contributor Author

kangzj commented May 7, 2026

Rebased onto latest trunk (#48446 foundation now upstream). Trimmed to just the new filter-wc-price block + its editor registration; the duplicated foundation work was dropped.

@claude please review.

@claude

This comment has been minimized.

@kangzj

This comment has been minimized.

@claude

This comment has been minimized.

@kangzj

This comment has been minimized.

@kangzj
Copy link
Copy Markdown
Contributor Author

kangzj commented May 8, 2026

🤖 Review-cycle summary — 38ffc44350838ffc443508

2 round(s); CI green; 0 threads opened; 1 AI reviewer addressed.

What changed during the cycle

Commits added: none — the rebase re-implementation already addressed every prior review item before the cycle started.

Diff summary: no new diff this cycle.

Review threads addressed:

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].
@kangzj kangzj force-pushed the search/product-filter-price branch from 38ffc44 to b694185 Compare May 8, 2026 02:41
@kangzj kangzj merged commit 229757d into trunk May 8, 2026
71 of 72 checks passed
@kangzj kangzj deleted the search/product-filter-price branch May 8, 2026 02:58
@github-actions github-actions Bot added [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. and removed [Status] Needs Team Review Obsolete. Use Needs Review instead. labels May 8, 2026
kangzj added a commit that referenced this pull request May 11, 2026
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.
kangzj added a commit that referenced this pull request May 11, 2026
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.
kangzj added a commit that referenced this pull request May 11, 2026
…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 `&amp;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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Docs [Package] Search Contains core Search functionality for Jetpack and Search plugins [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants