Skip to content

Commit

Permalink
feat(onScroll): add scrollBy and scrollIntoView methods
Browse files Browse the repository at this point in the history
close #640
  • Loading branch information
pikax committed Oct 31, 2020
1 parent 368e1f6 commit 3f6e559
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 47 deletions.
28 changes: 15 additions & 13 deletions docs/composable/event/onScroll.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ const scroll = useOnScroll(element, options?, wait?);

```
| Parameters | Type | Required | Default | Description |
| ---------- | ----------------------------------- | -------- | ----------------- | ----------------------------------------- |
| element | `Ref<Element>|Element` | `false` | `window` | DOM element used to track scroll position |
| options | `boolean | AddEventListenerOptions` | `false` | `{passive: true}` | Listener options |
| wait | `Number` | `false` | `undefined` | Debounce event in `ms` |
| Parameters | Type | Required | Default | Description |
| ---------- | ------------- | ------------------------ | ----------- | ---------------------- |
| element | `Ref<Element> | Element` | `false` | `window` | DOM element used to track scroll position |
| options | `boolean | AddEventListenerOptions` | `false` | `{passive: true}` | Listener options |
| wait | `Number` | `false` | `undefined` | Debounce event in `ms` |
::: tip
Expand Down Expand Up @@ -51,12 +51,14 @@ import { useOnScroll } from "vue-composable";
const { remove, scrollTo, scrollLeftTo, scrollTopTo } = useOnScroll();
```
| Signature | Description |
| -------------- | ------------------------------------------------------------------------------------------------------ |
| `remove` | Manually removes the event listener |
| `scrollTo` | Same as calling [element.scrollTo()](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo) |
| `scrollLeftTo` | Calls scrollTo with left argument |
| `scrollTopTo` | Calls scrollTo with top argument |
| Signature | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------------------- |
| `remove` | Manually removes the event listener |
| `scrollTo` | Same as calling [element.scrollTo()](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTo) |
| `scrollBy` | Same as calling [element.scrollBy()](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollBy) |
| `scrollIntoView` | Same as calling [element.scrollIntoView()](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) |
| `scrollLeftTo` | Calls scrollTo with left argument |
| `scrollTopTo` | Calls scrollTo with top argument |
## Example
Expand Down Expand Up @@ -94,9 +96,9 @@ export default {
elref,
scrollTop,
scrollLeft,
remove
remove,
};
}
},
};
</script>
```
86 changes: 64 additions & 22 deletions packages/vue-composable/__tests__/event/onScroll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe("onScroll", () => {
removeEventListener: jest.fn(),
scrollTop: 0,
scrollLeft: 0,
tagName: "div"
tagName: "div",
} as any;
let handler: ((ev: Partial<MouseEvent>) => void) | undefined = undefined;
let use: ScrollResult | undefined = undefined;
Expand All @@ -21,14 +21,14 @@ describe("onScroll", () => {
template: "<div></div>",
setup() {
use = useOnScroll(element);
}
},
}).mount();

expect(element.addEventListener).toHaveBeenCalled();

expect(use).toMatchObject({
scrollTop: { value: 0 },
scrollLeft: { value: 0 }
scrollLeft: { value: 0 },
});

(element as any).scrollTop = 50;
Expand All @@ -39,7 +39,7 @@ describe("onScroll", () => {

expect(use).toMatchObject({
scrollTop: { value: 50 },
scrollLeft: { value: 50 }
scrollLeft: { value: 50 },
});
});

Expand All @@ -49,15 +49,15 @@ describe("onScroll", () => {
removeEventListener: jest.fn(),
scrollTop: 0,
scrollLeft: 0,
tagName: "div"
tagName: "div",
} as any;
let use: ScrollResult | undefined = undefined;

createVue({
template: "<div></div>",
setup() {
use = useOnScroll(element);
}
},
}).mount();
expect(element.removeEventListener).not.toHaveBeenCalled();

Expand All @@ -75,7 +75,7 @@ describe("onScroll", () => {
removeEventListener: jest.fn(),
scrollTop: 0,
scrollLeft: 0,
tagName: "div"
tagName: "div",
} as any;
let use: ScrollResult | undefined = undefined;
let handler: ((ev: Partial<MouseEvent>) => void) | undefined = undefined;
Expand All @@ -85,7 +85,7 @@ describe("onScroll", () => {
template: "<div></div>",
setup() {
use = useOnScroll(element, wait);
}
},
}).mount();
expect(element.addEventListener).toHaveBeenCalled();

Expand All @@ -101,13 +101,13 @@ describe("onScroll", () => {
// still waiting to set the values
expect(use).toMatchObject({
scrollTop: { value: 0 },
scrollLeft: { value: 0 }
scrollLeft: { value: 0 },
});

await promisedTimeout(wait);
expect(use).toMatchObject({
scrollTop: { value: 19 },
scrollLeft: { value: 19 }
scrollLeft: { value: 19 },
});
});

Expand All @@ -117,17 +117,17 @@ describe("onScroll", () => {
removeEventListener: jest.fn(),
scrollTop: 0,
scrollLeft: 0,
tagName: "div"
tagName: "div",
} as any;
const options = {
passive: true
passive: true,
};

createVue({
template: "<div></div>",
setup() {
return useOnScroll(element, options);
}
},
}).mount();
expect(element.addEventListener).toHaveBeenCalledWith(
"scroll",
Expand All @@ -145,20 +145,20 @@ describe("onScroll", () => {
removeEventListener: jest.fn(),
scrollTop: 0,
scrollLeft: 0,
tagName: "div"
tagName: "div",
} as any;
let use: ScrollResult | undefined = undefined;
let handler: ((ev: Partial<MouseEvent>) => void) | undefined = undefined;
const wait = 50;
const options = {
passive: true
passive: true,
};

createVue({
template: "<div></div>",
setup() {
use = useOnScroll(element, options, wait);
}
},
}).mount();
expect(element.addEventListener).toHaveBeenCalledWith(
"scroll",
Expand All @@ -178,21 +178,21 @@ describe("onScroll", () => {
// still waiting to set the values
expect(use).toMatchObject({
scrollTop: { value: 0 },
scrollLeft: { value: 0 }
scrollLeft: { value: 0 },
});

await promisedTimeout(wait);
expect(use).toMatchObject({
scrollTop: { value: 19 },
scrollLeft: { value: 19 }
scrollLeft: { value: 19 },
});
});

it("should use window if no element is passed", async () => {
const element = ((window.document.scrollingElement as any) = ({
scrollTop: 0,
scrollLeft: 0,
tagName: "div"
tagName: "div",
} as any) as Element);

(window as any).addEventListener = jest
Expand All @@ -210,7 +210,7 @@ describe("onScroll", () => {
template: "<div></div>",
setup() {
use = useOnScroll(wait);
}
},
}).mount();
expect(window.addEventListener).toHaveBeenCalled();

Expand All @@ -226,13 +226,55 @@ describe("onScroll", () => {
// still waiting to set the values
expect(use).toMatchObject({
scrollTop: { value: 0 },
scrollLeft: { value: 0 }
scrollLeft: { value: 0 },
});

await promisedTimeout(wait);
expect(use).toMatchObject({
scrollTop: { value: 19 },
scrollLeft: { value: 19 }
scrollLeft: { value: 19 },
});
});

describe("methods", () => {
const methods = ["scrollBy", "scrollTo", "scrollIntoView"];
const element = document.createElement("div");
methods.forEach((m) => {
// @ts-ignore
element[m] = jest.fn();
});

const prevMethods = {};

beforeEach(() => {
methods.forEach((m) => {
//@ts-ignore
element[m].mockClear();
});
});

afterAll(() => {});

afterEach(() => {
methods.forEach((m) => {
//@ts-ignore
window[m] = prevMethods[m];
});
});

test.each(methods)("call %s", (method) => {
const args = {};
const use = useOnScroll(element);
// @ts-ignore
use[method](args);
//@ts-ignore
expect(element[method]).toHaveBeenCalledTimes(1);
});

test.each(methods)("not fail if %s doesnt exist", (method) => {
const use = useOnScroll({});
// @ts-ignore
use[method]();
});
});
});
41 changes: 29 additions & 12 deletions packages/vue-composable/src/event/onScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,29 @@ import {
PASSIVE_EV,
isBoolean,
isElement,
isClient
isClient,
} from "../utils";
import { useEvent, RemoveEventFunction } from "./event";
import { useDebounce } from "../debounce";

const SCROLL_METHODS = ["scrollBy", "scrollTo", "scrollIntoView"];
interface ScrollMethods {
scrollBy: Element["scrollBy"];
scrollTo: Element["scrollTo"];
scrollIntoView: Element["scrollIntoView"];
}

export interface ScrollResult {
scrollTop: Ref<number>;
scrollLeft: Ref<number>;
remove: RemoveEventFunction;

scrollTo: Element["scrollTo"];
scrollTopTo: (y: number) => void;
scrollLeftTo: (x: number) => void;

scrollTo: Element["scrollTo"];
scrollBy: Element["scrollBy"];
scrollIntoView: Element["scrollIntoView"];
}

export function useOnScroll(): ScrollResult;
Expand Down Expand Up @@ -81,13 +91,20 @@ export function useOnScroll(
scrollLeft.value = scrollableElement.value!.scrollLeft;
};

const scrollTo: Element["scrollTo"] = (...args: any) =>
scrollableElement.value &&
scrollableElement.value.scrollTo &&
scrollableElement.value.scrollTo.apply(scrollableElement.value, args);

const scrollTopTo = (top: number) => scrollTo({ top });
const scrollLeftTo = (left: number) => scrollTo({ left });
const methods = SCROLL_METHODS.reduce((p, c) => {
//@ts-ignore
p[c] = (...args: any) =>
//@ts-ignore
scrollableElement.value &&
//@ts-ignore
scrollableElement.value[c] &&
//@ts-ignore
scrollableElement.value[c].apply(scrollableElement.value, args);
return p;
}, {}) as ScrollMethods;

const scrollTopTo = (top: number) => methods.scrollTo({ top });
const scrollLeftTo = (left: number) => methods.scrollTo({ left });

const [eventOptions, ms] =
isNumber(el) || !el
Expand All @@ -113,10 +130,10 @@ export function useOnScroll(
scrollTop,
scrollLeft,

scrollTo,

remove,
scrollTopTo,
scrollLeftTo
scrollLeftTo,

...methods,
};
}

0 comments on commit 3f6e559

Please sign in to comment.