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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react'; // A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
2
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
3
|
+
// https://github.com/facebook/react/issues/26320
|
|
4
|
+
|
|
5
|
+
export default function useEffectDontMountTwiceInStrictMode(useEffect, handler, dependencies) {
|
|
6
|
+
if (!Array.isArray(dependencies)) {
|
|
7
|
+
throw new Error('Dependencies argument must be an array');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
var _useEffectStatus = useEffectStatus(),
|
|
11
|
+
onEffect = _useEffectStatus.onEffect;
|
|
12
|
+
|
|
13
|
+
var _usePrevousValue = usePrevousValue(dependencies),
|
|
14
|
+
onChange = _usePrevousValue.onChange;
|
|
15
|
+
|
|
16
|
+
useEffect(function () {
|
|
17
|
+
var _onEffect = onEffect(),
|
|
18
|
+
isInitialRun = _onEffect.isInitialRun;
|
|
19
|
+
|
|
20
|
+
var previousDependencies = onChange(dependencies);
|
|
21
|
+
|
|
22
|
+
if (isInitialRun || !isShallowEqualArrays(previousDependencies, dependencies)) {
|
|
23
|
+
var cleanUpFunction = handler();
|
|
24
|
+
|
|
25
|
+
if (typeof cleanUpFunction === 'function') {
|
|
26
|
+
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');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}, dependencies);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function useEffectStatus() {
|
|
33
|
+
var hasMounted = useRef(false);
|
|
34
|
+
var onEffect = useCallback(function () {
|
|
35
|
+
var wasAlreadyMounted = hasMounted.current;
|
|
36
|
+
hasMounted.current = true;
|
|
37
|
+
return {
|
|
38
|
+
isInitialRun: !wasAlreadyMounted
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
return {
|
|
42
|
+
onEffect: onEffect
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function usePrevousValue(value) {
|
|
47
|
+
var prevValue = useRef(value);
|
|
48
|
+
var onChange = useCallback(function (value) {
|
|
49
|
+
var previousValue = prevValue.current;
|
|
50
|
+
prevValue.current = value;
|
|
51
|
+
return previousValue;
|
|
52
|
+
}, []);
|
|
53
|
+
return {
|
|
54
|
+
onChange: onChange
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isShallowEqualArrays(a, b) {
|
|
59
|
+
if (a.length !== b.length) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var i = 0;
|
|
64
|
+
|
|
65
|
+
while (i < a.length) {
|
|
66
|
+
if (a[i] !== b[i]) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
i++;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=useEffectDontMountTwiceInStrictMode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEffectDontMountTwiceInStrictMode.js","names":["useRef","useCallback","useEffectDontMountTwiceInStrictMode","useEffect","handler","dependencies","Array","isArray","Error","useEffectStatus","onEffect","usePrevousValue","onChange","isInitialRun","previousDependencies","isShallowEqualArrays","cleanUpFunction","hasMounted","wasAlreadyMounted","current","value","prevValue","previousValue","a","b","length","i"],"sources":["../../source/react/useEffectDontMountTwiceInStrictMode.js"],"sourcesContent":["import { useRef, useCallback } from 'react'\r\n\r\n// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount\r\n// in \"strict\" mode unlike `useEffect()` and `useLayoutEffect()` do.\r\n// https://github.com/facebook/react/issues/26320\r\nexport default function useEffectDontMountTwiceInStrictMode(useEffect, handler, dependencies) {\r\n if (!Array.isArray(dependencies)) {\r\n throw new Error('Dependencies argument must be an array')\r\n }\r\n\r\n const { onEffect } = useEffectStatus()\r\n const { onChange } = usePrevousValue(dependencies)\r\n\r\n useEffect(() => {\r\n const { isInitialRun } = onEffect()\r\n const previousDependencies = onChange(dependencies)\r\n if (isInitialRun || !isShallowEqualArrays(previousDependencies, dependencies)) {\r\n const cleanUpFunction = handler()\r\n if (typeof cleanUpFunction === 'function') {\r\n 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')\r\n }\r\n }\r\n }, dependencies)\r\n}\r\n\r\nfunction useEffectStatus() {\r\n const hasMounted = useRef(false)\r\n\r\n const onEffect = useCallback(() => {\r\n const wasAlreadyMounted = hasMounted.current\r\n hasMounted.current = true\r\n return {\r\n isInitialRun: !wasAlreadyMounted\r\n }\r\n }, [])\r\n\r\n return {\r\n onEffect\r\n }\r\n}\r\n\r\nfunction usePrevousValue(value) {\r\n const prevValue = useRef(value)\r\n\r\n const onChange = useCallback((value) => {\r\n const previousValue = prevValue.current\r\n prevValue.current = value\r\n return previousValue\r\n }, [])\r\n\r\n return {\r\n onChange\r\n }\r\n}\r\n\r\nfunction isShallowEqualArrays(a, b) {\r\n if (a.length !== b.length) {\r\n return false\r\n }\r\n let i = 0\r\n while (i < a.length) {\r\n if (a[i] !== b[i]) {\r\n return false\r\n }\r\n i++\r\n }\r\n return true\r\n}"],"mappings":"AAAA,SAASA,MAAT,EAAiBC,WAAjB,QAAoC,OAApC,C,CAEA;AACA;AACA;;AACA,eAAe,SAASC,mCAAT,CAA6CC,SAA7C,EAAwDC,OAAxD,EAAiEC,YAAjE,EAA+E;EAC5F,IAAI,CAACC,KAAK,CAACC,OAAN,CAAcF,YAAd,CAAL,EAAkC;IAChC,MAAM,IAAIG,KAAJ,CAAU,wCAAV,CAAN;EACD;;EAED,uBAAqBC,eAAe,EAApC;EAAA,IAAQC,QAAR,oBAAQA,QAAR;;EACA,uBAAqBC,eAAe,CAACN,YAAD,CAApC;EAAA,IAAQO,QAAR,oBAAQA,QAAR;;EAEAT,SAAS,CAAC,YAAM;IACd,gBAAyBO,QAAQ,EAAjC;IAAA,IAAQG,YAAR,aAAQA,YAAR;;IACA,IAAMC,oBAAoB,GAAGF,QAAQ,CAACP,YAAD,CAArC;;IACA,IAAIQ,YAAY,IAAI,CAACE,oBAAoB,CAACD,oBAAD,EAAuBT,YAAvB,CAAzC,EAA+E;MAC7E,IAAMW,eAAe,GAAGZ,OAAO,EAA/B;;MACA,IAAI,OAAOY,eAAP,KAA2B,UAA/B,EAA2C;QACzC,MAAM,IAAIR,KAAJ,CAAU,uKAAV,CAAN;MACD;IACF;EACF,CATQ,EASNH,YATM,CAAT;AAUD;;AAED,SAASI,eAAT,GAA2B;EACzB,IAAMQ,UAAU,GAAGjB,MAAM,CAAC,KAAD,CAAzB;EAEA,IAAMU,QAAQ,GAAGT,WAAW,CAAC,YAAM;IACjC,IAAMiB,iBAAiB,GAAGD,UAAU,CAACE,OAArC;IACAF,UAAU,CAACE,OAAX,GAAqB,IAArB;IACA,OAAO;MACLN,YAAY,EAAE,CAACK;IADV,CAAP;EAGD,CAN2B,EAMzB,EANyB,CAA5B;EAQA,OAAO;IACLR,QAAQ,EAARA;EADK,CAAP;AAGD;;AAED,SAASC,eAAT,CAAyBS,KAAzB,EAAgC;EAC9B,IAAMC,SAAS,GAAGrB,MAAM,CAACoB,KAAD,CAAxB;EAEA,IAAMR,QAAQ,GAAGX,WAAW,CAAC,UAACmB,KAAD,EAAW;IACtC,IAAME,aAAa,GAAGD,SAAS,CAACF,OAAhC;IACAE,SAAS,CAACF,OAAV,GAAoBC,KAApB;IACA,OAAOE,aAAP;EACD,CAJ2B,EAIzB,EAJyB,CAA5B;EAMA,OAAO;IACLV,QAAQ,EAARA;EADK,CAAP;AAGD;;AAED,SAASG,oBAAT,CAA8BQ,CAA9B,EAAiCC,CAAjC,EAAoC;EAClC,IAAID,CAAC,CAACE,MAAF,KAAaD,CAAC,CAACC,MAAnB,EAA2B;IACzB,OAAO,KAAP;EACD;;EACD,IAAIC,CAAC,GAAG,CAAR;;EACA,OAAOA,CAAC,GAAGH,CAAC,CAACE,MAAb,EAAqB;IACnB,IAAIF,CAAC,CAACG,CAAD,CAAD,KAASF,CAAC,CAACE,CAAD,CAAd,EAAmB;MACjB,OAAO,KAAP;IACD;;IACDA,CAAC;EACF;;EACD,OAAO,IAAP;AACD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useInsertionEffect } from 'react';
|
|
2
|
+
import useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'; // A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
3
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
4
|
+
// https://github.com/facebook/react/issues/26320
|
|
5
|
+
|
|
6
|
+
export default function useInsertionEffectDontMountTwiceInStrictMode(handler, dependencies) {
|
|
7
|
+
return useEffectDontMountTwiceInStrictMode(useInsertionEffect, handler, dependencies);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=useInsertionEffectDontMountTwiceInStrictMode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useInsertionEffectDontMountTwiceInStrictMode.js","names":["useInsertionEffect","useEffectDontMountTwiceInStrictMode","useInsertionEffectDontMountTwiceInStrictMode","handler","dependencies"],"sources":["../../source/react/useInsertionEffectDontMountTwiceInStrictMode.js"],"sourcesContent":["import { useInsertionEffect } from 'react'\r\n\r\nimport useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'\r\n\r\n// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount\r\n// in \"strict\" mode unlike `useEffect()` and `useLayoutEffect()` do.\r\n// https://github.com/facebook/react/issues/26320\r\nexport default function useInsertionEffectDontMountTwiceInStrictMode(handler, dependencies) {\r\n return useEffectDontMountTwiceInStrictMode(useInsertionEffect, handler, dependencies)\r\n}"],"mappings":"AAAA,SAASA,kBAAT,QAAmC,OAAnC;AAEA,OAAOC,mCAAP,MAAgD,0CAAhD,C,CAEA;AACA;AACA;;AACA,eAAe,SAASC,4CAAT,CAAsDC,OAAtD,EAA+DC,YAA/D,EAA6E;EAC1F,OAAOH,mCAAmC,CAACD,kBAAD,EAAqBG,OAArB,EAA8BC,YAA9B,CAA1C;AACD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'; // A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
3
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
4
|
+
// https://github.com/facebook/react/issues/26320
|
|
5
|
+
|
|
6
|
+
export default function useLayoutEffectDontMountTwiceInStrictMode(handler, dependencies) {
|
|
7
|
+
return useEffectDontMountTwiceInStrictMode(useLayoutEffect, handler, dependencies);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=useLayoutEffectDontMountTwiceInStrictMode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLayoutEffectDontMountTwiceInStrictMode.js","names":["useLayoutEffect","useEffectDontMountTwiceInStrictMode","useLayoutEffectDontMountTwiceInStrictMode","handler","dependencies"],"sources":["../../source/react/useLayoutEffectDontMountTwiceInStrictMode.js"],"sourcesContent":["import { useLayoutEffect } from 'react'\r\n\r\nimport useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'\r\n\r\n// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount\r\n// in \"strict\" mode unlike `useEffect()` and `useLayoutEffect()` do.\r\n// https://github.com/facebook/react/issues/26320\r\nexport default function useLayoutEffectDontMountTwiceInStrictMode(handler, dependencies) {\r\n return useEffectDontMountTwiceInStrictMode(useLayoutEffect, handler, dependencies)\r\n}"],"mappings":"AAAA,SAASA,eAAT,QAAgC,OAAhC;AAEA,OAAOC,mCAAP,MAAgD,0CAAhD,C,CAEA;AACA;AACA;;AACA,eAAe,SAASC,yCAAT,CAAmDC,OAAnD,EAA4DC,YAA5D,EAA0E;EACvF,OAAOH,mCAAmC,CAACD,eAAD,EAAkBG,OAAlB,EAA2BC,YAA3B,CAA1C;AACD"}
|
|
@@ -12,7 +12,10 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
|
12
12
|
|
|
13
13
|
import log, { isDebug } from '../utility/debug.js';
|
|
14
14
|
import getStateSnapshot from '../utility/getStateSnapshot.js';
|
|
15
|
-
import {
|
|
15
|
+
import { useRef, useCallback } from 'react';
|
|
16
|
+
import useStateNoStaleBug from './useStateNoStaleBug.js';
|
|
17
|
+
import useInsertionEffectDontMountTwiceInStrictMode from './useInsertionEffectDontMountTwiceInStrictMode.js';
|
|
18
|
+
import useLayoutEffectDontMountTwiceInStrictMode from './useLayoutEffectDontMountTwiceInStrictMode.js'; // Creates state management functions.
|
|
16
19
|
|
|
17
20
|
export default function _useState(_ref) {
|
|
18
21
|
var initialState = _ref.initialState,
|
|
@@ -24,10 +27,10 @@ export default function _useState(_ref) {
|
|
|
24
27
|
// `VirtualScroller` state gets updated from this variable.
|
|
25
28
|
// The reason for that is that `VirtualScroller` state must always
|
|
26
29
|
// correspond exactly to what's currently rendered on the screen.
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
_newState =
|
|
30
|
-
_setNewState =
|
|
30
|
+
var _useStateNoStaleBug = useStateNoStaleBug(initialState),
|
|
31
|
+
_useStateNoStaleBug2 = _slicedToArray(_useStateNoStaleBug, 2),
|
|
32
|
+
_newState = _useStateNoStaleBug2[0],
|
|
33
|
+
_setNewState = _useStateNoStaleBug2[1]; // This `state` reference is what `VirtualScroller` uses internally.
|
|
31
34
|
// It's the "source of truth" on the actual `VirtualScroller` state.
|
|
32
35
|
|
|
33
36
|
|
|
@@ -145,7 +148,7 @@ export default function _useState(_ref) {
|
|
|
145
148
|
// it could be replaced with using `ref`s on `ItemComponent`s to measure the DOM element heights.
|
|
146
149
|
//
|
|
147
150
|
|
|
148
|
-
|
|
151
|
+
useInsertionEffectDontMountTwiceInStrictMode(function () {
|
|
149
152
|
// Update the actual `VirtualScroller` state right before the DOM changes
|
|
150
153
|
// are going to be applied for the requested state update.
|
|
151
154
|
//
|
|
@@ -168,13 +171,13 @@ export default function _useState(_ref) {
|
|
|
168
171
|
// This hook doesn't do anything at the initial render.
|
|
169
172
|
//
|
|
170
173
|
if (isDebug()) {
|
|
171
|
-
log('React: ~ The requested state is about to be applied in DOM.
|
|
174
|
+
log('React: ~ The requested state is about to be applied in DOM. Setting it as the `VirtualScroller` state. ~');
|
|
172
175
|
log(getStateSnapshot(_newState));
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
setState(_newState);
|
|
176
179
|
}, [_newState]);
|
|
177
|
-
|
|
180
|
+
useLayoutEffectDontMountTwiceInStrictMode(function () {
|
|
178
181
|
// Call `onRender()` right after a requested state update has been applied,
|
|
179
182
|
// and also right after the initial render.
|
|
180
183
|
onRender();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useState.js","names":["log","isDebug","getStateSnapshot","useState","useRef","useCallback","useLayoutEffect","useInsertionEffect","_useState","initialState","onRender","itemsProperty","_newState","_setNewState","state","getState","current","setState","newState","stateToRender"],"sources":["../../source/react/useState.js"],"sourcesContent":["import log, { isDebug } from '../utility/debug.js'\r\nimport getStateSnapshot from '../utility/getStateSnapshot.js'\r\n\r\nimport { useState, useRef, useCallback, useLayoutEffect, useInsertionEffect } from 'react'\r\n\r\n// Creates state management functions.\r\nexport default function _useState({\r\n\tinitialState,\r\n\tonRender,\r\n\titemsProperty\r\n}) {\r\n\t// This is a state variable that is used to re-render the component.\r\n\t// Right after the component has finished re-rendering,\r\n\t// `VirtualScroller` state gets updated from this variable.\r\n\t// The reason for that is that `VirtualScroller` state must always\r\n\t// correspond exactly to what's currently rendered on the screen.\r\n\tconst [_newState, _setNewState] = useState(initialState)\r\n\r\n\t// This `state` reference is what `VirtualScroller` uses internally.\r\n\t// It's the \"source of truth\" on the actual `VirtualScroller` state.\r\n\tconst state = useRef(initialState)\r\n\r\n\tconst getState = useCallback(() => {\r\n\t\treturn state.current\r\n\t}, [])\r\n\r\n\tconst setState = useCallback((newState) => {\r\n\t\tstate.current = newState\r\n\t}, [])\r\n\r\n\t// Updating of the actual `VirtualScroller` state is done in a\r\n\t// `useInsertionEffect()` rather than in a `useLayoutEffect()`.\r\n\t//\r\n\t// The reason is that using `useLayoutEffect()` would result in\r\n\t// \"breaking\" the `<VirtualScroller/>` when an `itemComponent`\r\n\t// called `onHeightDidChange()` from its own `useLayoutEffect()`.\r\n\t// In those cases, the `itemCompoent`'s effect would run before\r\n\t// the `<VirtualScroller/>`'s effect, resulting in\r\n\t// `VirtualScroller.onItemHeightDidChange(i)` being run at a moment in time\r\n\t// when the DOM has already been updated for the next `VirtualScroller` state\r\n\t// but the actual `VirtualScroller` state is still a previous (\"stale\") one\r\n\t// containing \"stale\" first/last shown item indexes, which would result in an\r\n\t// \"index out of bounds\" error when `onItemHeightDidChange(i)` tries to access\r\n\t// and measure the DOM element from item index `i` which doesn't already/yet exist.\r\n\t//\r\n\t// An example of such situation could be seen from a `VirtualScroller` debug log\r\n\t// which was captured for a case when using `useLayoutEffect()` to update the\r\n\t// \"actual\" `VirtualScroller` state after the corresponding DOM changes have been applied:\r\n\r\n\t// The user has scrolled far enough: perform a re-layout\r\n\t// ~ Update Layout (on scroll) ~\r\n\t//\r\n\t// Item index 2 height is required for calculations but hasn't been measured yet. Mark the item as \"shown\", rerender the list, measure the item's height and redo the layout.\r\n\t//\r\n\t// ~ Calculated Layout ~\r\n\t// Columns count 1\r\n\t// First shown item index 2\r\n\t// Last shown item index 5\r\n\t// …\r\n\t// Item heights (231) [1056.578125, 783.125, empty × 229]\r\n\t// Item states (231) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]\r\n\t//\r\n\t// ~ Set state ~\r\n\t// {firstShownItemIndex: 2, lastShownItemIndex: 5, …}\r\n\t//\r\n\t// ~ Rendered ~\r\n\t// State {firstShownItemIndex: 2, lastShownItemIndex: 5, …}\r\n\t//\r\n\t// ~ Measure item heights ~\r\n\t// Item index 2 height 719.8828125\r\n\t// Item index 3 height 961.640625\r\n\t// Item index 4 height 677.6640625\r\n\t// Item index 5 height 1510.1953125\r\n\t//\r\n\t// ~ Update Layout (on non-measured item heights have been measured) ~\r\n\t//\r\n\t// ~ Calculated Layout ~\r\n\t// Columns count 1\r\n\t// First shown item index 4\r\n\t// Last shown item index 5\r\n\t// …\r\n\t// Item heights (231) [1056.578125, 783.125, 719.8828125, 961.640625, 677.6640625, 1510.1953125, empty × 225]\r\n\t// Item states (231) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]\r\n\t//\r\n\t// ~ Set state ~\r\n\t// {firstShownItemIndex: 4, lastShownItemIndex: 5, beforeItemsHeight: 3521.2265625, afterItemsHeight: 214090.72265624942}\r\n\t//\r\n\t// ~ On Item Height Did Change was called ~\r\n\t// Item index 5\r\n\t// ~ Re-measure item height ~\r\n\t// ERROR \"onItemHeightDidChange()\" has been called for item index 5 but the item is not currently rendered and can't be measured. The exact error was: Element with index 3 was not found in the list of Rendered Item Elements in the Items Container of Virtual Scroller. There're only 2 Elements there.\r\n\t//\r\n\t// React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~\r\n\t// {firstShownItemIndex: 4, lastShownItemIndex: 5, …}\r\n\t//\r\n\t// ~ Rendered ~\r\n\r\n\t// \"~ Rendered ~\" is what gets output when `onRender()` function gets called.\r\n\t// It means that `useLayoutEffect()` was triggered after `onItemHeightDidChange(i)`\r\n\t// was called and after the \"ERROR\" happened.\r\n\t//\r\n\t// The \"ERROR\" happened because new item indexes 4…5 were actually rendered instead of\r\n\t// item indexes 2…5 by the time the application called `onItemHeightDidChange(i)` function\r\n\t// inside `itemComponent`'s `useLayoutEffect()`.\r\n\t// Item indexes 4…5 is what was requested in a `setState()` call, which called `_setNewState()`.\r\n\t// This means that `_newState` changes have been applied to the DOM\r\n\t// but `useLayoutEffect()` wasn't triggered immediately after that.\r\n\t// Instead, it was triggered a right after the `itemComponent`'s `useLayoutEffect()`\r\n\t// because child effects run before parent effects.\r\n\t// So, the `itemComponent`'s `onHeightDidChange()` function call caught the\r\n\t// `VirtualScroller` in an inconsistent state.\r\n\t//\r\n\t// To fix that, `useLayoutEffect()` gets replaced with `useInsertionEffect()`:\r\n\t// https://blog.saeloun.com/2022/06/02/react-18-useinsertioneffect\r\n\t// https://beta.reactjs.org/reference/react/useInsertionEffect\r\n\t//\r\n\t// After replacing `useLayoutEffect()` with `useInsertionEffect()`,\r\n\t// the log shows that there's no more error:\r\n\t//\r\n\t// ~ Set state ~\r\n\t// {firstShownItemIndex: 0, lastShownItemIndex: 2, …}\r\n\t//\r\n\t// React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~\r\n\t// {firstShownItemIndex: 0, lastShownItemIndex: 2, …}\r\n\t//\r\n\t// ~ On Item Height Did Change was called ~\r\n\t// Item index 0\r\n\t// ~ Re-measure item height ~\r\n\t// Previous height 917\r\n\t// New height 1064.453125\r\n\t// ~ Item height has changed ~\r\n\t//\r\n\t// An alternative solution would be demanding the `itemComponent` to\r\n\t// accept a `ref` and then measuring the corresponding DOM element height\r\n\t// directly using the `ref`-ed DOM element rather than searching for that\r\n\t// DOM element in the `ItemsContainer`.\r\n\t// So if `useInsertionEffect()` gets removed from React in some hypothetical future,\r\n\t// it could be replaced with using `ref`s on `ItemComponent`s to measure the DOM element heights.\r\n\t//\r\n\tuseInsertionEffect(() => {\r\n\t\t// Update the actual `VirtualScroller` state right before the DOM changes\r\n\t\t// are going to be applied for the requested state update.\r\n\t\t//\r\n\t\t// This hook will run right before `useLayoutEffect()`.\r\n\t\t//\r\n\t\t// It doesn't make any difference which one of the two hooks to use to update\r\n\t\t// the actual `VirtualScroller` state in this scenario because the two hooks\r\n\t\t// run synchronously one right after another (insertion effect → DOM update → layout effect)\r\n\t\t// without any free space for any `VirtualScroller` code (like the scroll event handler)\r\n\t\t// to squeeze in and run in-between them, so the `VirtualScroller`'s `state`\r\n\t\t// is always gonna stay consistent with what's currently rendered on screen\r\n\t\t// from the `VirtualScroler`'s point of view, and the short transition period\r\n\t\t// it simply doesn't see because it doesn't \"wake up\" during that period.\r\n\t\t//\r\n\t\t// Updating the actual `VirtualScroller` state right before `useLayoutEffect()`\r\n\t\t// fixes the bug when an `itemComponent` calls `onHeightDidChange()` in its own\r\n\t\t// `useLayoutEffect()` which would run before this `useLayoutEffect()`\r\n\t\t// because children's effects run before parent's.\r\n\t\t//\r\n\t\t// This hook doesn't do anything at the initial render.\r\n\t\t//\r\n\t\tif (isDebug()) {\r\n\t\t\tlog('React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~')\r\n\t\t\tlog(getStateSnapshot(_newState))\r\n\t\t}\r\n\t\tsetState(_newState)\r\n\t}, [_newState])\r\n\r\n\tuseLayoutEffect(() => {\r\n\t\t// Call `onRender()` right after a requested state update has been applied,\r\n\t\t// and also right after the initial render.\r\n\t\tonRender()\r\n\t}, [_newState])\r\n\r\n\treturn {\r\n\t\t// This is the state the component should render.\r\n\t\tstateToRender: _newState,\r\n\r\n\t\t// Returns the current state of the `VirtualScroller`.\r\n\t\t// This function is used in the `VirtualScroller` itself\r\n\t\t// because the `state` is managed outside of it.\r\n\t\tgetState,\r\n\r\n\t\t// Requests a state update.\r\n\t\tsetState: _setNewState\r\n\t}\r\n}"],"mappings":";;;;;;;;;;;;AAAA,OAAOA,GAAP,IAAcC,OAAd,QAA6B,qBAA7B;AACA,OAAOC,gBAAP,MAA6B,gCAA7B;AAEA,SAASC,QAAT,EAAmBC,MAAnB,EAA2BC,WAA3B,EAAwCC,eAAxC,EAAyDC,kBAAzD,QAAmF,OAAnF,C,CAEA;;AACA,eAAe,SAASC,SAAT,OAIZ;EAAA,IAHFC,YAGE,QAHFA,YAGE;EAAA,IAFFC,QAEE,QAFFA,QAEE;EAAA,IADFC,aACE,QADFA,aACE;;EACF;EACA;EACA;EACA;EACA;EACA,iBAAkCR,QAAQ,CAACM,YAAD,CAA1C;EAAA;EAAA,IAAOG,SAAP;EAAA,IAAkBC,YAAlB,iBANE,CAQF;EACA;;;EACA,IAAMC,KAAK,GAAGV,MAAM,CAACK,YAAD,CAApB;EAEA,IAAMM,QAAQ,GAAGV,WAAW,CAAC,YAAM;IAClC,OAAOS,KAAK,CAACE,OAAb;EACA,CAF2B,EAEzB,EAFyB,CAA5B;EAIA,IAAMC,QAAQ,GAAGZ,WAAW,CAAC,UAACa,QAAD,EAAc;IAC1CJ,KAAK,CAACE,OAAN,GAAgBE,QAAhB;EACA,CAF2B,EAEzB,EAFyB,CAA5B,CAhBE,CAoBF;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EACAX,kBAAkB,CAAC,YAAM;IACxB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,IAAIN,OAAO,EAAX,EAAe;MACdD,GAAG,CAAC,sGAAD,CAAH;MACAA,GAAG,CAACE,gBAAgB,CAACU,SAAD,CAAjB,CAAH;IACA;;IACDK,QAAQ,CAACL,SAAD,CAAR;EACA,CA3BiB,EA2Bf,CAACA,SAAD,CA3Be,CAAlB;EA6BAN,eAAe,CAAC,YAAM;IACrB;IACA;IACAI,QAAQ;EACR,CAJc,EAIZ,CAACE,SAAD,CAJY,CAAf;EAMA,OAAO;IACN;IACAO,aAAa,EAAEP,SAFT;IAIN;IACA;IACA;IACAG,QAAQ,EAARA,QAPM;IASN;IACAE,QAAQ,EAAEJ;EAVJ,CAAP;AAYA"}
|
|
1
|
+
{"version":3,"file":"useState.js","names":["log","isDebug","getStateSnapshot","useRef","useCallback","useStateNoStaleBug","useInsertionEffectDontMountTwiceInStrictMode","useLayoutEffectDontMountTwiceInStrictMode","_useState","initialState","onRender","itemsProperty","_newState","_setNewState","state","getState","current","setState","newState","stateToRender"],"sources":["../../source/react/useState.js"],"sourcesContent":["import log, { isDebug } from '../utility/debug.js'\r\nimport getStateSnapshot from '../utility/getStateSnapshot.js'\r\n\r\nimport { useRef, useCallback } from 'react'\r\nimport useStateNoStaleBug from './useStateNoStaleBug.js'\r\nimport useInsertionEffectDontMountTwiceInStrictMode from './useInsertionEffectDontMountTwiceInStrictMode.js'\r\nimport useLayoutEffectDontMountTwiceInStrictMode from './useLayoutEffectDontMountTwiceInStrictMode.js'\r\n\r\n// Creates state management functions.\r\nexport default function _useState({\r\n\tinitialState,\r\n\tonRender,\r\n\titemsProperty\r\n}) {\r\n\t// This is a state variable that is used to re-render the component.\r\n\t// Right after the component has finished re-rendering,\r\n\t// `VirtualScroller` state gets updated from this variable.\r\n\t// The reason for that is that `VirtualScroller` state must always\r\n\t// correspond exactly to what's currently rendered on the screen.\r\n\tconst [_newState, _setNewState] = useStateNoStaleBug(initialState)\r\n\r\n\t// This `state` reference is what `VirtualScroller` uses internally.\r\n\t// It's the \"source of truth\" on the actual `VirtualScroller` state.\r\n\tconst state = useRef(initialState)\r\n\r\n\tconst getState = useCallback(() => {\r\n\t\treturn state.current\r\n\t}, [])\r\n\r\n\tconst setState = useCallback((newState) => {\r\n\t\tstate.current = newState\r\n\t}, [])\r\n\r\n\t// Updating of the actual `VirtualScroller` state is done in a\r\n\t// `useInsertionEffect()` rather than in a `useLayoutEffect()`.\r\n\t//\r\n\t// The reason is that using `useLayoutEffect()` would result in\r\n\t// \"breaking\" the `<VirtualScroller/>` when an `itemComponent`\r\n\t// called `onHeightDidChange()` from its own `useLayoutEffect()`.\r\n\t// In those cases, the `itemCompoent`'s effect would run before\r\n\t// the `<VirtualScroller/>`'s effect, resulting in\r\n\t// `VirtualScroller.onItemHeightDidChange(i)` being run at a moment in time\r\n\t// when the DOM has already been updated for the next `VirtualScroller` state\r\n\t// but the actual `VirtualScroller` state is still a previous (\"stale\") one\r\n\t// containing \"stale\" first/last shown item indexes, which would result in an\r\n\t// \"index out of bounds\" error when `onItemHeightDidChange(i)` tries to access\r\n\t// and measure the DOM element from item index `i` which doesn't already/yet exist.\r\n\t//\r\n\t// An example of such situation could be seen from a `VirtualScroller` debug log\r\n\t// which was captured for a case when using `useLayoutEffect()` to update the\r\n\t// \"actual\" `VirtualScroller` state after the corresponding DOM changes have been applied:\r\n\r\n\t// The user has scrolled far enough: perform a re-layout\r\n\t// ~ Update Layout (on scroll) ~\r\n\t//\r\n\t// Item index 2 height is required for calculations but hasn't been measured yet. Mark the item as \"shown\", rerender the list, measure the item's height and redo the layout.\r\n\t//\r\n\t// ~ Calculated Layout ~\r\n\t// Columns count 1\r\n\t// First shown item index 2\r\n\t// Last shown item index 5\r\n\t// …\r\n\t// Item heights (231) [1056.578125, 783.125, empty × 229]\r\n\t// Item states (231) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]\r\n\t//\r\n\t// ~ Set state ~\r\n\t// {firstShownItemIndex: 2, lastShownItemIndex: 5, …}\r\n\t//\r\n\t// ~ Rendered ~\r\n\t// State {firstShownItemIndex: 2, lastShownItemIndex: 5, …}\r\n\t//\r\n\t// ~ Measure item heights ~\r\n\t// Item index 2 height 719.8828125\r\n\t// Item index 3 height 961.640625\r\n\t// Item index 4 height 677.6640625\r\n\t// Item index 5 height 1510.1953125\r\n\t//\r\n\t// ~ Update Layout (on non-measured item heights have been measured) ~\r\n\t//\r\n\t// ~ Calculated Layout ~\r\n\t// Columns count 1\r\n\t// First shown item index 4\r\n\t// Last shown item index 5\r\n\t// …\r\n\t// Item heights (231) [1056.578125, 783.125, 719.8828125, 961.640625, 677.6640625, 1510.1953125, empty × 225]\r\n\t// Item states (231) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]\r\n\t//\r\n\t// ~ Set state ~\r\n\t// {firstShownItemIndex: 4, lastShownItemIndex: 5, beforeItemsHeight: 3521.2265625, afterItemsHeight: 214090.72265624942}\r\n\t//\r\n\t// ~ On Item Height Did Change was called ~\r\n\t// Item index 5\r\n\t// ~ Re-measure item height ~\r\n\t// ERROR \"onItemHeightDidChange()\" has been called for item index 5 but the item is not currently rendered and can't be measured. The exact error was: Element with index 3 was not found in the list of Rendered Item Elements in the Items Container of Virtual Scroller. There're only 2 Elements there.\r\n\t//\r\n\t// React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~\r\n\t// {firstShownItemIndex: 4, lastShownItemIndex: 5, …}\r\n\t//\r\n\t// ~ Rendered ~\r\n\r\n\t// \"~ Rendered ~\" is what gets output when `onRender()` function gets called.\r\n\t// It means that `useLayoutEffect()` was triggered after `onItemHeightDidChange(i)`\r\n\t// was called and after the \"ERROR\" happened.\r\n\t//\r\n\t// The \"ERROR\" happened because new item indexes 4…5 were actually rendered instead of\r\n\t// item indexes 2…5 by the time the application called `onItemHeightDidChange(i)` function\r\n\t// inside `itemComponent`'s `useLayoutEffect()`.\r\n\t// Item indexes 4…5 is what was requested in a `setState()` call, which called `_setNewState()`.\r\n\t// This means that `_newState` changes have been applied to the DOM\r\n\t// but `useLayoutEffect()` wasn't triggered immediately after that.\r\n\t// Instead, it was triggered a right after the `itemComponent`'s `useLayoutEffect()`\r\n\t// because child effects run before parent effects.\r\n\t// So, the `itemComponent`'s `onHeightDidChange()` function call caught the\r\n\t// `VirtualScroller` in an inconsistent state.\r\n\t//\r\n\t// To fix that, `useLayoutEffect()` gets replaced with `useInsertionEffect()`:\r\n\t// https://blog.saeloun.com/2022/06/02/react-18-useinsertioneffect\r\n\t// https://beta.reactjs.org/reference/react/useInsertionEffect\r\n\t//\r\n\t// After replacing `useLayoutEffect()` with `useInsertionEffect()`,\r\n\t// the log shows that there's no more error:\r\n\t//\r\n\t// ~ Set state ~\r\n\t// {firstShownItemIndex: 0, lastShownItemIndex: 2, …}\r\n\t//\r\n\t// React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~\r\n\t// {firstShownItemIndex: 0, lastShownItemIndex: 2, …}\r\n\t//\r\n\t// ~ On Item Height Did Change was called ~\r\n\t// Item index 0\r\n\t// ~ Re-measure item height ~\r\n\t// Previous height 917\r\n\t// New height 1064.453125\r\n\t// ~ Item height has changed ~\r\n\t//\r\n\t// An alternative solution would be demanding the `itemComponent` to\r\n\t// accept a `ref` and then measuring the corresponding DOM element height\r\n\t// directly using the `ref`-ed DOM element rather than searching for that\r\n\t// DOM element in the `ItemsContainer`.\r\n\t// So if `useInsertionEffect()` gets removed from React in some hypothetical future,\r\n\t// it could be replaced with using `ref`s on `ItemComponent`s to measure the DOM element heights.\r\n\t//\r\n\tuseInsertionEffectDontMountTwiceInStrictMode(() => {\r\n\t\t// Update the actual `VirtualScroller` state right before the DOM changes\r\n\t\t// are going to be applied for the requested state update.\r\n\t\t//\r\n\t\t// This hook will run right before `useLayoutEffect()`.\r\n\t\t//\r\n\t\t// It doesn't make any difference which one of the two hooks to use to update\r\n\t\t// the actual `VirtualScroller` state in this scenario because the two hooks\r\n\t\t// run synchronously one right after another (insertion effect → DOM update → layout effect)\r\n\t\t// without any free space for any `VirtualScroller` code (like the scroll event handler)\r\n\t\t// to squeeze in and run in-between them, so the `VirtualScroller`'s `state`\r\n\t\t// is always gonna stay consistent with what's currently rendered on screen\r\n\t\t// from the `VirtualScroler`'s point of view, and the short transition period\r\n\t\t// it simply doesn't see because it doesn't \"wake up\" during that period.\r\n\t\t//\r\n\t\t// Updating the actual `VirtualScroller` state right before `useLayoutEffect()`\r\n\t\t// fixes the bug when an `itemComponent` calls `onHeightDidChange()` in its own\r\n\t\t// `useLayoutEffect()` which would run before this `useLayoutEffect()`\r\n\t\t// because children's effects run before parent's.\r\n\t\t//\r\n\t\t// This hook doesn't do anything at the initial render.\r\n\t\t//\r\n\t\tif (isDebug()) {\r\n\t\t\tlog('React: ~ The requested state is about to be applied in DOM. Setting it as the `VirtualScroller` state. ~')\r\n\t\t\tlog(getStateSnapshot(_newState))\r\n\t\t}\r\n\t\tsetState(_newState)\r\n\t}, [_newState])\r\n\r\n\tuseLayoutEffectDontMountTwiceInStrictMode(() => {\r\n\t\t// Call `onRender()` right after a requested state update has been applied,\r\n\t\t// and also right after the initial render.\r\n\t\tonRender()\r\n\t}, [_newState])\r\n\r\n\treturn {\r\n\t\t// This is the state the component should render.\r\n\t\tstateToRender: _newState,\r\n\r\n\t\t// Returns the current state of the `VirtualScroller`.\r\n\t\t// This function is used in the `VirtualScroller` itself\r\n\t\t// because the `state` is managed outside of it.\r\n\t\tgetState,\r\n\r\n\t\t// Requests a state update.\r\n\t\tsetState: _setNewState\r\n\t}\r\n}"],"mappings":";;;;;;;;;;;;AAAA,OAAOA,GAAP,IAAcC,OAAd,QAA6B,qBAA7B;AACA,OAAOC,gBAAP,MAA6B,gCAA7B;AAEA,SAASC,MAAT,EAAiBC,WAAjB,QAAoC,OAApC;AACA,OAAOC,kBAAP,MAA+B,yBAA/B;AACA,OAAOC,4CAAP,MAAyD,mDAAzD;AACA,OAAOC,yCAAP,MAAsD,gDAAtD,C,CAEA;;AACA,eAAe,SAASC,SAAT,OAIZ;EAAA,IAHFC,YAGE,QAHFA,YAGE;EAAA,IAFFC,QAEE,QAFFA,QAEE;EAAA,IADFC,aACE,QADFA,aACE;;EACF;EACA;EACA;EACA;EACA;EACA,0BAAkCN,kBAAkB,CAACI,YAAD,CAApD;EAAA;EAAA,IAAOG,SAAP;EAAA,IAAkBC,YAAlB,2BANE,CAQF;EACA;;;EACA,IAAMC,KAAK,GAAGX,MAAM,CAACM,YAAD,CAApB;EAEA,IAAMM,QAAQ,GAAGX,WAAW,CAAC,YAAM;IAClC,OAAOU,KAAK,CAACE,OAAb;EACA,CAF2B,EAEzB,EAFyB,CAA5B;EAIA,IAAMC,QAAQ,GAAGb,WAAW,CAAC,UAACc,QAAD,EAAc;IAC1CJ,KAAK,CAACE,OAAN,GAAgBE,QAAhB;EACA,CAF2B,EAEzB,EAFyB,CAA5B,CAhBE,CAoBF;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EACAZ,4CAA4C,CAAC,YAAM;IAClD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,IAAIL,OAAO,EAAX,EAAe;MACdD,GAAG,CAAC,0GAAD,CAAH;MACAA,GAAG,CAACE,gBAAgB,CAACU,SAAD,CAAjB,CAAH;IACA;;IACDK,QAAQ,CAACL,SAAD,CAAR;EACA,CA3B2C,EA2BzC,CAACA,SAAD,CA3ByC,CAA5C;EA6BAL,yCAAyC,CAAC,YAAM;IAC/C;IACA;IACAG,QAAQ;EACR,CAJwC,EAItC,CAACE,SAAD,CAJsC,CAAzC;EAMA,OAAO;IACN;IACAO,aAAa,EAAEP,SAFT;IAIN;IACA;IACA;IACAG,QAAQ,EAARA,QAPM;IASN;IACAE,QAAQ,EAAEJ;EAVJ,CAAP;AAYA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
2
|
+
|
|
3
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
4
|
+
|
|
5
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
6
|
+
|
|
7
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
8
|
+
|
|
9
|
+
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
|
|
10
|
+
|
|
11
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
12
|
+
|
|
13
|
+
import { useRef, useState, useCallback } from 'react'; // This hook fixes any weird intermediate inconsistent/invalid/stale state values.
|
|
14
|
+
// https://github.com/facebook/react/issues/25023#issuecomment-1480463544
|
|
15
|
+
|
|
16
|
+
export default function useStateNoStaleBug(initialState) {
|
|
17
|
+
// const latestValidState = useRef(initialState)
|
|
18
|
+
var latestWrittenState = useRef(initialState);
|
|
19
|
+
|
|
20
|
+
var _useState = useState(initialState),
|
|
21
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
22
|
+
_state = _useState2[0],
|
|
23
|
+
_setState = _useState2[1]; // Instead of dealing with a potentially out-of-sync (stale) state value,
|
|
24
|
+
// simply use the correct latest one.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
var state = latestWrittenState.current;
|
|
28
|
+
/*
|
|
29
|
+
let state
|
|
30
|
+
if (_state === latestWrittenState.current) {
|
|
31
|
+
state = _state
|
|
32
|
+
latestValidState.current = _state
|
|
33
|
+
} else {
|
|
34
|
+
// React bug detected: an out-of-sync (stale) state value received.
|
|
35
|
+
// Ignore the out-of-sync (stale) state value.
|
|
36
|
+
state = latestValidState.current
|
|
37
|
+
}
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
var setState = useCallback(function (newState) {
|
|
41
|
+
if (typeof newState === 'function') {
|
|
42
|
+
throw new Error('Function argument of `setState()` function is not supported by this hook');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
latestWrittenState.current = newState;
|
|
46
|
+
|
|
47
|
+
_setState(newState);
|
|
48
|
+
}, []);
|
|
49
|
+
return [state, setState];
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=useStateNoStaleBug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStateNoStaleBug.js","names":["useRef","useState","useCallback","useStateNoStaleBug","initialState","latestWrittenState","_state","_setState","state","current","setState","newState","Error"],"sources":["../../source/react/useStateNoStaleBug.js"],"sourcesContent":["import { useRef, useState, useCallback } from 'react'\r\n\r\n// This hook fixes any weird intermediate inconsistent/invalid/stale state values.\r\n// https://github.com/facebook/react/issues/25023#issuecomment-1480463544\r\nexport default function useStateNoStaleBug(initialState) {\r\n // const latestValidState = useRef(initialState)\r\n const latestWrittenState = useRef(initialState)\r\n const [_state, _setState] = useState(initialState)\r\n\r\n // Instead of dealing with a potentially out-of-sync (stale) state value,\r\n // simply use the correct latest one.\r\n const state = latestWrittenState.current\r\n\r\n /*\r\n let state\r\n if (_state === latestWrittenState.current) {\r\n state = _state\r\n latestValidState.current = _state\r\n } else {\r\n // React bug detected: an out-of-sync (stale) state value received.\r\n // Ignore the out-of-sync (stale) state value.\r\n state = latestValidState.current\r\n }\r\n */\r\n\r\n const setState = useCallback((newState) => {\r\n if (typeof newState === 'function') {\r\n throw new Error('Function argument of `setState()` function is not supported by this hook')\r\n }\r\n latestWrittenState.current = newState\r\n _setState(newState)\r\n }, [])\r\n\r\n return [state, setState]\r\n}"],"mappings":";;;;;;;;;;;;AAAA,SAASA,MAAT,EAAiBC,QAAjB,EAA2BC,WAA3B,QAA8C,OAA9C,C,CAEA;AACA;;AACA,eAAe,SAASC,kBAAT,CAA4BC,YAA5B,EAA0C;EACvD;EACA,IAAMC,kBAAkB,GAAGL,MAAM,CAACI,YAAD,CAAjC;;EACA,gBAA4BH,QAAQ,CAACG,YAAD,CAApC;EAAA;EAAA,IAAOE,MAAP;EAAA,IAAeC,SAAf,iBAHuD,CAKvD;EACA;;;EACA,IAAMC,KAAK,GAAGH,kBAAkB,CAACI,OAAjC;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;EAEE,IAAMC,QAAQ,GAAGR,WAAW,CAAC,UAACS,QAAD,EAAc;IACzC,IAAI,OAAOA,QAAP,KAAoB,UAAxB,EAAoC;MAClC,MAAM,IAAIC,KAAJ,CAAU,0EAAV,CAAN;IACD;;IACDP,kBAAkB,CAACI,OAAnB,GAA6BE,QAA7B;;IACAJ,SAAS,CAACI,QAAD,CAAT;EACD,CAN2B,EAMzB,EANyB,CAA5B;EAQA,OAAO,CAACH,KAAD,EAAQE,QAAR,CAAP;AACD"}
|
package/package.json
CHANGED
package/source/Layout.js
CHANGED
|
@@ -54,7 +54,7 @@ export default class Layout {
|
|
|
54
54
|
return getValue()
|
|
55
55
|
} catch (error) {
|
|
56
56
|
if (error instanceof ScrollableContainerNotReadyError) {
|
|
57
|
-
log(
|
|
57
|
+
log(`Scrollable container size is not known at this point, so "${name}" can't be calculated yet. Default to`, defaultValue);
|
|
58
58
|
return defaultValue
|
|
59
59
|
} else {
|
|
60
60
|
throw error
|
|
@@ -356,14 +356,12 @@ export default class Layout {
|
|
|
356
356
|
*/
|
|
357
357
|
getShownItemIndexes({
|
|
358
358
|
itemsCount,
|
|
359
|
-
|
|
360
|
-
visibleAreaBottom
|
|
359
|
+
visibleAreaInsideTheList
|
|
361
360
|
}) {
|
|
362
361
|
let indexes = this._getShownItemIndex({
|
|
363
362
|
itemsCount,
|
|
364
363
|
fromIndex: 0,
|
|
365
|
-
|
|
366
|
-
visibleAreaBottom,
|
|
364
|
+
visibleAreaInsideTheList,
|
|
367
365
|
findFirstShownItemIndex: true
|
|
368
366
|
})
|
|
369
367
|
|
|
@@ -381,8 +379,7 @@ export default class Layout {
|
|
|
381
379
|
itemsCount,
|
|
382
380
|
fromIndex: firstShownItemIndex,
|
|
383
381
|
beforeItemsHeight,
|
|
384
|
-
|
|
385
|
-
visibleAreaBottom,
|
|
382
|
+
visibleAreaInsideTheList,
|
|
386
383
|
findLastShownItemIndex: true
|
|
387
384
|
})
|
|
388
385
|
|
|
@@ -406,8 +403,7 @@ export default class Layout {
|
|
|
406
403
|
const {
|
|
407
404
|
beforeResize,
|
|
408
405
|
itemsCount,
|
|
409
|
-
|
|
410
|
-
visibleAreaBottom,
|
|
406
|
+
visibleAreaInsideTheList,
|
|
411
407
|
findFirstShownItemIndex,
|
|
412
408
|
findLastShownItemIndex,
|
|
413
409
|
// backwards
|
|
@@ -441,7 +437,8 @@ export default class Layout {
|
|
|
441
437
|
// If "previously calculated layout" would be used then it would first find
|
|
442
438
|
// `firstShownItemIndex` and then find `lastShownItemIndex` as part of two
|
|
443
439
|
// separate calls of this function, each with or without `backwards` flag,
|
|
444
|
-
// depending on whether `
|
|
440
|
+
// depending on whether `visibleAreaInsideTheList.top` and `visibleAreaInsideTheList.top`
|
|
441
|
+
// have shifted up or down.
|
|
445
442
|
|
|
446
443
|
let firstShownItemIndex
|
|
447
444
|
let lastShownItemIndex
|
|
@@ -525,7 +522,7 @@ export default class Layout {
|
|
|
525
522
|
itemsCount,
|
|
526
523
|
firstShownItemIndex: findLastShownItemIndex ? fromIndex : undefined,
|
|
527
524
|
indexOfTheFirstItemInTheRow: currentRowFirstItemIndex,
|
|
528
|
-
nonMeasuredAreaHeight: (
|
|
525
|
+
nonMeasuredAreaHeight: (visibleAreaInsideTheList.bottom + this.getPrerenderMargin()) - beforeItemsHeight
|
|
529
526
|
})
|
|
530
527
|
}
|
|
531
528
|
|
|
@@ -537,8 +534,8 @@ export default class Layout {
|
|
|
537
534
|
|
|
538
535
|
const itemsHeightFromFirstRowToThisRow = beforeItemsHeight + currentRowHeight
|
|
539
536
|
|
|
540
|
-
const rowStepsIntoVisibleAreaTop = itemsHeightFromFirstRowToThisRow >
|
|
541
|
-
const rowStepsOutOfVisibleAreaBottomOrIsAtTheBorder = itemsHeightFromFirstRowToThisRow + verticalSpacingAfterCurrentRow >=
|
|
537
|
+
const rowStepsIntoVisibleAreaTop = itemsHeightFromFirstRowToThisRow > visibleAreaInsideTheList.top - this.getPrerenderMargin()
|
|
538
|
+
const rowStepsOutOfVisibleAreaBottomOrIsAtTheBorder = itemsHeightFromFirstRowToThisRow + verticalSpacingAfterCurrentRow >= visibleAreaInsideTheList.bottom + this.getPrerenderMargin()
|
|
542
539
|
|
|
543
540
|
// if (backwards) {
|
|
544
541
|
// if (findFirstShownItemIndex) {
|
package/source/Layout.test.js
CHANGED
|
@@ -31,8 +31,10 @@ describe('Layout', function() {
|
|
|
31
31
|
// Initial render.
|
|
32
32
|
layout.getShownItemIndexes({
|
|
33
33
|
itemsCount: items.length,
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
visibleAreaInsideTheList: {
|
|
35
|
+
top: 0,
|
|
36
|
+
bottom: SCREEN_HEIGHT
|
|
37
|
+
}
|
|
36
38
|
}).should.deep.equal({
|
|
37
39
|
firstShownItemIndex: 0,
|
|
38
40
|
lastShownItemIndex: 2
|
|
@@ -41,8 +43,10 @@ describe('Layout', function() {
|
|
|
41
43
|
// The first item is almost hidden.
|
|
42
44
|
layout.getShownItemIndexes({
|
|
43
45
|
itemsCount: items.length,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
visibleAreaInsideTheList: {
|
|
47
|
+
top: SCREEN_HEIGHT + ITEM_HEIGHT - 1,
|
|
48
|
+
bottom: (SCREEN_HEIGHT + ITEM_HEIGHT - 1) + SCREEN_HEIGHT
|
|
49
|
+
}
|
|
46
50
|
}).should.deep.equal({
|
|
47
51
|
firstShownItemIndex: 0,
|
|
48
52
|
lastShownItemIndex: 4
|
|
@@ -51,8 +55,10 @@ describe('Layout', function() {
|
|
|
51
55
|
// The first item is hidden.
|
|
52
56
|
layout.getShownItemIndexes({
|
|
53
57
|
itemsCount: items.length,
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
visibleAreaInsideTheList: {
|
|
59
|
+
top: SCREEN_HEIGHT + ITEM_HEIGHT,
|
|
60
|
+
bottom: (SCREEN_HEIGHT + ITEM_HEIGHT) + SCREEN_HEIGHT
|
|
61
|
+
}
|
|
56
62
|
}).should.deep.equal({
|
|
57
63
|
firstShownItemIndex: 1,
|
|
58
64
|
lastShownItemIndex: 4
|
|
@@ -61,8 +67,10 @@ describe('Layout', function() {
|
|
|
61
67
|
// A new item at the bottom is almost visible.
|
|
62
68
|
layout.getShownItemIndexes({
|
|
63
69
|
itemsCount: items.length,
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
visibleAreaInsideTheList: {
|
|
71
|
+
top: (ITEM_HEIGHT + VERTICAL_SPACING) * 5 - SCREEN_HEIGHT * 2,
|
|
72
|
+
bottom: (ITEM_HEIGHT + VERTICAL_SPACING) * 5 - SCREEN_HEIGHT
|
|
73
|
+
}
|
|
66
74
|
}).should.deep.equal({
|
|
67
75
|
firstShownItemIndex: 1,
|
|
68
76
|
lastShownItemIndex: 4
|
|
@@ -71,8 +79,10 @@ describe('Layout', function() {
|
|
|
71
79
|
// A new item at the bottom is visible.
|
|
72
80
|
layout.getShownItemIndexes({
|
|
73
81
|
itemsCount: items.length,
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
visibleAreaInsideTheList: {
|
|
83
|
+
top: (ITEM_HEIGHT + VERTICAL_SPACING) * 5 + 1 - SCREEN_HEIGHT * 2,
|
|
84
|
+
bottom: (ITEM_HEIGHT + VERTICAL_SPACING) * 5 + 1 - SCREEN_HEIGHT
|
|
85
|
+
}
|
|
76
86
|
}).should.deep.equal({
|
|
77
87
|
firstShownItemIndex: 1,
|
|
78
88
|
lastShownItemIndex: 5
|
package/source/Scroll.js
CHANGED
|
@@ -42,6 +42,7 @@ export default class Scroll {
|
|
|
42
42
|
start() {
|
|
43
43
|
if (this.initialScrollPosition !== undefined) {
|
|
44
44
|
this.scrollToY(this.initialScrollPosition)
|
|
45
|
+
// Don't restore this scroll position on restart.
|
|
45
46
|
this.initialScrollPosition = undefined
|
|
46
47
|
}
|
|
47
48
|
if (this.onScrollPositionChange) {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import DOMEngine from './DOM/Engine.js'
|
|
7
7
|
|
|
8
8
|
import Layout, { LAYOUT_REASON } from './Layout.js'
|
|
9
|
-
import
|
|
9
|
+
import ScrollableContainerResizeHandler from './ScrollableContainerResizeHandler.js'
|
|
10
10
|
import BeforeResize from './BeforeResize.js'
|
|
11
11
|
import Scroll from './Scroll.js'
|
|
12
12
|
import ListHeightMeasurement from './ListHeightMeasurement.js'
|
|
@@ -19,7 +19,7 @@ import createVerticalSpacingHelpers from './VirtualScroller.verticalSpacing.js'
|
|
|
19
19
|
import createColumnsHelpers from './VirtualScroller.columns.js'
|
|
20
20
|
import createLayoutHelpers from './VirtualScroller.layout.js'
|
|
21
21
|
import createOnRenderHelpers from './VirtualScroller.onRender.js'
|
|
22
|
-
import
|
|
22
|
+
import createScrollableContainerResizeHelpers from './VirtualScroller.onContainerResize.js'
|
|
23
23
|
import createItemsHelpers from './VirtualScroller.items.js'
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -193,7 +193,7 @@ export default function VirtualScrollerConstructor(
|
|
|
193
193
|
|
|
194
194
|
createLayoutHelpers.call(this)
|
|
195
195
|
createOnRenderHelpers.call(this)
|
|
196
|
-
|
|
196
|
+
createScrollableContainerResizeHelpers.call(this)
|
|
197
197
|
createItemsHelpers.call(this)
|
|
198
198
|
|
|
199
199
|
createHelpers.call(this, {
|
|
@@ -277,7 +277,7 @@ function createHelpers({
|
|
|
277
277
|
getPreviouslyCalculatedLayout: () => this.previouslyCalculatedLayout
|
|
278
278
|
})
|
|
279
279
|
|
|
280
|
-
this.
|
|
280
|
+
this.scrollableContainerResizeHandler = new ScrollableContainerResizeHandler({
|
|
281
281
|
bypass: this.bypass,
|
|
282
282
|
getWidth: () => this.scrollableContainer.getWidth(),
|
|
283
283
|
getHeight: () => this.scrollableContainer.getHeight(),
|
|
@@ -303,7 +303,7 @@ function createHelpers({
|
|
|
303
303
|
}),
|
|
304
304
|
onWidthChange: (prevWidth, newWidth) => {
|
|
305
305
|
log('~ Scrollable container width changed from', prevWidth, 'to', newWidth, '~')
|
|
306
|
-
this.
|
|
306
|
+
this.onContainerResize()
|
|
307
307
|
}
|
|
308
308
|
})
|
|
309
309
|
|
|
@@ -215,7 +215,7 @@ export default function() {
|
|
|
215
215
|
// `this.newItemsWillBeRendered` is cleared in `onRender()`.
|
|
216
216
|
//
|
|
217
217
|
// The values in `this.newItemsWillBeRendered` are used, for example,
|
|
218
|
-
// in `.
|
|
218
|
+
// in `.onContainerResize()` handler in order to not break state consistency when
|
|
219
219
|
// state updates are "asynchronous" (delayed) and there's a window resize event
|
|
220
220
|
// in between calling `updateState()` below and that call actually being applied.
|
|
221
221
|
//
|