diff --git a/packages/multi-trait-rubric/configure/src/common.jsx b/packages/multi-trait-rubric/configure/src/common.jsx index 2e30569a9e..1f3819268a 100644 --- a/packages/multi-trait-rubric/configure/src/common.jsx +++ b/packages/multi-trait-rubric/configure/src/common.jsx @@ -117,6 +117,7 @@ export const Block = styled('div')(({ theme }) => ({ const StyledSecondaryBlock = styled('div')({ display: 'flex', overflowX: 'hidden', + overflowY: 'hidden', alignItems: 'flex-end', // this is needed to show the editor toolbar!!! paddingBottom: '30px', diff --git a/packages/multi-trait-rubric/configure/src/scale.jsx b/packages/multi-trait-rubric/configure/src/scale.jsx index b742311f13..b156ea5ec1 100644 --- a/packages/multi-trait-rubric/configure/src/scale.jsx +++ b/packages/multi-trait-rubric/configure/src/scale.jsx @@ -38,7 +38,7 @@ export class Scale extends React.Component { componentDidMount() { if (this.state.showRight === null && this.secondaryBlockRef) { - this.setState({ showRight: this.secondaryBlockRef.scrollWidth - this.secondaryBlockRef.offsetWidth }); + this.setState({ showRight: this.getMaxScroll() }); } } @@ -48,7 +48,7 @@ export class Scale extends React.Component { prevProps.showDescription !== this.props.showDescription || prevProps.excludeZero !== this.props.excludeZero ) { - this.setState({ showRight: this.secondaryBlockRef.scrollWidth - this.secondaryBlockRef.offsetWidth }); + this.setState({ showRight: this.getMaxScroll() }); } } @@ -61,7 +61,7 @@ export class Scale extends React.Component { this.setState({ currentPosition: 0, showLeft: false, - showRight: this.secondaryBlockRef && this.secondaryBlockRef.scrollWidth - this.secondaryBlockRef.offsetWidth, + showRight: this.getMaxScroll(), }); } } @@ -76,7 +76,7 @@ export class Scale extends React.Component { this.setState({ currentPosition: 0, showLeft: false, - showRight: this.secondaryBlockRef && this.secondaryBlockRef.scrollWidth - this.secondaryBlockRef.offsetWidth, + showRight: this.getMaxScroll(), }); if (numberValue < maxPoints) { @@ -203,23 +203,46 @@ export class Scale extends React.Component { decreasePosition = () => { const { currentPosition } = this.state; - const decreasedPosition = - currentPosition - (currentPosition === AdjustedBlockWidth / 2 ? AdjustedBlockWidth / 2 : AdjustedBlockWidth); + const decreasedPosition = Math.max(currentPosition - AdjustedBlockWidth, 0); + const maxScroll = this.getMaxScroll(); this.setState({ currentPosition: decreasedPosition, - showRight: decreasedPosition < this.secondaryBlockRef.scrollWidth - this.secondaryBlockRef.offsetWidth, + showRight: decreasedPosition < maxScroll, showLeft: decreasedPosition > 0, }); }; + getMaxScroll = () => { + if (!this.secondaryBlockRef) return 0; + + // block count * BlockWidth + // the header may have slightly larger scrollWidth due to inner component styling (borders, padding), + // so we use the minimum to keep header and trait rows aligned. + const headerScrollWidth = this.secondaryBlockRef.scrollWidth; + const numberOfBlocks = this.getNumberOfBlocks(); + const expectedContentWidth = numberOfBlocks * BlockWidth; + const scrollWidth = Math.min(headerScrollWidth, expectedContentWidth); + + return Math.max(0, scrollWidth - this.secondaryBlockRef.offsetWidth); + }; + + getNumberOfBlocks = () => { + const { scale, showStandards, showDescription, excludeZero } = this.props; + const { maxPoints } = scale || {}; + const scorePointCount = maxPoints - (excludeZero ? 1 : 0) + 1; + + return (showStandards ? 1 : 0) + (showDescription ? 1 : 0) + scorePointCount; + }; + increasePosition = () => { const { currentPosition } = this.state; - const increasedPosition = currentPosition + (currentPosition === 0 ? AdjustedBlockWidth / 2 : AdjustedBlockWidth); + const maxScroll = this.getMaxScroll(); + const increasedPosition = Math.min(currentPosition + AdjustedBlockWidth, maxScroll); this.setState({ currentPosition: increasedPosition, - showRight: increasedPosition < this.secondaryBlockRef.scrollWidth - this.secondaryBlockRef.offsetWidth, + showRight: increasedPosition < maxScroll, showLeft: increasedPosition > 0, }); }; diff --git a/packages/multi-trait-rubric/configure/src/trait.jsx b/packages/multi-trait-rubric/configure/src/trait.jsx index 24cba31b35..8d6dbeda94 100644 --- a/packages/multi-trait-rubric/configure/src/trait.jsx +++ b/packages/multi-trait-rubric/configure/src/trait.jsx @@ -98,13 +98,12 @@ function TraitTile({ }, }); - React.useEffect(() => { + React.useLayoutEffect(() => { if ( currentPosition !== undefined && - secondaryBlockRef.current && - secondaryBlockRef.current.scrollLeft !== currentPosition + secondaryBlockRef.current ) { - scrollToPosition(currentPosition); + secondaryBlockRef.current.scrollTo({ left: currentPosition }); } }, [currentPosition]); @@ -128,8 +127,6 @@ function TraitTile({ const handleClose = () => setAnchorEl(null); - const scrollToPosition = (position) => secondaryBlockRef.current?.scrollTo({ left: position }); - const openMenu = () => { onTraitRemoved(); handleClose(); diff --git a/packages/multi-trait-rubric/configure/src/traitsHeader.jsx b/packages/multi-trait-rubric/configure/src/traitsHeader.jsx index a5f5b13f73..0bd63a97da 100644 --- a/packages/multi-trait-rubric/configure/src/traitsHeader.jsx +++ b/packages/multi-trait-rubric/configure/src/traitsHeader.jsx @@ -87,7 +87,11 @@ export class TraitsHeaderTile extends React.Component { handleClose = () => this.setState({ anchorEl: null }); - scrollToPosition = (position) => this.secondaryBlock.scrollTo({ left: position }); + scrollToPosition = (position) => { + if (this.secondaryBlock) { + this.secondaryBlock.scrollTo({ left: position }); + } + }; openMenu = () => { this.props.showDeleteScaleModal();