mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
web: replace react-virtual with react-virtuouso
This commit is contained in:
19
apps/web/package-lock.json
generated
19
apps/web/package-lock.json
generated
@@ -33,7 +33,7 @@
|
|||||||
"@react-pdf-viewer/core": "^3.12.0",
|
"@react-pdf-viewer/core": "^3.12.0",
|
||||||
"@react-pdf-viewer/toolbar": "^3.12.0",
|
"@react-pdf-viewer/toolbar": "^3.12.0",
|
||||||
"@tanstack/react-query": "^4.29.19",
|
"@tanstack/react-query": "^4.29.19",
|
||||||
"@tanstack/react-virtual": "^3.0.0-beta.68",
|
"@tanstack/react-virtual": "^3.0.1",
|
||||||
"@theme-ui/color": "^0.16.1",
|
"@theme-ui/color": "^0.16.1",
|
||||||
"@theme-ui/components": "^0.16.1",
|
"@theme-ui/components": "^0.16.1",
|
||||||
"@theme-ui/core": "^0.16.1",
|
"@theme-ui/core": "^0.16.1",
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
"react-modal": "3.16.1",
|
"react-modal": "3.16.1",
|
||||||
"react-qrcode-logo": "^2.9.0",
|
"react-qrcode-logo": "^2.9.0",
|
||||||
"react-scroll-sync": "^0.11.2",
|
"react-scroll-sync": "^0.11.2",
|
||||||
|
"react-virtuoso": "^4.6.2",
|
||||||
"timeago.js": "4.0.2",
|
"timeago.js": "4.0.2",
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
"w3c-keyname": "^2.2.6",
|
"w3c-keyname": "^2.2.6",
|
||||||
@@ -37715,6 +37716,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@tanstack/react-virtual": {
|
"node_modules/@tanstack/react-virtual": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-IFOFuRUTaiM/yibty9qQ9BfycQnYXIDHGP2+cU+0LrFFGNhVxCXSQnaY6wkX8uJVteFEBjUondX0Hmpp7TNcag==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/virtual-core": "3.1.3"
|
"@tanstack/virtual-core": "3.1.3"
|
||||||
@@ -37730,6 +37733,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@tanstack/virtual-core": {
|
"node_modules/@tanstack/virtual-core": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -43449,6 +43454,18 @@
|
|||||||
"react-dom": "16.x || 17.x || 18.x"
|
"react-dom": "16.x || 17.x || 18.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-virtuoso": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-vvlqvzPif+MvBrJ09+hJJrVY0xJK9yran+A+/1iwY78k0YCVKsyoNPqoLxOxzYPggspNBNXqUXEcvckN29OxyQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16 || >=17 || >= 18",
|
||||||
|
"react-dom": ">=16 || >=17 || >= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
"@react-pdf-viewer/core": "^3.12.0",
|
"@react-pdf-viewer/core": "^3.12.0",
|
||||||
"@react-pdf-viewer/toolbar": "^3.12.0",
|
"@react-pdf-viewer/toolbar": "^3.12.0",
|
||||||
"@tanstack/react-query": "^4.29.19",
|
"@tanstack/react-query": "^4.29.19",
|
||||||
"@tanstack/react-virtual": "^3.0.0-beta.68",
|
"@tanstack/react-virtual": "^3.0.1",
|
||||||
"@theme-ui/color": "^0.16.1",
|
"@theme-ui/color": "^0.16.1",
|
||||||
"@theme-ui/components": "^0.16.1",
|
"@theme-ui/components": "^0.16.1",
|
||||||
"@theme-ui/core": "^0.16.1",
|
"@theme-ui/core": "^0.16.1",
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
"react-modal": "3.16.1",
|
"react-modal": "3.16.1",
|
||||||
"react-qrcode-logo": "^2.9.0",
|
"react-qrcode-logo": "^2.9.0",
|
||||||
"react-scroll-sync": "^0.11.2",
|
"react-scroll-sync": "^0.11.2",
|
||||||
|
"react-virtuoso": "^4.6.2",
|
||||||
"timeago.js": "4.0.2",
|
"timeago.js": "4.0.2",
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
"w3c-keyname": "^2.2.6",
|
"w3c-keyname": "^2.2.6",
|
||||||
|
|||||||
@@ -17,18 +17,18 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||||
import { Flex, Button } from "@theme-ui/components";
|
import { Flex, Button, Box } from "@theme-ui/components";
|
||||||
import { Plus } from "../icons";
|
import { Plus } from "../icons";
|
||||||
import {
|
import {
|
||||||
useStore as useSelectionStore,
|
useStore as useSelectionStore,
|
||||||
store as selectionStore
|
store as selectionStore
|
||||||
} from "../../stores/selection-store";
|
} from "../../stores/selection-store";
|
||||||
import GroupHeader from "../group-header";
|
import GroupHeader from "../group-header";
|
||||||
import { ListItemWrapper } from "./list-profiles";
|
import { DEFAULT_ITEM_HEIGHT, ListItemWrapper } from "./list-profiles";
|
||||||
import Announcements from "../announcements";
|
import Announcements from "../announcements";
|
||||||
import { ListLoader } from "../loaders/list-loader";
|
import { ListLoader } from "../loaders/list-loader";
|
||||||
import { FlexScrollContainer } from "../scroll-container";
|
import ScrollContainer from "../scroll-container";
|
||||||
import { useKeyboardListNavigation } from "../../hooks/use-keyboard-list-navigation";
|
import { useKeyboardListNavigation } from "../../hooks/use-keyboard-list-navigation";
|
||||||
import { Context } from "./types";
|
import { Context } from "./types";
|
||||||
import {
|
import {
|
||||||
@@ -37,9 +37,29 @@ import {
|
|||||||
Item,
|
Item,
|
||||||
isGroupHeader
|
isGroupHeader
|
||||||
} from "@notesnook/core";
|
} from "@notesnook/core";
|
||||||
import { VirtualizedList } from "../virtualized-list";
|
|
||||||
import { ScrollToOptions, Virtualizer } from "@tanstack/react-virtual";
|
|
||||||
import { useResolvedItem } from "./resolved-item";
|
import { useResolvedItem } from "./resolved-item";
|
||||||
|
import {
|
||||||
|
ItemProps,
|
||||||
|
ScrollerProps,
|
||||||
|
Virtuoso,
|
||||||
|
VirtuosoHandle
|
||||||
|
} from "react-virtuoso";
|
||||||
|
import Skeleton from "react-loading-skeleton";
|
||||||
|
|
||||||
|
export const CustomScrollbarsVirtualList = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ScrollerProps
|
||||||
|
>(function CustomScrollbarsVirtualList(props, ref) {
|
||||||
|
return (
|
||||||
|
<ScrollContainer
|
||||||
|
{...props}
|
||||||
|
forwardedRef={(sRef) => {
|
||||||
|
if (typeof ref === "function") ref(sRef);
|
||||||
|
else if (ref) ref.current = sRef;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
type ListContainerProps = {
|
type ListContainerProps = {
|
||||||
group?: GroupingKey;
|
group?: GroupingKey;
|
||||||
@@ -68,7 +88,8 @@ function ListContainer(props: ListContainerProps) {
|
|||||||
(store) => store.toggleSelectionMode
|
(store) => store.toggleSelectionMode
|
||||||
);
|
);
|
||||||
|
|
||||||
const listRef = useRef<Virtualizer<Element, Element>>();
|
const listRef = useRef<VirtuosoHandle>(null);
|
||||||
|
const listContainerRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -119,25 +140,24 @@ function ListContainer(props: ListContainerProps) {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<FlexScrollContainer
|
<Flex
|
||||||
style={{ display: "flex", flexDirection: "column", flex: 1 }}
|
ref={listContainerRef}
|
||||||
|
variant="columnFill"
|
||||||
data-test-id={`${group}-list`}
|
data-test-id={`${group}-list`}
|
||||||
>
|
>
|
||||||
{header ? header : <Announcements />}
|
<Virtuoso
|
||||||
<VirtualizedList
|
ref={listRef}
|
||||||
virtualizerRef={listRef}
|
computeItemKey={(index) => items.getKey(index)}
|
||||||
estimatedSize={50}
|
defaultItemHeight={DEFAULT_ITEM_HEIGHT}
|
||||||
getItemKey={(index) => items.getKey(index)}
|
totalCount={items.ids.length}
|
||||||
items={items.ids}
|
|
||||||
mode="dynamic"
|
|
||||||
tabIndex={-1}
|
|
||||||
overscan={10}
|
|
||||||
onBlur={() => setFocusedGroupIndex(-1)}
|
onBlur={() => setFocusedGroupIndex(-1)}
|
||||||
onKeyDown={(e) => onKeyDown(e.nativeEvent)}
|
onKeyDown={(e) => onKeyDown(e.nativeEvent)}
|
||||||
itemWrapperProps={(_, index) => ({
|
components={{
|
||||||
onFocus: () => onFocus(index),
|
Scroller: CustomScrollbarsVirtualList,
|
||||||
onMouseDown: (e) => onMouseDown(e.nativeEvent, index)
|
Item: VirtuosoItem,
|
||||||
})}
|
Header: () => (header ? header : <Announcements />)
|
||||||
|
}}
|
||||||
|
increaseViewportBy={{ top: 10, bottom: 10 }}
|
||||||
context={{
|
context={{
|
||||||
items,
|
items,
|
||||||
group,
|
group,
|
||||||
@@ -147,11 +167,15 @@ function ListContainer(props: ListContainerProps) {
|
|||||||
scrollToIndex: listRef.current?.scrollToIndex,
|
scrollToIndex: listRef.current?.scrollToIndex,
|
||||||
focusGroup: setFocusedGroupIndex,
|
focusGroup: setFocusedGroupIndex,
|
||||||
context,
|
context,
|
||||||
compact
|
compact,
|
||||||
|
onMouseDown,
|
||||||
|
onFocus
|
||||||
}}
|
}}
|
||||||
renderItem={ItemRenderer}
|
itemContent={(index, _data, context) => (
|
||||||
|
<ItemRenderer context={context} index={index} />
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</FlexScrollContainer>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{button && (
|
{button && (
|
||||||
@@ -194,6 +218,9 @@ type ListContext = {
|
|||||||
focusGroup: (index: number) => void;
|
focusGroup: (index: number) => void;
|
||||||
context?: Context;
|
context?: Context;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
|
|
||||||
|
onMouseDown: (e: MouseEvent, itemIndex: number) => void;
|
||||||
|
onFocus: (itemIndex: number) => void;
|
||||||
};
|
};
|
||||||
function ItemRenderer({
|
function ItemRenderer({
|
||||||
index,
|
index,
|
||||||
@@ -214,7 +241,37 @@ function ItemRenderer({
|
|||||||
compact
|
compact
|
||||||
} = context;
|
} = context;
|
||||||
const resolvedItem = useResolvedItem({ index, items });
|
const resolvedItem = useResolvedItem({ index, items });
|
||||||
if (!resolvedItem) return <div style={{ height: 50, width: "100%" }}></div>;
|
if (!resolvedItem || !resolvedItem.item)
|
||||||
|
return (
|
||||||
|
<Box key="list-item-skeleton" sx={{ py: 2, px: 1 }}>
|
||||||
|
<Skeleton
|
||||||
|
enableAnimation={false}
|
||||||
|
height={16}
|
||||||
|
width={`50%`}
|
||||||
|
style={{ marginBottom: 5 }}
|
||||||
|
/>
|
||||||
|
<Skeleton height={12} count={2} />
|
||||||
|
<Flex>
|
||||||
|
<Skeleton enableAnimation={false} height={10} inline width={50} />
|
||||||
|
<Skeleton
|
||||||
|
enableAnimation={false}
|
||||||
|
height={10}
|
||||||
|
inline
|
||||||
|
width={10}
|
||||||
|
circle
|
||||||
|
style={{ marginLeft: 5 }}
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
enableAnimation={false}
|
||||||
|
height={10}
|
||||||
|
inline
|
||||||
|
width={10}
|
||||||
|
circle
|
||||||
|
style={{ marginLeft: 5 }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -241,7 +298,7 @@ function ItemRenderer({
|
|||||||
groups={async () => (items.groups ? items.groups() : [])}
|
groups={async () => (items.groups ? items.groups() : [])}
|
||||||
onJump={(index) => {
|
onJump={(index) => {
|
||||||
scrollToIndex?.(index, {
|
scrollToIndex?.(index, {
|
||||||
align: "center",
|
// align: "center",
|
||||||
behavior: "auto"
|
behavior: "auto"
|
||||||
});
|
});
|
||||||
focusGroup(index);
|
focusGroup(index);
|
||||||
@@ -260,6 +317,26 @@ function ItemRenderer({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function VirtuosoItem({
|
||||||
|
item: _item,
|
||||||
|
context,
|
||||||
|
...props
|
||||||
|
}: ItemProps<string> & {
|
||||||
|
context?: ListContext;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
onFocus={() => context?.onFocus(props["data-item-index"])}
|
||||||
|
onMouseDown={(e) =>
|
||||||
|
context?.onMouseDown(e.nativeEvent, props["data-item-index"])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll the element at the specified index into view and
|
* Scroll the element at the specified index into view and
|
||||||
* wait until it renders into the DOM. This function keeps
|
* wait until it renders into the DOM. This function keeps
|
||||||
@@ -268,28 +345,29 @@ function ItemRenderer({
|
|||||||
* 50ms interval.
|
* 50ms interval.
|
||||||
*/
|
*/
|
||||||
function waitForElement(
|
function waitForElement(
|
||||||
list: Virtualizer<Element, Element>,
|
list: VirtuosoHandle,
|
||||||
index: number,
|
index: number,
|
||||||
elementId: string,
|
elementId: string,
|
||||||
callback: (element: HTMLElement) => void
|
callback: (element: HTMLElement) => void
|
||||||
) {
|
) {
|
||||||
let waitInterval = 0;
|
let waitInterval = 0;
|
||||||
let maxAttempts = 3;
|
let maxAttempts = 3;
|
||||||
list.scrollToIndex(index);
|
list.scrollIntoView({
|
||||||
function scrollDone() {
|
index,
|
||||||
if (!maxAttempts) return;
|
done: function scrollDone() {
|
||||||
clearTimeout(waitInterval);
|
if (!maxAttempts) return;
|
||||||
|
clearTimeout(waitInterval);
|
||||||
|
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
--maxAttempts;
|
--maxAttempts;
|
||||||
waitInterval = setTimeout(() => {
|
waitInterval = setTimeout(() => {
|
||||||
scrollDone();
|
scrollDone();
|
||||||
}, 50) as unknown as number;
|
}, 50) as unknown as number;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(element);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
callback(element);
|
|
||||||
}
|
|
||||||
scrollDone();
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user