virtual-scroller 1.7.7 → 1.8.1
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/.gitlab-ci.yml +1 -1
- package/CHANGELOG.md +24 -1
- package/README.md +139 -33
- package/babel.config.js +25 -0
- package/babel.js +5 -0
- package/bundle/index-bypass.html +1 -1
- package/bundle/index-dom.html +1 -1
- package/bundle/index-grid.html +1 -2
- package/bundle/index-scrollableContainer.html +1 -1
- package/bundle/index-tbody-scrollableContainer.html +2 -0
- package/bundle/index-tbody.html +2 -0
- package/bundle/virtual-scroller-dom.js +1 -1
- package/bundle/virtual-scroller-dom.js.map +1 -1
- package/bundle/virtual-scroller-react.js +1 -1
- package/bundle/virtual-scroller-react.js.map +1 -1
- package/bundle/virtual-scroller.js +1 -1
- package/bundle/virtual-scroller.js.map +1 -1
- package/commonjs/BeforeResize.js +319 -0
- package/commonjs/BeforeResize.js.map +1 -0
- package/commonjs/DOM/Engine.js +46 -0
- package/commonjs/DOM/Engine.js.map +1 -0
- package/commonjs/DOM/ItemsContainer.js +78 -0
- package/commonjs/DOM/ItemsContainer.js.map +1 -0
- package/commonjs/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +56 -35
- package/commonjs/DOM/ListTopOffsetWatcher.js.map +1 -0
- package/commonjs/DOM/ScrollableContainer.js +56 -81
- package/commonjs/DOM/ScrollableContainer.js.map +1 -1
- package/commonjs/DOM/VirtualScroller.js +20 -15
- package/commonjs/DOM/VirtualScroller.js.map +1 -1
- package/commonjs/DOM/tbody.js +2 -2
- package/commonjs/ItemHeights.js +22 -29
- package/commonjs/ItemHeights.js.map +1 -1
- package/commonjs/Layout.js +588 -215
- package/commonjs/Layout.js.map +1 -1
- package/commonjs/Layout.test.js +191 -0
- package/commonjs/Layout.test.js.map +1 -0
- package/commonjs/ListHeightChangeWatcher.js +126 -0
- package/commonjs/ListHeightChangeWatcher.js.map +1 -0
- package/commonjs/Resize.js +22 -21
- package/commonjs/Resize.js.map +1 -1
- package/commonjs/Scroll.js +148 -88
- package/commonjs/Scroll.js.map +1 -1
- package/commonjs/VirtualScroller.js +1269 -390
- package/commonjs/VirtualScroller.js.map +1 -1
- package/commonjs/getItemCoordinates.js.map +1 -1
- package/commonjs/getItemsDiff.js.map +1 -1
- package/commonjs/getVerticalSpacing.js +8 -8
- package/commonjs/getVerticalSpacing.js.map +1 -1
- package/commonjs/react/VirtualScroller.js +31 -37
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/utility/debounce.js +26 -4
- package/commonjs/utility/debounce.js.map +1 -1
- package/commonjs/utility/debug.js +51 -12
- package/commonjs/utility/debug.js.map +1 -1
- package/commonjs/utility/getStateSnapshot.js +50 -0
- package/commonjs/utility/getStateSnapshot.js.map +1 -0
- package/commonjs/utility/px.js +1 -1
- package/commonjs/utility/px.js.map +1 -1
- package/commonjs/utility/px.test.js +14 -0
- package/commonjs/utility/px.test.js.map +1 -0
- package/commonjs/utility/shallowEqual.js +1 -1
- package/commonjs/utility/shallowEqual.js.map +1 -1
- package/commonjs/utility/throttle.js.map +1 -1
- package/dom/index.d.ts +23 -0
- package/index.d.ts +84 -0
- package/modules/BeforeResize.js +310 -0
- package/modules/BeforeResize.js.map +1 -0
- package/modules/DOM/Engine.js +27 -0
- package/modules/DOM/Engine.js.map +1 -0
- package/modules/DOM/ItemsContainer.js +71 -0
- package/modules/DOM/ItemsContainer.js.map +1 -0
- package/modules/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +57 -35
- package/modules/DOM/ListTopOffsetWatcher.js.map +1 -0
- package/modules/DOM/ScrollableContainer.js +55 -80
- package/modules/DOM/ScrollableContainer.js.map +1 -1
- package/modules/DOM/VirtualScroller.js +15 -14
- package/modules/DOM/VirtualScroller.js.map +1 -1
- package/modules/ItemHeights.js +17 -28
- package/modules/ItemHeights.js.map +1 -1
- package/modules/Layout.js +582 -213
- package/modules/Layout.js.map +1 -1
- package/modules/Layout.test.js +185 -0
- package/modules/Layout.test.js.map +1 -0
- package/modules/ListHeightChangeWatcher.js +119 -0
- package/modules/ListHeightChangeWatcher.js.map +1 -0
- package/modules/Resize.js +21 -20
- package/modules/Resize.js.map +1 -1
- package/modules/Scroll.js +148 -87
- package/modules/Scroll.js.map +1 -1
- package/modules/VirtualScroller.js +1263 -390
- package/modules/VirtualScroller.js.map +1 -1
- package/modules/getItemCoordinates.js.map +1 -1
- package/modules/getItemsDiff.js.map +1 -1
- package/modules/getVerticalSpacing.js +8 -8
- package/modules/getVerticalSpacing.js.map +1 -1
- package/modules/react/VirtualScroller.js +31 -37
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/utility/debounce.js +26 -4
- package/modules/utility/debounce.js.map +1 -1
- package/modules/utility/debug.js +47 -10
- package/modules/utility/debug.js.map +1 -1
- package/modules/utility/getStateSnapshot.js +43 -0
- package/modules/utility/getStateSnapshot.js.map +1 -0
- package/modules/utility/px.js +1 -1
- package/modules/utility/px.js.map +1 -1
- package/modules/utility/px.test.js +9 -0
- package/modules/utility/px.test.js.map +1 -0
- package/modules/utility/shallowEqual.js +1 -1
- package/modules/utility/shallowEqual.js.map +1 -1
- package/modules/utility/throttle.js.map +1 -1
- package/package.json +24 -22
- package/react/index.d.ts +27 -0
- package/source/BeforeResize.js +317 -0
- package/source/DOM/Engine.js +32 -0
- package/source/DOM/ItemsContainer.js +48 -0
- package/source/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +48 -22
- package/source/DOM/ScrollableContainer.js +39 -56
- package/source/DOM/VirtualScroller.js +6 -7
- package/source/ItemHeights.js +19 -24
- package/source/Layout.js +626 -252
- package/source/Layout.test.js +171 -0
- package/source/ListHeightChangeWatcher.js +94 -0
- package/source/Resize.js +23 -15
- package/source/Scroll.js +139 -78
- package/source/VirtualScroller.js +1243 -286
- package/source/getVerticalSpacing.js +7 -7
- package/source/react/VirtualScroller.js +2 -18
- package/source/utility/debounce.js +20 -3
- package/source/utility/debug.js +34 -3
- package/source/utility/getStateSnapshot.js +36 -0
- package/source/utility/px.js +1 -1
- package/source/utility/px.test.js +9 -0
- package/website/index-bypass.html +195 -0
- package/website/index-grid.html +0 -1
- package/website/index-scrollableContainer.html +208 -0
- package/website/index-tbody-scrollableContainer.html +68 -0
- package/website/index-tbody.html +55 -0
- package/commonjs/DOM/RenderingEngine.js +0 -33
- package/commonjs/DOM/RenderingEngine.js.map +0 -1
- package/commonjs/DOM/Screen.js +0 -87
- package/commonjs/DOM/Screen.js.map +0 -1
- package/commonjs/DOM/WaitForStylesToLoad.js.map +0 -1
- package/commonjs/RestoreScroll.js +0 -118
- package/commonjs/RestoreScroll.js.map +0 -1
- package/modules/DOM/RenderingEngine.js +0 -19
- package/modules/DOM/RenderingEngine.js.map +0 -1
- package/modules/DOM/Screen.js +0 -80
- package/modules/DOM/Screen.js.map +0 -1
- package/modules/DOM/WaitForStylesToLoad.js.map +0 -1
- package/modules/RestoreScroll.js +0 -111
- package/modules/RestoreScroll.js.map +0 -1
- package/source/DOM/RenderingEngine.js +0 -22
- package/source/DOM/Screen.js +0 -51
- package/source/RestoreScroll.js +0 -86
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export default function getVerticalSpacing({
|
|
2
|
-
if (
|
|
3
|
-
const firstShownRowTopOffset =
|
|
4
|
-
let firstShownRowHeight =
|
|
1
|
+
export default function getVerticalSpacing({ itemsContainer, renderedItemsCount }) {
|
|
2
|
+
if (renderedItemsCount > 1) {
|
|
3
|
+
const firstShownRowTopOffset = itemsContainer.getNthRenderedItemTopOffset(0)
|
|
4
|
+
let firstShownRowHeight = itemsContainer.getNthRenderedItemHeight(0)
|
|
5
5
|
let i = 1
|
|
6
|
-
while (i <
|
|
7
|
-
const itemTopOffset =
|
|
8
|
-
const itemHeight =
|
|
6
|
+
while (i < renderedItemsCount) {
|
|
7
|
+
const itemTopOffset = itemsContainer.getNthRenderedItemTopOffset(i)
|
|
8
|
+
const itemHeight = itemsContainer.getNthRenderedItemHeight(i)
|
|
9
9
|
// If next row is detected.
|
|
10
10
|
if (itemTopOffset !== firstShownRowTopOffset) {
|
|
11
11
|
// Measure inter-row spacing.
|
|
@@ -17,7 +17,7 @@ const elementType = PropTypes.elementType || PropTypes.oneOfType([
|
|
|
17
17
|
export default class VirtualScroller extends React.Component {
|
|
18
18
|
static propTypes = {
|
|
19
19
|
as: elementType,
|
|
20
|
-
items: PropTypes.arrayOf(PropTypes.
|
|
20
|
+
items: PropTypes.arrayOf(PropTypes.any).isRequired,
|
|
21
21
|
itemComponent: elementType.isRequired,
|
|
22
22
|
itemComponentProps: PropTypes.object,
|
|
23
23
|
estimatedItemHeight: PropTypes.number,
|
|
@@ -27,10 +27,6 @@ export default class VirtualScroller extends React.Component {
|
|
|
27
27
|
// `preserveScrollPosition` property name is deprecated,
|
|
28
28
|
// use `preserveScrollPositionOnPrependItems` instead.
|
|
29
29
|
preserveScrollPosition: PropTypes.bool,
|
|
30
|
-
preserveScrollPositionOfTheBottomOfTheListOnMount: PropTypes.bool,
|
|
31
|
-
// `preserveScrollPositionAtBottomOnMount` property name is deprecated,
|
|
32
|
-
// use `preserveScrollPositionOfTheBottomOfTheListOnMount` property instead.
|
|
33
|
-
preserveScrollPositionAtBottomOnMount: PropTypes.bool,
|
|
34
30
|
measureItemsBatchSize: PropTypes.number,
|
|
35
31
|
scrollableContainer: PropTypes.any,
|
|
36
32
|
// `getScrollableContainer` property is deprecated.
|
|
@@ -109,10 +105,6 @@ export default class VirtualScroller extends React.Component {
|
|
|
109
105
|
initialCustomState,
|
|
110
106
|
onStateChange,
|
|
111
107
|
estimatedItemHeight,
|
|
112
|
-
preserveScrollPositionOfTheBottomOfTheListOnMount,
|
|
113
|
-
// `preserveScrollPositionAtBottomOnMount` property name is deprecated,
|
|
114
|
-
// use `preserveScrollPositionOfTheBottomOfTheListOnMount` property instead.
|
|
115
|
-
preserveScrollPositionAtBottomOnMount,
|
|
116
108
|
initialScrollPosition,
|
|
117
109
|
onScrollPositionChange,
|
|
118
110
|
measureItemsBatchSize,
|
|
@@ -137,10 +129,6 @@ export default class VirtualScroller extends React.Component {
|
|
|
137
129
|
onItemInitialRender: this.onItemInitialRender,
|
|
138
130
|
// `onItemFirstRender(i)` is deprecated, use `onItemInitialRender(item)` instead.
|
|
139
131
|
onItemFirstRender: this.onItemFirstRender,
|
|
140
|
-
preserveScrollPositionOfTheBottomOfTheListOnMount,
|
|
141
|
-
// `preserveScrollPositionAtBottomOnMount` property name is deprecated,
|
|
142
|
-
// use `preserveScrollPositionOfTheBottomOfTheListOnMount` property instead.
|
|
143
|
-
preserveScrollPositionAtBottomOnMount,
|
|
144
132
|
initialScrollPosition,
|
|
145
133
|
onScrollPositionChange,
|
|
146
134
|
shouldUpdateLayoutOnScreenResize: this.shouldUpdateLayoutOnScreenResize,
|
|
@@ -451,10 +439,6 @@ export default class VirtualScroller extends React.Component {
|
|
|
451
439
|
// `preserveScrollPosition` property name is deprecated,
|
|
452
440
|
// use `preserveScrollPositionOnPrependItems` instead.
|
|
453
441
|
preserveScrollPosition,
|
|
454
|
-
preserveScrollPositionOfTheBottomOfTheListOnMount,
|
|
455
|
-
// `preserveScrollPositionAtBottomOnMount` property name is deprecated,
|
|
456
|
-
// use `preserveScrollPositionOfTheBottomOfTheListOnMount` property instead.
|
|
457
|
-
preserveScrollPositionAtBottomOnMount,
|
|
458
442
|
initialScrollPosition,
|
|
459
443
|
onScrollPositionChange,
|
|
460
444
|
measureItemsBatchSize,
|
|
@@ -558,7 +542,7 @@ export default class VirtualScroller extends React.Component {
|
|
|
558
542
|
if (prependedItemsCount > 0) {
|
|
559
543
|
if (preserveScrollPositionOnPrependItems || preserveScrollPosition) {
|
|
560
544
|
if (firstShownItemIndex === 0) {
|
|
561
|
-
this.virtualScroller.
|
|
545
|
+
this.virtualScroller.listHeightChangeWatcher.snapshot({
|
|
562
546
|
previousItems,
|
|
563
547
|
newItems,
|
|
564
548
|
prependedItemsCount
|
|
@@ -8,12 +8,29 @@ import { setTimeout, clearTimeout } from 'request-animation-frame-timeout'
|
|
|
8
8
|
* Same as `lodash`'s `debounce()` for functions with no arguments.
|
|
9
9
|
* @param {function} func
|
|
10
10
|
* @param {number} interval
|
|
11
|
+
* @param {function} [options.onStart]
|
|
12
|
+
* @param {function} [options.onStop]
|
|
11
13
|
* @return {function}
|
|
12
14
|
*/
|
|
13
|
-
export default function debounce(func, interval) {
|
|
15
|
+
export default function debounce(func, interval, { onStart, onStop } = {}) {
|
|
14
16
|
let timeout
|
|
15
17
|
return function(...args) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
if (timeout) {
|
|
20
|
+
clearTimeout(timeout)
|
|
21
|
+
} else {
|
|
22
|
+
if (onStart) {
|
|
23
|
+
onStart()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
timeout = setTimeout(() => {
|
|
27
|
+
timeout = undefined
|
|
28
|
+
if (onStop) {
|
|
29
|
+
onStop()
|
|
30
|
+
}
|
|
31
|
+
func.apply(this, args)
|
|
32
|
+
resolve()
|
|
33
|
+
}, interval)
|
|
34
|
+
})
|
|
18
35
|
}
|
|
19
36
|
}
|
package/source/utility/debug.js
CHANGED
|
@@ -5,7 +5,12 @@ export default function log(...args) {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function warn(...args) {
|
|
8
|
-
|
|
8
|
+
if (isWarn()) {
|
|
9
|
+
if (warningsAreErrors()) {
|
|
10
|
+
return reportError.apply(this, args)
|
|
11
|
+
}
|
|
12
|
+
console.warn(...['[virtual-scroller]'].concat(args))
|
|
13
|
+
}
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
export function reportError(...args) {
|
|
@@ -30,8 +35,34 @@ export function reportError(...args) {
|
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export function isDebug() {
|
|
38
|
+
const debug = getDebug()
|
|
39
|
+
if (debug !== undefined) {
|
|
40
|
+
return debug === true || debug === 'debug'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isWarn() {
|
|
45
|
+
// const debug = getDebug()
|
|
46
|
+
// return debug === undefined
|
|
47
|
+
// || debug === true
|
|
48
|
+
// || debug === 'debug'
|
|
49
|
+
// || debug === 'warn'
|
|
50
|
+
//
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getDebug() {
|
|
55
|
+
return getGlobalVariable('VirtualScrollerDebug')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function warningsAreErrors() {
|
|
59
|
+
return getGlobalVariable('VirtualScrollerWarningsAreErrors')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getGlobalVariable(name) {
|
|
33
63
|
if (typeof window !== 'undefined') {
|
|
34
|
-
return window
|
|
35
|
-
|
|
64
|
+
return window[name]
|
|
65
|
+
} else if (typeof global !== 'undefined') {
|
|
66
|
+
return global[name]
|
|
36
67
|
}
|
|
37
68
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Creates a snapshot of a `state` or a partial update of a `state`.
|
|
2
|
+
// Is only used for logging state snapshots for later debug.
|
|
3
|
+
//
|
|
4
|
+
// When `state` is output to the browser console via `console.log()`,
|
|
5
|
+
// it is explorable in real time. That also means that if that `state`
|
|
6
|
+
// is modified later, the user will see the modified state, not the
|
|
7
|
+
// original one. In the current implementation, `state` is not strictly
|
|
8
|
+
// "immutable": things like individual item heights (including "before resize" ones)
|
|
9
|
+
// or states are updated in-place — `state.itemHeights[i] = newItemHeight` or
|
|
10
|
+
// `state.itemStates[i] = newItemState`. That's because those `state` properties
|
|
11
|
+
// are the ones that don’t affect the presentation, so there's no need to re-render
|
|
12
|
+
// the list when those do change — updating those properties is just an effect of
|
|
13
|
+
// some change rather than cause for one.
|
|
14
|
+
//
|
|
15
|
+
// So, when outputting `state` via `console.log()` for debug, it makes sense to
|
|
16
|
+
// snapshot it so that the developer, while debugging later, sees the correct
|
|
17
|
+
// item heights or item states.
|
|
18
|
+
//
|
|
19
|
+
export default function getStateSnapshot(state) {
|
|
20
|
+
let stateSnapshot = {
|
|
21
|
+
...state
|
|
22
|
+
}
|
|
23
|
+
if (state.itemHeights) {
|
|
24
|
+
stateSnapshot.itemHeights = state.itemHeights.slice()
|
|
25
|
+
}
|
|
26
|
+
if (state.itemStates) {
|
|
27
|
+
stateSnapshot.itemStates = state.itemStates.slice()
|
|
28
|
+
}
|
|
29
|
+
if (state.beforeResize) {
|
|
30
|
+
stateSnapshot.beforeResize = {
|
|
31
|
+
...state.beforeResize
|
|
32
|
+
}
|
|
33
|
+
stateSnapshot.beforeResize.itemHeights = state.beforeResize.itemHeights.slice()
|
|
34
|
+
}
|
|
35
|
+
return stateSnapshot
|
|
36
|
+
}
|
package/source/utility/px.js
CHANGED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<!-- Fix encoding. -->
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<!-- Fix document width for mobile devices. -->
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
|
|
9
|
+
<title>React VirtualScroller Demo</title>
|
|
10
|
+
|
|
11
|
+
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
|
|
12
|
+
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
|
|
13
|
+
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
|
|
14
|
+
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
|
15
|
+
|
|
16
|
+
<script src="./virtual-scroller-react.js"></script>
|
|
17
|
+
<script src="./on-scroll-to-react.js"></script>
|
|
18
|
+
<script src="./messages.js"></script>
|
|
19
|
+
|
|
20
|
+
<link rel="stylesheet" href="./style.css"/>
|
|
21
|
+
|
|
22
|
+
<!--
|
|
23
|
+
<style>
|
|
24
|
+
#messages {
|
|
25
|
+
border-top: 2px solid rgb(230, 236, 240);
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-gap: 2em;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
30
|
+
-->
|
|
31
|
+
</head>
|
|
32
|
+
|
|
33
|
+
<body>
|
|
34
|
+
<!-- http://tholman.com/github-corners/ -->
|
|
35
|
+
<a title="Go to GitHub repo" href="https://github.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
|
|
36
|
+
|
|
37
|
+
<div id="root"></div>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
// Enable debug output to console.
|
|
41
|
+
window.VirtualScrollerDebug = true
|
|
42
|
+
// Whether "dynamically loaded list" mode is enabled.
|
|
43
|
+
window.dynamic = new URL(window.location).searchParams.get('dynamic')
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<script type="text/babel">
|
|
47
|
+
const BATCH_SIZE = 6
|
|
48
|
+
|
|
49
|
+
class FeedMessages extends React.Component {
|
|
50
|
+
constructor(props) {
|
|
51
|
+
super(props)
|
|
52
|
+
const { messages } = this.props
|
|
53
|
+
if (window.dynamic) {
|
|
54
|
+
const fromIndex = Math.floor(messages.length / 2 - BATCH_SIZE / 2)
|
|
55
|
+
const toIndex = fromIndex + BATCH_SIZE - 1
|
|
56
|
+
this.state = {
|
|
57
|
+
fromIndex,
|
|
58
|
+
toIndex,
|
|
59
|
+
messages: messages.slice(fromIndex, toIndex + 1)
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
this.state = {
|
|
63
|
+
fromIndex: 0,
|
|
64
|
+
toIndex: messages.length,
|
|
65
|
+
messages
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onShowPrevious = () => {
|
|
71
|
+
const { messages } = this.props
|
|
72
|
+
let { fromIndex } = this.state
|
|
73
|
+
const { toIndex } = this.state
|
|
74
|
+
fromIndex = Math.max(fromIndex - BATCH_SIZE, 0)
|
|
75
|
+
this.setState({
|
|
76
|
+
fromIndex,
|
|
77
|
+
messages: messages.slice(fromIndex, toIndex + 1)
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
onShowNext = () => {
|
|
82
|
+
const { messages } = this.props
|
|
83
|
+
const { fromIndex } = this.state
|
|
84
|
+
let { toIndex } = this.state
|
|
85
|
+
toIndex = Math.min(toIndex + BATCH_SIZE, messages.length - 1)
|
|
86
|
+
this.setState({
|
|
87
|
+
toIndex,
|
|
88
|
+
messages: messages.slice(fromIndex, toIndex + 1)
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
render() {
|
|
93
|
+
const {
|
|
94
|
+
fromIndex,
|
|
95
|
+
toIndex,
|
|
96
|
+
messages
|
|
97
|
+
} = this.state
|
|
98
|
+
return (
|
|
99
|
+
<React.Fragment>
|
|
100
|
+
{window.dynamic && fromIndex > 0 &&
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onClick={this.onShowPrevious}
|
|
104
|
+
className="load-items-button">
|
|
105
|
+
Show previous
|
|
106
|
+
</button>
|
|
107
|
+
}
|
|
108
|
+
<VirtualScroller
|
|
109
|
+
bypass
|
|
110
|
+
id="messages"
|
|
111
|
+
items={messages}
|
|
112
|
+
itemComponent={Message}
|
|
113
|
+
preserveScrollPositionOnPrependItems/>
|
|
114
|
+
{window.dynamic && toIndex < this.props.messages.length - 1 &&
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
onClick={this.onShowNext}
|
|
118
|
+
className="load-items-button">
|
|
119
|
+
Show next
|
|
120
|
+
</button>
|
|
121
|
+
}
|
|
122
|
+
</React.Fragment>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const message = PropTypes.shape({
|
|
128
|
+
username: PropTypes.string.isRequired,
|
|
129
|
+
date: PropTypes.instanceOf(Date).isRequired,
|
|
130
|
+
text: PropTypes.string.isRequired
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
FeedMessages.propTypes = {
|
|
134
|
+
messages: PropTypes.arrayOf(message).isRequired
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function Message(props) {
|
|
138
|
+
const { children: message } = props
|
|
139
|
+
const {
|
|
140
|
+
username,
|
|
141
|
+
date,
|
|
142
|
+
text
|
|
143
|
+
} = message
|
|
144
|
+
return (
|
|
145
|
+
<article className="feed-message">
|
|
146
|
+
<a target="_blank" href={`https://twitter.com/${username}`}>
|
|
147
|
+
@{username}
|
|
148
|
+
</a>
|
|
149
|
+
<time dateTime={date.toISOString()}>
|
|
150
|
+
{date.getMonth() + 1}/{date.getDate()}/{date.getFullYear()}
|
|
151
|
+
</time>
|
|
152
|
+
<p>
|
|
153
|
+
{text}
|
|
154
|
+
</p>
|
|
155
|
+
</article>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Message.propTypes = {
|
|
160
|
+
children: message.isRequired
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
class Feed extends React.Component {
|
|
164
|
+
render() {
|
|
165
|
+
return (
|
|
166
|
+
<section className="container">
|
|
167
|
+
<h1>
|
|
168
|
+
<TwitterLogo/>
|
|
169
|
+
Latest Tweets on #politics
|
|
170
|
+
</h1>
|
|
171
|
+
<FeedMessages
|
|
172
|
+
messages={messages}/>
|
|
173
|
+
<footer>
|
|
174
|
+
© Twitter Inc., 2019
|
|
175
|
+
</footer>
|
|
176
|
+
</section>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function TwitterLogo() {
|
|
182
|
+
return (
|
|
183
|
+
<svg className="twitter-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
|
184
|
+
<path fill="#00AAEC" fillRule="evenodd" d="M128 23.294a51.28 51.28 0 0 1-15.079 4.237c5.424-3.328 9.587-8.606 11.548-14.892a51.718 51.718 0 0 1-16.687 6.526c-4.778-5.231-11.608-8.498-19.166-8.498-14.493 0-26.251 12.057-26.251 26.927 0 2.111.225 4.16.676 6.133-21.824-1.126-41.17-11.835-54.131-28.145a27.422 27.422 0 0 0-3.554 13.552c0 9.338 4.636 17.581 11.683 22.412-4.297-.131-8.355-1.356-11.901-3.359v.331c0 13.051 9.053 23.937 21.074 26.403-2.201.632-4.523.948-6.92.948-1.69 0-3.343-.162-4.944-.478 3.343 10.694 13.035 18.483 24.53 18.691-8.986 7.227-20.315 11.533-32.614 11.533-2.119 0-4.215-.123-6.266-.37 11.623 7.627 25.432 12.088 40.255 12.088 48.309 0 74.717-41.026 74.717-76.612a89.39 89.39 0 0 0-.068-3.49A53.862 53.862 0 0 0 128 23.294" clipRule="evenodd"/>
|
|
185
|
+
</svg>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
ReactDOM.render(
|
|
190
|
+
<Feed/>,
|
|
191
|
+
document.getElementById('root')
|
|
192
|
+
)
|
|
193
|
+
</script>
|
|
194
|
+
</body>
|
|
195
|
+
</html>
|
package/website/index-grid.html
CHANGED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<!-- Fix encoding. -->
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<!-- Fix document width for mobile devices. -->
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
|
|
9
|
+
<title>React VirtualScroller Demo (Scrollable Container)</title>
|
|
10
|
+
|
|
11
|
+
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
|
|
12
|
+
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
|
|
13
|
+
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
|
|
14
|
+
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
|
15
|
+
|
|
16
|
+
<script src="./virtual-scroller-react.js"></script>
|
|
17
|
+
<script src="./on-scroll-to-react.js"></script>
|
|
18
|
+
<script src="./messages.js"></script>
|
|
19
|
+
|
|
20
|
+
<link rel="stylesheet" href="./style.css"/>
|
|
21
|
+
</head>
|
|
22
|
+
|
|
23
|
+
<body>
|
|
24
|
+
<!-- http://tholman.com/github-corners/ -->
|
|
25
|
+
<a title="Go to GitHub repo" href="https://github.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
|
|
26
|
+
|
|
27
|
+
<div id="root"></div>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
// Enable debug output to console.
|
|
31
|
+
window.VirtualScrollerDebug = true
|
|
32
|
+
// Whether "dynamically loaded list" mode is enabled.
|
|
33
|
+
window.dynamic = new URL(window.location).searchParams.get('dynamic')
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<script type="text/babel">
|
|
37
|
+
const BATCH_SIZE = 6
|
|
38
|
+
|
|
39
|
+
const SCROLLABLE_CONTAINER_STYLE = {
|
|
40
|
+
marginTop: '20vh',
|
|
41
|
+
maxHeight: '60vh',
|
|
42
|
+
overflow: 'auto'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class FeedMessages extends React.Component {
|
|
46
|
+
constructor(props) {
|
|
47
|
+
super(props)
|
|
48
|
+
const { messages } = this.props
|
|
49
|
+
if (window.dynamic) {
|
|
50
|
+
const fromIndex = Math.floor(messages.length / 2 - BATCH_SIZE / 2)
|
|
51
|
+
const toIndex = fromIndex + BATCH_SIZE - 1
|
|
52
|
+
this.state = {
|
|
53
|
+
fromIndex,
|
|
54
|
+
toIndex,
|
|
55
|
+
messages: messages.slice(fromIndex, toIndex + 1)
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
this.state = {
|
|
59
|
+
fromIndex: 0,
|
|
60
|
+
toIndex: messages.length,
|
|
61
|
+
messages
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onShowPrevious = () => {
|
|
67
|
+
const { messages } = this.props
|
|
68
|
+
let { fromIndex } = this.state
|
|
69
|
+
const { toIndex } = this.state
|
|
70
|
+
fromIndex = Math.max(fromIndex - BATCH_SIZE, 0)
|
|
71
|
+
this.setState({
|
|
72
|
+
fromIndex,
|
|
73
|
+
messages: messages.slice(fromIndex, toIndex + 1)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onShowNext = () => {
|
|
78
|
+
const { messages } = this.props
|
|
79
|
+
const { fromIndex } = this.state
|
|
80
|
+
let { toIndex } = this.state
|
|
81
|
+
toIndex = Math.min(toIndex + BATCH_SIZE, messages.length - 1)
|
|
82
|
+
this.setState({
|
|
83
|
+
toIndex,
|
|
84
|
+
messages: messages.slice(fromIndex, toIndex + 1)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getScrollableContainer = () => {
|
|
89
|
+
return this.scrollableContainer
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setScrollableContainer = (node) => {
|
|
93
|
+
this.scrollableContainer = node
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
componentDidMount() {
|
|
97
|
+
this.setState({ isMounted: true })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
render() {
|
|
101
|
+
const {
|
|
102
|
+
fromIndex,
|
|
103
|
+
toIndex,
|
|
104
|
+
messages,
|
|
105
|
+
isMounted
|
|
106
|
+
} = this.state
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
ref={this.setScrollableContainer}
|
|
110
|
+
style={SCROLLABLE_CONTAINER_STYLE}>
|
|
111
|
+
{window.dynamic && fromIndex > 0 &&
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
onClick={this.onShowPrevious}
|
|
115
|
+
className="load-items-button">
|
|
116
|
+
Show previous
|
|
117
|
+
</button>
|
|
118
|
+
}
|
|
119
|
+
{isMounted &&
|
|
120
|
+
<VirtualScroller
|
|
121
|
+
id="messages"
|
|
122
|
+
items={messages}
|
|
123
|
+
itemComponent={Message}
|
|
124
|
+
preserveScrollPosition
|
|
125
|
+
scrollableContainer={this.scrollableContainer}/>
|
|
126
|
+
}
|
|
127
|
+
{window.dynamic && toIndex < this.props.messages.length - 1 &&
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
onClick={this.onShowNext}
|
|
131
|
+
className="load-items-button">
|
|
132
|
+
Show next
|
|
133
|
+
</button>
|
|
134
|
+
}
|
|
135
|
+
</div>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const message = PropTypes.shape({
|
|
141
|
+
username: PropTypes.string.isRequired,
|
|
142
|
+
date: PropTypes.instanceOf(Date).isRequired,
|
|
143
|
+
text: PropTypes.string.isRequired
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
FeedMessages.propTypes = {
|
|
147
|
+
messages: PropTypes.arrayOf(message).isRequired
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function Message(props) {
|
|
151
|
+
const { children: message } = props
|
|
152
|
+
const {
|
|
153
|
+
username,
|
|
154
|
+
date,
|
|
155
|
+
text
|
|
156
|
+
} = message
|
|
157
|
+
return (
|
|
158
|
+
<article className="feed-message">
|
|
159
|
+
<a target="_blank" href={`https://twitter.com/${username}`}>
|
|
160
|
+
@{username}
|
|
161
|
+
</a>
|
|
162
|
+
<time dateTime={date.toISOString()}>
|
|
163
|
+
{date.getMonth() + 1}/{date.getDate()}/{date.getFullYear()}
|
|
164
|
+
</time>
|
|
165
|
+
<p>
|
|
166
|
+
{text}
|
|
167
|
+
</p>
|
|
168
|
+
</article>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Message.propTypes = {
|
|
173
|
+
children: message.isRequired
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
class Feed extends React.Component {
|
|
177
|
+
render() {
|
|
178
|
+
return (
|
|
179
|
+
<section className="container">
|
|
180
|
+
<h1>
|
|
181
|
+
<TwitterLogo/>
|
|
182
|
+
Latest Tweets on #politics
|
|
183
|
+
</h1>
|
|
184
|
+
<FeedMessages
|
|
185
|
+
messages={messages}/>
|
|
186
|
+
<footer>
|
|
187
|
+
© Twitter Inc., 2019
|
|
188
|
+
</footer>
|
|
189
|
+
</section>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function TwitterLogo() {
|
|
195
|
+
return (
|
|
196
|
+
<svg className="twitter-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
|
197
|
+
<path fill="#00AAEC" fillRule="evenodd" d="M128 23.294a51.28 51.28 0 0 1-15.079 4.237c5.424-3.328 9.587-8.606 11.548-14.892a51.718 51.718 0 0 1-16.687 6.526c-4.778-5.231-11.608-8.498-19.166-8.498-14.493 0-26.251 12.057-26.251 26.927 0 2.111.225 4.16.676 6.133-21.824-1.126-41.17-11.835-54.131-28.145a27.422 27.422 0 0 0-3.554 13.552c0 9.338 4.636 17.581 11.683 22.412-4.297-.131-8.355-1.356-11.901-3.359v.331c0 13.051 9.053 23.937 21.074 26.403-2.201.632-4.523.948-6.92.948-1.69 0-3.343-.162-4.944-.478 3.343 10.694 13.035 18.483 24.53 18.691-8.986 7.227-20.315 11.533-32.614 11.533-2.119 0-4.215-.123-6.266-.37 11.623 7.627 25.432 12.088 40.255 12.088 48.309 0 74.717-41.026 74.717-76.612a89.39 89.39 0 0 0-.068-3.49A53.862 53.862 0 0 0 128 23.294" clipRule="evenodd"/>
|
|
198
|
+
</svg>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
ReactDOM.render(
|
|
203
|
+
<Feed/>,
|
|
204
|
+
document.getElementById('root')
|
|
205
|
+
)
|
|
206
|
+
</script>
|
|
207
|
+
</body>
|
|
208
|
+
</html>
|