Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Fixes and improvements for the Price Slider Block #2784

Closed
wants to merge 10 commits into from

Conversation

mikejolley
Copy link
Member

Resolves some related issues reported with the Price Slider. Tackled in one pull due to touching the same code for each.

  1. Makes the size of the input based on the maxConstraint to avoid cutting off prices in the input boxes Fixes Filter by Price value is truncated when Price value is more than 4 digits. #2752
  2. Prevents input values going below zero Fixes [GlobalStep] User is able to set negative values in "Filter Products by Price" block.  #2695
  3. Fixes keyboard input by removing the value from the range sliders Fixes Price slider: can't move handles with the keyboard #1679
  4. Adds aria value to read correct price without minor units Fixes Price slider: prices are read without accounting currency minorUnit value #1657
  5. Fixes active filter display of prices so they match the slider
  6. Resolves linting errors in price slider component

How to test the changes in this Pull Request:

Add All Products, Price Filter, and Active Filters to a page.

  1. Add a product to the store with a high price like 999999. View slider with inputs enabled. Price should not be cut off.
  2. Confirm Active Filters display prices using the correct format (with commas for thousands etc)
  3. Click a handle or tab to it and try using the keyboard to change the value of the slider. It will change. Focus will be lost after update.
  4. With a screen reader, check what prices are announced when the slider has focus. It should be something like $100 rather than 10000 (without minor units).
  5. Set min to 10 and max to 100, then set max to 0 via the inputs. Ensure min remains at 0 and does not go negative.

Changelog

Accessibility: Fixes keyboard input and screen reader text in the Price Slider Component.
Fix: Prevent negative values in the price slider.
Fix: Fixes formatting of prices in the active filters block and price slider inputs.

