react-native-nitro-list 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -16
- package/android/build.gradle +5 -4
- package/android/src/main/java/com/nitrolist/HybridNitroRecyclerView.kt +26 -0
- package/android/src/main/java/com/nitrolist/HybridNitroRecyclerViewManager.kt +29 -0
- package/android/src/main/java/com/nitrolist/NitroListPackage.kt +13 -24
- package/android/src/main/java/com/nitrolist/NitroRecyclerAdapter.kt +32 -0
- package/android/src/main/java/com/nitrolist/NitroRecyclerViewHolder.kt +8 -0
- package/ios/HybridNitroList.swift +32 -15
- package/lib/commonjs/NitroList.js +9 -0
- package/lib/commonjs/NitroList.js.map +1 -0
- package/lib/commonjs/RecyclerList.internal.js +25 -0
- package/lib/commonjs/RecyclerList.internal.js.map +1 -0
- package/lib/commonjs/RecyclerList.js +171 -0
- package/lib/commonjs/RecyclerList.js.map +1 -0
- package/lib/commonjs/RecyclerList.types.js +6 -0
- package/lib/commonjs/RecyclerList.types.js.map +1 -0
- package/lib/commonjs/ReusableView.js +2 -0
- package/lib/commonjs/ReusableView.js.map +1 -0
- package/lib/commonjs/cell/Cell.js +2 -0
- package/lib/{module/specs/nitro-list.nitro.js.map → commonjs/cell/Cell.js.map} +1 -1
- package/lib/commonjs/cell/CellRecycler.js +62 -0
- package/lib/commonjs/cell/CellRecycler.js.map +1 -0
- package/lib/commonjs/cell/StableKey.js +10 -0
- package/lib/commonjs/cell/StableKey.js.map +1 -0
- package/lib/commonjs/debug/useDebugOverlay.js +15 -0
- package/lib/commonjs/debug/useDebugOverlay.js.map +1 -0
- package/lib/commonjs/getVisibleIndices.js +38 -0
- package/lib/commonjs/getVisibleIndices.js.map +1 -0
- package/lib/commonjs/hooks/useCellRenderer.js +62 -0
- package/lib/commonjs/hooks/useCellRenderer.js.map +1 -0
- package/lib/commonjs/hooks/useStableCallback.js +13 -0
- package/lib/commonjs/hooks/useStableCallback.js.map +1 -0
- package/lib/commonjs/index.js +7 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/layout/EstimatedLayoutProvider.js +30 -0
- package/lib/commonjs/layout/EstimatedLayoutProvider.js.map +1 -0
- package/lib/commonjs/layout/LayoutEngine.js +21 -0
- package/lib/commonjs/layout/LayoutEngine.js.map +1 -0
- package/lib/commonjs/{specs/nitro-list.nitro.js → layout/LayoutProvider.js} +1 -1
- package/lib/commonjs/{specs/nitro-list.nitro.js.map → layout/LayoutProvider.js.map} +1 -1
- package/lib/commonjs/layout/LayoutRectangle.js +2 -0
- package/lib/commonjs/layout/LayoutRectangle.js.map +1 -0
- package/lib/commonjs/layout/MutableLinearLayout.js +65 -0
- package/lib/commonjs/layout/MutableLinearLayout.js.map +1 -0
- package/lib/commonjs/layout/index.js +20 -0
- package/lib/commonjs/layout/index.js.map +1 -0
- package/lib/commonjs/measurement/MeasureLayout.js +20 -0
- package/lib/commonjs/measurement/MeasureLayout.js.map +1 -0
- package/lib/commonjs/measurement/useItemMeasurement.js +28 -0
- package/lib/commonjs/measurement/useItemMeasurement.js.map +1 -0
- package/lib/commonjs/native/NitroLayoutEngine.js +9 -0
- package/lib/commonjs/native/NitroLayoutEngine.js.map +1 -0
- package/lib/commonjs/native/NitroList.types.js +6 -0
- package/lib/commonjs/native/NitroList.types.js.map +1 -0
- package/lib/commonjs/native/NitroRecyclerView.js +9 -0
- package/lib/commonjs/native/NitroRecyclerView.js.map +1 -0
- package/lib/commonjs/prefetch/PrefetchHelper.js +39 -0
- package/lib/commonjs/prefetch/PrefetchHelper.js.map +1 -0
- package/lib/commonjs/specs/nitro-layout-engine.nitro.js +6 -0
- package/lib/commonjs/specs/nitro-layout-engine.nitro.js.map +1 -0
- package/lib/commonjs/utils/arrayEqual.js +15 -0
- package/lib/commonjs/utils/arrayEqual.js.map +1 -0
- package/lib/commonjs/utils/assertNever.js +10 -0
- package/lib/commonjs/utils/assertNever.js.map +1 -0
- package/lib/commonjs/utils/clamp.js +10 -0
- package/lib/commonjs/utils/clamp.js.map +1 -0
- package/lib/commonjs/utils/devAssert.js +12 -0
- package/lib/commonjs/utils/devAssert.js.map +1 -0
- package/lib/commonjs/utils/invariant.js +12 -0
- package/lib/commonjs/utils/invariant.js.map +1 -0
- package/lib/commonjs/utils/isDefined.js +10 -0
- package/lib/commonjs/utils/isDefined.js.map +1 -0
- package/lib/commonjs/utils/isNumber.js +10 -0
- package/lib/commonjs/utils/isNumber.js.map +1 -0
- package/lib/commonjs/utils/noop.js +10 -0
- package/lib/commonjs/utils/noop.js.map +1 -0
- package/lib/commonjs/utils/shallowEqual.js +22 -0
- package/lib/commonjs/utils/shallowEqual.js.map +1 -0
- package/lib/commonjs/utils/throttle.js +17 -0
- package/lib/commonjs/utils/throttle.js.map +1 -0
- package/lib/commonjs/viewability/ViewabilityHelper.js +121 -0
- package/lib/commonjs/viewability/ViewabilityHelper.js.map +1 -0
- package/lib/commonjs/windowing/ScrollMetrics.js +2 -0
- package/lib/commonjs/windowing/ScrollMetrics.js.map +1 -0
- package/lib/commonjs/windowing/findVisibleIndexRange.js +48 -0
- package/lib/commonjs/windowing/findVisibleIndexRange.js.map +1 -0
- package/lib/commonjs/windowing/index.js +20 -0
- package/lib/commonjs/windowing/index.js.map +1 -0
- package/lib/commonjs/windowing/useScrollMetrics.js +39 -0
- package/lib/commonjs/windowing/useScrollMetrics.js.map +1 -0
- package/lib/module/NitroList.js +5 -0
- package/lib/module/NitroList.js.map +1 -0
- package/lib/module/RecyclerList.internal.js +22 -0
- package/lib/module/RecyclerList.internal.js.map +1 -0
- package/lib/module/RecyclerList.js +166 -0
- package/lib/module/RecyclerList.js.map +1 -0
- package/lib/module/RecyclerList.types.js +4 -0
- package/lib/module/RecyclerList.types.js.map +1 -0
- package/lib/module/ReusableView.js +2 -0
- package/lib/module/ReusableView.js.map +1 -0
- package/lib/module/cell/Cell.js +2 -0
- package/lib/module/cell/Cell.js.map +1 -0
- package/lib/module/cell/CellRecycler.js +57 -0
- package/lib/module/cell/CellRecycler.js.map +1 -0
- package/lib/module/cell/StableKey.js +6 -0
- package/lib/module/cell/StableKey.js.map +1 -0
- package/lib/module/debug/useDebugOverlay.js +11 -0
- package/lib/module/debug/useDebugOverlay.js.map +1 -0
- package/lib/module/getVisibleIndices.js +34 -0
- package/lib/module/getVisibleIndices.js.map +1 -0
- package/lib/module/hooks/useCellRenderer.js +58 -0
- package/lib/module/hooks/useCellRenderer.js.map +1 -0
- package/lib/module/hooks/useStableCallback.js +9 -0
- package/lib/module/hooks/useStableCallback.js.map +1 -0
- package/lib/module/index.js +1 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/layout/EstimatedLayoutProvider.js +25 -0
- package/lib/module/layout/EstimatedLayoutProvider.js.map +1 -0
- package/lib/module/layout/LayoutEngine.js +17 -0
- package/lib/module/layout/LayoutEngine.js.map +1 -0
- package/lib/module/layout/LayoutProvider.js +4 -0
- package/lib/module/layout/LayoutProvider.js.map +1 -0
- package/lib/module/layout/LayoutRectangle.js +2 -0
- package/lib/module/layout/LayoutRectangle.js.map +1 -0
- package/lib/module/layout/MutableLinearLayout.js +60 -0
- package/lib/module/layout/MutableLinearLayout.js.map +1 -0
- package/lib/module/layout/index.js +5 -0
- package/lib/module/layout/index.js.map +1 -0
- package/lib/module/measurement/MeasureLayout.js +16 -0
- package/lib/module/measurement/MeasureLayout.js.map +1 -0
- package/lib/module/measurement/useItemMeasurement.js +24 -0
- package/lib/module/measurement/useItemMeasurement.js.map +1 -0
- package/lib/module/native/NitroLayoutEngine.js +5 -0
- package/lib/module/native/NitroLayoutEngine.js.map +1 -0
- package/lib/module/native/NitroList.types.js +4 -0
- package/lib/module/native/NitroList.types.js.map +1 -0
- package/lib/module/native/NitroRecyclerView.js +5 -0
- package/lib/module/native/NitroRecyclerView.js.map +1 -0
- package/lib/module/prefetch/PrefetchHelper.js +34 -0
- package/lib/module/prefetch/PrefetchHelper.js.map +1 -0
- package/lib/module/specs/nitro-layout-engine.nitro.js +4 -0
- package/lib/module/specs/nitro-layout-engine.nitro.js.map +1 -0
- package/lib/module/utils/arrayEqual.js +11 -0
- package/lib/module/utils/arrayEqual.js.map +1 -0
- package/lib/module/utils/assertNever.js +6 -0
- package/lib/module/utils/assertNever.js.map +1 -0
- package/lib/module/utils/clamp.js +6 -0
- package/lib/module/utils/clamp.js.map +1 -0
- package/lib/module/utils/devAssert.js +8 -0
- package/lib/module/utils/devAssert.js.map +1 -0
- package/lib/module/utils/invariant.js +8 -0
- package/lib/module/utils/invariant.js.map +1 -0
- package/lib/module/utils/isDefined.js +6 -0
- package/lib/module/utils/isDefined.js.map +1 -0
- package/lib/module/utils/isNumber.js +6 -0
- package/lib/module/utils/isNumber.js.map +1 -0
- package/lib/module/utils/noop.js +6 -0
- package/lib/module/utils/noop.js.map +1 -0
- package/lib/module/utils/shallowEqual.js +18 -0
- package/lib/module/utils/shallowEqual.js.map +1 -0
- package/lib/module/utils/throttle.js +13 -0
- package/lib/module/utils/throttle.js.map +1 -0
- package/lib/module/viewability/ViewabilityHelper.js +116 -0
- package/lib/module/viewability/ViewabilityHelper.js.map +1 -0
- package/lib/module/windowing/ScrollMetrics.js +2 -0
- package/lib/module/windowing/ScrollMetrics.js.map +1 -0
- package/lib/module/windowing/findVisibleIndexRange.js +44 -0
- package/lib/module/windowing/findVisibleIndexRange.js.map +1 -0
- package/lib/module/windowing/index.js +5 -0
- package/lib/module/windowing/index.js.map +1 -0
- package/lib/module/windowing/useScrollMetrics.js +35 -0
- package/lib/module/windowing/useScrollMetrics.js.map +1 -0
- package/lib/typescript/src/NitroList.d.ts +5 -0
- package/lib/typescript/src/NitroList.d.ts.map +1 -0
- package/lib/typescript/src/RecyclerList.d.ts +26 -0
- package/lib/typescript/src/RecyclerList.d.ts.map +1 -0
- package/lib/typescript/src/RecyclerList.internal.d.ts +9 -0
- package/lib/typescript/src/RecyclerList.internal.d.ts.map +1 -0
- package/lib/typescript/src/RecyclerList.types.d.ts +18 -0
- package/lib/typescript/src/RecyclerList.types.d.ts.map +1 -0
- package/lib/typescript/src/ReusableView.d.ts +9 -0
- package/lib/typescript/src/ReusableView.d.ts.map +1 -0
- package/lib/typescript/src/__tests__/windowing/findVisibleIndexRange.test.d.ts +2 -0
- package/lib/typescript/src/__tests__/windowing/findVisibleIndexRange.test.d.ts.map +1 -0
- package/lib/typescript/src/cell/Cell.d.ts +13 -0
- package/lib/typescript/src/cell/Cell.d.ts.map +1 -0
- package/lib/typescript/src/cell/CellRecycler.d.ts +19 -0
- package/lib/typescript/src/cell/CellRecycler.d.ts.map +1 -0
- package/lib/typescript/src/cell/StableKey.d.ts +2 -0
- package/lib/typescript/src/cell/StableKey.d.ts.map +1 -0
- package/lib/typescript/src/debug/useDebugOverlay.d.ts +2 -0
- package/lib/typescript/src/debug/useDebugOverlay.d.ts.map +1 -0
- package/lib/typescript/src/getVisibleIndices.d.ts +8 -0
- package/lib/typescript/src/getVisibleIndices.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useCellRenderer.d.ts +19 -0
- package/lib/typescript/src/hooks/useCellRenderer.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useStableCallback.d.ts +2 -0
- package/lib/typescript/src/hooks/useStableCallback.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -4
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/layout/EstimatedLayoutProvider.d.ts +15 -0
- package/lib/typescript/src/layout/EstimatedLayoutProvider.d.ts.map +1 -0
- package/lib/typescript/src/layout/LayoutEngine.d.ts +10 -0
- package/lib/typescript/src/layout/LayoutEngine.d.ts.map +1 -0
- package/lib/typescript/src/layout/LayoutProvider.d.ts +16 -0
- package/lib/typescript/src/layout/LayoutProvider.d.ts.map +1 -0
- package/lib/typescript/src/layout/LayoutRectangle.d.ts +11 -0
- package/lib/typescript/src/layout/LayoutRectangle.d.ts.map +1 -0
- package/lib/typescript/src/layout/MutableLinearLayout.d.ts +22 -0
- package/lib/typescript/src/layout/MutableLinearLayout.d.ts.map +1 -0
- package/lib/typescript/src/layout/index.d.ts +5 -0
- package/lib/typescript/src/layout/index.d.ts.map +1 -0
- package/lib/typescript/src/measurement/MeasureLayout.d.ts +10 -0
- package/lib/typescript/src/measurement/MeasureLayout.d.ts.map +1 -0
- package/lib/typescript/src/measurement/useItemMeasurement.d.ts +11 -0
- package/lib/typescript/src/measurement/useItemMeasurement.d.ts.map +1 -0
- package/lib/typescript/src/native/NitroLayoutEngine.d.ts +3 -0
- package/lib/typescript/src/native/NitroLayoutEngine.d.ts.map +1 -0
- package/lib/typescript/src/native/NitroList.types.d.ts +9 -0
- package/lib/typescript/src/native/NitroList.types.d.ts.map +1 -0
- package/lib/typescript/src/native/NitroRecyclerView.d.ts +5 -0
- package/lib/typescript/src/native/NitroRecyclerView.d.ts.map +1 -0
- package/lib/typescript/src/prefetch/PrefetchHelper.d.ts +17 -0
- package/lib/typescript/src/prefetch/PrefetchHelper.d.ts.map +1 -0
- package/lib/typescript/src/specs/nitro-layout-engine.nitro.d.ts +14 -0
- package/lib/typescript/src/specs/nitro-layout-engine.nitro.d.ts.map +1 -0
- package/lib/typescript/src/utils/arrayEqual.d.ts +2 -0
- package/lib/typescript/src/utils/arrayEqual.d.ts.map +1 -0
- package/lib/typescript/src/utils/assertNever.d.ts +2 -0
- package/lib/typescript/src/utils/assertNever.d.ts.map +1 -0
- package/lib/typescript/src/utils/clamp.d.ts +2 -0
- package/lib/typescript/src/utils/clamp.d.ts.map +1 -0
- package/lib/typescript/src/utils/devAssert.d.ts +2 -0
- package/lib/typescript/src/utils/devAssert.d.ts.map +1 -0
- package/lib/typescript/src/utils/invariant.d.ts +2 -0
- package/lib/typescript/src/utils/invariant.d.ts.map +1 -0
- package/lib/typescript/src/utils/isDefined.d.ts +2 -0
- package/lib/typescript/src/utils/isDefined.d.ts.map +1 -0
- package/lib/typescript/src/utils/isNumber.d.ts +2 -0
- package/lib/typescript/src/utils/isNumber.d.ts.map +1 -0
- package/lib/typescript/src/utils/noop.d.ts +2 -0
- package/lib/typescript/src/utils/noop.d.ts.map +1 -0
- package/lib/typescript/src/utils/shallowEqual.d.ts +2 -0
- package/lib/typescript/src/utils/shallowEqual.d.ts.map +1 -0
- package/lib/typescript/src/utils/throttle.d.ts +2 -0
- package/lib/typescript/src/utils/throttle.d.ts.map +1 -0
- package/lib/typescript/src/viewability/ViewabilityHelper.d.ts +50 -0
- package/lib/typescript/src/viewability/ViewabilityHelper.d.ts.map +1 -0
- package/lib/typescript/src/windowing/ScrollMetrics.d.ts +11 -0
- package/lib/typescript/src/windowing/ScrollMetrics.d.ts.map +1 -0
- package/lib/typescript/src/windowing/findVisibleIndexRange.d.ts +11 -0
- package/lib/typescript/src/windowing/findVisibleIndexRange.d.ts.map +1 -0
- package/lib/typescript/src/windowing/index.d.ts +4 -0
- package/lib/typescript/src/windowing/index.d.ts.map +1 -0
- package/lib/typescript/src/windowing/useScrollMetrics.d.ts +14 -0
- package/lib/typescript/src/windowing/useScrollMetrics.d.ts.map +1 -0
- package/nitro.json +20 -13
- package/nitrogen/generated/android/NitroList+autolinking.cmake +2 -4
- package/nitrogen/generated/android/NitroListOnLoad.cpp +3 -13
- package/nitrogen/generated/android/c++/JHybridNitroLayoutEngineSpec.cpp +69 -0
- package/nitrogen/generated/android/c++/{JHybridNitroListSpec.hpp → JHybridNitroLayoutEngineSpec.hpp} +12 -13
- package/nitrogen/generated/android/c++/JLayoutRectangle.hpp +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolist/{HybridNitroListSpec.kt → HybridNitroLayoutEngineSpec.kt} +11 -13
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolist/LayoutRectangle.kt +47 -0
- package/nitrogen/generated/ios/NitroList-Swift-Cxx-Bridge.cpp +9 -9
- package/nitrogen/generated/ios/NitroList-Swift-Cxx-Bridge.hpp +50 -13
- package/nitrogen/generated/ios/NitroList-Swift-Cxx-Umbrella.hpp +11 -5
- package/nitrogen/generated/ios/c++/{HybridNitroListSpecSwift.cpp → HybridNitroLayoutEngineSpecSwift.cpp} +2 -2
- package/nitrogen/generated/ios/c++/HybridNitroLayoutEngineSpecSwift.hpp +78 -0
- package/nitrogen/generated/ios/swift/HybridNitroLayoutEngineSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/{HybridNitroListSpec_cxx.swift → HybridNitroLayoutEngineSpec_cxx.swift} +35 -38
- package/nitrogen/generated/ios/swift/LayoutRectangle.swift +45 -0
- package/nitrogen/generated/shared/c++/{HybridNitroListSpec.cpp → HybridNitroLayoutEngineSpec.cpp} +4 -5
- package/nitrogen/generated/shared/c++/{HybridNitroListSpec.hpp → HybridNitroLayoutEngineSpec.hpp} +15 -14
- package/nitrogen/generated/shared/c++/LayoutRectangle.hpp +95 -0
- package/package.json +12 -4
- package/src/NitroList.ts +8 -0
- package/src/RecyclerList.internal.ts +38 -0
- package/src/RecyclerList.tsx +252 -0
- package/src/RecyclerList.types.ts +24 -0
- package/src/ReusableView.ts +12 -0
- package/src/__tests__/windowing/findVisibleIndexRange.test.ts +55 -0
- package/src/cell/Cell.ts +14 -0
- package/src/cell/CellRecycler.ts +66 -0
- package/src/cell/StableKey.ts +6 -0
- package/src/debug/useDebugOverlay.ts +14 -0
- package/src/getVisibleIndices.ts +53 -0
- package/src/hooks/useCellRenderer.ts +86 -0
- package/src/hooks/useStableCallback.ts +13 -0
- package/src/index.ts +2 -14
- package/src/layout/EstimatedLayoutProvider.ts +35 -0
- package/src/layout/LayoutEngine.ts +21 -0
- package/src/layout/LayoutProvider.ts +17 -0
- package/src/layout/LayoutRectangle.ts +10 -0
- package/src/layout/MutableLinearLayout.ts +79 -0
- package/src/layout/index.ts +4 -0
- package/src/measurement/MeasureLayout.ts +20 -0
- package/src/measurement/useItemMeasurement.ts +32 -0
- package/src/native/NitroLayoutEngine.ts +7 -0
- package/src/native/NitroList.types.ts +12 -0
- package/src/native/NitroRecyclerView.ts +8 -0
- package/src/prefetch/PrefetchHelper.ts +47 -0
- package/src/specs/nitro-layout-engine.nitro.ts +17 -0
- package/src/utils/arrayEqual.ts +13 -0
- package/src/utils/assertNever.ts +3 -0
- package/src/utils/clamp.ts +7 -0
- package/src/utils/devAssert.ts +8 -0
- package/src/utils/invariant.ts +8 -0
- package/src/utils/isDefined.ts +5 -0
- package/src/utils/isNumber.ts +3 -0
- package/src/utils/noop.ts +3 -0
- package/src/utils/shallowEqual.ts +34 -0
- package/src/utils/throttle.ts +13 -0
- package/src/viewability/ViewabilityHelper.ts +130 -0
- package/src/windowing/ScrollMetrics.ts +11 -0
- package/src/windowing/findVisibleIndexRange.ts +60 -0
- package/src/windowing/index.ts +3 -0
- package/src/windowing/useScrollMetrics.ts +56 -0
- package/android/src/main/java/com/nitrolist/HybridNitroList.kt +0 -27
- package/lib/module/specs/nitro-list.nitro.js +0 -4
- package/lib/typescript/src/specs/nitro-list.nitro.d.ts +0 -11
- package/lib/typescript/src/specs/nitro-list.nitro.d.ts.map +0 -1
- package/nitrogen/generated/android/c++/JHybridNitroListSpec.cpp +0 -56
- package/nitrogen/generated/android/c++/views/JHybridNitroListStateUpdater.cpp +0 -56
- package/nitrogen/generated/android/c++/views/JHybridNitroListStateUpdater.hpp +0 -49
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolist/views/HybridNitroListManager.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolist/views/HybridNitroListStateUpdater.kt +0 -23
- package/nitrogen/generated/ios/NitroListAutolinking.mm +0 -33
- package/nitrogen/generated/ios/NitroListAutolinking.swift +0 -25
- package/nitrogen/generated/ios/c++/HybridNitroListSpecSwift.hpp +0 -74
- package/nitrogen/generated/ios/c++/views/HybridNitroListComponent.mm +0 -96
- package/nitrogen/generated/ios/swift/HybridNitroListSpec.swift +0 -56
- package/nitrogen/generated/shared/c++/views/HybridNitroListComponent.cpp +0 -88
- package/nitrogen/generated/shared/c++/views/HybridNitroListComponent.hpp +0 -107
- package/nitrogen/generated/shared/json/NitroListConfig.json +0 -10
- package/src/specs/nitro-list.nitro.ts +0 -13
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useMemo,
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
} from 'react'
|
|
6
|
+
import {
|
|
7
|
+
ScrollView,
|
|
8
|
+
View,
|
|
9
|
+
StyleSheet,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
|
|
12
|
+
import type { Cell } from './cell/Cell'
|
|
13
|
+
import type { LayoutRectangle } from './layout/LayoutRectangle'
|
|
14
|
+
|
|
15
|
+
import { useRecyclerListInternal } from './RecyclerList.internal'
|
|
16
|
+
import { useScrollMetrics } from './windowing'
|
|
17
|
+
import { MutableLinearLayout } from './layout/MutableLinearLayout'
|
|
18
|
+
import { ViewabilityHelper } from './viewability/ViewabilityHelper'
|
|
19
|
+
import { PrefetchHelper } from './prefetch/PrefetchHelper'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Public API — mirrors FlashList behavior, not internals.
|
|
23
|
+
*/
|
|
24
|
+
export type RecyclerListProps = {
|
|
25
|
+
containerWidth: number
|
|
26
|
+
itemCount?: number
|
|
27
|
+
itemHeights?: readonly number[]
|
|
28
|
+
estimatedItemHeight?: number
|
|
29
|
+
renderBufferRatio?: number
|
|
30
|
+
getItemType?: (index: number) => string
|
|
31
|
+
renderItem: (index: number) => React.ReactElement
|
|
32
|
+
onPrefetch?: (indices: number[]) => void
|
|
33
|
+
onViewableItemsChanged?: (info: {
|
|
34
|
+
viewableItems: { index: number; isViewable: boolean }[]
|
|
35
|
+
changed: { index: number; isViewable: boolean }[]
|
|
36
|
+
}) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* FlashList invariant:
|
|
41
|
+
* Never allow viewport height = 0 to gate windowing.
|
|
42
|
+
*/
|
|
43
|
+
const DEFAULT_VIEWPORT_HEIGHT = 800
|
|
44
|
+
|
|
45
|
+
export function RecyclerList(props: RecyclerListProps): React.ReactElement {
|
|
46
|
+
const {
|
|
47
|
+
containerWidth,
|
|
48
|
+
itemCount,
|
|
49
|
+
itemHeights,
|
|
50
|
+
estimatedItemHeight = 80,
|
|
51
|
+
renderBufferRatio = 1.3,
|
|
52
|
+
getItemType = () => 'default',
|
|
53
|
+
renderItem,
|
|
54
|
+
onPrefetch,
|
|
55
|
+
onViewableItemsChanged,
|
|
56
|
+
} = props
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 🚨 DEV WARNINGS ONLY (never throw in render!)
|
|
60
|
+
*/
|
|
61
|
+
if (__DEV__) {
|
|
62
|
+
if (!itemHeights && itemCount == null) {
|
|
63
|
+
console.error(
|
|
64
|
+
'RecyclerList requires either itemCount or itemHeights.'
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (itemHeights && itemCount != null) {
|
|
69
|
+
console.error(
|
|
70
|
+
'Provide only one of itemCount or itemHeights.'
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* STEP 1: Resolve item count safely (props only).
|
|
77
|
+
* Never depend on hooks for structural decisions.
|
|
78
|
+
*/
|
|
79
|
+
const itemLength =
|
|
80
|
+
itemHeights?.length ??
|
|
81
|
+
itemCount ??
|
|
82
|
+
0
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* STEP 2: Resolve initial heights (always defined).
|
|
86
|
+
*/
|
|
87
|
+
const resolvedHeights = useMemo<readonly number[]>(() => {
|
|
88
|
+
if (itemHeights) return itemHeights
|
|
89
|
+
|
|
90
|
+
return Array.from(
|
|
91
|
+
{ length: itemLength },
|
|
92
|
+
() => estimatedItemHeight
|
|
93
|
+
)
|
|
94
|
+
}, [itemHeights, itemLength, estimatedItemHeight])
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* STEP 3: Mutable layout lifecycle.
|
|
98
|
+
* Recreate layout only when shape changes.
|
|
99
|
+
*/
|
|
100
|
+
const layoutRef = useRef<MutableLinearLayout | null>(null)
|
|
101
|
+
const lastLayoutKeyRef = useRef<string | null>(null)
|
|
102
|
+
|
|
103
|
+
const layoutKey = `${containerWidth}-${itemLength}`
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
!layoutRef.current ||
|
|
107
|
+
lastLayoutKeyRef.current !== layoutKey
|
|
108
|
+
) {
|
|
109
|
+
layoutRef.current = new MutableLinearLayout(
|
|
110
|
+
resolvedHeights,
|
|
111
|
+
containerWidth
|
|
112
|
+
)
|
|
113
|
+
lastLayoutKeyRef.current = layoutKey
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* STEP 4: Read layout snapshot.
|
|
118
|
+
*/
|
|
119
|
+
const layouts: readonly LayoutRectangle[] =
|
|
120
|
+
layoutRef.current.getLayouts()
|
|
121
|
+
|
|
122
|
+
const contentHeight =
|
|
123
|
+
layoutRef.current.getContentHeight()
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* STEP 5: Scroll metrics.
|
|
127
|
+
*/
|
|
128
|
+
const {
|
|
129
|
+
metrics,
|
|
130
|
+
onScroll,
|
|
131
|
+
onLayout,
|
|
132
|
+
} = useScrollMetrics()
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* STEP 6: Guard initial windowing.
|
|
136
|
+
*/
|
|
137
|
+
const effectiveViewportHeight =
|
|
138
|
+
metrics.height > 0
|
|
139
|
+
? metrics.height
|
|
140
|
+
: DEFAULT_VIEWPORT_HEIGHT
|
|
141
|
+
|
|
142
|
+
const bufferPx =
|
|
143
|
+
effectiveViewportHeight * renderBufferRatio
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* STEP 7: Windowing + recycling.
|
|
147
|
+
*/
|
|
148
|
+
const cells: readonly Cell[] =
|
|
149
|
+
useRecyclerListInternal(
|
|
150
|
+
layouts,
|
|
151
|
+
metrics,
|
|
152
|
+
bufferPx,
|
|
153
|
+
getItemType
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* STEP 8: Phase-1 observers.
|
|
158
|
+
*/
|
|
159
|
+
const viewabilityRef = useRef(
|
|
160
|
+
new ViewabilityHelper({
|
|
161
|
+
itemVisiblePercentThreshold: 50,
|
|
162
|
+
})
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const prefetchRef = useRef(
|
|
166
|
+
new PrefetchHelper()
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
prefetchRef.current = new PrefetchHelper()
|
|
171
|
+
}, [itemLength])
|
|
172
|
+
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (metrics.height === 0) return
|
|
175
|
+
|
|
176
|
+
if (onViewableItemsChanged) {
|
|
177
|
+
const visibleLayouts: LayoutRectangle[] = []
|
|
178
|
+
|
|
179
|
+
for (const cell of cells) {
|
|
180
|
+
const layout = layouts[cell.index]
|
|
181
|
+
if (layout) visibleLayouts.push(layout)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const info =
|
|
185
|
+
viewabilityRef.current.computeViewableItems(
|
|
186
|
+
visibleLayouts,
|
|
187
|
+
metrics
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
onViewableItemsChanged(info)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (onPrefetch && itemLength > 0) {
|
|
194
|
+
prefetchRef.current.runPrefetch(
|
|
195
|
+
cells.map(c => c.index),
|
|
196
|
+
itemLength,
|
|
197
|
+
5,
|
|
198
|
+
onPrefetch
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}, [
|
|
202
|
+
cells,
|
|
203
|
+
metrics.offsetY,
|
|
204
|
+
metrics.height,
|
|
205
|
+
onViewableItemsChanged,
|
|
206
|
+
onPrefetch,
|
|
207
|
+
itemLength,
|
|
208
|
+
layouts,
|
|
209
|
+
])
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* STEP 9: Render absolutely positioned cells.
|
|
213
|
+
*/
|
|
214
|
+
return (
|
|
215
|
+
<ScrollView
|
|
216
|
+
style={styles.container}
|
|
217
|
+
onLayout={onLayout}
|
|
218
|
+
onScroll={onScroll}
|
|
219
|
+
scrollEventThrottle={16}
|
|
220
|
+
removeClippedSubviews
|
|
221
|
+
>
|
|
222
|
+
<View style={[styles.content, { height: contentHeight }]}>
|
|
223
|
+
{cells.map(cell => {
|
|
224
|
+
const layout = layouts[cell.index]
|
|
225
|
+
if (!layout) return null
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<View
|
|
229
|
+
key={cell.key}
|
|
230
|
+
style={[
|
|
231
|
+
styles.cell,
|
|
232
|
+
{
|
|
233
|
+
top: layout.y,
|
|
234
|
+
width: layout.width,
|
|
235
|
+
height: layout.height,
|
|
236
|
+
},
|
|
237
|
+
]}
|
|
238
|
+
>
|
|
239
|
+
{renderItem(cell.index)}
|
|
240
|
+
</View>
|
|
241
|
+
)
|
|
242
|
+
})}
|
|
243
|
+
</View>
|
|
244
|
+
</ScrollView>
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const styles = StyleSheet.create({
|
|
249
|
+
container: { flex: 1 },
|
|
250
|
+
content: { position: 'relative', width: '100%' },
|
|
251
|
+
cell: { position: 'absolute', left: 0 },
|
|
252
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
|
|
3
|
+
export type RecyclerListProps = {
|
|
4
|
+
/** Width of the list container (required) */
|
|
5
|
+
readonly containerWidth: number
|
|
6
|
+
|
|
7
|
+
/** Total number of items (used with estimatedItemHeight) */
|
|
8
|
+
readonly itemCount?: number
|
|
9
|
+
|
|
10
|
+
/** Explicit item heights (fast path, skips estimation) */
|
|
11
|
+
readonly itemHeights?: readonly number[]
|
|
12
|
+
|
|
13
|
+
/** Estimated item height when itemHeights not provided */
|
|
14
|
+
readonly estimatedItemHeight?: number
|
|
15
|
+
|
|
16
|
+
/** Render buffer multiplier (FlashList-style, default ~1.3) */
|
|
17
|
+
readonly renderBufferRatio?: number
|
|
18
|
+
|
|
19
|
+
/** Optional item type resolver for recycling */
|
|
20
|
+
readonly getItemType?: (index: number) => string
|
|
21
|
+
|
|
22
|
+
/** Render function */
|
|
23
|
+
readonly renderItem: (index: number) => React.ReactElement
|
|
24
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { findVisibleIndexRange } from '../../windowing/findVisibleIndexRange'
|
|
2
|
+
|
|
3
|
+
describe('findVisibleIndexRange', () => {
|
|
4
|
+
/**
|
|
5
|
+
* Layouts:
|
|
6
|
+
* index 0 → y: 0–50
|
|
7
|
+
* index 1 → y: 50–100
|
|
8
|
+
* index 2 → y: 100–150
|
|
9
|
+
* ...
|
|
10
|
+
*/
|
|
11
|
+
const layouts = Array.from({ length: 10 }, (_, i) => ({
|
|
12
|
+
x: 0,
|
|
13
|
+
y: i * 50,
|
|
14
|
+
width: 100,
|
|
15
|
+
height: 50,
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
it('returns empty array when layouts are empty', () => {
|
|
19
|
+
const result = findVisibleIndexRange([], { offsetY: 0, height: 300 }, 100)
|
|
20
|
+
|
|
21
|
+
expect(result).toEqual([])
|
|
22
|
+
})
|
|
23
|
+
it('returns correct visible indices without buffer', () => {
|
|
24
|
+
const result = findVisibleIndexRange(
|
|
25
|
+
layouts,
|
|
26
|
+
{ offsetY: 100, height: 150 },
|
|
27
|
+
0
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Viewport: 100–250 (inclusive)
|
|
32
|
+
* Items that intersect this window:
|
|
33
|
+
* indices 1–5
|
|
34
|
+
*/
|
|
35
|
+
expect(result).toEqual([1, 2, 3, 4, 5])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('extends window using bufferPx (inclusive boundaries)', () => {
|
|
39
|
+
const result = findVisibleIndexRange(
|
|
40
|
+
layouts,
|
|
41
|
+
{ offsetY: 100, height: 100 },
|
|
42
|
+
100
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Viewport: 100–200
|
|
47
|
+
* Buffer: ±100
|
|
48
|
+
* Effective win: 0–300 (inclusive)
|
|
49
|
+
*
|
|
50
|
+
* index 6 starts at y = 300 → included by design
|
|
51
|
+
* This matches FlashList / RecyclerView behavior
|
|
52
|
+
*/
|
|
53
|
+
expect(result).toEqual([0, 1, 2, 3, 4, 5, 6])
|
|
54
|
+
})
|
|
55
|
+
})
|
package/src/cell/Cell.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physical reusable cell.
|
|
3
|
+
* Mirrors FlashList's Cell abstraction.
|
|
4
|
+
*/
|
|
5
|
+
export interface Cell {
|
|
6
|
+
/** Stable physical identity (never changes) */
|
|
7
|
+
readonly key: string
|
|
8
|
+
|
|
9
|
+
/** Logical data index currently bound */
|
|
10
|
+
index: number
|
|
11
|
+
|
|
12
|
+
/** Compatibility type (row, header, etc.) */
|
|
13
|
+
readonly type: string
|
|
14
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Cell } from './Cell'
|
|
2
|
+
|
|
3
|
+
const MAX_CELLS = 40
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Owns physical cell reuse.
|
|
7
|
+
* Stateful, imperative, React-agnostic.
|
|
8
|
+
*
|
|
9
|
+
* FlashList equivalent: CellRecycler
|
|
10
|
+
*/
|
|
11
|
+
export class CellRecycler {
|
|
12
|
+
private readonly activeCells = new Map<number, Cell>()
|
|
13
|
+
private readonly reusableCellsByType = new Map<string, Cell[]>()
|
|
14
|
+
private readonly orderedCells: Cell[] = []
|
|
15
|
+
private nextCellId = 0
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reconcile visible indices against existing cells.
|
|
19
|
+
* Returns a stable ordered snapshot.
|
|
20
|
+
*/
|
|
21
|
+
reconcile(
|
|
22
|
+
visibleIndices: readonly number[],
|
|
23
|
+
getCellType: (index: number) => string
|
|
24
|
+
): readonly Cell[] {
|
|
25
|
+
const visibleSet = new Set(visibleIndices)
|
|
26
|
+
|
|
27
|
+
// 1️⃣ Release cells no longer visible
|
|
28
|
+
for (const [index, cell] of this.activeCells) {
|
|
29
|
+
if (visibleSet.has(index)) continue
|
|
30
|
+
|
|
31
|
+
this.activeCells.delete(index)
|
|
32
|
+
|
|
33
|
+
const orderedIndex = this.orderedCells.indexOf(cell)
|
|
34
|
+
if (orderedIndex !== -1) {
|
|
35
|
+
this.orderedCells.splice(orderedIndex, 1)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const pool = this.reusableCellsByType.get(cell.type) ?? []
|
|
39
|
+
if (pool.length < MAX_CELLS) pool.push(cell)
|
|
40
|
+
this.reusableCellsByType.set(cell.type, pool)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2️⃣ Acquire cells for newly visible indices
|
|
44
|
+
for (const index of visibleIndices) {
|
|
45
|
+
if (this.activeCells.has(index)) continue
|
|
46
|
+
|
|
47
|
+
const type = getCellType(index)
|
|
48
|
+
const pool = this.reusableCellsByType.get(type)
|
|
49
|
+
let cell = pool?.pop()
|
|
50
|
+
|
|
51
|
+
if (!cell) {
|
|
52
|
+
cell = {
|
|
53
|
+
key: `cell-${this.nextCellId++}`,
|
|
54
|
+
type,
|
|
55
|
+
index,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
cell.index = index
|
|
60
|
+
this.activeCells.set(index, cell)
|
|
61
|
+
this.orderedCells.push(cell)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this.orderedCells
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useDebugOverlay(
|
|
4
|
+
enabled: boolean,
|
|
5
|
+
visibleCount: number
|
|
6
|
+
): void {
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (__DEV__ && enabled) {
|
|
9
|
+
console.log(
|
|
10
|
+
`[RecyclerList] visible items: ${visibleCount}`
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
}, [enabled, visibleCount])
|
|
14
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { LayoutRectangle } from './layout/LayoutRectangle'
|
|
2
|
+
import type { ScrollMetrics } from './windowing/ScrollMetrics'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Binary-search based windowing.
|
|
6
|
+
* Returns indices intersecting viewport ± buffer.
|
|
7
|
+
*/
|
|
8
|
+
export function getVisibleRange(
|
|
9
|
+
layouts: readonly LayoutRectangle[],
|
|
10
|
+
metrics: ScrollMetrics,
|
|
11
|
+
bufferPx: number
|
|
12
|
+
): readonly number[] {
|
|
13
|
+
if (
|
|
14
|
+
layouts.length === 0 ||
|
|
15
|
+
metrics.height <= 0
|
|
16
|
+
) {
|
|
17
|
+
return []
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const startY = Math.max(
|
|
21
|
+
0,
|
|
22
|
+
metrics.offsetY - bufferPx
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const endY =
|
|
26
|
+
metrics.offsetY + metrics.height + bufferPx
|
|
27
|
+
|
|
28
|
+
let low = 0
|
|
29
|
+
let high = layouts.length - 1
|
|
30
|
+
let first = layouts.length
|
|
31
|
+
|
|
32
|
+
while (low <= high) {
|
|
33
|
+
const mid = (low + high) >> 1
|
|
34
|
+
const rect = layouts[mid]!
|
|
35
|
+
|
|
36
|
+
if (rect.y + rect.height >= startY) {
|
|
37
|
+
first = mid
|
|
38
|
+
high = mid - 1
|
|
39
|
+
} else {
|
|
40
|
+
low = mid + 1
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const visible: number[] = []
|
|
45
|
+
|
|
46
|
+
for (let i = first; i < layouts.length; i++) {
|
|
47
|
+
const rect = layouts[i]!
|
|
48
|
+
if (rect.y > endY) break
|
|
49
|
+
visible.push(i)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return visible
|
|
53
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import type { Cell } from '../cell/Cell'
|
|
3
|
+
import { CellRecycler } from '../cell/CellRecycler'
|
|
4
|
+
import { findVisibleIndexRange } from '../windowing/findVisibleIndexRange'
|
|
5
|
+
import type { LayoutRectangle } from '../layout/LayoutRectangle'
|
|
6
|
+
import type { ScrollMetrics } from '../windowing/ScrollMetrics'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 🔒 Stable empty references (module-level)
|
|
10
|
+
* These MUST NOT be recreated per render.
|
|
11
|
+
*/
|
|
12
|
+
const EMPTY_INDICES: readonly number[] = []
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* React bridge for CellRecycler.
|
|
16
|
+
* FlashList equivalent: useCellRenderer
|
|
17
|
+
*
|
|
18
|
+
* Responsibilities:
|
|
19
|
+
* - Translate scroll state → visible indices
|
|
20
|
+
* - Ask recycler for physical cells
|
|
21
|
+
* - Commit results with referential stability
|
|
22
|
+
*
|
|
23
|
+
* Responsibilities it does NOT have:
|
|
24
|
+
* ❌ Layout mutation
|
|
25
|
+
* ❌ Measurement
|
|
26
|
+
* ❌ Rendering
|
|
27
|
+
*/
|
|
28
|
+
export function useCellRenderer(
|
|
29
|
+
layouts: readonly LayoutRectangle[],
|
|
30
|
+
metrics: ScrollMetrics,
|
|
31
|
+
bufferPx: number,
|
|
32
|
+
getCellType: (index: number) => string
|
|
33
|
+
): readonly Cell[] {
|
|
34
|
+
/**
|
|
35
|
+
* Recycler instance is stable across renders.
|
|
36
|
+
*/
|
|
37
|
+
const recyclerRef = useRef<CellRecycler>(new CellRecycler())
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compute visible indices.
|
|
41
|
+
* PURE + TOTAL:
|
|
42
|
+
* - Never throws
|
|
43
|
+
* - Always returns the same reference when empty
|
|
44
|
+
*/
|
|
45
|
+
const visibleIndices = useMemo(
|
|
46
|
+
() =>
|
|
47
|
+
layouts.length === 0
|
|
48
|
+
? EMPTY_INDICES
|
|
49
|
+
: findVisibleIndexRange(
|
|
50
|
+
layouts,
|
|
51
|
+
metrics,
|
|
52
|
+
bufferPx
|
|
53
|
+
),
|
|
54
|
+
[layouts, metrics.offsetY, metrics.height, bufferPx]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Physical cells to render.
|
|
59
|
+
*/
|
|
60
|
+
const [cells, setCells] =
|
|
61
|
+
useState<readonly Cell[]>([])
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reconcile visible indices → physical cells.
|
|
65
|
+
*
|
|
66
|
+
* FlashList invariant:
|
|
67
|
+
* - Only update state if identity truly changed
|
|
68
|
+
* - Prevents pointless re-renders
|
|
69
|
+
*/
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const next =
|
|
72
|
+
recyclerRef.current.reconcile(
|
|
73
|
+
visibleIndices,
|
|
74
|
+
getCellType
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
setCells(prev =>
|
|
78
|
+
prev.length === next.length &&
|
|
79
|
+
prev.every((c, i) => c === next[i])
|
|
80
|
+
? prev
|
|
81
|
+
: next
|
|
82
|
+
)
|
|
83
|
+
}, [visibleIndices, getCellType])
|
|
84
|
+
|
|
85
|
+
return cells
|
|
86
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useStableCallback<T extends (...args: never[]) => unknown>(
|
|
4
|
+
fn: T
|
|
5
|
+
): T {
|
|
6
|
+
const ref = useRef(fn)
|
|
7
|
+
ref.current = fn
|
|
8
|
+
|
|
9
|
+
return useCallback(
|
|
10
|
+
((...args: never[]) => ref.current(...args)) as T,
|
|
11
|
+
[]
|
|
12
|
+
)
|
|
13
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
NitroListProps,
|
|
5
|
-
NitroListMethods,
|
|
6
|
-
} from './specs/nitro-list.nitro'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export const NitroList = getHostComponent<NitroListProps, NitroListMethods>(
|
|
10
|
-
'NitroList',
|
|
11
|
-
() => NitroListConfig
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
export type NitroListRef = HybridRef<NitroListProps, NitroListMethods>
|
|
1
|
+
export { RecyclerList } from './RecyclerList'
|
|
2
|
+
export type { RecyclerListProps } from './RecyclerList.types'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { LayoutProvider } from './LayoutProvider'
|
|
2
|
+
import type { LayoutRectangle } from './LayoutRectangle'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Layout provider using estimated item heights.
|
|
6
|
+
* Used before real measurements are available.
|
|
7
|
+
*/
|
|
8
|
+
export class EstimatedLayoutProvider implements LayoutProvider {
|
|
9
|
+
private readonly itemCount: number
|
|
10
|
+
private readonly itemHeight: number
|
|
11
|
+
private readonly width: number
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
itemCount: number,
|
|
15
|
+
itemHeight: number,
|
|
16
|
+
width: number
|
|
17
|
+
) {
|
|
18
|
+
this.itemCount = itemCount
|
|
19
|
+
this.itemHeight = itemHeight
|
|
20
|
+
this.width = width
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getItemCount(): number {
|
|
24
|
+
return this.itemCount
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getLayout(index: number): LayoutRectangle {
|
|
28
|
+
return {
|
|
29
|
+
x: 0,
|
|
30
|
+
y: index * this.itemHeight,
|
|
31
|
+
width: this.width,
|
|
32
|
+
height: this.itemHeight,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LayoutProvider } from './LayoutProvider'
|
|
2
|
+
import type { LayoutRectangle } from './LayoutRectangle'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves layouts eagerly into a contiguous array.
|
|
6
|
+
* Pure, deterministic, and reusable.
|
|
7
|
+
*
|
|
8
|
+
* FlashList equivalent: internal layout resolver.
|
|
9
|
+
*/
|
|
10
|
+
export function computeLayouts(
|
|
11
|
+
provider: LayoutProvider
|
|
12
|
+
): readonly LayoutRectangle[] {
|
|
13
|
+
const count = provider.getItemCount()
|
|
14
|
+
const layouts: LayoutRectangle[] = new Array(count)
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < count; i++) {
|
|
17
|
+
layouts[i] = provider.getLayout(i)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return layouts
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LayoutRectangle } from './LayoutRectangle'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provides layout information for items.
|
|
5
|
+
* FlashList equivalent: LayoutProvider
|
|
6
|
+
*/
|
|
7
|
+
export interface LayoutProvider {
|
|
8
|
+
/**
|
|
9
|
+
* Total number of items
|
|
10
|
+
*/
|
|
11
|
+
getItemCount(): number
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Layout rectangle for a given index
|
|
15
|
+
*/
|
|
16
|
+
getLayout(index: number): LayoutRectangle
|
|
17
|
+
}
|