@dataloop-ai/components 0.19.146 → 0.19.148

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataloop-ai/components",
3
- "version": "0.19.146",
3
+ "version": "0.19.148",
4
4
  "exports": {
5
5
  ".": "./index.ts",
6
6
  "./models": "./models.ts",
@@ -6,6 +6,7 @@
6
6
  class="dl-chip"
7
7
  :class="chipClass"
8
8
  :style="cssChipVars"
9
+ @click="$emit('click')"
9
10
  >
10
11
  <slot name="prefix" />
11
12
  <span
@@ -29,7 +30,8 @@
29
30
  ref="dlChipRef"
30
31
  :class="{
31
32
  'dl-chip--ellipsis': overflow,
32
- 'dl-chip--no-overflow': !overflow
33
+ 'dl-chip--no-overflow': !overflow,
34
+ 'dl-chip--clickable': clickable
33
35
  }"
34
36
  >
35
37
  <slot>
@@ -94,9 +96,10 @@ export default defineComponent({
94
96
  Object.values(DlTextTransformOptions).includes(value)
95
97
  },
96
98
  overflow: { type: Boolean, default: false },
97
- fit: { type: Boolean, default: false }
99
+ fit: { type: Boolean, default: false },
100
+ clickable: { type: Boolean, default: false }
98
101
  },
