diff --git a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js b/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js index 54683e48beeea4..33d677910a712e 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js @@ -45,33 +45,39 @@ export function GridItemResizer( { clientId, onChange } ) { const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); - const gridColumnLines = getGridLines( + const gridColumnTracks = getGridTracks( getComputedCSS( gridElement, 'grid-template-columns' ), columnGap ); - const gridRowLines = getGridLines( + const gridRowTracks = getGridTracks( getComputedCSS( gridElement, 'grid-template-rows' ), rowGap ); - const columnStart = getClosestLine( - gridColumnLines, - blockElement.offsetLeft - ); - const rowStart = getClosestLine( - gridRowLines, - blockElement.offsetTop - ); - const columnEnd = getClosestLine( - gridColumnLines, - blockElement.offsetLeft + boxElement.offsetWidth - ); - const rowEnd = getClosestLine( - gridRowLines, - blockElement.offsetTop + boxElement.offsetHeight - ); + const columnStart = + getClosestTrack( + gridColumnTracks, + blockElement.offsetLeft + ) + 1; + const rowStart = + getClosestTrack( + gridRowTracks, + blockElement.offsetTop + ) + 1; + const columnEnd = + getClosestTrack( + gridColumnTracks, + blockElement.offsetLeft + boxElement.offsetWidth, + 'end' + ) + 1; + const rowEnd = + getClosestTrack( + gridRowTracks, + blockElement.offsetTop + boxElement.offsetHeight, + 'end' + ) + 1; onChange( { - columnSpan: Math.max( columnEnd - columnStart, 1 ), - rowSpan: Math.max( rowEnd - rowStart, 1 ), + columnSpan: columnEnd - columnStart + 1, + rowSpan: rowEnd - rowStart + 1, } ); } } /> @@ -79,20 +85,50 @@ export function GridItemResizer( { clientId, onChange } ) { ); } -function getGridLines( template, gap ) { - const lines = [ 0 ]; +/** + * Given a grid-template-columns or grid-template-rows CSS property value, gets the start and end + * position in pixels of each grid track. + * + * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track + * + * @param {string} template The grid-template-columns or grid-template-rows CSS property value. + * Only supports fixed sizes in pixels. + * @param {number} gap The gap between grid tracks in pixels. + * + * @return {Array<{start: number, end: number}>} An array of objects with the start and end + * position in pixels of each grid track. + */ +function getGridTracks( template, gap ) { + const tracks = []; for ( const size of template.split( ' ' ) ) { - const line = parseFloat( size ); - lines.push( lines[ lines.length - 1 ] + line + gap ); + const previousTrack = tracks[ tracks.length - 1 ]; + const start = previousTrack ? previousTrack.end + gap : 0; + const end = start + parseFloat( size ); + tracks.push( { start, end } ); } - return lines; + return tracks; } -function getClosestLine( lines, position ) { - return lines.reduce( - ( closest, line, index ) => - Math.abs( line - position ) < - Math.abs( lines[ closest ] - position ) +/** + * Given an array of grid tracks and a position in pixels, gets the index of the closest track to + * that position. + * + * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track + * + * @param {Array<{start: number, end: number}>} tracks An array of objects with the start and end + * position in pixels of each grid track. + * @param {number} position The position in pixels. + * @param {string} edge The edge of the track to compare the + * position to. Either 'start' or 'end'. + * + * @return {number} The index of the closest track to the position. 0-based, unlike CSS grid which + * is 1-based. + */ +function getClosestTrack( tracks, position, edge = 'start' ) { + return tracks.reduce( + ( closest, track, index ) => + Math.abs( track[ edge ] - position ) < + Math.abs( tracks[ closest ][ edge ] - position ) ? index : closest, 0