react-native-nitro-list 0.1.2 → 0.1.3
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 +16 -44
- package/ios/HybridNitroList.swift +7 -5
- package/lib/commonjs/index.js +31 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/layout/MutableLinearLayout.js.map +1 -1
- package/lib/commonjs/native/NitroListView.js +20 -0
- package/lib/commonjs/native/NitroListView.js.map +1 -0
- package/lib/commonjs/recycler/CellPool.js +5 -18
- package/lib/commonjs/recycler/CellPool.js.map +1 -1
- package/lib/commonjs/recycler/{RecyclerList.js → NitroList.js} +91 -92
- package/lib/commonjs/recycler/NitroList.js.map +1 -0
- package/lib/commonjs/scroll/useScrollVelocity.js +20 -0
- package/lib/commonjs/scroll/useScrollVelocity.js.map +1 -0
- package/lib/commonjs/{native/NitroList.types.js → specs/NitroList.nitro.js} +1 -1
- package/lib/commonjs/{native/NitroList.types.js.map → specs/NitroList.nitro.js.map} +1 -1
- package/lib/commonjs/types/CellType.js +25 -0
- package/lib/commonjs/types/CellType.js.map +1 -1
- package/lib/commonjs/types/recycler/CellPool.js +42 -0
- package/lib/commonjs/types/recycler/CellPool.js.map +1 -0
- package/lib/commonjs/types/recycler/{RecyclerListProps.js → NitroListProps.js} +1 -1
- package/lib/commonjs/types/recycler/NitroListProps.js.map +1 -0
- package/lib/commonjs/types/recycler/{RecyclerCellInstance.js → RecyclerCell.js} +1 -1
- package/lib/commonjs/types/recycler/RecyclerCell.js.map +1 -0
- package/lib/commonjs/windowing/computeVisibleItemRange.js +22 -32
- package/lib/commonjs/windowing/computeVisibleItemRange.js.map +1 -1
- package/lib/module/index.js +5 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/layout/MutableLinearLayout.js.map +1 -1
- package/lib/module/native/NitroListView.js +17 -0
- package/lib/module/native/NitroListView.js.map +1 -0
- package/lib/module/recycler/CellPool.js +5 -18
- package/lib/module/recycler/CellPool.js.map +1 -1
- package/lib/module/recycler/NitroList.js +220 -0
- package/lib/module/recycler/NitroList.js.map +1 -0
- package/lib/module/scroll/useScrollVelocity.js +16 -0
- package/lib/module/scroll/useScrollVelocity.js.map +1 -0
- package/lib/module/specs/NitroList.nitro.js +4 -0
- package/lib/module/{native/NitroList.types.js.map → specs/NitroList.nitro.js.map} +1 -1
- package/lib/module/types/CellType.js +20 -0
- package/lib/module/types/CellType.js.map +1 -1
- package/lib/module/types/recycler/CellPool.js +37 -0
- package/lib/module/types/recycler/CellPool.js.map +1 -0
- package/lib/module/types/recycler/NitroListProps.js +4 -0
- package/lib/module/types/recycler/NitroListProps.js.map +1 -0
- package/lib/module/types/recycler/RecyclerCell.js +4 -0
- package/lib/module/types/recycler/RecyclerCell.js.map +1 -0
- package/lib/module/windowing/computeVisibleItemRange.js +22 -32
- package/lib/module/windowing/computeVisibleItemRange.js.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/NitroListView.d.ts +20 -0
- package/lib/typescript/src/native/NitroListView.d.ts.map +1 -0
- package/lib/typescript/src/recycler/CellPool.d.ts +4 -7
- package/lib/typescript/src/recycler/CellPool.d.ts.map +1 -1
- package/lib/typescript/src/recycler/NitroList.d.ts +4 -0
- package/lib/typescript/src/recycler/NitroList.d.ts.map +1 -0
- package/lib/typescript/src/scroll/useScrollVelocity.d.ts +2 -0
- package/lib/typescript/src/scroll/useScrollVelocity.d.ts.map +1 -0
- package/lib/typescript/src/specs/NitroList.nitro.d.ts +26 -0
- package/lib/typescript/src/specs/NitroList.nitro.d.ts.map +1 -0
- package/lib/typescript/src/types/CellKey.d.ts +1 -9
- package/lib/typescript/src/types/CellKey.d.ts.map +1 -1
- package/lib/typescript/src/types/CellType.d.ts +12 -1
- package/lib/typescript/src/types/CellType.d.ts.map +1 -1
- package/lib/typescript/src/types/recycler/CellPool.d.ts +16 -0
- package/lib/typescript/src/types/recycler/CellPool.d.ts.map +1 -0
- package/lib/typescript/src/types/recycler/{RecyclerListProps.d.ts → NitroListProps.d.ts} +3 -3
- package/lib/typescript/src/types/recycler/NitroListProps.d.ts.map +1 -0
- package/lib/typescript/src/types/recycler/RecyclerCell.d.ts +12 -0
- package/lib/typescript/src/types/recycler/RecyclerCell.d.ts.map +1 -0
- package/lib/typescript/src/types/recycler/RecyclerItemRenderer.d.ts +6 -2
- package/lib/typescript/src/types/recycler/RecyclerItemRenderer.d.ts.map +1 -1
- package/lib/typescript/src/types/recycler/index.d.ts +2 -2
- package/lib/typescript/src/types/recycler/index.d.ts.map +1 -1
- package/lib/typescript/src/windowing/computeVisibleItemRange.d.ts +16 -12
- package/lib/typescript/src/windowing/computeVisibleItemRange.d.ts.map +1 -1
- package/nitro.json +5 -5
- package/nitrogen/generated/android/NitroList+autolinking.cmake +2 -2
- package/nitrogen/generated/android/NitroListOnLoad.cpp +2 -2
- package/nitrogen/generated/android/c++/{JHybridNitroLayoutEngineSpec.cpp → JHybridNitroListSpec.cpp} +15 -15
- package/nitrogen/generated/android/c++/{JHybridNitroLayoutEngineSpec.hpp → JHybridNitroListSpec.hpp} +11 -11
- package/nitrogen/generated/android/c++/{JLayoutRect.hpp → JItemLayout.hpp} +10 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolist/{HybridNitroLayoutEngineSpec.kt → HybridNitroListSpec.kt} +7 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolist/{LayoutRect.kt → ItemLayout.kt} +5 -5
- package/nitrogen/generated/ios/NitroList-Swift-Cxx-Bridge.cpp +9 -9
- package/nitrogen/generated/ios/NitroList-Swift-Cxx-Bridge.hpp +27 -27
- package/nitrogen/generated/ios/NitroList-Swift-Cxx-Umbrella.hpp +8 -8
- package/nitrogen/generated/ios/c++/{HybridNitroLayoutEngineSpecSwift.cpp → HybridNitroListSpecSwift.cpp} +2 -2
- package/nitrogen/generated/ios/c++/{HybridNitroLayoutEngineSpecSwift.hpp → HybridNitroListSpecSwift.hpp} +16 -16
- package/nitrogen/generated/ios/swift/HybridNitroListSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/{HybridNitroLayoutEngineSpec_cxx.swift → HybridNitroListSpec_cxx.swift} +23 -23
- package/nitrogen/generated/ios/swift/{LayoutRect.swift → ItemLayout.swift} +5 -5
- package/nitrogen/generated/shared/c++/{HybridNitroLayoutEngineSpec.cpp → HybridNitroListSpec.cpp} +4 -4
- package/nitrogen/generated/shared/c++/{HybridNitroLayoutEngineSpec.hpp → HybridNitroListSpec.hpp} +13 -13
- package/nitrogen/generated/shared/c++/{LayoutRect.hpp → ItemLayout.hpp} +11 -11
- package/package.json +5 -5
- package/src/index.ts +7 -1
- package/src/layout/MutableLinearLayout.ts +1 -1
- package/src/native/NitroListView.ts +22 -0
- package/src/recycler/CellPool.ts +10 -24
- package/src/recycler/NitroList.tsx +317 -0
- package/src/scroll/useScrollVelocity.ts +16 -0
- package/src/specs/NitroList.nitro.ts +29 -0
- package/src/types/CellKey.ts +2 -9
- package/src/types/CellType.ts +8 -9
- package/src/types/recycler/CellPool.ts +45 -0
- package/src/types/recycler/{RecyclerListProps.ts → NitroListProps.ts} +2 -2
- package/src/types/recycler/RecyclerCell.ts +12 -0
- package/src/types/recycler/RecyclerItemRenderer.ts +6 -2
- package/src/types/recycler/index.ts +2 -2
- package/src/windowing/computeVisibleItemRange.ts +42 -38
- package/lib/commonjs/NitroList.js +0 -9
- package/lib/commonjs/NitroList.js.map +0 -1
- package/lib/commonjs/native/NitroLayoutEngine.js +0 -9
- package/lib/commonjs/native/NitroLayoutEngine.js.map +0 -1
- package/lib/commonjs/native/NitroRecyclerView.js +0 -9
- package/lib/commonjs/native/NitroRecyclerView.js.map +0 -1
- package/lib/commonjs/recycler/RecyclerList.js.map +0 -1
- package/lib/commonjs/specs/nitro-layout-engine.nitro.js +0 -6
- package/lib/commonjs/specs/nitro-layout-engine.nitro.js.map +0 -1
- package/lib/commonjs/types/recycler/RecyclerCellInstance.js.map +0 -1
- package/lib/commonjs/types/recycler/RecyclerListProps.js.map +0 -1
- package/lib/module/NitroList.js +0 -5
- package/lib/module/NitroList.js.map +0 -1
- package/lib/module/native/NitroLayoutEngine.js +0 -5
- package/lib/module/native/NitroLayoutEngine.js.map +0 -1
- package/lib/module/native/NitroList.types.js +0 -4
- package/lib/module/native/NitroRecyclerView.js +0 -5
- package/lib/module/native/NitroRecyclerView.js.map +0 -1
- package/lib/module/recycler/RecyclerList.js +0 -221
- package/lib/module/recycler/RecyclerList.js.map +0 -1
- package/lib/module/specs/nitro-layout-engine.nitro.js +0 -4
- package/lib/module/specs/nitro-layout-engine.nitro.js.map +0 -1
- package/lib/module/types/recycler/RecyclerCellInstance.js +0 -4
- package/lib/module/types/recycler/RecyclerCellInstance.js.map +0 -1
- package/lib/module/types/recycler/RecyclerListProps.js +0 -4
- package/lib/module/types/recycler/RecyclerListProps.js.map +0 -1
- package/lib/typescript/src/NitroList.d.ts +0 -5
- package/lib/typescript/src/NitroList.d.ts.map +0 -1
- package/lib/typescript/src/native/NitroLayoutEngine.d.ts +0 -3
- package/lib/typescript/src/native/NitroLayoutEngine.d.ts.map +0 -1
- package/lib/typescript/src/native/NitroList.types.d.ts +0 -9
- package/lib/typescript/src/native/NitroList.types.d.ts.map +0 -1
- package/lib/typescript/src/native/NitroRecyclerView.d.ts +0 -5
- package/lib/typescript/src/native/NitroRecyclerView.d.ts.map +0 -1
- package/lib/typescript/src/recycler/RecyclerList.d.ts +0 -4
- package/lib/typescript/src/recycler/RecyclerList.d.ts.map +0 -1
- package/lib/typescript/src/specs/nitro-layout-engine.nitro.d.ts +0 -14
- package/lib/typescript/src/specs/nitro-layout-engine.nitro.d.ts.map +0 -1
- package/lib/typescript/src/types/recycler/RecyclerCellInstance.d.ts +0 -37
- package/lib/typescript/src/types/recycler/RecyclerCellInstance.d.ts.map +0 -1
- package/lib/typescript/src/types/recycler/RecyclerListProps.d.ts.map +0 -1
- package/nitrogen/generated/ios/swift/HybridNitroLayoutEngineSpec.swift +0 -56
- package/src/NitroList.ts +0 -8
- package/src/native/NitroLayoutEngine.ts +0 -7
- package/src/native/NitroList.types.ts +0 -13
- package/src/native/NitroRecyclerView.ts +0 -8
- package/src/recycler/RecyclerList.tsx +0 -304
- package/src/specs/nitro-layout-engine.nitro.ts +0 -17
- package/src/types/recycler/RecyclerCellInstance.ts +0 -40
package/nitrogen/generated/shared/c++/{HybridNitroLayoutEngineSpec.hpp → HybridNitroListSpec.hpp}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
///
|
|
2
|
-
///
|
|
2
|
+
/// HybridNitroListSpec.hpp
|
|
3
3
|
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
4
|
/// https://github.com/mrousavy/nitro
|
|
5
5
|
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
14
|
#endif
|
|
15
15
|
|
|
16
|
-
// Forward declaration of `
|
|
17
|
-
namespace margelo::nitro::nitrolist { struct
|
|
16
|
+
// Forward declaration of `ItemLayout` to properly resolve imports.
|
|
17
|
+
namespace margelo::nitro::nitrolist { struct ItemLayout; }
|
|
18
18
|
|
|
19
|
-
#include "
|
|
19
|
+
#include "ItemLayout.hpp"
|
|
20
20
|
#include <vector>
|
|
21
21
|
|
|
22
22
|
namespace margelo::nitro::nitrolist {
|
|
@@ -24,25 +24,25 @@ namespace margelo::nitro::nitrolist {
|
|
|
24
24
|
using namespace margelo::nitro;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* An abstract base class for `
|
|
28
|
-
* Inherit this class to create instances of `
|
|
27
|
+
* An abstract base class for `NitroList`
|
|
28
|
+
* Inherit this class to create instances of `HybridNitroListSpec` in C++.
|
|
29
29
|
* You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
|
|
30
30
|
* @example
|
|
31
31
|
* ```cpp
|
|
32
|
-
* class
|
|
32
|
+
* class HybridNitroList: public HybridNitroListSpec {
|
|
33
33
|
* public:
|
|
34
|
-
*
|
|
34
|
+
* HybridNitroList(...): HybridObject(TAG) { ... }
|
|
35
35
|
* // ...
|
|
36
36
|
* };
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
|
-
class
|
|
39
|
+
class HybridNitroListSpec: public virtual HybridObject {
|
|
40
40
|
public:
|
|
41
41
|
// Constructor
|
|
42
|
-
explicit
|
|
42
|
+
explicit HybridNitroListSpec(): HybridObject(TAG) { }
|
|
43
43
|
|
|
44
44
|
// Destructor
|
|
45
|
-
~
|
|
45
|
+
~HybridNitroListSpec() override = default;
|
|
46
46
|
|
|
47
47
|
public:
|
|
48
48
|
// Properties
|
|
@@ -50,7 +50,7 @@ namespace margelo::nitro::nitrolist {
|
|
|
50
50
|
|
|
51
51
|
public:
|
|
52
52
|
// Methods
|
|
53
|
-
virtual std::vector<
|
|
53
|
+
virtual std::vector<ItemLayout> computeLayout(double containerWidth, const std::vector<double>& itemHeights) = 0;
|
|
54
54
|
|
|
55
55
|
protected:
|
|
56
56
|
// Hybrid Setup
|
|
@@ -58,7 +58,7 @@ namespace margelo::nitro::nitrolist {
|
|
|
58
58
|
|
|
59
59
|
protected:
|
|
60
60
|
// Tag for logging
|
|
61
|
-
static constexpr auto TAG = "
|
|
61
|
+
static constexpr auto TAG = "NitroList";
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
} // namespace margelo::nitro::nitrolist
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
///
|
|
2
|
-
///
|
|
2
|
+
/// ItemLayout.hpp
|
|
3
3
|
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
4
|
/// https://github.com/mrousavy/nitro
|
|
5
5
|
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
namespace margelo::nitro::nitrolist {
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* A struct which can be represented as a JavaScript object (
|
|
38
|
+
* A struct which can be represented as a JavaScript object (ItemLayout).
|
|
39
39
|
*/
|
|
40
|
-
struct
|
|
40
|
+
struct ItemLayout final {
|
|
41
41
|
public:
|
|
42
42
|
double x SWIFT_PRIVATE;
|
|
43
43
|
double y SWIFT_PRIVATE;
|
|
@@ -45,30 +45,30 @@ namespace margelo::nitro::nitrolist {
|
|
|
45
45
|
double height SWIFT_PRIVATE;
|
|
46
46
|
|
|
47
47
|
public:
|
|
48
|
-
|
|
49
|
-
explicit
|
|
48
|
+
ItemLayout() = default;
|
|
49
|
+
explicit ItemLayout(double x, double y, double width, double height): x(x), y(y), width(width), height(height) {}
|
|
50
50
|
|
|
51
51
|
public:
|
|
52
|
-
friend bool operator==(const
|
|
52
|
+
friend bool operator==(const ItemLayout& lhs, const ItemLayout& rhs) = default;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
} // namespace margelo::nitro::nitrolist
|
|
56
56
|
|
|
57
57
|
namespace margelo::nitro {
|
|
58
58
|
|
|
59
|
-
// C++
|
|
59
|
+
// C++ ItemLayout <> JS ItemLayout (object)
|
|
60
60
|
template <>
|
|
61
|
-
struct JSIConverter<margelo::nitro::nitrolist::
|
|
62
|
-
static inline margelo::nitro::nitrolist::
|
|
61
|
+
struct JSIConverter<margelo::nitro::nitrolist::ItemLayout> final {
|
|
62
|
+
static inline margelo::nitro::nitrolist::ItemLayout fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
63
63
|
jsi::Object obj = arg.asObject(runtime);
|
|
64
|
-
return margelo::nitro::nitrolist::
|
|
64
|
+
return margelo::nitro::nitrolist::ItemLayout(
|
|
65
65
|
JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "x"))),
|
|
66
66
|
JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "y"))),
|
|
67
67
|
JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "width"))),
|
|
68
68
|
JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "height")))
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
|
-
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitrolist::
|
|
71
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitrolist::ItemLayout& arg) {
|
|
72
72
|
jsi::Object obj(runtime);
|
|
73
73
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "x"), JSIConverter<double>::toJSI(runtime, arg.x));
|
|
74
74
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "y"), JSIConverter<double>::toJSI(runtime, arg.y));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-list",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "High-performance list for React Native powered by Nitro",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|
|
7
7
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"workspaces": [
|
|
46
46
|
"example"
|
|
47
47
|
],
|
|
48
|
-
"repository": "https://github.com/
|
|
48
|
+
"repository": "https://github.com/pythonsst/nitro-list",
|
|
49
49
|
"author": "Shiv Shankar Tiwari",
|
|
50
50
|
"license": "MIT",
|
|
51
|
-
"bugs": "https://github.com/
|
|
52
|
-
"homepage": "https://github.com/
|
|
51
|
+
"bugs": "https://github.com/pythonsst/nitro-list/issues",
|
|
52
|
+
"homepage": "https://github.com/pythonsst/nitro-list#readme",
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public",
|
|
55
55
|
"registry": "https://registry.npmjs.org/"
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Axis } from '../types/Axis'
|
|
2
2
|
import type { LinearLayoutInput } from '../types/layout'
|
|
3
3
|
import type { LayoutRect } from '../types/layout/LayoutRect'
|
|
4
|
-
|
|
5
4
|
import { DEFAULT_ITEM_SPACING } from './constants/layoutDefaults'
|
|
6
5
|
|
|
6
|
+
|
|
7
7
|
/**
|
|
8
8
|
* Mutable, deterministic linear layout engine.
|
|
9
9
|
*
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { requireNativeComponent, type ViewProps } from 'react-native'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Props for the NitroListView.
|
|
5
|
+
* * We extend ViewProps so that developers can use standard styles
|
|
6
|
+
* like 'flex: 1' or 'backgroundColor' on the list.
|
|
7
|
+
*/
|
|
8
|
+
export type NitroListViewProps = ViewProps & {
|
|
9
|
+
/**
|
|
10
|
+
* The total number of items to be displayed in the list.
|
|
11
|
+
* This is passed to the native side (Swift/Kotlin) for layout math.
|
|
12
|
+
*/
|
|
13
|
+
itemCount: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The Native Component that maps to the physical view in Swift/Kotlin.
|
|
18
|
+
* * 'NitroListView' is the "Secret Handshake" name. It must match
|
|
19
|
+
* the name exported by your Native ViewManagers.
|
|
20
|
+
*/
|
|
21
|
+
export const NitroListView =
|
|
22
|
+
requireNativeComponent<NitroListViewProps>('NitroListView')
|
package/src/recycler/CellPool.ts
CHANGED
|
@@ -1,47 +1,33 @@
|
|
|
1
|
+
import type { RecyclerCell } from '../types/recycler/RecyclerCell'
|
|
1
2
|
import type { CellType } from '../types/CellType'
|
|
2
|
-
import type { RecyclerCellInstance } from '../types/recycler'
|
|
3
3
|
|
|
4
|
-
export class CellPool {
|
|
5
|
-
private readonly pools = new Map<CellType,
|
|
4
|
+
export class CellPool<T extends RecyclerCell<any>> {
|
|
5
|
+
private readonly pools = new Map<CellType, T[]>()
|
|
6
6
|
private readonly maxPerType = new Map<CellType, number>()
|
|
7
7
|
|
|
8
|
-
/** ✅ NEW: check if a type is registered */
|
|
9
8
|
hasType(type: CellType): boolean {
|
|
10
9
|
return this.pools.has(type)
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
registerType(type: CellType, maxCount: number): void {
|
|
14
|
-
if (this.
|
|
15
|
-
this.maxPerType.set(type, maxCount)
|
|
13
|
+
if (this.pools.has(type)) return
|
|
16
14
|
this.pools.set(type, [])
|
|
15
|
+
this.maxPerType.set(type, maxCount)
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
acquire(type: CellType):
|
|
18
|
+
acquire(type: CellType): T | null {
|
|
20
19
|
const bucket = this.pools.get(type)
|
|
21
20
|
if (!bucket || bucket.length === 0) return null
|
|
22
|
-
return bucket.pop()
|
|
21
|
+
return bucket.pop() ?? null
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
release(cell:
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const max = this.maxPerType.get(type)
|
|
29
|
-
|
|
24
|
+
release(cell: T): void {
|
|
25
|
+
const bucket = this.pools.get(cell.type)
|
|
26
|
+
const max = this.maxPerType.get(cell.type)
|
|
30
27
|
if (!bucket || max === undefined) return
|
|
31
28
|
|
|
32
29
|
cell.index = -1
|
|
33
|
-
|
|
34
30
|
if (bucket.length >= max) return
|
|
35
31
|
bucket.push(cell)
|
|
36
32
|
}
|
|
37
|
-
|
|
38
|
-
clear(): void {
|
|
39
|
-
for (const bucket of this.pools.values()) {
|
|
40
|
-
bucket.length = 0
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
getPoolSize(type: CellType): number {
|
|
45
|
-
return this.pools.get(type)?.length ?? 0
|
|
46
|
-
}
|
|
47
33
|
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ScrollView,
|
|
4
|
+
View,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
type NativeSyntheticEvent,
|
|
7
|
+
type NativeScrollEvent,
|
|
8
|
+
type LayoutChangeEvent,
|
|
9
|
+
} from 'react-native'
|
|
10
|
+
|
|
11
|
+
import type { LayoutRect, VisibleRange } from '../types'
|
|
12
|
+
import type { RecyclerCell, NitroListProps } from '../types/recycler'
|
|
13
|
+
import type { CellType } from '../types/CellType'
|
|
14
|
+
import type { CellKey } from '../types/CellKey'
|
|
15
|
+
|
|
16
|
+
import { MutableLinearLayout } from '../layout/MutableLinearLayout'
|
|
17
|
+
import { computeVisibleItemRange } from '../windowing'
|
|
18
|
+
import { CellPool } from './CellPool'
|
|
19
|
+
|
|
20
|
+
/* ========================================================= */
|
|
21
|
+
|
|
22
|
+
const DEFAULT_VIEWPORT_SIZE = 800
|
|
23
|
+
const DEFAULT_ITEM_SIZE = 60
|
|
24
|
+
const SCROLL_EVENT_THROTTLE = 16
|
|
25
|
+
|
|
26
|
+
/* ========================================================= */
|
|
27
|
+
/* Internal (mutable) cell */
|
|
28
|
+
/* ========================================================= */
|
|
29
|
+
|
|
30
|
+
interface InternalRecyclerCell extends RecyclerCell<CellKey> {
|
|
31
|
+
active: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ========================================================= */
|
|
35
|
+
/* NitroList */
|
|
36
|
+
/* ========================================================= */
|
|
37
|
+
|
|
38
|
+
export default function NitroList<T>(
|
|
39
|
+
props: NitroListProps<T>
|
|
40
|
+
): React.ReactElement {
|
|
41
|
+
const {
|
|
42
|
+
data,
|
|
43
|
+
renderItem,
|
|
44
|
+
getCellType,
|
|
45
|
+
scrollDirection = 'vertical',
|
|
46
|
+
bufferRatio = 1.3,
|
|
47
|
+
containerCrossAxisSize,
|
|
48
|
+
itemMainAxisSizes,
|
|
49
|
+
padding = { start: 0, end: 0 },
|
|
50
|
+
itemSpacing,
|
|
51
|
+
} = props
|
|
52
|
+
|
|
53
|
+
const isVertical = scrollDirection === 'vertical'
|
|
54
|
+
const [, forceRender] = useState(0)
|
|
55
|
+
|
|
56
|
+
const DEBUG = true
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/* ======================================================= */
|
|
60
|
+
/* Layout engine (recomputed when signature changes) */
|
|
61
|
+
/* ======================================================= */
|
|
62
|
+
|
|
63
|
+
const layoutEngineRef = useRef<MutableLinearLayout | null>(null)
|
|
64
|
+
const layoutSignatureRef = useRef<string>('')
|
|
65
|
+
|
|
66
|
+
const layoutSignature = [
|
|
67
|
+
scrollDirection,
|
|
68
|
+
containerCrossAxisSize,
|
|
69
|
+
data.length,
|
|
70
|
+
itemMainAxisSizes,
|
|
71
|
+
padding.start,
|
|
72
|
+
padding.end,
|
|
73
|
+
itemSpacing ?? 'none',
|
|
74
|
+
].join('|')
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
layoutEngineRef.current === null ||
|
|
78
|
+
layoutSignatureRef.current !== layoutSignature
|
|
79
|
+
) {
|
|
80
|
+
const engine = new MutableLinearLayout(scrollDirection)
|
|
81
|
+
engine.compute({
|
|
82
|
+
crossAxisSize: containerCrossAxisSize,
|
|
83
|
+
itemMainAxisSizes,
|
|
84
|
+
padding,
|
|
85
|
+
itemSpacing,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
layoutEngineRef.current = engine
|
|
89
|
+
layoutSignatureRef.current = layoutSignature
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const layouts: readonly LayoutRect[] =
|
|
93
|
+
layoutEngineRef.current.getLayouts()
|
|
94
|
+
|
|
95
|
+
const contentSize: number =
|
|
96
|
+
layoutEngineRef.current.getContentSize()
|
|
97
|
+
|
|
98
|
+
/* ======================================================= */
|
|
99
|
+
/* Scroll metrics */
|
|
100
|
+
/* ======================================================= */
|
|
101
|
+
|
|
102
|
+
const scrollOffsetRef = useRef<number>(0)
|
|
103
|
+
const viewportSizeRef = useRef<number>(DEFAULT_VIEWPORT_SIZE)
|
|
104
|
+
|
|
105
|
+
/* ======================================================= */
|
|
106
|
+
/* Cell pool */
|
|
107
|
+
/* ======================================================= */
|
|
108
|
+
|
|
109
|
+
const cellPoolRef = useRef<CellPool<InternalRecyclerCell>>(
|
|
110
|
+
new CellPool<InternalRecyclerCell>()
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
const activeCellsRef = useRef<InternalRecyclerCell[]>([])
|
|
114
|
+
const nextKeyRef = useRef<number>(0)
|
|
115
|
+
|
|
116
|
+
const createCell = (type: CellType): InternalRecyclerCell => ({
|
|
117
|
+
key: nextKeyRef.current++,
|
|
118
|
+
type,
|
|
119
|
+
index: -1,
|
|
120
|
+
active: false,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
/* ======================================================= */
|
|
124
|
+
/* Visible window coordination */
|
|
125
|
+
/* ======================================================= */
|
|
126
|
+
|
|
127
|
+
const updateVisibleCells = (): void => {
|
|
128
|
+
const range: VisibleRange | null =
|
|
129
|
+
computeVisibleItemRange({
|
|
130
|
+
layouts,
|
|
131
|
+
offset: scrollOffsetRef.current,
|
|
132
|
+
viewportSize: viewportSizeRef.current,
|
|
133
|
+
buffer: viewportSizeRef.current * bufferRatio,
|
|
134
|
+
isVertical,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if (range === null) return
|
|
140
|
+
|
|
141
|
+
if (DEBUG) {
|
|
142
|
+
console.log(
|
|
143
|
+
'[NitroList] range:',
|
|
144
|
+
range.startIndex,
|
|
145
|
+
range.endIndex
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
const active = activeCellsRef.current
|
|
151
|
+
|
|
152
|
+
// 1. Mark all inactive
|
|
153
|
+
for (const cell of active) {
|
|
154
|
+
cell.active = false
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let writeIndex = 0
|
|
158
|
+
|
|
159
|
+
// 2. Activate visible range
|
|
160
|
+
for (
|
|
161
|
+
let index = range.startIndex;
|
|
162
|
+
index <= range.endIndex;
|
|
163
|
+
index++
|
|
164
|
+
) {
|
|
165
|
+
const item = data[index]
|
|
166
|
+
if (item === undefined) continue
|
|
167
|
+
|
|
168
|
+
const type = getCellType(item, index)
|
|
169
|
+
|
|
170
|
+
// Seed pool once per type
|
|
171
|
+
if (!cellPoolRef.current.hasType(type)) {
|
|
172
|
+
const maxCells =
|
|
173
|
+
Math.ceil(
|
|
174
|
+
(viewportSizeRef.current / DEFAULT_ITEM_SIZE) *
|
|
175
|
+
bufferRatio
|
|
176
|
+
) + 2
|
|
177
|
+
|
|
178
|
+
cellPoolRef.current.registerType(type, maxCells)
|
|
179
|
+
for (let i = 0; i < maxCells; i++) {
|
|
180
|
+
cellPoolRef.current.release(createCell(type))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let cell: InternalRecyclerCell | null =
|
|
185
|
+
cellPoolRef.current.acquire(type)
|
|
186
|
+
|
|
187
|
+
if (cell === null) {
|
|
188
|
+
const reusable = active.find(
|
|
189
|
+
c => !c.active && c.type === type
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
cell = reusable ?? createCell(type)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
cell.index = index
|
|
197
|
+
cell.active = true
|
|
198
|
+
active[writeIndex++] = cell
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 3. Recycle unused cells
|
|
202
|
+
for (let i = writeIndex; i < active.length; i++) {
|
|
203
|
+
const cell = active[i]
|
|
204
|
+
if (cell === undefined) continue
|
|
205
|
+
|
|
206
|
+
cell.index = -1
|
|
207
|
+
cell.active = false
|
|
208
|
+
cellPoolRef.current.release(cell)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 4. Compact once
|
|
212
|
+
if (active.length !== writeIndex) {
|
|
213
|
+
active.length = writeIndex
|
|
214
|
+
forceRender(v => v + 1)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ======================================================= */
|
|
219
|
+
/* Handlers */
|
|
220
|
+
/* ======================================================= */
|
|
221
|
+
|
|
222
|
+
const onScroll = (
|
|
223
|
+
e: NativeSyntheticEvent<NativeScrollEvent>
|
|
224
|
+
): void => {
|
|
225
|
+
scrollOffsetRef.current = isVertical
|
|
226
|
+
? e.nativeEvent.contentOffset.y
|
|
227
|
+
: e.nativeEvent.contentOffset.x
|
|
228
|
+
|
|
229
|
+
updateVisibleCells()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const onLayout = (e: LayoutChangeEvent): void => {
|
|
233
|
+
viewportSizeRef.current = isVertical
|
|
234
|
+
? e.nativeEvent.layout.height
|
|
235
|
+
: e.nativeEvent.layout.width
|
|
236
|
+
|
|
237
|
+
updateVisibleCells()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
updateVisibleCells()
|
|
242
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
243
|
+
}, [])
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if (DEBUG) {
|
|
247
|
+
console.log('[NitroList] layouts:', layouts.length)
|
|
248
|
+
console.log('[NitroList] contentSize:', contentSize)
|
|
249
|
+
console.log('[NitroList] data.length:', data.length)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ======================================================= */
|
|
253
|
+
/* Render */
|
|
254
|
+
/* ======================================================= */
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<ScrollView
|
|
258
|
+
onScroll={onScroll}
|
|
259
|
+
onLayout={onLayout}
|
|
260
|
+
horizontal={!isVertical}
|
|
261
|
+
scrollEventThrottle={SCROLL_EVENT_THROTTLE}
|
|
262
|
+
removeClippedSubviews
|
|
263
|
+
>
|
|
264
|
+
{/* REQUIRED positioning container */}
|
|
265
|
+
<View
|
|
266
|
+
style={
|
|
267
|
+
isVertical
|
|
268
|
+
? { height: contentSize }
|
|
269
|
+
: { width: contentSize }
|
|
270
|
+
}
|
|
271
|
+
>
|
|
272
|
+
{activeCellsRef.current.map(cell => {
|
|
273
|
+
const layout = layouts[cell.index]
|
|
274
|
+
const item = data[cell.index]
|
|
275
|
+
|
|
276
|
+
if (layout === undefined || item === undefined) {
|
|
277
|
+
return null
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<View
|
|
282
|
+
key={cell.key}
|
|
283
|
+
style={[
|
|
284
|
+
styles.cell,
|
|
285
|
+
isVertical
|
|
286
|
+
? {
|
|
287
|
+
top: layout.y,
|
|
288
|
+
height: layout.height,
|
|
289
|
+
width: layout.width,
|
|
290
|
+
}
|
|
291
|
+
: {
|
|
292
|
+
left: layout.x,
|
|
293
|
+
width: layout.width,
|
|
294
|
+
height: layout.height,
|
|
295
|
+
},
|
|
296
|
+
]}
|
|
297
|
+
>
|
|
298
|
+
{renderItem({
|
|
299
|
+
item,
|
|
300
|
+
index: cell.index,
|
|
301
|
+
cell,
|
|
302
|
+
})}
|
|
303
|
+
</View>
|
|
304
|
+
)
|
|
305
|
+
})}
|
|
306
|
+
</View>
|
|
307
|
+
</ScrollView>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* ========================================================= */
|
|
312
|
+
|
|
313
|
+
const styles = StyleSheet.create({
|
|
314
|
+
cell: {
|
|
315
|
+
position: 'absolute',
|
|
316
|
+
},
|
|
317
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function createScrollVelocityTracker() {
|
|
2
|
+
let lastOffset = 0
|
|
3
|
+
let lastTime = Date.now()
|
|
4
|
+
|
|
5
|
+
return function getVelocity(offset: number): number {
|
|
6
|
+
const now = Date.now()
|
|
7
|
+
const dt = now - lastTime
|
|
8
|
+
const dy = Math.abs(offset - lastOffset)
|
|
9
|
+
|
|
10
|
+
lastOffset = offset
|
|
11
|
+
lastTime = now
|
|
12
|
+
|
|
13
|
+
if (dt === 0) return 0
|
|
14
|
+
return dy / dt // px per ms
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HybridObject } from 'react-native-nitro-modules'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents the physical area an item occupies on the screen.
|
|
5
|
+
* Standard: 'Layout' suffix tells the user this is a data object, not a logic object.
|
|
6
|
+
*/
|
|
7
|
+
export interface ItemLayout {
|
|
8
|
+
readonly x: number
|
|
9
|
+
readonly y: number
|
|
10
|
+
readonly width: number
|
|
11
|
+
readonly height: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The high-performance layout engine for NitroList.
|
|
16
|
+
* This interface will be used by Nitro to generate your Swift and Kotlin code.
|
|
17
|
+
*/
|
|
18
|
+
export interface NitroList
|
|
19
|
+
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Calculates exactly where every item should sit on the screen.
|
|
23
|
+
* Standard: Use descriptive parameter names so developers know what to provide.
|
|
24
|
+
*/
|
|
25
|
+
computeLayout(
|
|
26
|
+
containerWidth: number,
|
|
27
|
+
itemHeights: readonly number[]
|
|
28
|
+
): readonly ItemLayout[]
|
|
29
|
+
}
|
package/src/types/CellKey.ts
CHANGED
package/src/types/CellType.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Used to group compatible cells for recycling.
|
|
5
|
-
*
|
|
6
|
-
* Cross-platform equivalent:
|
|
7
|
-
* - Android: viewType
|
|
8
|
-
* - iOS: reuseIdentifier
|
|
1
|
+
/** * CellType is just a string, but we give it a name
|
|
2
|
+
* so the code is easier to read.
|
|
9
3
|
*/
|
|
10
|
-
export type CellType = string
|
|
4
|
+
export type CellType = string;
|
|
5
|
+
|
|
6
|
+
/** * This is an "Identity" function. It doesn't do anything
|
|
7
|
+
* to the computer, but it helps the human reading the code.
|
|
8
|
+
*/
|
|
9
|
+
export const cellType = (name: string): CellType => name;
|