Skip to content
This repository was archived by the owner on Nov 5, 2023. It is now read-only.

Commit 6d9b9b6

Browse files
committed
feat: add prop respectScrollToOnResize
1 parent 7db1339 commit 6d9b9b6

File tree

6 files changed

+89
-56
lines changed

6 files changed

+89
-56
lines changed

README.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,17 @@ npm install vue-virtual-scroll-grid
3030

3131
## Available Props
3232

33-
| Name | Description | Type | Validation |
34-
|----------------------------|-----------------------------------------------------------------------------------|----------------------------------------------------------------|------------------------------------------------------|
35-
| `tag` | The HTML tag used as container element. Default value is `div` | `string` | Any valid HTML tag |
36-
| `probeTag` | The HTML tag used as probe element. Default value is `div` | `string` | Any valid HTML tag |
37-
| `length` | The number of items in the list | `number` | Required, an integer greater than or equal to 0 |
38-
| `pageProvider` | The callback that returns a page of items as a promise. `pageNumber` start with 0 | `(pageNumber: number, pageSize: number) => Promise<unknown[]>` | Required |
39-
| `pageProviderDebounceTime` | Debounce window in milliseconds on the calls to `pageProvider` | `number` | Optional, an integer greater than or equal to 0 |
40-
| `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 |
41-
| `scrollTo` | Scroll to a specific item by index | `number` | Optional, an integer from 0 to the `length` prop - 1 |
42-
| `scrollBehavior` | The behavior of `scrollTo`. Default value is `smooth` | `smooth` &#124; `auto` | Optional, a string to be `smooth` or `auto` |
33+
| Name | Description | Type | Validation |
34+
| -------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------- |
35+
| `length` | The number of items in the list | `number` | Required, an integer greater than or equal to 0 |
36+
| `pageProvider` | The callback that returns a page of items as a promise. `pageNumber` start with 0 | `(pageNumber: number, pageSize: number) => Promise<unknown[]>` | Required |
37+
| `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 |
38+
| `pageProviderDebounceTime` | Debounce window in milliseconds on the calls to `pageProvider` | `number` | Optional, an integer greater than or equal to 0, defaults to `0` |
39+
| `probeTag` | The HTML tag used as probe element. Default value is `div` | `string` | Optional, any valid HTML tag, defaults to `div` |
40+
| `respectScrollToOnResize` | Snap to the position set by `scrollTo` when the grid container is resized | `boolean` | Optional, defaults to `false` |
41+
| `scrollBehavior` | The behavior of `scrollTo`. Default value is `smooth` | `smooth` &#124; `auto` | Optional, a string to be `smooth` or `auto`, defaults to `smooth` |
42+
| `scrollTo` | Scroll to a specific item by index | `number` | Optional, an integer from 0 to the `length` prop - 1, defaults to 0 |
43+
| `tag` | The HTML tag used as container element. Default value is `div` | `string` | Optional, any valid HTML tag, defaults to `div` |
4344

4445
Example:
4546