@mikejolley mikejolley added status: needs review block: filter by price Issues related to the Filter by Price block. labels Jun 25, 2020
@mikejolley mikejolley requested a review from a team as a code owner June 25, 2020 15:48
@mikejolley mikejolley requested review from senadir and removed request for a team June 25, 2020 15:48
@mikejolley mikejolley self-assigned this Jun 25, 2020
@@ -60,6 +59,7 @@ const FormattedMonetaryAmount = ( {
displayType: 'text',
...props,
...currencyToNumberFormat( currency ),
fixedDecimalScale: Math.round( priceValue ) !== priceValue,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this prevents .00 showing for prices in the slider.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this result in prices like 10.10 showing as 10.1 though? This component is used in a lot of different places, are we sure we don't want 10.00 showing in all cases where this component is used (as opposed to just the price slider)?

@@ -257,18 +265,16 @@ const PriceSlider = ( {
'Filter products by minimum price',
'woo-gutenberg-products-block'
) }
value={
Copy link
Member Author

@mikejolley mikejolley Jun 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing value here and instead changing values dynamically allows keyboard input to function.

Copy link
Contributor

@nerrad nerrad Jul 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an antipattern in React because you are changing things imperatively instead of declaratively (see "When to Use Refs"). In the case of inputs it's usually okay to use refs to get various attributes of the input when it changes but not to set values via the ref.

I understand that this might have appeared as a way to fix allowing the keyboard input to function but are we sure this is the only way to fix that? If necessary, to avoid blocking the other fixes from getting released - addressing that specific issue could be split out separately from this pull so we avoid implementing this anti-pattern.

I notice through this review that we're mutating other ref properties here for the input as well (zIndex on style) that should be done through react props instead of through the ref (although I realize they were pre-existing this pull...they still should be addressed).

@github-actions
Copy link
Contributor

github-actions bot commented Jun 25, 2020

Size Change: +5.29 kB (0%)

Total Size: 1.64 MB

Filename Size Change
build/active-filters-frontend.js 13.5 kB +4.99 kB (37%) 🚨
build/active-filters.js 9.11 kB +339 B (3%)
build/all-products-frontend.js 30.9 kB +3 B (0%)
build/all-products.js 31.2 kB -19 B (0%)
build/all-reviews.js 9.67 kB -1 B
build/atomic-block-components/add-to-cart-frontend.js 4.6 kB -1 B
build/atomic-block-components/button-frontend.js 1.99 kB -3 B (0%)
build/atomic-block-components/price-frontend.js 2.14 kB +55 B (2%)
build/atomic-block-components/price.js 2.15 kB +39 B (1%)
build/atomic-block-components/summary-frontend.js 917 B -1 B
build/attribute-filter-frontend.js 17.8 kB +2 B (0%)
build/attribute-filter.js 12.3 kB +1 B
build/cart-frontend.js 65.7 kB -130 B (0%)
build/cart.js 34.4 kB -6 B (0%)
build/checkout-frontend.js 82.2 kB -142 B (0%)
build/checkout.js 39.3 kB -8 B (0%)
build/featured-category.js 7.64 kB -1 B
build/handpicked-products.js 7.29 kB -3 B (0%)
build/price-filter-frontend.js 14.3 kB +184 B (1%)
build/price-filter.js 10.3 kB +143 B (1%)
build/product-best-sellers.js 7.37 kB -1 B
build/product-category.js 8.3 kB -2 B (0%)
build/product-new.js 7.53 kB -2 B (0%)
build/product-on-sale.js 7.91 kB -2 B (0%)
build/product-tag.js 6.45 kB -1 B
build/product-top-rated.js 7.5 kB -2 B (0%)
build/products-by-attribute.js 8.24 kB -3 B (0%)
build/reviews-by-category.js 11.7 kB -3 B (0%)
build/reviews-by-product.js 13.3 kB +1 B
build/reviews-frontend.js 9.06 kB +5 B (0%)
build/single-product-frontend.js 33.7 kB +9 B (0%)
build/single-product.js 10 kB +2 B (0%)
build/style-legacy-rtl.css 16.5 kB +5 B (0%)
build/style-legacy.css 16.5 kB +5 B (0%)
build/style-rtl.css 17.2 kB +4 B (0%)
build/style.css 17.2 kB +5 B (0%)
build/vendors.js 416 kB -173 B (0%)
build/vendors~atomic-block-components/price-frontend.js 5.65 kB -1 B
ℹ️ View Unchanged
Filename Size Change
build/all-reviews-legacy.js 9.34 kB 0 B
build/atomic-block-components/add-to-cart.js 3.17 kB 0 B
build/atomic-block-components/add-to-cart~atomic-block-components/button.js 3.12 kB 0 B
build/atomic-block-components/add-to-cart~atomic-block-components/image~atomic-block-components/title.js 334 B 0 B
build/atomic-block-components/button.js 837 B 0 B
build/atomic-block-components/category-list-frontend.js 468 B 0 B
build/atomic-block-components/category-list.js 475 B 0 B
build/atomic-block-components/image-frontend.js 1.7 kB 0 B
build/atomic-block-components/image.js 1.15 kB 0 B
build/atomic-block-components/rating-frontend.js 524 B 0 B
build/atomic-block-components/rating.js 527 B 0 B
build/atomic-block-components/sale-badge-frontend.js 862 B 0 B
build/atomic-block-components/sale-badge.js 865 B 0 B
build/atomic-block-components/sku-frontend.js 388 B 0 B
build/atomic-block-components/sku.js 393 B 0 B
build/atomic-block-components/stock-indicator-frontend.js 568 B 0 B
build/atomic-block-components/stock-indicator.js 571 B 0 B
build/atomic-block-components/summary.js 925 B 0 B
build/atomic-block-components/tag-list-frontend.js 464 B 0 B
build/atomic-block-components/tag-list.js 472 B 0 B
build/atomic-block-components/title-frontend.js 1.21 kB 0 B
build/atomic-block-components/title.js 1.05 kB 0 B
build/blocks-legacy.js 3.54 kB 0 B
build/blocks.js 3.54 kB 0 B
build/editor-legacy-rtl.css 13.8 kB 0 B
build/editor-legacy.css 13.8 kB 0 B
build/editor-rtl.css 14 kB 0 B
build/editor.css 14 kB 0 B
build/featured-category-legacy.js 7.28 kB 0 B
build/featured-product-legacy.js 9.53 kB 0 B
build/featured-product.js 9.9 kB 0 B
build/handpicked-products-legacy.js 6.93 kB 0 B
build/product-best-sellers-legacy.js 7.01 kB 0 B
build/product-categories-legacy.js 3.23 kB 0 B
build/product-categories.js 3.23 kB 0 B
build/product-category-legacy.js 7.92 kB 0 B
build/product-new-legacy.js 7.17 kB 0 B
build/product-on-sale-legacy.js 7.54 kB 0 B
build/product-search-legacy.js 3.15 kB 0 B
build/product-search.js 3.48 kB 0 B
build/product-tag-legacy.js 6.1 kB 0 B
build/product-top-rated-legacy.js 7.14 kB 0 B
build/products-by-attribute-legacy.js 7.91 kB 0 B
build/reviews-by-category-legacy.js 11.3 kB 0 B
build/reviews-by-product-legacy.js 12.8 kB 0 B
build/reviews-frontend-legacy.js 8.2 kB 0 B
build/vendors-legacy.js 367 kB 0 B
build/vendors-style-legacy-rtl.css 1.03 kB 0 B
build/vendors-style-legacy.css 1.03 kB 0 B
build/vendors-style-rtl.css 1.03 kB 0 B
build/vendors-style.css 1.03 kB 0 B
build/wc-blocks-data.js 7.09 kB 0 B
build/wc-blocks-middleware.js 931 B 0 B
build/wc-blocks-registry.js 2.28 kB 0 B
build/wc-payment-method-bacs.js 790 B 0 B
build/wc-payment-method-cheque.js 796 B 0 B
build/wc-payment-method-cod.js 875 B 0 B
build/wc-payment-method-paypal.js 831 B 0 B
build/wc-payment-method-stripe.js 11.9 kB 0 B
build/wc-settings.js 2.14 kB 0 B
build/wc-shared-context.js 1.53 kB 0 B
build/wc-shared-hocs.js 1.66 kB 0 B

compressed-size-action

@nerrad nerrad added the focus: accessibility The issue/PR is related to accessibility. label Jul 2, 2020
Copy link
Contributor

@nerrad nerrad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tackling a bunch of issues with this pull Mike! I haven't tested yet as the review surfaced a few things I had questions on that might need addressed.

@@ -60,6 +59,7 @@ const FormattedMonetaryAmount = ( {
displayType: 'text',
...props,
...currencyToNumberFormat( currency ),
fixedDecimalScale: Math.round( priceValue ) !== priceValue,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this result in prices like 10.10 showing as 10.1 though? This component is used in a lot of different places, are we sure we don't want 10.00 showing in all cases where this component is used (as opposed to just the price slider)?

const hasMinConstraint = Number.isFinite( minAllowed );
const hasMaxConstraint = Number.isFinite( maxAllowed );
const minConstraint = minAllowed || 0;
const maxConstraint = maxAllowed || step;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be extra validation on the step value as well?

Comment on lines +34 to +35
let minValue = parseValid( values[ 0 ], minConstraint );
let maxValue = parseValid( values[ 1 ], maxConstraint );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if values[0] or values[1] are not "valid" the fallback is minConstraint or maxConstraint (which in turn might be 0 or step). Should we have some additional validation on minConstraint/maxConstraint at this point to ensure they are valid values for comparing with (if they end up used as fallbacks) later on in the function? The original values are validated at the beginning of the function to determine hasMinConstraint and hasMaxConstraint values but that doesn't allow for the potential that they could be strings or some other non-truthy, non numeric value when used in these expressions as fallbacks?

(I'm not sure how far we should go with being defensive here, so any pushback with rationale you have on this is welcome)


const [ minPriceInput, setMinPriceInput ] = useState( minPrice );
const [ maxPriceInput, setMaxPriceInput ] = useState( maxPrice );

useEffect( () => {
if ( minRange.current ) {
minRange.current.value = minPrice;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize the value property on the ref comes from passing this through as a ref on the input. However, the ref is initialized as minRange.current = null. Currently, the code works because it just so happens when minRange.current is truthy, it's because it has been converted to an object but it is still fragile because it's implied via passing through as a ref on input (which isn't immediately apparent).

I think it might be better here if you explicitly initialize the ref with the expected data structure, so something like:

const minRange = useRef( {} );

Then you can modify your conditionals to be:

if ( minRange.current.value !== undefined ) { /* ... */ }

Comment on lines 133 to 134
const minX = minWidth * ( minValue / maxConstraint );
const maxX = maxWidth * ( maxValue / maxConstraint );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minWidth or maxWidth might be undefined here right? So this could result in NaN. Have you accounted for that in the various subsequent comparisons derived from these values?

Comment on lines +144 to +148
currentMinRange.style.zIndex = 20;
currentMaxRange.style.zIndex = 21;
} else {
minRange.current.style.zIndex = 21;
maxRange.current.style.zIndex = 20;
currentMinRange.style.zIndex = 21;
currentMaxRange.style.zIndex = 20;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an assumption here that style exists as a property on the range values. There's risk here of getting assignment warnings/error for zIndex.

@@ -257,18 +265,16 @@ const PriceSlider = ( {
'Filter products by minimum price',
'woo-gutenberg-products-block'
) }
value={
Copy link
Contributor

@nerrad nerrad Jul 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an antipattern in React because you are changing things imperatively instead of declaratively (see "When to Use Refs"). In the case of inputs it's usually okay to use refs to get various attributes of the input when it changes but not to set values via the ref.

I understand that this might have appeared as a way to fix allowing the keyboard input to function but are we sure this is the only way to fix that? If necessary, to avoid blocking the other fixes from getting released - addressing that specific issue could be split out separately from this pull so we avoid implementing this anti-pattern.

I notice through this review that we're mutating other ref properties here for the input as well (zIndex on style) that should be done through react props instead of through the ref (although I realize they were pre-existing this pull...they still should be addressed).

Comment on lines +330 to +332
if ( ! Number.isFinite( value ) ) {
value = 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this come before the comparison with minPriceInput (same question applies for the maxPriceInput comparison a bit later)?

Comment on lines +123 to +133
const formattedPrice = renderToString(
<FormattedMonetaryAmount
currency={ currency }
displayType="text"
value={ priceInt }
/>
);

// This uses a textarea to magically decode HTML currency symbols.
const txt = document.createElement( 'textarea' );
txt.innerHTML = formattedValue;
return txt.value;
// Remove HTML.
const tagsRegExp = /<\/?[a-z][^>]*?>/gi;
return formattedPrice.replace( tagsRegExp, '' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... interesting approach here. I originally did a double-take because I thought renderToString could only be used reliably in the server context but it looks like it can be used in both environments now.

It'd be good to add to the tests some cases around removing html.

Comment on lines -105 to +111
export const formatPrice = ( price, currencyData ) => {
export const formatPriceAsString = ( price, currencyData = {} ) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice name update here 👏

@senadir senadir force-pushed the update/price-slider-fixes branch from c9c8249 to 0dd3d2d Compare July 29, 2020 11:12
@senadir senadir self-assigned this Jul 29, 2020
@senadir
Copy link
Member

senadir commented Jul 29, 2020

I assigned this PR to me, will tackle it somewhere next week and address Darren's review.

@nerrad nerrad changed the base branch from main to trunk September 26, 2020 17:31
@senadir senadir removed their assignment Nov 8, 2020
@senadir senadir removed their request for review November 8, 2020 10:18
@senadir
Copy link
Member

senadir commented Nov 8, 2020

Unassigning myself from this because I don't intend on working on it anytime soon, and removing the review request so it doesn't influence the load balance algorithm.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2021

This PR has been marked as stale because it has not seen any activity within the past 60 days. Our team uses this tool to help surface pull requests that have slipped through review.

If deemed still relevant, the pr can be kept active by ensuring it's up to date with the main branch and removing the stale label - otherwise it will automatically be closed after 10 days.

@github-actions github-actions bot added the status: stale Stale issues and PRs have had no updates for 60 days. label Jan 8, 2021
@nerrad nerrad added type: cooldown Things that are queued for a cooldown period (assists with planning). and removed status: stale Stale issues and PRs have had no updates for 60 days. status: needs review labels Jan 12, 2021
@mikejolley
Copy link
Member Author

@ralucaStan I forgot this existed. This touches on some of the fixes you've been working on. This is out of sync so probably better to start fresh anyway.

@mikejolley mikejolley marked this pull request as draft February 11, 2021 16:51
@mikejolley
Copy link
Member Author

I'm going to bin this PR. It's stale and all the issues could use a fresh pair of eyes. I see @ralucaStan is assigned to some of the original issues now.

@mikejolley mikejolley closed this Apr 9, 2021
@ralucaStan ralucaStan deleted the update/price-slider-fixes branch August 4, 2021 14:33
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
block: filter by price Issues related to the Filter by Price block. focus: accessibility The issue/PR is related to accessibility. type: cooldown Things that are queued for a cooldown period (assists with planning).
Projects
None yet
3 participants