diff --git a/packages/material-ui/src/Rating/Rating.js b/packages/material-ui/src/Rating/Rating.js index a7703d7c2182ea..3eb36763cc5b0e 100644 --- a/packages/material-ui/src/Rating/Rating.js +++ b/packages/material-ui/src/Rating/Rating.js @@ -193,6 +193,116 @@ IconContainer.propTypes = { value: PropTypes.number.isRequired, }; +function RatingItem(props) { + const { + classes, + disabled, + emptyIcon, + focus, + getLabelText, + highlightSelectedOnly, + hover, + icon, + IconContainerComponent, + isActive, + itemValue, + labelProps, + name, + onBlur, + onChange, + onClick, + onFocus, + readOnly, + styleProps, + ratingValue, + ratingValueRounded, + } = props; + + const isFilled = highlightSelectedOnly ? itemValue === ratingValue : itemValue <= ratingValue; + const isHovered = itemValue <= hover; + const isFocused = itemValue <= focus; + const isChecked = itemValue === ratingValueRounded; + + const id = useId(); + const container = ( + + {emptyIcon && !isFilled ? emptyIcon : icon} + + ); + + if (readOnly) { + return {container}; + } + + return ( + + + {container} + {getLabelText(itemValue)} + + + + ); +} + +RatingItem.propTypes = { + classes: PropTypes.object.isRequired, + disabled: PropTypes.bool.isRequired, + emptyIcon: PropTypes.node, + focus: PropTypes.number.isRequired, + getLabelText: PropTypes.func.isRequired, + highlightSelectedOnly: PropTypes.bool.isRequired, + hover: PropTypes.number.isRequired, + icon: PropTypes.node, + IconContainerComponent: PropTypes.elementType.isRequired, + isActive: PropTypes.bool.isRequired, + itemValue: PropTypes.number.isRequired, + labelProps: PropTypes.object, + name: PropTypes.string, + onBlur: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, + ratingValue: PropTypes.number, + ratingValueRounded: PropTypes.number, + readOnly: PropTypes.bool.isRequired, + styleProps: PropTypes.object.isRequired, +}; + const defaultIcon = ; const defaultEmptyIcon = ; @@ -397,67 +507,6 @@ const Rating = React.forwardRef(function Rating(inProps, ref) { const classes = useUtilityClasses(styleProps); - const item = (state, labelProps) => { - const id = `${name}-${String(state.value).replace('.', '-')}`; - const container = ( - - {emptyIcon && !state.filled ? emptyIcon : icon} - - ); - - if (readOnly) { - return ( - - {container} - - ); - } - - return ( - - - {container} - {getLabelText(state.value)} - - - - ); - }; - return ( { const itemValue = index + 1; + const ratingItemProps = { + classes, + disabled, + emptyIcon, + focus, + getLabelText, + highlightSelectedOnly, + hover, + icon, + IconContainerComponent, + name, + onBlur: handleBlur, + onChange: handleChange, + onClick: handleClear, + onFocus: handleFocus, + ratingValue: value, + ratingValueRounded: valueRounded, + readOnly, + styleProps, + }; + + const isActive = itemValue === Math.ceil(value) && (hover !== -1 || focus !== -1); if (precision < 1) { const items = Array.from(new Array(1 / precision)); - const iconActive = itemValue === Math.ceil(value) && (hover !== -1 || focus !== -1); return ( {items.map(($, indexDecimal) => { @@ -490,44 +560,42 @@ const Rating = React.forwardRef(function Rating(inProps, ref) { precision, ); - return item( - { - value: itemDecimalValue, - filled: highlightSelectedOnly - ? itemDecimalValue === value - : itemDecimalValue <= value, - hover: itemDecimalValue <= hover, - focus: itemDecimalValue <= focus, - checked: itemDecimalValue === valueRounded, - }, - { - style: - items.length - 1 === indexDecimal - ? {} - : { - width: - itemDecimalValue === value - ? `${(indexDecimal + 1) * precision * 100}%` - : '0%', - overflow: 'hidden', - zIndex: 1, - position: 'absolute', - }, - }, + return ( + ); })} ); } - return item({ - value: itemValue, - active: itemValue === value && (hover !== -1 || focus !== -1), - filled: highlightSelectedOnly ? itemValue === value : itemValue <= value, - hover: itemValue <= hover, - focus: itemValue <= focus, - checked: itemValue === valueRounded, - }); + return ( + + ); })} {!readOnly && !disabled && ( ', () => { const handleChange = spy(); const { container } = render(); - fireEvent.click(container.querySelector('#rating-test-2'), { + fireEvent.click(container.querySelector('input[name="rating-test"][value="2"]'), { clientX: 1, }); @@ -77,7 +77,7 @@ describe('', () => { it('should select the rating', () => { const handleChange = spy(); const { container } = render(); - fireEvent.click(container.querySelector('#rating-test-3')); + fireEvent.click(container.querySelector('input[name="rating-test"][value="3"]')); expect(handleChange.callCount).to.equal(1); expect(handleChange.args[0][1]).to.deep.equal(3); const checked = container.querySelector('input[name="rating-test"]:checked'); @@ -105,7 +105,7 @@ describe('', () => { checked = container.querySelector('input[name="rating-test"]:checked'); expect(checked.value).to.equal('3'); - fireEvent.click(container.querySelector('#rating-test-2')); + fireEvent.click(container.querySelector('input[name="rating-test"][value="2"]')); checked = container.querySelector('input[name="rating-test"]:checked'); expect(checked.value).to.equal('2'); }); diff --git a/test/regressions/fixtures/Rating/PreciseFocusVisibleRating.js b/test/regressions/fixtures/Rating/PreciseFocusVisibleRating.js new file mode 100644 index 00000000000000..03c61edd42f721 --- /dev/null +++ b/test/regressions/fixtures/Rating/PreciseFocusVisibleRating.js @@ -0,0 +1,6 @@ +import * as React from 'react'; +import Rating from '@material-ui/core/Rating'; + +export default function FocusVisibleRating() { + return ; +} diff --git a/test/regressions/index.test.js b/test/regressions/index.test.js index 86daafffced0e7..b15cc2e3c75475 100644 --- a/test/regressions/index.test.js +++ b/test/regressions/index.test.js @@ -100,6 +100,17 @@ async function main() { await page.keyboard.press('ArrowLeft'); await takeScreenshot({ testcase, route: '/regression-Rating/FocusVisibleRating3' }); }); + + it('should handle focus-visible with precise ratings correctly', async () => { + const index = routes.findIndex( + (route) => route === '/regression-Rating/PreciseFocusVisibleRating', + ); + const testcase = await renderFixture(index); + await page.keyboard.press('Tab'); + await takeScreenshot({ testcase, route: '/regression-Rating/PreciseFocusVisibleRating2' }); + await page.keyboard.press('ArrowRight'); + await takeScreenshot({ testcase, route: '/regression-Rating/PreciseFocusVisibleRating3' }); + }); }); describe('DateTimePicker', () => {