Skip to content

Commit

Permalink
ListView: Update drop indicator line positioning to support rtl langu…
Browse files Browse the repository at this point in the history
…ages (WordPress#51284)

* ListView: Update drop indicator line positioning to support rtl languages

* Update tests to include rtl equivalents

* Ensure drop indicator line doesn't break out of scroll containers in both LTR and RTL languages

* Users shouldn't be able to drag below a non-empty expanded block

* Fix width issue when target is only slightly wider than the scroll container
  • Loading branch information
andrewserong authored and sethrubenstein committed Jul 13, 2023
1 parent f9a14de commit e4e558a
Show file tree
Hide file tree
Showing 3 changed files with 376 additions and 153 deletions.
103 changes: 94 additions & 9 deletions packages/block-editor/src/components/list-view/drop-indicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { Popover } from '@wordpress/components';
import { getScrollContainer } from '@wordpress/dom';
import { useCallback, useMemo } from '@wordpress/element';
import { isRTL } from '@wordpress/i18n';

export default function ListViewDropIndicator( {
listViewRef,
Expand Down Expand Up @@ -41,6 +42,8 @@ export default function ListViewDropIndicator( {
// is undefined, so the indicator will appear after the rootBlockElement.
const targetElement = blockElement || rootBlockElement;

const rtl = isRTL();

const getDropIndicatorIndent = useCallback(
( targetElementRect ) => {
if ( ! rootBlockElement ) {
Expand All @@ -55,9 +58,11 @@ export default function ListViewDropIndicator( {
);
const rootBlockIconRect =
rootBlockIconElement.getBoundingClientRect();
return rootBlockIconRect.right - targetElementRect.left;
return rtl
? targetElementRect.right - rootBlockIconRect.left
: rootBlockIconRect.right - targetElementRect.left;
},
[ rootBlockElement ]
[ rootBlockElement, rtl ]
);

const getDropIndicatorWidth = useCallback(
Expand All @@ -80,21 +85,56 @@ export default function ListViewDropIndicator( {
'horizontal'
);

if ( scrollContainer ) {
const ownerDocument = targetElement.ownerDocument;
const windowScroll =
scrollContainer === ownerDocument.body ||
scrollContainer === ownerDocument.documentElement;

if ( scrollContainer && ! windowScroll ) {
const scrollContainerRect =
scrollContainer.getBoundingClientRect();

if ( scrollContainer.clientWidth < width ) {
const distanceBetweenContainerAndTarget = isRTL()
? scrollContainerRect.right - targetElementRect.right
: targetElementRect.left - scrollContainerRect.left;

const scrollContainerWidth = scrollContainer.clientWidth;

if (
scrollContainerWidth <
width + distanceBetweenContainerAndTarget
) {
width =
scrollContainer.clientWidth -
( targetElementRect.left - scrollContainerRect.left );
scrollContainerWidth -
distanceBetweenContainerAndTarget;
}

// LTR logic for ensuring the drop indicator does not extend
// beyond the right edge of the scroll container.
if (
! rtl &&
targetElementRect.left + indent < scrollContainerRect.left
) {
width -= scrollContainerRect.left - targetElementRect.left;
return width;
}

// RTL logic for ensuring the drop indicator does not extend
// beyond the right edge of the scroll container.
if (
rtl &&
targetElementRect.right - indent > scrollContainerRect.right
) {
width -=
targetElementRect.right - scrollContainerRect.right;
return width;
}
}

// Subtract the indent from the final width of the indicator.
return width - indent;
},
[ targetElement ]
[ rtl, targetElement ]
);

const style = useMemo( () => {
Expand All @@ -119,15 +159,59 @@ export default function ListViewDropIndicator( {
return undefined;
}

const ownerDocument = targetElement.ownerDocument;

return {
ownerDocument: targetElement.ownerDocument,
ownerDocument,
getBoundingClientRect() {
const rect = targetElement.getBoundingClientRect();
const indent = getDropIndicatorIndent( rect );
const left = rect.left + indent;
// In RTL languages, the drop indicator should be positioned
// to the left of the target element, with the width of the
// indicator determining the indent at the right edge of the
// target element. In LTR languages, the drop indicator should
// end at the right edge of the target element, with the indent
// added to the position of the left edge of the target element.
let left = rtl ? rect.left : rect.left + indent;
let top = 0;
let bottom = 0;

// In deeply nested lists, where a scrollbar is present,
// the width of the drop indicator should be the width of
// the visible area of the scroll container. Additionally,
// the left edge of the drop indicator line needs to be
// offset by the distance the left edge of the target element
// and the left edge of the scroll container. The ensures
// that the drop indicator position never breaks out of the
// visible area of the scroll container.
const scrollContainer = getScrollContainer(
targetElement,
'horizontal'
);

const windowScroll =
scrollContainer === ownerDocument.body ||
scrollContainer === ownerDocument.documentElement;

// If the scroll container is not the window, offset the left position, if need be.
if ( scrollContainer && ! windowScroll ) {
const scrollContainerRect =
scrollContainer.getBoundingClientRect();

// In RTL languages, a vertical scrollbar is present on the
// left edge of the scroll container. The width of the
// scrollbar needs to be accounted for when positioning the
// drop indicator.
const scrollbarWidth = rtl
? scrollContainer.offsetWidth -
scrollContainer.clientWidth
: 0;

if ( left < scrollContainerRect.left + scrollbarWidth ) {
left = scrollContainerRect.left + scrollbarWidth;
}
}

if ( dropPosition === 'top' ) {
top = rect.top;
bottom = rect.top;
Expand All @@ -148,6 +232,7 @@ export default function ListViewDropIndicator( {
dropPosition,
getDropIndicatorIndent,
getDropIndicatorWidth,
rtl,
] );

if ( ! targetElement ) {
Expand Down
Loading

0 comments on commit e4e558a

Please sign in to comment.