Skip to content

Commit

Permalink
Support automatic start offset calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
inokawa committed Feb 9, 2024
1 parent 17bb493 commit 1e8a3f4
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 116 deletions.
85 changes: 48 additions & 37 deletions src/core/scroller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,39 @@ const normalizeOffset = (offset: number, isHorizontal: boolean): number => {
}
};

const calcOffsetToViewport = (
node: HTMLElement,
viewport: HTMLElement,
isHorizontal: boolean,
offset: number = 0
): number => {
// TODO calc offset only when it changes (maybe impossible)
const offsetSum =
offset +
(isHorizontal && isRTLDocument()
? viewport.offsetWidth - node.offsetLeft - node.offsetWidth
: node[isHorizontal ? "offsetLeft" : "offsetTop"]);

const parent = node.offsetParent;
if (node === viewport || !parent) {
return offsetSum;
}

return calcOffsetToViewport(
parent as HTMLElement,
viewport,
isHorizontal,
offsetSum
);
};

const createScrollObserver = (
store: VirtualStore,
viewport: HTMLElement | Window,
isHorizontal: boolean,
getScrollOffset: () => number,
updateScrollOffset: (value: number, isMomentumScrolling: boolean) => void
updateScrollOffset: (value: number, isMomentumScrolling: boolean) => void,
calcStartOffset: (() => number) | undefined
) => {
const now = Date.now;

Expand Down Expand Up @@ -69,7 +96,12 @@ const createScrollObserver = (
stillMomentumScrolling = true;
}

store._update(ACTION_SCROLL, getScrollOffset());
let offset = getScrollOffset();
if (calcStartOffset) {
offset -= calcStartOffset();
}

store._update(ACTION_SCROLL, offset);
onScrollEnd();
};

