@pdanpdan/virtual-scroll 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,10 +6,14 @@ import type {
6
6
  ScrollDetails,
7
7
  VirtualScrollProps,
8
8
  } from '../composables/useVirtualScroll';
9
+ import type { VNodeChild } from 'vue';
9
10
 
10
- import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
11
+ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
11
12
 
12
- import { useVirtualScroll } from '../composables/useVirtualScroll';
13
+ import {
14
+ DEFAULT_ITEM_SIZE,
15
+ useVirtualScroll,
16
+ } from '../composables/useVirtualScroll';
13
17
  import { getPaddingX, getPaddingY } from '../utils/scroll';
14
18
 
15
19
  export interface Props<T = unknown> {
@@ -89,7 +93,7 @@ const props = withDefaults(defineProps<Props<T>>(), {
89
93
  gap: 0,
90
94
  columnGap: 0,
91
95
  stickyIndices: () => [],
92
- loadDistance: 50,
96
+ loadDistance: 200,
93
97
  loading: false,
94
98
  restoreScrollOnPrepend: false,
95
99
  debug: false,
@@ -101,7 +105,29 @@ const emit = defineEmits<{
101
105
  (e: 'visibleRangeChange', range: { start: number; end: number; colStart: number; colEnd: number; }): void;
102
106
  }>();
103
107
 
104
- const isDebug = computed(() => props.debug);
108
+ const slots = defineSlots<{
109
+ /** Content rendered at the top of the scrollable area. Can be made sticky. */
110
+ header?: (props: Record<string, never>) => VNodeChild;
111
+ /** Slot for rendering each individual item. */
112
+ item?: (props: {
113
+ /** The data item being rendered. */
114
+ item: T;
115
+ /** The index of the item in the items array. */
116
+ index: number;
117
+ /** The current visible range of columns (for grid mode). */
118
+ columnRange: { start: number; end: number; padStart: number; padEnd: number; };
119
+ /** Function to get the width of a specific column. */
120
+ getColumnWidth: (index: number) => number;
121
+ /** Whether this item is configured to be sticky. */
122
+ isSticky?: boolean | undefined;
123
+ /** Whether this item is currently in a sticky state. */
124
+ isStickyActive?: boolean | undefined;
125
+ }) => VNodeChild;
126
+ /** Content shown when `loading` prop is true. */
127
+ loading?: (props: Record<string, never>) => VNodeChild;
128
+ /** Content rendered at the bottom of the scrollable area. Can be made sticky. */
129
+ footer?: (props: Record<string, never>) => VNodeChild;
130
+ }>();
105
131
 
106
132
  const hostRef = ref<HTMLElement | null>(null);
107
133
  const wrapperRef = ref<HTMLElement | null>(null);
@@ -172,7 +198,7 @@ const virtualScrollProps = computed(() => {
172
198
  initialScrollAlign: props.initialScrollAlign,
173
199
  defaultItemSize: props.defaultItemSize,
174
200
  defaultColumnWidth: props.defaultColumnWidth,
175
- debug: isDebug.value,
201
+ debug: props.debug,
176
202
  } as VirtualScrollProps<T>;
177
203
  });
178
204
 
@@ -188,10 +214,37 @@ const {
188
214
  scrollToOffset,
189
215
  updateHostOffset,
190
216
  updateItemSizes,
191
- refresh,
217
+ refresh: coreRefresh,
192
218
  stopProgrammaticScroll,
193
219
  } = useVirtualScroll(virtualScrollProps);
194
220
 
221
+ /**
222
+ * Resets all dynamic measurements and re-initializes from props.
223
+ * Also triggers manual re-measurement of all currently rendered items.
224
+ */
225
+ function refresh() {
226
+ coreRefresh();
227
+ nextTick(() => {
228
+ const updates: { index: number; inlineSize: number; blockSize: number; element?: HTMLElement; }[] = [];
229
+
230
+ for (const [ index, el ] of itemRefs.entries()) {
231
+ /* v8 ignore else -- @preserve */
232
+ if (el) {
233
+ updates.push({
234
+ index,
235
+ inlineSize: el.offsetWidth,
236
+ blockSize: el.offsetHeight,
237
+ element: el,
238
+ });
239
+ }
240
+ }
241
+
242
+ if (updates.length > 0) {
243
+ updateItemSizes(updates);
244
+ }
245
+ });
246
+ }
247
+
195
248
  // Watch for scroll details and emit event
196
249
  watch(scrollDetails, (details, oldDetails) => {
197
250
  if (!isHydrated.value) {
@@ -412,22 +465,22 @@ function handleKeyDown(event: KeyboardEvent) {
412
465
  }
413
466
  if (event.key === 'ArrowUp') {
414
467
  event.preventDefault();
415
- scrollToOffset(null, scrollOffset.y - 40);
468
+ scrollToOffset(null, scrollOffset.y - DEFAULT_ITEM_SIZE);
416
469
  return;
417
470
  }
418
471
  if (event.key === 'ArrowDown') {
419
472
  event.preventDefault();
420
- scrollToOffset(null, scrollOffset.y + 40);
473
+ scrollToOffset(null, scrollOffset.y + DEFAULT_ITEM_SIZE);
421
474
  return;
422
475
  }
423
476
  if (event.key === 'ArrowLeft') {
424
477
  event.preventDefault();
425
- scrollToOffset(scrollOffset.x - 40, null);
478
+ scrollToOffset(scrollOffset.x - DEFAULT_ITEM_SIZE, null);
426
479
  return;
427
480
  }
428
481
  if (event.key === 'ArrowRight') {
429
482
  event.preventDefault();
430
- scrollToOffset(scrollOffset.x + 40, null);
483
+ scrollToOffset(scrollOffset.x + DEFAULT_ITEM_SIZE, null);
431
484
  return;
432
485
  }
433
486
  if (event.key === 'PageUp') {
@@ -526,10 +579,10 @@ function getItemStyle(item: RenderedItem<T>) {
526
579
 
527
580
  if (isDynamic) {
528
581
  if (!isVertical) {
529
- style.minInlineSize = `${ item.size.width }px`;
582
+ style.minInlineSize = '1px';
530
583
  }
531
584
  if (!isHorizontal) {
532
- style.minBlockSize = `${ item.size.height }px`;
585
+ style.minBlockSize = '1px';
533
586
  }
534
587
  }
535
588
 
@@ -552,6 +605,11 @@ function getItemStyle(item: RenderedItem<T>) {
552
605
  return style;
553
606
  }
554
607
 
608
+ const isDebug = computed(() => props.debug);
609
+ const isTable = computed(() => props.containerTag === 'table');
610
+ const headerTag = computed(() => isTable.value ? 'thead' : 'div');
611
+ const footerTag = computed(() => isTable.value ? 'tfoot' : 'div');
612
+
555
613
  defineExpose({
556
614
  scrollDetails,
557
615
  columnRange,
@@ -573,7 +631,7 @@ defineExpose({
573
631
  {
574
632
  'virtual-scroll--hydrated': isHydrated,
575
633
  'virtual-scroll--window': isWindowContainer,
576
- 'virtual-scroll--table': containerTag === 'table',
634
+ 'virtual-scroll--table': isTable,
577
635
  },
578
636
  ]"
579
637
  :style="containerStyle"
@@ -583,15 +641,17 @@ defineExpose({
583
641
  @pointerdown.passive="stopProgrammaticScroll"
584
642
  @touchstart.passive="stopProgrammaticScroll"
585
643
  >
644
+ <!-- v8 ignore start -->
586
645
  <component
587
- :is="containerTag === 'table' ? 'thead' : 'div'"
588
- v-if="$slots.header"
646
+ :is="headerTag"
647
+ v-if="slots.header"
589
648
  ref="headerRef"
590
649
  class="virtual-scroll-header"
591
650
  :class="{ 'virtual-scroll--sticky': stickyHeader }"
592
651
  >
593
652
  <slot name="header" />
594
653
  </component>
654
+ <!-- v8 ignore stop -->
595
655
 
596
656
  <component
597
657
  :is="wrapperTag"
@@ -600,14 +660,16 @@ defineExpose({
600
660
  :style="wrapperStyle"
601
661
  >
602
662
  <!-- Phantom element to push scroll height -->
663
+ <!-- v8 ignore start -->
603
664
  <component
604
665
  :is="itemTag"
605
- v-if="containerTag === 'table'"
666
+ v-if="isTable"
606
667
  class="virtual-scroll-spacer"
607
668
  :style="spacerStyle"
608
669
  >
609
670
  <td style="padding: 0; border: none; block-size: inherit;" />
610
671
  </component>
672
+ <!-- v8 ignore stop -->
611
673
 
612
674
  <component
613
675
  :is="itemTag"
@@ -637,8 +699,9 @@ defineExpose({
637
699
  </component>
638
700
  </component>
639
701
 
702
+ <!-- v8 ignore start -->
640
703
  <div
641
- v-if="loading && $slots.loading"
704
+ v-if="loading && slots.loading"
642
705
  class="virtual-scroll-loading"
643
706
  :style="loadingStyle"
644
707
  >
@@ -646,14 +709,15 @@ defineExpose({
646
709
  </div>
647
710
 
648
711
  <component
649
- :is="containerTag === 'table' ? 'tfoot' : 'div'"
650
- v-if="$slots.footer"
712
+ :is="footerTag"
713
+ v-if="slots.footer"
651
714
  ref="footerRef"
652
715
  class="virtual-scroll-footer"
653
716
  :class="{ 'virtual-scroll--sticky': stickyFooter }"
654
717
  >
655
718
  <slot name="footer" />
656
719
  </component>
720
+ <!-- v8 ignore stop -->
657
721
  </component>
658
722
  </template>
659
723