From 60a5d9300435dfb7ca4aa5fd3dff7b5d7d96520d Mon Sep 17 00:00:00 2001 From: Eric Ribeiro Date: Sat, 17 Apr 2021 16:43:15 -0700 Subject: [PATCH] fix: Fix automatic sort of `Composite` items in fixed containers --- .../__utils/useSortBasedOnDOMPosition.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/reakit/src/Composite/__utils/useSortBasedOnDOMPosition.ts b/packages/reakit/src/Composite/__utils/useSortBasedOnDOMPosition.ts index b308b909..771e6f73 100644 --- a/packages/reakit/src/Composite/__utils/useSortBasedOnDOMPosition.ts +++ b/packages/reakit/src/Composite/__utils/useSortBasedOnDOMPosition.ts @@ -1,16 +1,30 @@ import * as React from "react"; +import { getDocument } from "reakit-utils/getDocument"; import { sortBasedOnDOMPosition } from "./sortBasedOnDOMPosition"; import { Item } from "./types"; type SetItems = (items: Item[]) => void; -function setItemsBasedOnDOMPosition(items: Item[], setItems: SetItems) { +function mutateItemsBasedOnDOMPosition(items: Item[], setItems: SetItems) { const sortedItems = sortBasedOnDOMPosition(items); if (items !== sortedItems) { setItems(sortedItems); } } +function getCommonParent(items: Item[]) { + const [firstItem, ...nextItems] = items; + let parentElement = firstItem?.ref.current?.parentElement; + while (parentElement) { + const parent = parentElement; + if (nextItems.every((item) => parent.contains(item.ref.current))) { + return parentElement; + } + parentElement = parentElement.parentElement; + } + return getDocument(parentElement).body; +} + // istanbul ignore next: JSDOM doesn't support IntersectionObverser // See https://github.com/jsdom/jsdom/issues/2032 function useIntersectionObserver(items: Item[], setItems: SetItems) { @@ -21,13 +35,12 @@ function useIntersectionObserver(items: Item[], setItems: SetItems) { const hasPreviousItems = !!previousItems.current.length; // We don't want to sort items if items have been just registered. if (hasPreviousItems) { - setItemsBasedOnDOMPosition(items, setItems); + mutateItemsBasedOnDOMPosition(items, setItems); } previousItems.current = items; }; - const observer = new IntersectionObserver(callback, { - root: document.body, - }); + const root = getCommonParent(items); + const observer = new IntersectionObserver(callback, { root }); for (const item of items) { if (item.ref.current) { observer.observe(item.ref.current); @@ -41,7 +54,7 @@ function useIntersectionObserver(items: Item[], setItems: SetItems) { function useTimeoutObserver(items: Item[], setItems: SetItems) { React.useEffect(() => { - const callback = () => setItemsBasedOnDOMPosition(items, setItems); + const callback = () => mutateItemsBasedOnDOMPosition(items, setItems); const timeout = setTimeout(callback, 250); return () => clearTimeout(timeout); });