Expand Down Expand Up @@ -140,7 +172,10 @@ type ScrollObserver = ReturnType<typeof createScrollObserver>;
* @internal
*/
export type Scroller = {
_observe: (viewportElement: HTMLElement) => void;
_observe: (
viewportElement: HTMLElement,
containerElement: HTMLElement
) => void;
_dispose(): void;
_scrollTo: (offset: number) => void;
_scrollBy: (offset: number) => void;
Expand All @@ -153,7 +188,8 @@ export type Scroller = {
*/
export const createScroller = (
store: VirtualStore,
isHorizontal: boolean
isHorizontal: boolean,
unbound?: boolean
): Scroller => {
let viewportElement: HTMLElement | undefined;
let scrollObserver: ScrollObserver | undefined;
Expand Down Expand Up @@ -245,7 +281,7 @@ export const createScroller = (
};

return {
_observe(viewport) {
_observe(viewport, container) {
viewportElement = viewport;

scrollObserver = createScrollObserver(
Expand All @@ -268,7 +304,10 @@ export const createScroller = (
}

viewport[scrollOffsetKey] += normalizeOffset(jump, isHorizontal);
}
},
unbound
? () => calcOffsetToViewport(container, viewport, isHorizontal)
: undefined
);
},
_dispose() {
Expand Down Expand Up @@ -352,47 +391,19 @@ export const createWindowScroller = (
const window = getCurrentWindow(document);
const documentBody = document.body;

const calcOffsetToViewport = (
node: HTMLElement,
viewport: HTMLElement,
isHorizontal: boolean,
offset: number = 0
): number => {
// TODO calc offset only when it changes (maybe impossible)
const offsetKey = isHorizontal ? "offsetLeft" : "offsetTop";
const offsetSum =
offset +
(isHorizontal && isRTLDocument()
? window.innerWidth - node[offsetKey] - node.offsetWidth
: node[offsetKey]);

const parent = node.offsetParent;
if (node === viewport || !parent) {
return offsetSum;
}

return calcOffsetToViewport(
parent as HTMLElement,
viewport,
isHorizontal,
offsetSum
);
};

scrollObserver = createScrollObserver(
store,
window,
isHorizontal,
() =>
normalizeOffset(window[scrollOffsetKey], isHorizontal) -
calcOffsetToViewport(container, documentBody, isHorizontal),
() => normalizeOffset(window[scrollOffsetKey], isHorizontal),
(jump) => {
// TODO support case two window scrollers exist in the same view
window.scrollBy(
isHorizontal ? normalizeOffset(jump, isHorizontal) : 0,
isHorizontal ? 0 : jump
);
}
},
() => calcOffsetToViewport(container, documentBody, isHorizontal)
);
},
_dispose() {
Expand Down
24 changes: 0 additions & 24 deletions src/react/VGrid.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,30 +63,6 @@ it("should pass attributes to element", () => {
expect(asFragment()).toMatchSnapshot();
});

// it("should change components", () => {
// const UlList = forwardRef<HTMLDivElement, CustomViewportComponentProps>(
// ({ children, attrs, scrollSize }, ref) => {
// return (
// <div ref={ref} {...attrs}>
// <ul style={{ position: "relative", height: scrollSize, margin: 0 }}>
// {children}
// </ul>
// </div>
// );
// }
// );
// const { asFragment } = render(
// <VList components={{ Root: UlList, Item: "li" }}>
// <div>0</div>
// <div>1</div>
// <div>2</div>
// <div>3</div>
// <div>4</div>
// </VList>
// );
// expect(asFragment()).toMatchSnapshot();
// });

describe("grid", () => {
it("should render 1 children", () => {
const { asFragment } = render(
Expand Down
7 changes: 5 additions & 2 deletions src/react/VGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ViewportComponentAttributes } from "./types";
import { flushSync } from "react-dom";
import { isRTLDocument } from "../core/environment";
import { useRerender } from "./useRerender";

const genKey = (i: number, j: number) => `${i}-${j}`;

/**
Expand Down Expand Up @@ -241,9 +242,11 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
const height = getScrollSize(vStore);
const width = getScrollSize(hStore);
const rootRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);

useIsomorphicLayoutEffect(() => {
const root = rootRef[refKey]!;
const container = containerRef[refKey]!;
// store must be subscribed first because others may dispatch update on init depending on implementation
const unsubscribeVStore = vStore._subscribe(
UPDATE_SCROLL_STATE + UPDATE_SIZE_STATE,
Expand All @@ -266,8 +269,8 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
}
);
resizer._observeRoot(root);
vScroller._observe(root);
hScroller._observe(root);
vScroller._observe(root, container);
hScroller._observe(root, container);
return () => {
unsubscribeVStore();
unsubscribeHStore();
Expand Down
12 changes: 9 additions & 3 deletions src/react/Virtualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ export interface VirtualizerProps {
* You can restore cache by passing a {@link CacheSnapshot} on mount. This is useful when you want to restore scroll position after navigation. The snapshot can be obtained from {@link VirtualizerHandle.cache}.
*/
cache?: CacheSnapshot;
/**
* TODO
*/
unbound?: boolean;
/**
* If you put an element before virtualizer, you have to define its height with this prop.
*/
Expand Down Expand Up @@ -178,6 +182,7 @@ export const Virtualizer = forwardRef<VirtualizerHandle, VirtualizerProps>(
horizontal: horizontalProp,
reverse,
cache,
unbound,
startMargin,
endMargin,
ssrCount,
Expand Down Expand Up @@ -215,7 +220,7 @@ export const Virtualizer = forwardRef<VirtualizerHandle, VirtualizerProps>(
return [
_store,
createResizer(_store, _isHorizontal),
createScroller(_store, _isHorizontal),
createScroller(_store, _isHorizontal, unbound),
_isHorizontal,
];
});
Expand Down Expand Up @@ -261,15 +266,16 @@ export const Virtualizer = forwardRef<VirtualizerHandle, VirtualizerProps>(
onScrollEnd[refKey] && onScrollEnd[refKey]();
}
);
const container = containerRef[refKey]!;
const assignScrollableElement = (e: HTMLElement) => {
resizer._observeRoot(e);
scroller._observe(e);
scroller._observe(e, container);
};
if (scrollRef) {
// parent's ref doesn't exist when useLayoutEffect is called
microtask(() => assignScrollableElement(scrollRef[refKey]!));
} else {
assignScrollableElement(containerRef[refKey]!.parentElement!);
assignScrollableElement(container.parentElement!);
}

return () => {
Expand Down
2 changes: 1 addition & 1 deletion src/solid/Virtualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export const Virtualizer = <T,>(props: VirtualizerProps<T>): JSX.Element => {

const scrollable = containerRef!.parentElement!;
resizer._observeRoot(scrollable);
scroller._observe(scrollable);
scroller._observe(scrollable, containerRef!);

onCleanup(() => {
if (props.ref) {
Expand Down
5 changes: 3 additions & 2 deletions src/vue/Virtualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ export const Virtualizer = /*#__PURE__*/ defineComponent({
);

onMounted(() => {
const scrollable = containerRef.value!.parentElement;
const container = containerRef.value!;
const scrollable = container!.parentElement;
if (!scrollable) return;
resizer._observeRoot(scrollable);
scroller._observe(scrollable);
scroller._observe(scrollable, container);
});
onUnmounted(() => {
unsubscribeStore();
Expand Down
Loading

0 comments on commit 1e8a3f4

Please sign in to comment.