@@ -50,7 +51,7 @@ Example:
5051
:pageSize="40"
5152
:scrollTo="10"
5253
>
53-
<!-- ...slots -->
54+
<!-- ...slots -->
5455
</Grid>
5556
```
5657

src/Grid.vue

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ export default defineComponent({
9090
required: false,
9191
validator: (value: number) => Number.isInteger(value) && value >= 0,
9292
},
93+
// Snap to `scrollTo` when the grid container is resized
94+
respectScrollToOnResize: {
95+
type: Boolean as PropType<boolean>,
96+
required: false,
97+
default: true,
98+
},
9399
scrollBehavior: {
94100
type: String as PropType<"smooth" | "auto">,
95101
required: false,
@@ -129,13 +135,14 @@ export default defineComponent({
129135
rootResize$: fromResizeObserver(rootRef, "target"),
130136
// a stream of root elements when scrolling
131137
scroll$: fromWindowScroll(rootRef),
138+
respectScrollToOnResize$: fromProp(props, "respectScrollToOnResize"),
132139
scrollTo$: fromProp(props, "scrollTo"),
133140
});
134141
135142
onUpdated(
136143
once(() => {
137-
scrollAction$.subscribe(({ target, offset }: ScrollAction) => {
138-
target.scrollTo({ ...offset, behavior: props.scrollBehavior });
144+
scrollAction$.subscribe(({ target, top, left }: ScrollAction) => {
145+
target.scrollTo({ top, left, behavior: props.scrollBehavior });
139146
});
140147
})
141148
);

src/demo/App.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
:pageProviderDebounceTime="0"
1111
:scrollTo="scrollTo"
1212
:scrollBehavior="scrollBehavior"
13+
:respectScrollToOnResize="respectScrollToOnResize"
1314
:class="[$style.grid, $style[scrollMode]]"
1415
>
1516
<template v-slot:probe>
@@ -56,6 +57,7 @@ import {
5657
scrollMode,
5758
scrollTo,
5859
scrollBehavior,
60+
respectScrollToOnResize,
5961
} from "./store";
6062
6163
export default defineComponent({
@@ -68,6 +70,7 @@ export default defineComponent({
6870
scrollMode,
6971
scrollTo,
7072
scrollBehavior,
73+
respectScrollToOnResize,
7174
}),
7275
});
7376
</script>

src/demo/Control.vue

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@
108108
/>
109109
</div>
110110

111+
<div :class="$style.respectScrollToOnResize">
112+
<label for="respectScrollToOnResize" :class="$style.radioLabel">
113+
<input
114+
type="checkbox"
115+
id="respectScrollToOnResize"
116+
v-model="respectScrollToOnResize"
117+
:class="$style.radio"
118+
/>
119+
Snap to "Scroll To" on resizing
120+
</label>
121+
</div>
122+
111123
<div :class="$style.scrollBehavior">
112124
<p :class="$style.category">Scroll Behavior:</p>
113125

@@ -147,6 +159,7 @@ import {
147159
scrollMode,
148160
scrollTo,
149161
scrollBehavior,
162+
respectScrollToOnResize,
150163
} from "./store";
151164
152165
export default defineComponent({
@@ -159,6 +172,7 @@ export default defineComponent({
159172
scrollMode,
160173
scrollTo,
161174
scrollBehavior,
175+
respectScrollToOnResize,
162176
};
163177
},
164178
});
@@ -176,34 +190,34 @@ export default defineComponent({
176190
177191
display: grid;
178192
grid-template:
179-
"length scrollMode pageProvider" auto
180-
"pageSize scrollMode pageProvider" auto
181-
"scrollTo scrollMode pageProvider" auto
182-
"scrollBehavior scrollMode pageProvider" auto
183-
/ 2fr 1fr 1fr;
184-
place-items: center stretch;
185-
grid-gap: 1.5rem;
193+
"length pageSize" auto
194+
"pageProvider scrollTo" auto
195+
"scrollMode scrollBehavior" auto
196+
"respectScrollTo respectScrollTo" auto
197+
/ 1fr 1fr;
198+
place-items: start;
199+
grid-gap: 1rem;
186200
}
187201
188202
.length {
189203
grid-area: length;
204+
justify-self: stretch;
190205
}
191206
192207
.pageSize {
193208
grid-area: pageSize;
209+
justify-self: stretch;
194210
}
195211
196212
.pageProvider {
197213
grid-area: pageProvider;
198-
place-self: stretch;
199214
display: flex;
200215
flex-flow: column nowrap;
201216
justify-content: flex-start;
202217
}
203218
204219
.scrollMode {
205220
grid-area: scrollMode;
206-
place-self: stretch;
207221
display: flex;
208222
flex-flow: column nowrap;
209223
justify-content: flex-start;
@@ -215,16 +229,19 @@ export default defineComponent({
215229
216230
.scrollBehavior {
217231
grid-area: scrollBehavior;
218-
place-self: stretch;
219232
display: flex;
220233
flex-flow: column nowrap;
221234
justify-content: flex-start;
222235
}
223236
237+
.respectScrollToOnResize {
238+
grid-area: respectScrollTo;
239+
}
240+
224241
.radioList {
225242
flex: 1 1 auto;
226243
display: flex;
227-
flex-flow: column nowrap;
244+
flex-flow: row nowrap;
228245
justify-content: space-between;
229246
}
230247
@@ -238,6 +255,7 @@ export default defineComponent({
238255
239256
.radioLabel {
240257
display: inline;
258+
margin-right: 1rem;
241259
}
242260
243261
.range {
@@ -266,8 +284,9 @@ export default defineComponent({
266284
@media (min-width: 760px) {
267285
.root {
268286
grid-template:
269-
"length pageSize pageProvider scrollMode scrollTo scrollBehavior" auto
270-
/ 2fr 2fr 2fr 2fr 1fr 1fr;
287+
"length pageSize pageProvider scrollMode scrollTo respectScrollTo scrollBehavior" auto
288+
/ 3fr 3fr 3fr 3fr 1fr 3fr 2fr;
289+
grid-gap: 1.5rem;
271290
}
272291
273292
.category {
@@ -282,5 +301,9 @@ export default defineComponent({
282301
.radioLabel {
283302
margin-right: 2rem;
284303
}
304+
305+
.respectScrollToOnResize {
306+
place-self: center;
307+
}
285308
}
286309
</style>

src/demo/store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { curry, prop } from "ramda";
55
export const length = ref<number>(1000);
66
export const pageSize = ref<number>(40);
77
export const scrollTo = ref<number | undefined>(undefined);
8+
export const respectScrollToOnResize = ref<boolean>(false);
89

910
export type ScrollBehavior = "smooth" | "auto";
1011
export const scrollBehavior = ref<ScrollBehavior>("smooth");

src/pipeline.ts

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ import {
55
filter,
66
map,
77
merge,
8-
mergeAll,
98
mergeMap,
109
Observable,
11-
of,
1210
range,
1311
scan,
1412
shareReplay,
1513
switchMap,
16-
take,
14+
withLatestFrom,
1715
} from "rxjs";
1816
import {
1917
__,
@@ -320,17 +318,14 @@ interface PipelineInput {
320318
itemRect$: Observable<DOMRectReadOnly>;
321319
rootResize$: Observable<Element>;
322320
scroll$: Observable<Element>;
321+
respectScrollToOnResize$: Observable<boolean>;
323322
scrollTo$: Observable<number | undefined>;
324323
}
325324

326-
interface ScrollOffset {
327-
left?: number;
328-
top?: number;
329-
}
330-
331325
export type ScrollAction = {
332326
target: Element;
333-
offset: ScrollOffset;
327+
top: number;
328+
left: number;
334329
};
335330

336331
interface PipelineOutput {
@@ -347,6 +342,7 @@ export function pipeline({
347342
itemRect$,
348343
rootResize$,
349344
scroll$,
345+
respectScrollToOnResize$,
350346
scrollTo$,
351347
}: PipelineInput): PipelineOutput {
352348
// region: measurements of the visual grid
@@ -370,12 +366,24 @@ export function pipeline({
370366
const scrollToNotNil$: Observable<number> = scrollTo$.pipe(
371367
filter(complement(isNil))
372368
);
373-
const scrollAction$: Observable<ScrollAction> = combineLatest([
374-
scrollToNotNil$,
375-
resizeMeasurement$,
376-
rootResize$,
377-
]).pipe(
378-
mergeMap<[number, ResizeMeasurement, Element], ScrollAction[]>(
369+
const scrollAction$: Observable<ScrollAction> = respectScrollToOnResize$.pipe(
370+
switchMap((respectScrollToOnResize) =>
371+
respectScrollToOnResize
372+
? // Emit when any input stream emits
373+
combineLatest<[number, ResizeMeasurement, Element]>([
374+
scrollToNotNil$,
375+
resizeMeasurement$,
376+
rootResize$,
377+
])
378+
: // Emit only when the source stream emmits
379+
scrollToNotNil$.pipe(
380+
withLatestFrom<number, [ResizeMeasurement, Element]>(
381+
resizeMeasurement$,
382+
rootResize$
383+
)
384+
)
385+
),
386+
map<[number, ResizeMeasurement, Element], ScrollAction>(
379387
([scrollTo, resizeMeasurement, rootEl]) => {
380388
const { vertical: verticalScrollEl, horizontal: horizontalScrollEl } =
381389
getScrollParents(rootEl);
@@ -409,21 +417,11 @@ export function pipeline({
409417

410418
const { x, y } = getItemOffsetByIndex(scrollTo, resizeMeasurement);
411419

412-
const scrollLeft =
413-
x + leftToGridContainer + gridPaddingLeft + gridBoarderLeft;
414-
const scrollTop =
415-
y + topToGridContainer + gridPaddingTop + gridBoarderTop;
416-
417-
return [
418-
{
419-
target: verticalScrollEl,
420-
offset: { top: scrollTop },
421-
},
422-
{
423-
target: horizontalScrollEl,
424-
offset: { left: scrollLeft },
425-
},
426-
];
420+
return {
421+
target: verticalScrollEl,
422+
top: y + topToGridContainer + gridPaddingTop + gridBoarderTop,
423+
left: x + leftToGridContainer + gridPaddingLeft + gridBoarderLeft,
424+
};
427425
}
428426
)
429427
);

0 commit comments

Comments
 (0)