virtual-scroller 1.12.2 → 1.12.4
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/CHANGELOG.md +7 -0
- package/CODE_OF_CONDUCT.md +78 -0
- package/README.md +1 -1
- package/bundle/index-bypass.html +2 -2
- package/bundle/index-grid.html +2 -2
- package/bundle/index-scrollableContainer.html +2 -2
- 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/Layout.js +10 -13
- package/commonjs/Layout.js.map +1 -1
- package/commonjs/Layout.test.js +20 -10
- package/commonjs/Layout.test.js.map +1 -1
- package/commonjs/Scroll.js +2 -1
- package/commonjs/Scroll.js.map +1 -1
- package/commonjs/{Resize.js → ScrollableContainerResizeHandler.js} +7 -7
- package/commonjs/ScrollableContainerResizeHandler.js.map +1 -0
- package/commonjs/VirtualScroller.constructor.js +5 -5
- package/commonjs/VirtualScroller.constructor.js.map +1 -1
- package/commonjs/VirtualScroller.items.js +1 -1
- package/commonjs/VirtualScroller.items.js.map +1 -1
- package/commonjs/VirtualScroller.js +67 -28
- package/commonjs/VirtualScroller.js.map +1 -1
- package/commonjs/VirtualScroller.layout.js +24 -22
- package/commonjs/VirtualScroller.layout.js.map +1 -1
- package/commonjs/{VirtualScroller.resize.js → VirtualScroller.onContainerResize.js} +2 -2
- package/commonjs/VirtualScroller.onContainerResize.js.map +1 -0
- package/commonjs/VirtualScroller.onRender.js +2 -2
- package/commonjs/VirtualScroller.onRender.js.map +1 -1
- package/commonjs/react/VirtualScroller.js +3 -0
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/react/useEffectDontMountTwiceInStrictMode.js +83 -0
- package/commonjs/react/useEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/commonjs/react/useInsertionEffectDontMountTwiceInStrictMode.js +20 -0
- package/commonjs/react/useInsertionEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/commonjs/react/useLayoutEffectDontMountTwiceInStrictMode.js +20 -0
- package/commonjs/react/useLayoutEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/commonjs/react/useState.js +13 -7
- package/commonjs/react/useState.js.map +1 -1
- package/commonjs/react/useStateNoStaleBug.js +59 -0
- package/commonjs/react/useStateNoStaleBug.js.map +1 -0
- package/modules/Layout.js +10 -13
- package/modules/Layout.js.map +1 -1
- package/modules/Layout.test.js +20 -10
- package/modules/Layout.test.js.map +1 -1
- package/modules/Scroll.js +2 -1
- package/modules/Scroll.js.map +1 -1
- package/modules/{Resize.js → ScrollableContainerResizeHandler.js} +7 -7
- package/modules/ScrollableContainerResizeHandler.js.map +1 -0
- package/modules/VirtualScroller.constructor.js +5 -5
- package/modules/VirtualScroller.constructor.js.map +1 -1
- package/modules/VirtualScroller.items.js +1 -1
- package/modules/VirtualScroller.items.js.map +1 -1
- package/modules/VirtualScroller.js +67 -28
- package/modules/VirtualScroller.js.map +1 -1
- package/modules/VirtualScroller.layout.js +24 -22
- package/modules/VirtualScroller.layout.js.map +1 -1
- package/modules/{VirtualScroller.resize.js → VirtualScroller.onContainerResize.js} +2 -2
- package/modules/VirtualScroller.onContainerResize.js.map +1 -0
- package/modules/VirtualScroller.onRender.js +2 -2
- package/modules/VirtualScroller.onRender.js.map +1 -1
- package/modules/react/VirtualScroller.js +3 -1
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/react/useEffectDontMountTwiceInStrictMode.js +75 -0
- package/modules/react/useEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/modules/react/useInsertionEffectDontMountTwiceInStrictMode.js +9 -0
- package/modules/react/useInsertionEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/modules/react/useLayoutEffectDontMountTwiceInStrictMode.js +9 -0
- package/modules/react/useLayoutEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/modules/react/useState.js +11 -8
- package/modules/react/useState.js.map +1 -1
- package/modules/react/useStateNoStaleBug.js +51 -0
- package/modules/react/useStateNoStaleBug.js.map +1 -0
- package/package.json +1 -1
- package/source/Layout.js +10 -13
- package/source/Layout.test.js +20 -10
- package/source/Scroll.js +1 -0
- package/source/{Resize.js → ScrollableContainerResizeHandler.js} +1 -1
- package/source/VirtualScroller.constructor.js +5 -5
- package/source/VirtualScroller.items.js +1 -1
- package/source/VirtualScroller.js +65 -25
- package/source/VirtualScroller.layout.js +22 -20
- package/source/{VirtualScroller.resize.js → VirtualScroller.onContainerResize.js} +1 -1
- package/source/VirtualScroller.onRender.js +2 -2
- package/source/react/VirtualScroller.js +3 -0
- package/source/react/useEffectDontMountTwiceInStrictMode.js +68 -0
- package/source/react/useInsertionEffectDontMountTwiceInStrictMode.js +10 -0
- package/source/react/useLayoutEffectDontMountTwiceInStrictMode.js +10 -0
- package/source/react/useState.js +8 -5
- package/source/react/useStateNoStaleBug.js +35 -0
- package/website/index-bypass.html +4 -14
- package/website/index-dom.html +1 -1
- package/website/index-grid.html +4 -4
- package/website/index-scrollableContainer.html +4 -4
- package/website/index-tbody-scrollableContainer.html +2 -0
- package/website/index-tbody.html +2 -0
- package/website/index.html +3 -3
- package/commonjs/Resize.js.map +0 -1
- package/commonjs/VirtualScroller.resize.js.map +0 -1
- package/modules/Resize.js.map +0 -1
- package/modules/VirtualScroller.resize.js.map +0 -1
|
@@ -49,7 +49,11 @@ export default class VirtualScroller {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
if (isRestart) {
|
|
53
|
+
log('~ Start (restart) ~')
|
|
54
|
+
} else {
|
|
55
|
+
log('~ Start ~')
|
|
56
|
+
}
|
|
53
57
|
|
|
54
58
|
// `this._isActive = true` should be placed somewhere at the start of this function.
|
|
55
59
|
this._isActive = true
|
|
@@ -71,18 +75,13 @@ export default class VirtualScroller {
|
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
// If there was a pending state update that didn't get applied
|
|
75
|
-
// because
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// * `beforeResize`
|
|
82
|
-
// All of those get rewritten in `onResize()` anyway.
|
|
83
|
-
//
|
|
84
|
-
let stateUpdate = this._stoppedStateUpdate
|
|
85
|
-
this._stoppedStateUpdate = undefined
|
|
78
|
+
// If there was a pending "after render" state update that didn't get applied
|
|
79
|
+
// because the `VirtualScroller` got stopped, then apply that pending "after render"
|
|
80
|
+
// state update now. Such state update could include properties like:
|
|
81
|
+
// * A `verticalSpacing` that has been measured in `onRender()`.
|
|
82
|
+
// * A cleaned-up `beforeResize` object that was cleaned-up in `onRender()`.
|
|
83
|
+
let stateUpdate = this._afterRenderStateUpdateThatWasStopped
|
|
84
|
+
this._afterRenderStateUpdateThatWasStopped = undefined
|
|
86
85
|
|
|
87
86
|
// Reset `this.verticalSpacing` so that it re-measures it in cases when
|
|
88
87
|
// the `VirtualScroller` was previously stopped and is now being restarted.
|
|
@@ -103,7 +102,7 @@ export default class VirtualScroller {
|
|
|
103
102
|
}
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
this.
|
|
105
|
+
this.scrollableContainerResizeHandler.start()
|
|
107
106
|
this.scroll.start()
|
|
108
107
|
|
|
109
108
|
// If `scrollableContainerWidth` hasn't been measured yet,
|
|
@@ -122,13 +121,11 @@ export default class VirtualScroller {
|
|
|
122
121
|
const prevWidth = this.getState().scrollableContainerWidth
|
|
123
122
|
if (newWidth !== prevWidth) {
|
|
124
123
|
log('~ Scrollable container width changed from', prevWidth, 'to', newWidth, '~')
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
// All of those get rewritten in `onResize()` anyway.
|
|
131
|
-
return this.onResize()
|
|
124
|
+
// The pending state update (if present) won't be applied in this case.
|
|
125
|
+
// That's ok because such state update could currently only originate in
|
|
126
|
+
// `this.onContainerResize()` function. Therefore, alling `this.onContainerResize()` again
|
|
127
|
+
// would rewrite all those `stateUpdate` properties anyway, so they're not passed.
|
|
128
|
+
return this.onContainerResize()
|
|
132
129
|
}
|
|
133
130
|
}
|
|
134
131
|
|
|
@@ -139,7 +136,7 @@ export default class VirtualScroller {
|
|
|
139
136
|
const columnsCount = this.getActualColumnsCount()
|
|
140
137
|
const columnsCountFromState = this.getState().columnsCount || 1
|
|
141
138
|
if (columnsCount !== columnsCountFromState) {
|
|
142
|
-
return this.
|
|
139
|
+
return this.onContainerResize()
|
|
143
140
|
}
|
|
144
141
|
}
|
|
145
142
|
|
|
@@ -161,7 +158,7 @@ export default class VirtualScroller {
|
|
|
161
158
|
|
|
162
159
|
log('~ Stop ~')
|
|
163
160
|
|
|
164
|
-
this.
|
|
161
|
+
this.scrollableContainerResizeHandler.stop()
|
|
165
162
|
this.scroll.stop()
|
|
166
163
|
|
|
167
164
|
// Stop `ListTopOffsetWatcher` if it has been started.
|
|
@@ -221,7 +218,9 @@ export default class VirtualScroller {
|
|
|
221
218
|
* @param {number} i — Item index
|
|
222
219
|
*/
|
|
223
220
|
onItemHeightDidChange(i) {
|
|
224
|
-
|
|
221
|
+
// See the comments in the `setItemState()` function below for the rationale
|
|
222
|
+
// on why the `hasToBeStarted()` check was commented out.
|
|
223
|
+
// this.hasToBeStarted()
|
|
225
224
|
this._onItemHeightDidChange(i)
|
|
226
225
|
}
|
|
227
226
|
|
|
@@ -231,7 +230,48 @@ export default class VirtualScroller {
|
|
|
231
230
|
* @param {any} i — Item's new state
|
|
232
231
|
*/
|
|
233
232
|
setItemState(i, newItemState) {
|
|
234
|
-
|
|
233
|
+
// There is an issue in React 18.2.0 when `useInsertionEffect()` doesn't run twice
|
|
234
|
+
// on mount unlike `useLayoutEffect()` in "strict" mode. That causes a bug in a React
|
|
235
|
+
// implementation of the `virtual-scroller`.
|
|
236
|
+
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/33
|
|
237
|
+
// https://github.com/facebook/react/issues/26320
|
|
238
|
+
// A workaround for that bug is ignoring the second-initial run of the effects at mount.
|
|
239
|
+
//
|
|
240
|
+
// But in that case, if an `ItemComponent` calls `setItemState()` in `useLayoutEffect()`,
|
|
241
|
+
// it could result in a bug.
|
|
242
|
+
//
|
|
243
|
+
// Consider a type of `useLayoutEffect()` that skips the initial mount:
|
|
244
|
+
// `useLayoutEffectSkipInitialMount()`.
|
|
245
|
+
// Suppose that effect is written in such a way that it only skips the first call of itself.
|
|
246
|
+
// In that case, if React is run in "strict" mode, the effect will no longer work as expected
|
|
247
|
+
// and it won't actually skip the initial mount and will be executed during the second initial run.
|
|
248
|
+
// But the `VirtualScroller` itself has already implemented a workaround that prevents
|
|
249
|
+
// its hooks from running twice on mount. This means that `useVirtualScrollerStartStop()`
|
|
250
|
+
// of the React component would have already stopped the `VirtualScroller` by the time
|
|
251
|
+
// `ItemComponent`'s incorrectly-behaving `useLayoutEffectSkipInitialMount()` effect is run,
|
|
252
|
+
// resulting in an error: "`VirtualScroller` hasn't been started".
|
|
253
|
+
//
|
|
254
|
+
// The log when not in "strict" mode would be:
|
|
255
|
+
//
|
|
256
|
+
// * `useLayoutEffect()` is run in `ItemComponent` — skips the initial run.
|
|
257
|
+
// * `useLayoutEffect()` is run in `useVirtualScrollerStartStop()`. It starts the `VirtualScroller`.
|
|
258
|
+
// * Some dependency property gets updated inside `ItemComponent`.
|
|
259
|
+
// * `useLayoutEffect()` is run in `ItemComponent` — no longer skips. Calls `setItemState()`.
|
|
260
|
+
// * The `VirtualScroller` is started so it handles `setState()` correctly.
|
|
261
|
+
//
|
|
262
|
+
// The log when in "strict" mode would be:
|
|
263
|
+
//
|
|
264
|
+
// * `useLayoutEffect()` is run in `ItemComponent` — skips the initial run.
|
|
265
|
+
// * `useLayoutEffect()` is run in `useVirtualScrollerStartStop()`. It starts the `VirtualScroller`.
|
|
266
|
+
// * `useLayoutEffect()` is unmounted in `useVirtualScrollerStartStop()`. It stops the `VirtualScroller`.
|
|
267
|
+
// * `useLayoutEffect()` is unmounted in `ItemComponent` — does nothing.
|
|
268
|
+
// * `useLayoutEffect()` is run the second time in `ItemComponent` — no longer skips. Calls `setItemState()`.
|
|
269
|
+
// * The `VirtualScroller` is stopped so it throws an error: "`VirtualScroller` hasn't been started".
|
|
270
|
+
//
|
|
271
|
+
// For that reason, the requirement of the `VirtualScroller` to be started was commented out.
|
|
272
|
+
// Commenting it out wouldn't result in any potential bugs because the code would work correctly
|
|
273
|
+
// in both cases.
|
|
274
|
+
// this.hasToBeStarted()
|
|
235
275
|
this._setItemState(i, newItemState)
|
|
236
276
|
}
|
|
237
277
|
|
|
@@ -217,25 +217,22 @@ export default function() {
|
|
|
217
217
|
})
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
function
|
|
221
|
-
const
|
|
222
|
-
this.latestLayoutVisibleArea =
|
|
220
|
+
function getCoordinatesOfVisibleAreaInsideTheList() {
|
|
221
|
+
const visibleAreaBounds = this.scroll.getVisibleAreaBounds()
|
|
222
|
+
this.latestLayoutVisibleArea = visibleAreaBounds
|
|
223
223
|
|
|
224
224
|
// Subtract the top offset of the list inside the scrollable container.
|
|
225
225
|
const listTopOffsetInsideScrollableContainer = this.getListTopOffsetInsideScrollableContainer()
|
|
226
226
|
return {
|
|
227
|
-
top:
|
|
228
|
-
bottom:
|
|
227
|
+
top: visibleAreaBounds.top - listTopOffsetInsideScrollableContainer,
|
|
228
|
+
bottom: visibleAreaBounds.bottom - listTopOffsetInsideScrollableContainer
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
function getShownItemIndexes() {
|
|
233
233
|
const itemsCount = this.getItemsCount()
|
|
234
234
|
|
|
235
|
-
const
|
|
236
|
-
top: visibleAreaTop,
|
|
237
|
-
bottom: visibleAreaBottom
|
|
238
|
-
} = getVisibleArea.call(this)
|
|
235
|
+
const visibleAreaInsideTheList = getCoordinatesOfVisibleAreaInsideTheList.call(this)
|
|
239
236
|
|
|
240
237
|
if (this.bypass) {
|
|
241
238
|
return {
|
|
@@ -254,7 +251,7 @@ export default function() {
|
|
|
254
251
|
// then it would also require listening for "scroll" events on the screen.
|
|
255
252
|
// Overall, I suppose that such "actual visibility" feature would be
|
|
256
253
|
// a very minor optimization and not something I'd deal with.
|
|
257
|
-
const isVisible =
|
|
254
|
+
const isVisible = visibleAreaInsideTheList.top < this.itemsContainer.getHeight() + this.layout.getPrerenderMargin() && visibleAreaInsideTheList.bottom > 0 - this.layout.getPrerenderMargin()
|
|
258
255
|
if (!isVisible) {
|
|
259
256
|
log('The entire list is off-screen. No items are visible.')
|
|
260
257
|
return this.layout.getNonVisibleListShownItemIndexes()
|
|
@@ -263,8 +260,7 @@ export default function() {
|
|
|
263
260
|
// Get shown item indexes.
|
|
264
261
|
return this.layout.getShownItemIndexes({
|
|
265
262
|
itemsCount: this.getItemsCount(),
|
|
266
|
-
|
|
267
|
-
visibleAreaBottom
|
|
263
|
+
visibleAreaInsideTheList
|
|
268
264
|
})
|
|
269
265
|
}
|
|
270
266
|
|
|
@@ -441,9 +437,13 @@ export default function() {
|
|
|
441
437
|
if (previousHeight !== newHeight) {
|
|
442
438
|
log('~ Item height has changed. Should update layout. ~')
|
|
443
439
|
|
|
444
|
-
// Update or reset a previously calculated layout
|
|
445
|
-
// so that the "diff"s based on that
|
|
446
|
-
//
|
|
440
|
+
// Update or reset a previously calculated layout with the new item height
|
|
441
|
+
// so that the potential future "diff"s based on that "previously calculated" layout
|
|
442
|
+
// would be correct.
|
|
443
|
+
//
|
|
444
|
+
// The "previously calculated layout" feature is not currently used
|
|
445
|
+
// so this function call doesn't really affect anything.
|
|
446
|
+
//
|
|
447
447
|
updatePreviouslyCalculatedLayoutOnItemHeightChange.call(this, i, previousHeight, newHeight)
|
|
448
448
|
|
|
449
449
|
// Recalculate layout.
|
|
@@ -455,11 +455,13 @@ export default function() {
|
|
|
455
455
|
// that might happen in the middle of the currently pending `setState()` operation
|
|
456
456
|
// being applied, resulting in weird "race condition" bugs.
|
|
457
457
|
//
|
|
458
|
-
if (this.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
458
|
+
if (this._isActive) {
|
|
459
|
+
if (this.waitingForRender) {
|
|
460
|
+
log('~ Another state update is already waiting to be rendered. Delay the layout update until then. ~')
|
|
461
|
+
this.updateLayoutAfterRenderBecauseItemHeightChanged = true
|
|
462
|
+
} else {
|
|
463
|
+
this.onUpdateShownItemIndexes({ reason: LAYOUT_REASON.ITEM_HEIGHT_CHANGED })
|
|
464
|
+
}
|
|
463
465
|
}
|
|
464
466
|
|
|
465
467
|
// If there was a request for `setState()` with new `items`, then the changes
|
|
@@ -63,7 +63,7 @@ export default function() {
|
|
|
63
63
|
// would construct its own state object.
|
|
64
64
|
if (!shallowEqual(newState, this.mostRecentSetStateValue)) {
|
|
65
65
|
warn('The most recent state that was set', getStateSnapshot(this.mostRecentSetStateValue))
|
|
66
|
-
reportError('
|
|
66
|
+
reportError('`VirtualScroller` has been rendered with a `state` that is not equal to the most recently set one')
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -199,7 +199,7 @@ export default function() {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
if (!this._isActive) {
|
|
202
|
-
this.
|
|
202
|
+
this._afterRenderStateUpdateThatWasStopped = stateUpdate
|
|
203
203
|
return
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -13,6 +13,8 @@ import useUpdateItemKeysOnItemsChange from './useUpdateItemKeysOnItemsChange.js'
|
|
|
13
13
|
import useClassName from './useClassName.js'
|
|
14
14
|
import useStyle from './useStyle.js'
|
|
15
15
|
|
|
16
|
+
import { warn } from '../utility/debug.js'
|
|
17
|
+
|
|
16
18
|
// When `items` property changes:
|
|
17
19
|
// * A new `items` property is supplied to the React component.
|
|
18
20
|
// * The React component re-renders itself.
|
|
@@ -172,6 +174,7 @@ function VirtualScroller({
|
|
|
172
174
|
// `onMount()` option is deprecated due to no longer being used.
|
|
173
175
|
// If someone thinks there's a valid use case for it, create an issue.
|
|
174
176
|
if (onMount) {
|
|
177
|
+
warn('`onMount` property is deprecated')
|
|
175
178
|
onMount()
|
|
176
179
|
}
|
|
177
180
|
}, [])
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
4
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
5
|
+
// https://github.com/facebook/react/issues/26320
|
|
6
|
+
export default function useEffectDontMountTwiceInStrictMode(useEffect, handler, dependencies) {
|
|
7
|
+
if (!Array.isArray(dependencies)) {
|
|
8
|
+
throw new Error('Dependencies argument must be an array')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { onEffect } = useEffectStatus()
|
|
12
|
+
const { onChange } = usePrevousValue(dependencies)
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const { isInitialRun } = onEffect()
|
|
16
|
+
const previousDependencies = onChange(dependencies)
|
|
17
|
+
if (isInitialRun || !isShallowEqualArrays(previousDependencies, dependencies)) {
|
|
18
|
+
const cleanUpFunction = handler()
|
|
19
|
+
if (typeof cleanUpFunction === 'function') {
|
|
20
|
+
throw new Error('An effect can\'t return a clean-up function when used with `useEffectDontMountTwiceInStrictMode()` because the clean-up function won\'t behave correctly in that case')
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, dependencies)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function useEffectStatus() {
|
|
27
|
+
const hasMounted = useRef(false)
|
|
28
|
+
|
|
29
|
+
const onEffect = useCallback(() => {
|
|
30
|
+
const wasAlreadyMounted = hasMounted.current
|
|
31
|
+
hasMounted.current = true
|
|
32
|
+
return {
|
|
33
|
+
isInitialRun: !wasAlreadyMounted
|
|
34
|
+
}
|
|
35
|
+
}, [])
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
onEffect
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function usePrevousValue(value) {
|
|
43
|
+
const prevValue = useRef(value)
|
|
44
|
+
|
|
45
|
+
const onChange = useCallback((value) => {
|
|
46
|
+
const previousValue = prevValue.current
|
|
47
|
+
prevValue.current = value
|
|
48
|
+
return previousValue
|
|
49
|
+
}, [])
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
onChange
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isShallowEqualArrays(a, b) {
|
|
57
|
+
if (a.length !== b.length) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
let i = 0
|
|
61
|
+
while (i < a.length) {
|
|
62
|
+
if (a[i] !== b[i]) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
i++
|
|
66
|
+
}
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useInsertionEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'
|
|
4
|
+
|
|
5
|
+
// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
6
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
7
|
+
// https://github.com/facebook/react/issues/26320
|
|
8
|
+
export default function useInsertionEffectDontMountTwiceInStrictMode(handler, dependencies) {
|
|
9
|
+
return useEffectDontMountTwiceInStrictMode(useInsertionEffect, handler, dependencies)
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'
|
|
4
|
+
|
|
5
|
+
// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
6
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
7
|
+
// https://github.com/facebook/react/issues/26320
|
|
8
|
+
export default function useLayoutEffectDontMountTwiceInStrictMode(handler, dependencies) {
|
|
9
|
+
return useEffectDontMountTwiceInStrictMode(useLayoutEffect, handler, dependencies)
|
|
10
|
+
}
|
package/source/react/useState.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import log, { isDebug } from '../utility/debug.js'
|
|
2
2
|
import getStateSnapshot from '../utility/getStateSnapshot.js'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { useRef, useCallback } from 'react'
|
|
5
|
+
import useStateNoStaleBug from './useStateNoStaleBug.js'
|
|
6
|
+
import useInsertionEffectDontMountTwiceInStrictMode from './useInsertionEffectDontMountTwiceInStrictMode.js'
|
|
7
|
+
import useLayoutEffectDontMountTwiceInStrictMode from './useLayoutEffectDontMountTwiceInStrictMode.js'
|
|
5
8
|
|
|
6
9
|
// Creates state management functions.
|
|
7
10
|
export default function _useState({
|
|
@@ -14,7 +17,7 @@ export default function _useState({
|
|
|
14
17
|
// `VirtualScroller` state gets updated from this variable.
|
|
15
18
|
// The reason for that is that `VirtualScroller` state must always
|
|
16
19
|
// correspond exactly to what's currently rendered on the screen.
|
|
17
|
-
const [_newState, _setNewState] =
|
|
20
|
+
const [_newState, _setNewState] = useStateNoStaleBug(initialState)
|
|
18
21
|
|
|
19
22
|
// This `state` reference is what `VirtualScroller` uses internally.
|
|
20
23
|
// It's the "source of truth" on the actual `VirtualScroller` state.
|
|
@@ -137,7 +140,7 @@ export default function _useState({
|
|
|
137
140
|
// So if `useInsertionEffect()` gets removed from React in some hypothetical future,
|
|
138
141
|
// it could be replaced with using `ref`s on `ItemComponent`s to measure the DOM element heights.
|
|
139
142
|
//
|
|
140
|
-
|
|
143
|
+
useInsertionEffectDontMountTwiceInStrictMode(() => {
|
|
141
144
|
// Update the actual `VirtualScroller` state right before the DOM changes
|
|
142
145
|
// are going to be applied for the requested state update.
|
|
143
146
|
//
|
|
@@ -160,13 +163,13 @@ export default function _useState({
|
|
|
160
163
|
// This hook doesn't do anything at the initial render.
|
|
161
164
|
//
|
|
162
165
|
if (isDebug()) {
|
|
163
|
-
log('React: ~ The requested state is about to be applied in DOM.
|
|
166
|
+
log('React: ~ The requested state is about to be applied in DOM. Setting it as the `VirtualScroller` state. ~')
|
|
164
167
|
log(getStateSnapshot(_newState))
|
|
165
168
|
}
|
|
166
169
|
setState(_newState)
|
|
167
170
|
}, [_newState])
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
useLayoutEffectDontMountTwiceInStrictMode(() => {
|
|
170
173
|
// Call `onRender()` right after a requested state update has been applied,
|
|
171
174
|
// and also right after the initial render.
|
|
172
175
|
onRender()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useRef, useState, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
// This hook fixes any weird intermediate inconsistent/invalid/stale state values.
|
|
4
|
+
// https://github.com/facebook/react/issues/25023#issuecomment-1480463544
|
|
5
|
+
export default function useStateNoStaleBug(initialState) {
|
|
6
|
+
// const latestValidState = useRef(initialState)
|
|
7
|
+
const latestWrittenState = useRef(initialState)
|
|
8
|
+
const [_state, _setState] = useState(initialState)
|
|
9
|
+
|
|
10
|
+
// Instead of dealing with a potentially out-of-sync (stale) state value,
|
|
11
|
+
// simply use the correct latest one.
|
|
12
|
+
const state = latestWrittenState.current
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
let state
|
|
16
|
+
if (_state === latestWrittenState.current) {
|
|
17
|
+
state = _state
|
|
18
|
+
latestValidState.current = _state
|
|
19
|
+
} else {
|
|
20
|
+
// React bug detected: an out-of-sync (stale) state value received.
|
|
21
|
+
// Ignore the out-of-sync (stale) state value.
|
|
22
|
+
state = latestValidState.current
|
|
23
|
+
}
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const setState = useCallback((newState) => {
|
|
27
|
+
if (typeof newState === 'function') {
|
|
28
|
+
throw new Error('Function argument of `setState()` function is not supported by this hook')
|
|
29
|
+
}
|
|
30
|
+
latestWrittenState.current = newState
|
|
31
|
+
_setState(newState)
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
return [state, setState]
|
|
35
|
+
}
|
|
@@ -8,31 +8,21 @@
|
|
|
8
8
|
|
|
9
9
|
<title>React VirtualScroller Demo</title>
|
|
10
10
|
|
|
11
|
-
<script crossorigin src="https://unpkg.com/react@
|
|
12
|
-
<script crossorigin src="https://unpkg.com/react-dom@
|
|
11
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
|
12
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
13
13
|
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
|
|
14
14
|
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
|
15
15
|
|
|
16
16
|
<script src="./virtual-scroller-react.js"></script>
|
|
17
|
-
<script src="./on-scroll-to-react.js"></script>
|
|
17
|
+
<script src="./lib/on-scroll-to-react.js"></script>
|
|
18
18
|
<script src="./messages.js"></script>
|
|
19
19
|
|
|
20
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
21
|
</head>
|
|
32
22
|
|
|
33
23
|
<body>
|
|
34
24
|
<!-- http://tholman.com/github-corners/ -->
|
|
35
|
-
<a title="Go to
|
|
25
|
+
<a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><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
26
|
|
|
37
27
|
<div id="root"></div>
|
|
38
28
|
|
package/website/index-dom.html
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
<body>
|
|
21
21
|
<!-- http://tholman.com/github-corners/ -->
|
|
22
|
-
<a title="Go to
|
|
22
|
+
<a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><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>
|
|
23
23
|
|
|
24
24
|
<section class="container">
|
|
25
25
|
<h1>
|
package/website/index-grid.html
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
<title>React VirtualScroller Demo (Grid Layout)</title>
|
|
10
10
|
|
|
11
|
-
<script crossorigin src="https://unpkg.com/react@
|
|
12
|
-
<script crossorigin src="https://unpkg.com/react-dom@
|
|
11
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
|
12
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
13
13
|
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
|
|
14
14
|
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
|
15
15
|
|
|
16
16
|
<script src="./virtual-scroller-react.js"></script>
|
|
17
|
-
<script src="./on-scroll-to-react.js"></script>
|
|
17
|
+
<script src="./lib/on-scroll-to-react.js"></script>
|
|
18
18
|
<script src="./messages.js"></script>
|
|
19
19
|
|
|
20
20
|
<link rel="stylesheet" href="./style.css"/>
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
<body>
|
|
47
47
|
<!-- http://tholman.com/github-corners/ -->
|
|
48
|
-
<a title="Go to
|
|
48
|
+
<a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><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>
|
|
49
49
|
|
|
50
50
|
<div id="root"></div>
|
|
51
51
|
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
<title>React VirtualScroller Demo (Scrollable Container)</title>
|
|
10
10
|
|
|
11
|
-
<script crossorigin src="https://unpkg.com/react@
|
|
12
|
-
<script crossorigin src="https://unpkg.com/react-dom@
|
|
11
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
|
12
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
13
13
|
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
|
|
14
14
|
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
|
15
15
|
|
|
16
16
|
<script src="./virtual-scroller-react.js"></script>
|
|
17
|
-
<script src="./on-scroll-to-react.js"></script>
|
|
17
|
+
<script src="./lib/on-scroll-to-react.js"></script>
|
|
18
18
|
<script src="./messages.js"></script>
|
|
19
19
|
|
|
20
20
|
<link rel="stylesheet" href="./style.css"/>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
<body>
|
|
24
24
|
<!-- http://tholman.com/github-corners/ -->
|
|
25
|
-
<a title="Go to
|
|
25
|
+
<a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><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
26
|
|
|
27
27
|
<div id="root"></div>
|
|
28
28
|
|
package/website/index-tbody.html
CHANGED
package/website/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
<title>React VirtualScroller Demo</title>
|
|
10
10
|
|
|
11
|
-
<script crossorigin src="https://unpkg.com/react@
|
|
12
|
-
<script crossorigin src="https://unpkg.com/react-dom@
|
|
11
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
|
12
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
13
13
|
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
|
|
14
14
|
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
|
15
15
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
<body>
|
|
24
24
|
<!-- http://tholman.com/github-corners/ -->
|
|
25
|
-
<a title="Go to
|
|
25
|
+
<a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><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
26
|
|
|
27
27
|
<div id="root"></div>
|
|
28
28
|
|