99
- emits: ['remove', 'ellipsis'],
102
+ emits: ['remove', 'ellipsis', 'click'],
100
103
  setup(props, ctx) {
101
104
  const isVisible = ref(true)
102
105
  const dlChipRef = ref(null)
@@ -136,7 +139,11 @@ export default defineComponent({
136
139
  return this.iconColor
137
140
  },
138
141
  chipClass(): string {
139
- return `dl-text-transform--${this.transform}`
142
+ let classes = `dl-text-transform--${this.transform}`
143
+ if (this.clickable) {
144
+ classes += ' dl-chip--clickable'
145
+ }
146
+ return classes
140
147
  },
141
148
  cssChipVars(): Record<string, string | number> {
142
149
  return {
@@ -225,6 +232,10 @@ export default defineComponent({
225
232
  &--no-overflow {
226
233
  overflow-wrap: break-word;
227
234
  }
235
+
236
+ &--clickable {
237
+ cursor: pointer;
238
+ }
228
239
  }
229
240
 
230
241
  .dl-chip-remove-icon-container {
@@ -1,17 +1,33 @@
1
1
  <template>
2
- <div v-if="hasVirtualScroll">
3
- <dl-virtual-scroll
4
- v-slot="{ item }"
5
- :scroll-debounce="scrollDebounce"
6
- style="height: var(--dl-virtual-scroll-height, 500px)"
2
+ <div v-if="infiniteScroll">
3
+ <dl-infinite-scroll
7
4
  :items="items"
8
- :styles="{ gridStyles, gridClass }"
5
+ :page-size="infiniteScrollPageSize"
6
+ :style="$attrs.style"
7
+ :class="$attrs.class"
8
+ style="height: var(--dl-virtual-scroll-height, 500px)"
9
+ @scroll-to-top="$emit('scroll-to-top')"
10
+ @scroll-to-bottom="$emit('scroll-to-bottom')"
9
11
  >
10
- <slot
11
- name="item-slot"
12
- v-bind="{ item }"
13
- />
14
- </dl-virtual-scroll>
12
+ <template #content="{ items }">
13
+ <div
14
+ ref="grid"
15
+ :style="gridStyles"
16
+ :class="gridClass"
17
+ >
18
+ <div
19
+ v-for="item in items"
20
+ :key="item.id"
21
+ class="item-wrapper"
22
+ >
23
+ <slot
24
+ name="item-slot"
25
+ v-bind="{ item }"
26
+ />
27
+ </div>
28
+ </div>
29
+ </template>
30
+ </dl-infinite-scroll>
15
31
  </div>
16
32
  <div
17
33
  v-else-if="!hasItems"
@@ -57,11 +73,11 @@ import { getGridTemplate, swapElementsInMatrix } from './utils'
57
73
  import { isCustomEvent } from '../utils'
58
74
  import { getElementAbove } from '../../../utils'
59
75
  import { DlGridMode, GridItem } from './types'
60
- import { DlVirtualScroll } from '../../shared/DlVirtualScroll'
76
+ import { DlInfiniteScroll } from '../../shared'
61
77
 
62
78
  export default defineComponent({
63
79
  components: {
64
- DlVirtualScroll
80
+ DlInfiniteScroll
65
81
  },
66
82
  model: {
67
83
  prop: 'modelValue',
@@ -92,20 +108,21 @@ export default defineComponent({
92
108
  type: String as PropType<DlGridMode>,
93
109
  default: DlGridMode.LAYOUT
94
110
  },
95
- scrollDebounce: {
96
- type: Number,
97
- default: 100
98
- },
99
- virtualScroll: {
111
+ infiniteScroll: {
100
112
  type: Boolean,
101
113
  default: false
102
114
  },
103
- virtualScrollThreshold: {
115
+ infiniteScrollPageSize: {
104
116
  type: Number,
105
- default: 100
117
+ default: 15
106
118
  }
107
119
  },
108
- emits: ['update:model-value', 'layout-changed'],
120
+ emits: [
121
+ 'update:model-value',
122
+ 'layout-changed',
123
+ 'scroll-to-top',
124
+ 'scroll-to-bottom'
125
+ ],
109
126
  setup(props, { emit }) {
110
127
  const vm = getCurrentInstance()
111
128
  const grid = ref<HTMLElement | null>(null)
@@ -115,8 +132,7 @@ export default defineComponent({
115
132
  rowGap,
116
133
  columnGap,
117
134
  maxElementsPerRow,
118
- items,
119
- virtualScroll
135
+ items
120
136
  } = toRefs(props)
121
137
 
122
138
  const isLayoutMode = computed(() => mode.value == DlGridMode.LAYOUT)
@@ -129,12 +145,6 @@ export default defineComponent({
129
145
  : 'dl-grid-wrapper__flex'
130
146
  )
131
147
 
132
- const hasVirtualScroll = computed(
133
- () =>
134
- items.value?.length > props.virtualScrollThreshold ||
135
- virtualScroll.value
136
- )
137
-
138
148
  const gridStyles = computed(() => {
139
149
  const gridStyles: Dictionary<string | number> = {
140
150
  '--row-gap': rowGap.value,
@@ -258,7 +268,6 @@ export default defineComponent({
258
268
  gridClass,
259
269
  gridStyles,
260
270
  grid,
261
- hasVirtualScroll,
262
271
  hasItems
263
272
  }
264
273
  }
@@ -61,16 +61,16 @@
61
61
  v-bind="virtProps"
62
62
  ref="virtScrollRef"
63
63
  type="__dltable"
64
- :class="tableClass + additionalClasses"
64
+ :class="virtualScrollClasses"
65
65
  :style="tableStyle"
66
- :table-colspan="colspanWithActionsRow"
66
+ :table-colspan="colspanWithExpandableRow"
67
67
  :scroll-target="virtualScrollTarget"
68
68
  :items="computedRows"
69
69
  :scroll-debounce="scrollDebounce"
70
70
  @virtual-scroll="onVScroll"
71
71
  >
72
72
  <template #before>
73
- <thead :colspan="columns.length">
73
+ <thead :colspan="colspanWithExpandableRow">
74
74
  <slot
75
75
  v-if="!hideHeader"
76
76
  name="header"
@@ -235,22 +235,21 @@
235
235
  </slot>
236
236
  </DlTh>
237
237
  </DlTr>
238
-
239
- <tr
240
- v-if="loading && !hasLoadingSlot"
241
- class="dl-table__progress"
242
- >
243
- <th
244
- :colspan="colspanWithActionsRow"
245
- class="relative-position"
246
- >
247
- <dl-progress-bar
248
- indeterminate
249
- :color="color"
250
- />
251
- </th>
252
- </tr>
253
238
  </slot>
239
+ <tr
240
+ v-if="loading && !hasLoadingSlot"
241
+ class="dl-table__progress"
242
+ >
243
+ <th
244
+ :colspan="colspanWithExpandableRow"
245
+ class="relative-position"
246
+ >
247
+ <dl-progress-bar
248
+ indeterminate
249
+ :color="color"
250
+ />
251
+ </th>
252
+ </tr>
254
253
  </thead>
255
254
  </template>
256
255
  <template #default="props">
@@ -415,8 +414,7 @@
415
414
  v-if="isRowExpanded(props.item)"
416
415
  :key="getRowExpandedKey(props.item)"
417
416
  >
418
- <!-- cols + icon col + expandable icon col-->
419
- <td :colspan="columns.length + 1 + 1">
417
+ <td :colspan="colspanWithExpandableRow">
420
418
  <slot
421
419
  v-bind="{ row: props.item }"
422
420
  name="body-cell-expandable-content"
@@ -455,6 +453,7 @@
455
453
 
456
454
  <div
457
455
  v-else
456
+ ref="tableScroll"
458
457
  class="dl-table__middle scroll"
459
458
  >
460
459
  <table
@@ -462,7 +461,7 @@
462
461
  class="dl-table"
463
462
  :class="additionalClasses"
464
463
  >
465
- <thead :colspan="columns.length">
464
+ <thead :colspan="colspanWithExpandableRow">
466
465
  <slot
467
466
  v-if="!hideHeader"
468
467
  name="header"
@@ -628,22 +627,21 @@
628
627
  </slot>
629
628
  </DlTh>
630
629
  </DlTr>
631
-
632
- <tr
633
- v-if="loading && !hasLoadingSlot"
634
- class="dl-table__progress"
635
- >
636
- <th
637
- :colspan="colspanWithActionsRow"
638
- class="relative-position"
639
- >
640
- <dl-progress-bar
641
- indeterminate
642
- :color="color"
643
- />
644
- </th>
645
- </tr>
646
630
  </slot>
631
+ <tr
632
+ v-if="loading && !hasLoadingSlot"
633
+ class="dl-table__progress"
634
+ >
635
+ <th
636
+ :colspan="colspanWithExpandableRow"
637
+ class="relative-position"
638
+ >
639
+ <dl-progress-bar
640
+ indeterminate
641
+ :color="color"
642
+ />
643
+ </th>
644
+ </tr>
647
645
  </thead>
648
646
  <slot
649
647
  name="tbody"
@@ -661,13 +659,7 @@
661
659
  onEnd: handleSortableEvent
662
660
  }"
663
661
  :is-sortable="hasDraggableRows"
664
- :options="{
665
- group: 'nested',
666
- animation: 150,
667
- fallbackOnBody: true,
668
- invertSwap: true,
669
- swapThreshold: 0.5
670
- }"
662
+ :options="sortableOptions"
671
663
  >
672
664
  <slot
673
665
  name="top-row"
@@ -677,6 +669,17 @@
677
669
  name="table-body"
678
670
  :computed-rows="computedRows"
679
671
  >
672
+ <dl-top-scroll
673
+ v-if="tableScroll && infiniteScroll"
674
+ :container-ref="tableScroll"
675
+ @scroll-to-top="
676
+ $emit(
677
+ 'scroll-to-top',
678
+ computedPagination.rowsPerPage,
679
+ tableScroll
680
+ )
681
+ "
682
+ />
680
683
  <slot
681
684
  v-for="(row, pageIndex) in computedRows"
682
685
  v-bind="
@@ -819,7 +822,7 @@
819
822
  v-if="isRowExpanded(row)"
820
823
  :key="getRowExpandedKey(row)"
821
824
  >
822
- <td :colspan="columns.length + 1 + 1">
825
+ <td :colspan="colspanWithExpandableRow">
823
826
  <slot
824
827
  v-bind="{ row }"
825
828
  name="body-cell-expandable-content"
@@ -831,6 +834,17 @@
831
834
  </td>
832
835
  </tr>
833
836
  </slot>
837
+ <dl-bottom-scroll
838
+ v-if="tableScroll && infiniteScroll"
839
+ :container-ref="tableScroll"
840
+ @scroll-to-bottom="
841
+ $emit(
842
+ 'scroll-to-bottom',
843
+ computedPagination.rowsPerPage,
844
+ tableScroll
845
+ )
846
+ "
847
+ />
834
848
  </slot>
835
849
 
836
850
  <slot
@@ -992,6 +1006,7 @@ import { insertAtIndex } from './utils/insertAtIndex'
992
1006
  import { getCellValue } from './utils/getCellValue'
993
1007
  import { getContainerClass } from './utils/tableClasses'
994
1008
  import { isEqual } from 'lodash'
1009
+ import { DlTopScroll, DlBottomScroll } from '../../shared/DlInfiniteScroll'
995
1010
 
996
1011
  const commonVirtPropsObj = {} as Record<string, any>
997
1012
  commonVirtPropsList.forEach((p) => {
@@ -1015,7 +1030,9 @@ export default defineComponent({
1015
1030
  DlPopup,
1016
1031
  DlList,
1017
1032
  Sortable,
1018
- DlEllipsis
1033
+ DlEllipsis,
1034
+ DlTopScroll,
1035
+ DlBottomScroll
1019
1036
  },
1020
1037
  props: {
1021
1038
  /**
@@ -1118,6 +1135,13 @@ export default defineComponent({
1118
1135
  type: Boolean,
1119
1136
  default: false
1120
1137
  },
1138
+ /**
1139
+ * Enable infinite scroll
1140
+ */
1141
+ infiniteScroll: {
1142
+ type: Boolean,
1143
+ default: false
1144
+ },
1121
1145
  /**
1122
1146
  * Hide table pagination
1123
1147
  */
@@ -1246,7 +1270,7 @@ export default defineComponent({
1246
1270
  default: 'No data'
1247
1271
  },
1248
1272
  stickyColumns: {
1249
- type: Object as PropType<TableStickyPosition>,
1273
+ type: String as PropType<TableStickyPosition>,
1250
1274
  default: null,
1251
1275
  validator: (value: string) =>
1252
1276
  ['first', 'last', 'both'].includes(value)
@@ -1300,8 +1324,13 @@ export default defineComponent({
1300
1324
  const rootRef = ref<HTMLDivElement>(null)
1301
1325
  const tableRef = ref<HTMLTableElement>(null)
1302
1326
  const virtScrollRef = ref(null)
1327
+ const tableScroll = ref(null)
1328
+
1329
+ const hasExpandableSlot = computed(() =>
1330
+ hasSlotByName('body-cell-expandable-content')
1331
+ )
1303
1332
  const hasVirtScroll = computed<boolean>(
1304
- () => virtualScroll.value === true
1333
+ () => virtualScroll.value && !hasExpandableSlot.value
1305
1334
  )
1306
1335
 
1307
1336
  const hasEmptyStateProps = computed(() =>
@@ -1604,6 +1633,7 @@ export default defineComponent({
1604
1633
  )
1605
1634
 
1606
1635
  const computedRows = computed(() => {
1636
+ if (props.infiniteScroll) return filteredSortedRows.value
1607
1637
  let filtered = filteredSortedRows.value
1608
1638
 
1609
1639
  const { rowsPerPage } = computedPagination.value
@@ -1628,7 +1658,7 @@ export default defineComponent({
1628
1658
  return filtered
1629
1659
  })
1630
1660
 
1631
- const additionalClasses = computed(() => {
1661
+ const additionalClasses = computed<string[]>(() => {
1632
1662
  const classes: string[] = []
1633
1663
 
1634
1664
  if (hasDraggableRows.value === true) {
@@ -1643,7 +1673,9 @@ export default defineComponent({
1643
1673
  })
1644
1674
 
1645
1675
  const displayPagination = computed(
1646
- () => !(hidePagination.value || nothingToDisplay.value)
1676
+ () =>
1677
+ !props.infiniteScroll &&
1678
+ !(hidePagination.value || nothingToDisplay.value)
1647
1679
  )
1648
1680
 
1649
1681
  const {
@@ -1675,6 +1707,12 @@ export default defineComponent({
1675
1707
  return computedColspan.value + (showRowActions.value ? 1 : 0)
1676
1708
  })
1677
1709
 
1710
+ const colspanWithExpandableRow = computed(() => {
1711
+ return (
1712
+ colspanWithActionsRow.value + (hasExpandableSlot.value ? 1 : 0)
1713
+ )
1714
+ })
1715
+
1678
1716
  const { columnToSort, computedSortMethod, sort } = useTableSort(
1679
1717
  props as unknown as DlTableProps,
1680
1718
  computedPagination,
@@ -1988,6 +2026,35 @@ export default defineComponent({
1988
2026
  return slots.length ? slots.map((slot: any) => slot.name) : null
1989
2027
  })
1990
2028
 
2029
+ const sortableOptions: any = {
2030
+ group: 'nested',
2031
+ animation: 150,
2032
+ fallbackOnBody: true,
2033
+ invertSwap: true,
2034
+ swapThreshold: 0.5
2035
+ }
2036
+
2037
+ const virtualScrollClasses = computed(() => {
2038
+ let classes: string[] = []
2039
+
2040
+ if (tableClass.value) {
2041
+ if (Array.isArray(tableClass.value)) {
2042
+ classes = (tableClass.value as string[]) ?? []
2043
+ } else if (typeof tableClass.value === 'string') {
2044
+ classes = (tableClass.value as string)?.split(' ')
2045
+ } else if (typeof tableClass.value === 'object') {
2046
+ classes = Object.keys(
2047
+ tableClass.value as Record<string, any>
2048
+ ).filter(
2049
+ (key: string) =>
2050
+ !!(tableClass.value as Record<string, any>)[key]
2051
+ )
2052
+ }
2053
+ }
2054
+
2055
+ return classes.concat(additionalClasses.value)
2056
+ })
2057
+
1991
2058
  const updatePagination = (value: any, key: string) => {
1992
2059
  return setPagination({ [`${key}`]: value })
1993
2060
  }
@@ -2027,6 +2094,9 @@ export default defineComponent({
2027
2094
  computedCols,
2028
2095
  computedColspan,
2029
2096
  colspanWithActionsRow,
2097
+ colspanWithExpandableRow,
2098
+ virtualScrollClasses,
2099
+ sortableOptions,
2030
2100
  getRowKey,
2031
2101
  additionalClasses,
2032
2102
  getHeaderScope,
@@ -2085,7 +2155,9 @@ export default defineComponent({
2085
2155
  tableRef,
2086
2156
  getRowExpandedIcon,
2087
2157
  computedPagination,
2088
- getRowExpandedKey
2158
+ getRowExpandedKey,
2159
+ hasExpandableSlot,
2160
+ tableScroll
2089
2161
  }
2090
2162
  }
2091
2163
  })
@@ -8,6 +8,13 @@
8
8
  border-right: 1px solid var(--dl-color-separator);
9
9
  }
10
10
 
11
+ thead {
12
+ position: sticky;
13
+ top: 0;
14
+ background-color: var(--dl-color-panel-background);
15
+ z-index: 50;
16
+ }
17
+
11
18
  .sticky-col {
12
19
  position: sticky;
13
20
  background-color: var(--dl-color-panel-background);
@@ -11,6 +11,8 @@ export const emits = [
11
11
  'row-dblclick',
12
12
  'row-contextmenu',
13
13
  'update:pagination',
14
+ 'scroll-to-bottom',
15
+ 'scroll-to-top',
14
16
  ...useTableRowExpandEmits,
15
17
  ...useTableRowSelectionEmits
16
18
  ]
@@ -0,0 +1,188 @@
1
+ <template>
2
+ <div
3
+ ref="containerRef"
4
+ class="dl-infinite-scroll"
5
+ :style="computedStyles"
6
+ :class="computedClasses"
7
+ >
8
+ <DlTopScroll
9
+ :container-ref="containerRef"
10
+ @scroll-to-top="onScrollToTop"
11
+ />
12
+ <slot
13
+ name="content"
14
+ :items="displayItems"
15
+ >
16
+ <div
17
+ v-for="item in displayItems"
18
+ :key="itemKey(item)"
19
+ >
20
+ <slot :item="item" />
21
+ </div>
22
+ </slot>
23
+ <DlBottomScroll
24
+ :container-ref="containerRef"
25
+ @scroll-to-bottom="onScrollToBottom"
26
+ />
27
+ </div>
28
+ </template>
29
+ <script lang="ts">
30
+ import { cloneDeep } from 'lodash'
31
+ import {
32
+ computed,
33
+ defineComponent,
34
+ nextTick,
35
+ PropType,
36
+ ref,
37
+ toRefs,
38
+ watch
39
+ } from 'vue-demi'
40
+ import { DlTopScroll, DlBottomScroll } from './components'
41
+
42
+ const MAX_SLICE_SIZE = 100
43
+ const DEFAULT_SLICE_SIZE = 15
44
+ const MIN_SLICE_SIZE = 0
45
+
46
+ export default defineComponent({
47
+ name: 'DlInfiniteScroll',
48
+ components: { DlTopScroll, DlBottomScroll },
49
+ props: {
50
+ items: {
51
+ type: Array as PropType<Record<string, any>[]>,
52
+ default: () => [] as Record<string, any>[]
53
+ },
54
+ pageSize: {
55
+ type: Number,
56
+ default: DEFAULT_SLICE_SIZE,
57
+ validator: (val: number) =>
58
+ val <= MAX_SLICE_SIZE && val >= MIN_SLICE_SIZE
59
+ },
60
+ scrollDebounce: {
61
+ type: Number,
62
+ default: 100
63
+ }
64
+ },
65
+ emits: ['scroll-to-top', 'scroll-to-bottom'],
66
+ setup(props, { emit, attrs }) {
67
+ const { items, pageSize, scrollDebounce } = toRefs(props)
68
+ const containerRef = ref(null)
69
+ const currentPage = ref(0)
70
+ const pagesCount = ref(0)
71
+ const itemPages = ref(new Map<number, Record<string, any>[]>())
72
+ const lastOp = ref<string>('')
73
+
74
+ const computedStyles = computed<any>(() => {
75
+ return attrs.style
76
+ })
77
+ const computedClasses = computed<any>(() => {
78
+ return attrs.class
79
+ })
80
+
81
+ const displayItems = computed(() => {
82
+ const page = currentPage.value
83
+ const items = cloneDeep(itemPages.value.get(page))
84
+
85
+ const prevPage = cloneDeep(itemPages.value.get(page - 1))
86
+ const nextPage = cloneDeep(itemPages.value.get(page + 1))
87
+
88
+ const toDisplay = items ?? []
89
+
90
+ if (prevPage?.length && nextPage?.length) {
91
+ toDisplay.unshift(...prevPage.slice(-pageSize.value))
92
+ toDisplay.push(...nextPage.slice(0, pageSize.value))
93
+ } else if (prevPage?.length) {
94
+ toDisplay.unshift(...prevPage.slice(-pageSize.value))
95
+ } else if (nextPage?.length) {
96
+ toDisplay.push(...nextPage.slice(0, pageSize.value))
97
+ }
98
+
99
+ nextTick(() => {
100
+ if (lastOp.value === 'top' && page !== 0) {
101
+ containerRef.value.scrollTop +=
102
+ containerRef.value.scrollHeight * 0.333
103
+ } else if (
104
+ lastOp.value === 'bottom' &&
105
+ page !== pagesCount.value
106
+ ) {
107
+ if (currentPage.value - 1 === 0) {
108
+ return
109
+ }
110
+
111
+ containerRef.value.scrollTop -=
112
+ containerRef.value.scrollHeight * 0.333
113
+ }
114
+ })
115
+
116
+ return toDisplay
117
+ })
118
+
119
+ const itemKey = (item: any) => {
120
+ return item.id ?? item.key ?? JSON.stringify(item)
121
+ }
122
+
123
+ const onScrollToTop = () => {
124
+ lastOp.value = 'top'
125
+ if (currentPage.value <= 0) {
126
+ emit('scroll-to-top')
127
+ } else {
128
+ currentPage.value--
129
+ }
130
+ }
131
+ const onScrollToBottom = () => {
132
+ lastOp.value = 'bottom'
133
+ if (currentPage.value >= pagesCount.value - 1) {
134
+ emit('scroll-to-bottom')
135
+ } else {
136
+ currentPage.value++
137
+ }
138
+ }
139
+
140
+ const splitArrayIntoPages = (
141
+ arr: Record<string, any>[],
142
+ pageSize: number
143
+ ): Map<number, any[]> => {
144
+ pagesCount.value = Math.ceil(arr.length / pageSize)
145
+ const pages = new Map<number, any[]>()
146
+
147
+ for (let i = 0; i < pagesCount.value; i++) {
148
+ const start = i * pageSize
149
+ const end = start + pageSize
150
+ pages.set(i, arr.slice(start, end))
151
+ }
152
+
153
+ return pages
154
+ }
155
+
156
+ watch(
157
+ [items, pageSize],
158
+ () => {
159
+ itemPages.value = splitArrayIntoPages(
160
+ items.value,
161
+ pageSize.value
162
+ )
163
+ },
164
+ { immediate: true }
165
+ )
166
+
167
+ return {
168
+ containerRef,
169
+ onScrollToTop,
170
+ onScrollToBottom,
171
+ displayItems,
172
+ itemKey,
173
+ computedStyles,
174
+ computedClasses
175
+ }
176
+ }
177
+ })
178
+ </script>
179
+ <style scoped lang="scss">
180
+ .dl-infinite-scroll {
181
+ overflow-y: auto;
182
+ height: 100%;
183
+ width: 100%;
184
+ position: relative;
185
+ // To not allow any weird scroll jumping behavior
186
+ scroll-behavior: auto !important;
187
+ }
188
+ </style>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <div
3
+ ref="bottomRow"
4
+ class="infinite-scroll-bottom"
5
+ />
6
+ </template>
7
+
8
+ <script lang="ts">
9
+ import { defineComponent, PropType, ref, toRefs, watch } from 'vue-demi'
10
+ import { createIntersectionObserver } from '../utils'
11
+
12
+ export default defineComponent({
13
+ props: {
14
+ containerRef: {
15
+ // todo: fix typing here
16
+ type: null as any as PropType<HTMLElement>,
17
+ default: null
18
+ }
19
+ },
20
+ emits: ['scroll-to-bottom'],
21
+ setup(props, { emit }) {
22
+ const { containerRef } = toRefs(props)
23
+ const bottomRow = ref(null)
24
+ const observer = ref(null)
25
+
26
+ const initObserver = () => {
27
+ if (!bottomRow.value) {
28
+ return
29
+ }
30
+
31
+ observer.value?.disconnect()
32
+ observer.value = null
33
+
34
+ observer.value = createIntersectionObserver(
35
+ containerRef.value,
36
+ () => {
37
+ emit('scroll-to-bottom')
38
+ }
39
+ )
40
+ observer.value.observe(bottomRow.value as HTMLElement)
41
+ }
42
+
43
+ watch([containerRef, bottomRow], initObserver, { immediate: true })
44
+
45
+ return {
46
+ bottomRow
47
+ }
48
+ }
49
+ })
50
+ </script>
51
+
52
+ <style scoped lang="scss">
53
+ .infinite-scroll-bottom {
54
+ content: '';
55
+ height: 3px;
56
+ }
57
+ </style>
58
+ createIntersectionObservercreateIntersectionObserver./utils/create-observer./utils/createIntersectionObserver
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <div
3
+ ref="topRow"
4
+ class="infinite-scroll-top"
5
+ />
6
+ </template>
7
+
8
+ <script lang="ts">
9
+ import { defineComponent, PropType, ref, toRefs, watch } from 'vue-demi'
10
+ import { createIntersectionObserver } from '../utils'
11
+
12
+ export default defineComponent({
13
+ props: {
14
+ containerRef: {
15
+ // todo: fix typing here
16
+ type: null as any as PropType<HTMLElement>,
17
+ default: null
18
+ }
19
+ },
20
+ emits: ['scroll-to-top'],
21
+ setup(props, { emit }) {
22
+ const { containerRef } = toRefs(props)
23
+ const topRow = ref(null)
24
+ const observer = ref(null)
25
+
26
+ const initObserver = () => {
27
+ if (!topRow.value) {
28
+ return
29
+ }
30
+
31
+ observer.value?.disconnect()
32
+ observer.value = null
33
+
34
+ observer.value = createIntersectionObserver(
35
+ containerRef.value,
36
+ () => {
37
+ emit('scroll-to-top')
38
+ }
39
+ )
40
+ observer.value.observe(topRow.value as HTMLElement)
41
+ }
42
+
43
+ watch([containerRef, topRow], initObserver, { immediate: true })
44
+
45
+ return {
46
+ topRow
47
+ }
48
+ }
49
+ })
50
+ </script>
51
+
52
+ <style scoped lang="scss">
53
+ .infinite-scroll-top {
54
+ content: '';
55
+ height: 3px;
56
+ }
57
+ </style>
58
+ createIntersectionObservercreateIntersectionObserver./utils/create-observer./utils/createIntersectionObserver
@@ -0,0 +1,4 @@
1
+ import DlTopScroll from './DlTopScroll.vue'
2
+ import DlBottomScroll from './DlBottomScroll.vue'
3
+
4
+ export { DlTopScroll, DlBottomScroll }
@@ -0,0 +1,4 @@
1
+ export * from './components'
2
+
3
+ import DlInfiniteScroll from './DlInfiniteScroll.vue'
4
+ export { DlInfiniteScroll }
@@ -0,0 +1,14 @@
1
+ export const createIntersectionObserver = (ref: any, emit: Function) => {
2
+ const root = ref?.$el || ref
3
+ let wasIntersecting = true
4
+ return new IntersectionObserver(
5
+ ([entry]) => {
6
+ const isCurrentlyIntersecting = entry.isIntersecting
7
+ if (!wasIntersecting && isCurrentlyIntersecting) {
8
+ emit()
9
+ }
10
+ wasIntersecting = isCurrentlyIntersecting
11
+ },
12
+ { root, threshold: 0.5 }
13
+ )
14
+ }
@@ -0,0 +1 @@
1
+ export * from './createIntersectionObserver'
@@ -2,3 +2,4 @@ export * from './DlItemSection'
2
2
  export * from './DlInfoErrorMessage'
3
3
  export * from './DlVirtualScroll'
4
4
  export * from './DlTooltip'
5
+ export * from './DlInfiniteScroll'
@@ -183,6 +183,8 @@
183
183
  <DlChip
184
184
  label="Long Chip Label With Alot Of Text"
185
185
  fit
186
+ clickable
187
+ @click="log"
186
188
  />
187
189
  </div>
188
190
  </div>
@@ -204,7 +204,10 @@
204
204
  </dl-grid>
205
205
  </div>
206
206
  <div v-if="activeContentType.value === 'prop'">
207
- <dl-grid :items="images">
207
+ <dl-grid
208
+ :items="images"
209
+ infinite-scroll
210
+ >
208
211
  <template #item-slot="{ item }">
209
212
  <img :src="item.src">
210
213
  </template>
@@ -0,0 +1,236 @@
1
+ <template>
2
+ <div>
3
+ <div>
4
+ <span> full implementation using the component </span>
5
+ <DlInfiniteScroll
6
+ :items="rows"
7
+ :page-size="50"
8
+ style="height: 500px; width: 600px"
9
+ @scroll-to-bottom="pushRows"
10
+ >
11
+ <template #default="{ item }">
12
+ {{ item.name }}
13
+ </template>
14
+ </DlInfiniteScroll>
15
+ </div>
16
+ <div style="margin-top: 10px">
17
+ <span>
18
+ Custom implementation with the top and bottom components
19
+ </span>
20
+ <span>List</span>
21
+ <dl-list
22
+ ref="listRef"
23
+ class="item-list"
24
+ >
25
+ <dl-top-scroll
26
+ :container-ref="listRef"
27
+ @scroll-to-top="handleListScrollToTop"
28
+ />
29
+ <dl-list-item
30
+ v-for="row in rows"
31
+ :key="row.id"
32
+ >
33
+ {{ row.name }}
34
+ </dl-list-item>
35
+ <dl-bottom-scroll
36
+ :container-ref="listRef"
37
+ @scroll-to-bottom="handleListScrollToBottom"
38
+ />
39
+ </dl-list>
40
+ <div style="margin-top: 100px">
41
+ <p>Infinite scrolling With custom data and weird expandable</p>
42
+ <dl-table
43
+ :loading="loading"
44
+ :rows="rows"
45
+ :columns="columns"
46
+ style="height: 500px; width: 600px"
47
+ row-key="index"
48
+ infinite-scroll
49
+ expandable-rows
50
+ :rows-per-page-options="[rowsPerPage]"
51
+ @scroll-to-bottom="handleTableScrollToBottom"
52
+ @scroll-to-top="handleTableScrollToTop"
53
+ >
54
+ <template #body-cell-expandable-content="{ row }">
55
+ <div>
56
+ {{ row }}
57
+ </div>
58
+ <div>
59
+ {{ row }}
60
+ </div>
61
+ <div>
62
+ {{ row }}
63
+ </div>
64
+ <div>
65
+ {{ row }}
66
+ </div>
67
+ <div>
68
+ {{ row }}
69
+ </div>
70
+ </template>
71
+ </dl-table>
72
+ </div>
73
+ </div>
74
+ <div style="margin-top: 10px">
75
+ <span> IN A GRID </span>
76
+ <dl-grid
77
+ :items="allRows"
78
+ style="--dl-virtual-scroll-height: 40vh"
79
+ :max-elements-per-row="4"
80
+ column-gap="20px"
81
+ row-gap="20px"
82
+ :infinite-scroll-page-size="32"
83
+ infinite-scroll
84
+ >
85
+ <template #item-slot="{ item }">
86
+ <div
87
+ style="
88
+ height: 50px;
89
+ width: 180px;
90
+ display: grid;
91
+ place-items: center;
92
+ "
93
+ >
94
+ <div>{{ item.name }}</div>
95
+ </div>
96
+ </template>
97
+ </dl-grid>
98
+ </div>
99
+ </div>
100
+ </template>
101
+
102
+ <script lang="ts">
103
+ import { defineComponent, ref } from 'vue-demi'
104
+ import {
105
+ DlTopScroll,
106
+ DlBottomScroll,
107
+ DlList,
108
+ DlListItem,
109
+ DlTable,
110
+ DlInfiniteScroll,
111
+ DlGrid
112
+ } from '../components'
113
+ import { columns } from './DlTableDemo.vue'
114
+ import { cloneDeep, times } from 'lodash'
115
+
116
+ const getRows = (count: number) =>
117
+ times(count, (index) => ({
118
+ name: 'KitKat' + index,
119
+ calories: 518,
120
+ fat: 26.0,
121
+ carbs: 65,
122
+ protein: 7,
123
+ sodium: 54,
124
+ calcium: '12%',
125
+ iron: '6%'
126
+ }))
127
+
128
+ const items = getRows(500)
129
+
130
+ export default defineComponent({
131
+ components: {
132
+ DlTopScroll,
133
+ DlBottomScroll,
134
+ DlList,
135
+ DlListItem,
136
+ DlTable,
137
+ DlInfiniteScroll,
138
+ DlGrid
139
+ },
140
+ setup() {
141
+ const loading = ref(false)
142
+ const scrollOffset = 500
143
+ const rowsPerPage = 30
144
+ const sliceIndex = { from: 0, to: 0 }
145
+ const allRows = ref<any[]>(cloneDeep(items))
146
+ const rows = ref<any[]>(cloneDeep(items.slice(0, 30)))
147
+ const listRef = ref(null)
148
+
149
+ const page = ref(0)
150
+
151
+ const pushRows = () => {
152
+ page.value++
153
+ const start = page.value * 30
154
+ rows.value = rows.value.concat(items.slice(start, start + 30))
155
+ }
156
+
157
+ const handleInfiniteScroll = (
158
+ rowsPerPage: number,
159
+ direction: 'top' | 'bottom',
160
+ maxLength?: number,
161
+ ref?: HTMLElement
162
+ ) => {
163
+ loading.value = true
164
+ setTimeout(() => {
165
+ if (!sliceIndex.to) sliceIndex.to = rowsPerPage
166
+ if (maxLength) {
167
+ if (direction === 'bottom') {
168
+ sliceIndex.to += rowsPerPage
169
+ if (rows.value.length > maxLength) {
170
+ sliceIndex.from += rowsPerPage
171
+ if (ref) {
172
+ ref.scrollTop -= scrollOffset
173
+ }
174
+ }
175
+ } else {
176
+ sliceIndex.from -= rowsPerPage
177
+ if (rows.value.length > maxLength) {
178
+ sliceIndex.to -= rowsPerPage
179
+ if (ref) {
180
+ ref.scrollTop += scrollOffset
181
+ }
182
+ }
183
+ if (sliceIndex.from < 0) {
184
+ sliceIndex.from = 0
185
+ }
186
+ }
187
+ }
188
+ rows.value = items.slice(sliceIndex.from, sliceIndex.to)
189
+ loading.value = false
190
+ }, 500)
191
+ }
192
+ const handleListScrollToBottom = () => {
193
+ handleInfiniteScroll(rowsPerPage, 'bottom', 100, listRef.value.$el)
194
+ }
195
+ const handleListScrollToTop = () => {
196
+ handleInfiniteScroll(rowsPerPage, 'top', 100, listRef.value.$el)
197
+ }
198
+ const handleTableScrollToBottom = (
199
+ rowsPerPage: number,
200
+ ref: HTMLElement
201
+ ) => {
202
+ handleInfiniteScroll(rowsPerPage, 'bottom', 100, ref)
203
+ }
204
+ const handleTableScrollToTop = (
205
+ rowsPerPage: number,
206
+ ref: HTMLElement
207
+ ) => {
208
+ handleInfiniteScroll(rowsPerPage, 'top', 100, ref)
209
+ }
210
+
211
+ return {
212
+ items,
213
+ rows,
214
+ columns,
215
+ loading,
216
+ listRef,
217
+ rowsPerPage,
218
+ handleInfiniteScroll,
219
+ handleListScrollToBottom,
220
+ handleListScrollToTop,
221
+ handleTableScrollToBottom,
222
+ handleTableScrollToTop,
223
+ pushRows,
224
+ allRows
225
+ }
226
+ }
227
+ })
228
+ </script>
229
+
230
+ <style scoped>
231
+ .item-list {
232
+ height: 300px;
233
+ overflow-y: auto;
234
+ padding: 10px;
235
+ }
236
+ </style>
@@ -108,12 +108,12 @@
108
108
  :loading="loading"
109
109
  :rows="tableRows"
110
110
  :resizable="resizable"
111
+ :rows-per-page-options="[30]"
111
112
  row-key="name"
112
113
  color="dl-color-secondary"
113
114
  title="Table Title"
114
115
  :virtual-scroll="vScroll"
115
116
  style="height: 500px"
116
- :rows-per-page-options="rowsPerPageOptions"
117
117
  @row-click="log"
118
118
  @th-click="log"
119
119
  @update:selected="updateSeleted"
@@ -681,48 +681,6 @@
681
681
  </template>
682
682
  </DlTable>
683
683
  </div>
684
- <div style="margin-top: 100px">
685
- <p>Infinite scrolling With custom data and weird expandable</p>
686
- <DlTable
687
- :selected="selected"
688
- :separator="separator"
689
- :draggable="draggable"
690
- :filter="filter"
691
- :resizable="resizable"
692
- :selection="selection"
693
- :dense="dense"
694
- title="Treats"
695
- color="dl-color-secondary"
696
- :loading="infiniteLoading"
697
- :rows="generatedRows"
698
- :columns="tableColumns"
699
- style="height: 500px"
700
- row-key="index"
701
- virtual-scroll
702
- expandable-rows
703
- @virtual-scroll="onScrollGenerate"
704
- @update:pagination="log('hi')"
705
- @col-update="updateColumns"
706
- >
707
- <template #body-cell-expandable-content="{ row }">
708
- <div>
709
- {{ row }}
710
- </div>
711
- <div>
712
- {{ row }}
713
- </div>
714
- <div>
715
- {{ row }}
716
- </div>
717
- <div>
718
- {{ row }}
719
- </div>
720
- <div>
721
- {{ row }}
722
- </div>
723
- </template>
724
- </DlTable>
725
- </div>
726
684
  </div>
727
685
  </template>
728
686
 
@@ -739,7 +697,7 @@ import { defineComponent, ref, computed, nextTick } from 'vue-demi'
739
697
  import { times, cloneDeep, isNumber } from 'lodash'
740
698
  import { DlTablePagination, DlVirtualScrollEvent } from '../types'
741
699
 
742
- const columns = [
700
+ export const columns = [
743
701
  {
744
702
  name: 'name',
745
703
  required: true,
@@ -839,108 +797,8 @@ const columns2 = [
839
797
  }
840
798
  ]
841
799
 
842
- const rows = [
843
- {
844
- name: 'Frozen Yogurt',
845
- calories: 159,
846
- fat: 6.0,
847
- carbs: 24,
848
- protein: 4.0,
849
- sodium: 87,
850
- calcium: '14%',
851
- iron: '1%'
852
- },
853
- {
854
- name: 'Ice cream sandwich',
855
- calories: 237,
856
- fat: 9.0,
857
- carbs: 37,
858
- protein: 4.3,
859
- sodium: 129,
860
- calcium: '8%',
861
- iron: '1%'
862
- },
863
- {
864
- name: 'Eclair',
865
- calories: 262,
866
- fat: 16.0,
867
- carbs: 23,
868
- protein: 6.0,
869
- sodium: 337,
870
- calcium: '6%',
871
- iron: '7%'
872
- },
873
- {
874
- name: 'Cupcake',
875
- calories: 305,
876
- fat: 3.7,
877
- carbs: 67,
878
- protein: 4.3,
879
- sodium: 413,
880
- calcium: '3%',
881
- iron: '8%'
882
- },
883
- {
884
- name: 'Gingerbread',
885
- calories: 356,
886
- fat: 16.0,
887
- carbs: 49,
888
- protein: 3.9,
889
- sodium: 327,
890
- calcium: '7%',
891
- iron: '16%'
892
- },
893
- {
894
- name: 'Jelly bean',
895
- calories: 375,
896
- fat: 0.0,
897
- carbs: 94,
898
- protein: 0.0,
899
- sodium: 50,
900
- calcium: '0%',
901
- iron: '0%'
902
- },
903
- {
904
- name: 'Lollipop',
905
- calories: 392,
906
- fat: 0.2,
907
- carbs: 98,
908
- protein: 0,
909
- sodium: 38,
910
- calcium: '0%',
911
- iron: '2%'
912
- },
913
- {
914
- name: 'Honeycomb',
915
- calories: 408,
916
- fat: 3.2,
917
- carbs: 87,
918
- protein: 6.5,
919
- sodium: 562,
920
- calcium: '0%',
921
- iron: '45%'
922
- },
923
- {
924
- name: 'Donut',
925
- calories: 452,
926
- fat: 25.0,
927
- carbs: 51,
928
- protein: 4.9,
929
- sodium: 326,
930
- calcium: '2%',
931
- iron: '22%'
932
- },
933
- {
934
- name: 'KitKat',
935
- calories: 518,
936
- fat: 26.0,
937
- carbs: 65,
938
- protein: 7,
939
- sodium: 54,
940
- calcium: '12%',
941
- iron: '6%'
942
- },
943
- ...times(1000, (index) => ({
800
+ const getRows = (count: number) => [
801
+ ...times(count, (index) => ({
944
802
  name: 'KitKat' + index,
945
803
  calories: 518,
946
804
  fat: 26.0,
@@ -952,6 +810,8 @@ const rows = [
952
810
  }))
953
811
  ]
954
812
 
813
+ const rows = getRows(1000)
814
+
955
815
  const columnsAligned = [
956
816
  {
957
817
  name: 'name',
@@ -1058,7 +918,7 @@ export default defineComponent({
1058
918
  const draggable = ref('both')
1059
919
  const tableColumns = ref(columns)
1060
920
  const tableColumnsAligned = ref(columnsAligned)
1061
- const rowsPerPageOptions = ref([5, 10, 12, 14, 16, 100])
921
+ const rowsPerPageOptions = ref([10, 12, 14, 16, 100])
1062
922
 
1063
923
  const infiniteLoading = ref(false)
1064
924
 
@@ -67,6 +67,7 @@ import DlThumbnailGallery from './DlThumbnailGalleryDemo.vue'
67
67
  import DlLayoutDemo from './DlLayoutDemo.vue'
68
68
  import { DlCodeEditorDemo } from './DlCodeEditor'
69
69
  import DlLabelPickerDemo from './DlLabelPickerDemo.vue'
70
+ import DlInfiniteScrollDemo from './DlInfiniteScrollDemo.vue'
70
71
 
71
72
  export default {
72
73
  AvatarDemo,
@@ -134,5 +135,6 @@ export default {
134
135
  DlThumbnailGallery,
135
136
  DlCodeEditorDemo,
136
137
  DlLayoutDemo,
137
- DlLabelPickerDemo
138
+ DlLabelPickerDemo,
139
+ DlInfiniteScrollDemo
138
140
  }