diff --git a/README.md b/README.md index ec539ca..4e58d47 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,12 @@ npm install vue-virtual-scroll-grid ## Available Props -| Name | Description | Type | Validation | -|----------------|---------------------------------------------------------------------------|----------------------------------------------------------------|-------------------------------------------------| -| `length` | The number of items in the list | `number` | Required, an integer greater than or equal to 0 | -| `pageProvider` | The callback that returns a page of items as a promise | `(pageNumber: number, pageSize: number) => Promise` | Required | -| `pageSize` | The number of items in a page from the item provider (e.g. a backend API) | `number` | Required, an integer greater than or equal to 1 | +| Name | Description | Type | Validation | +|----------------|---------------------------------------------------------------------------|----------------------------------------------------------------|---------------------------------------------------------------------------------| +| `length` | The number of items in the list | `number` | Required, an integer greater than or equal to 0 | +| `pageProvider` | The callback that returns a page of items as a promise | `(pageNumber: number, pageSize: number) => Promise` | Required | +| `pageSize` | The number of items in a page from the item provider (e.g. a backend API) | `number` | Required, an integer greater than or equal to 1 | +| `scrollTo` | Scroll to a specific item by index | `number` | Optional, an integer from 0 to the `length` prop - 1 | Example: @@ -41,6 +42,7 @@ Example: diff --git a/src/Grid.vue b/src/Grid.vue index 591a227..834c618 100644 --- a/src/Grid.vue +++ b/src/Grid.vue @@ -40,7 +40,7 @@ diff --git a/src/demo/Control.vue b/src/demo/Control.vue index 465e1d6..9809a9a 100644 --- a/src/demo/Control.vue +++ b/src/demo/Control.vue @@ -1,7 +1,7 @@ @@ -96,6 +108,7 @@ export default defineComponent({ grid-template: "length pageProvider" auto "pageSize pageProvider" auto + "scrollTo pageProvider" auto / 2fr 1fr; place-items: center stretch; grid-gap: 1.5rem; @@ -117,6 +130,10 @@ export default defineComponent({ justify-content: flex-start; } +.scrollTo { + grid-area: scrollTo; +} + .radioList { flex: 1 1 auto; display: flex; @@ -140,7 +157,16 @@ export default defineComponent({ width: 100%; } -.rangeLabel { +.number { + background-color: var(--color-rice); + width: 100%; + border: 1px solid var(--color-black); + padding: 5px; + font-size: 1.4rem; + color: var(--color-black); +} + +.label { margin-bottom: 0.5rem; font-weight: 700; } @@ -153,11 +179,8 @@ export default defineComponent({ @media (min-width: 760px) { .root { grid-template: - "length pageSize pageProvider" auto - / 1fr 1fr 1fr; - } - - .pageProvider { + "length pageSize pageProvider scrollTo" auto + / 2fr 2fr 2fr 1fr; } .category { diff --git a/src/demo/store.ts b/src/demo/store.ts index bf3a44c..0bbf441 100644 --- a/src/demo/store.ts +++ b/src/demo/store.ts @@ -4,6 +4,7 @@ import { curry, prop } from "ramda"; export const length = ref(1000); export const pageSize = ref(40); +export const scrollTo = ref(undefined); export type Collection = "" | "all-mens" | "womens-view-all"; export const collection = ref(""); diff --git a/src/pipeline.ts b/src/pipeline.ts index ac6a5c2..92eb174 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -1,24 +1,28 @@ -import { combineLatest, merge, Observable, range } from "rxjs"; +import { combineLatest, merge, Observable, of, range } from "rxjs"; import { distinct, distinctUntilChanged, + filter, map, mergeMap, scan, shareReplay, switchMap, + take, withLatestFrom, } from "rxjs/operators"; import { __, addIndex, apply, + complement, concat, difference, equals, identity, ifElse, insertAll, + isNil, map as ramdaMap, pipe, remove, @@ -221,11 +225,13 @@ interface PipelineInput { itemRect$: Observable; rootResize$: Observable; scroll$: Observable; + scrollTo$: Observable; } interface PipelineOutput { buffer$: Observable; contentHeight$: Observable; + windowScrollTo$: Observable; } export function pipeline({ @@ -235,6 +241,7 @@ export function pipeline({ itemRect$, rootResize$, scroll$, + scrollTo$, }: PipelineInput): PipelineOutput { // region: measurements of the visual grid const heightAboveWindow$: Observable = merge( @@ -253,6 +260,25 @@ export function pipeline({ ); // endregion + // region: scroll to a given item by index + const windowScrollTo$ = scrollTo$.pipe( + filter(complement(isNil)), + switchMap((scrollTo) => + combineLatest([of(scrollTo), resizeMeasurement$, rootResize$]).pipe( + take(1) + ) + ), + map( + ([scrollTo, { columns, itemHeightWithGap }, rootEl]) => + // The offset within the grid + Math.floor(scrollTo / columns) * itemHeightWithGap + + // The offset of grid root to the document + (rootEl.getBoundingClientRect().top + + document.documentElement.scrollTop) + ) + ); + // endregion + // region: rendering buffer const bufferMeta$: Observable = combineLatest( [heightAboveWindow$, resizeMeasurement$], @@ -285,5 +311,5 @@ export function pipeline({ ).pipe(scan(accumulateBuffer, [])); // endregion - return { buffer$, contentHeight$ }; + return { buffer$, contentHeight$, windowScrollTo$ }; }