react-native-list 2.0.0-alpha.1 → 2.0.0-alpha.2

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.
Files changed (37) hide show
  1. package/README.md +26 -28
  2. package/lib/ReactFabricMirror.d.ts +4 -0
  3. package/lib/ReactFabricMirror.js +515 -0
  4. package/lib/UiListModule.d.ts +2 -0
  5. package/lib/UiListModule.js +2 -0
  6. package/lib/index.d.ts +8 -0
  7. package/lib/index.js +21 -0
  8. package/lib/privateGlobals.d.ts +14 -0
  9. package/lib/privateGlobals.js +2 -0
  10. package/lib/renderer/RenderHelper.d.ts +2 -0
  11. package/lib/renderer/RenderHelper.js +11 -0
  12. package/lib/renderer/UiManagerHelper.d.ts +2 -0
  13. package/lib/renderer/UiManagerHelper.js +2 -0
  14. package/lib/renderer/fabric/RenderHelper.d.ts +2 -0
  15. package/lib/renderer/fabric/RenderHelper.js +11 -0
  16. package/lib/renderer/fabric/UiManagerHelper.d.ts +2 -0
  17. package/lib/renderer/fabric/UiManagerHelper.js +2 -0
  18. package/lib/renderer/react/ReactFabricMirror.d.ts +4 -0
  19. package/lib/renderer/react/ReactFabricMirror.js +515 -0
  20. package/lib/renderer/react/ReactFabricRenderer.d.ts +3 -0
  21. package/lib/renderer/react/ReactFabricRenderer.js +9 -0
  22. package/lib/specs/IOSWorkletsModuleProxyHolder.nitro.d.ts +6 -0
  23. package/lib/specs/IOSWorkletsModuleProxyHolder.nitro.js +1 -0
  24. package/lib/specs/UIListModule.nitro.d.ts +9 -0
  25. package/lib/specs/UIListModule.nitro.js +1 -0
  26. package/lib/specs/UIManagerHelper.nitro.d.ts +13 -0
  27. package/lib/specs/UIManagerHelper.nitro.js +1 -0
  28. package/lib/specs/UiListView.nitro.d.ts +20 -0
  29. package/lib/specs/UiListView.nitro.js +1 -0
  30. package/lib/specs/ViewHolder.nitro.d.ts +6 -0
  31. package/lib/specs/ViewHolder.nitro.js +1 -0
  32. package/lib/views/List.d.ts +35 -0
  33. package/lib/views/List.js +225 -0
  34. package/lib/views/UiListHostComponent.d.ts +2 -0
  35. package/lib/views/UiListHostComponent.js +3 -0
  36. package/metro-config.js +77 -5
  37. package/package.json +2 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # React Native List
2
2
 
3
- High-performance list primitives for React Native.
3
+ High-performance list for React Native.
4
4
 
5
5
  - 📱 True native [`UICollectionView`](https://developer.apple.com/documentation/uikit/uicollectionview) on iOS
6
6
  - 🤖 True native [`RecyclerView`](https://developer.android.com/develop/ui/views/layout/recyclerview) on android
@@ -8,26 +8,15 @@ High-performance list primitives for React Native.
8
8
  - 🐎 Platform native animations out of the box for list item transitions
9
9
  - 📉 Low memory usage due to true native view recycling
10
10
 
11
- <video src="https://github.com/user-attachments/assets/bd1eddd7-e166-4b91-b7ee-d177ce8edb58" width="320" controls align="right"></video>
11
+ https://github.com/user-attachments/assets/4a2684bf-7337-45e0-bb17-ee7b8a382943
12
12
 
13
13
  ## Installation
14
14
 
15
15
  > [!WARNING]
16
16
  > Using this library requires you to use [react-native-worklets **bundle mode**](https://docs.swmansion.com/react-native-worklets/docs/bundleMode/setup).
17
17
  >
18
- > - Please follow the setup instructions of it first, and make sure your app works before using react-native-list!
19
- > - You need at least version 0.9.1!
20
-
21
- > [!CAUTION]
22
- > Right now you will have to patch react-native-worklets. See the patch file here and ask your friendly AI to apply it:
23
- >
24
- > - [react-native-worklets@0.9.1.patch](https://github.com/hannojg/react-native-list/blob/main/patches/react-native-worklets%400.9.1.patch)
25
- >
26
- > Once those PRs are landed upstream it will no longer be necessary:
27
- >
28
- > - [fix(bundlemode): getBundleModeMetroConfig consider user config's resolveRequest](https://github.com/software-mansion/react-native-reanimated/pull/9327)
29
- > - [worklets plugin: allow react-native imports in bundle mode](https://github.com/software-mansion/react-native-reanimated/pull/9213)
30
- > - [worklets plugin: Add opt-in bundle mode JSX component capture](https://github.com/software-mansion/react-native-reanimated/pull/9212)
18
+ > - Please follow [the setup instructions of it first](https://docs.swmansion.com/react-native-worklets/docs/bundleMode/setup), and make sure your app works before using react-native-list!
19
+ > - You need at least version 0.10.0!
31
20
 
32
21
  Once that's out of the way, you can start with the regular setup of the library:
33
22
 
@@ -41,8 +30,15 @@ Simply wrap your config with `getReactNativeListMetroConfig` in `metro.config.js
41
30
 
42
31
  ```js
43
32
  const { getDefaultConfig } = require("expo/metro-config");
44
- const rnlistConfig = getReactNativeListMetroConfig(getDefaultConfig());
45
- module.exports = rnlistConfig;
33
+ const {
34
+ getBundleModeMetroConfig,
35
+ } = require("react-native-worklets/bundleMode");
36
+
37
+ let config = getDefaultConfig(__dirname);
38
+ config = getBundleModeMetroConfig(config);
39
+ // ⚠️ Make sure to apply this _after_ the bundle mode metro config:
40
+ config = getReactNativeListMetroConfig(config);
41
+ module.exports = config;
46
42
  ```
47
43
 
48
44
  > [!NOTE]
@@ -87,9 +83,10 @@ type Items = TextItem | ImageItem
87
83
  const renderers: ListRenderers<Items> = {
88
84
  text: {
89
85
  renderItemWorklet: ({ item }) => {
90
- "worklet";
86
+ "worklet"; // 👀 Note: our render function is a worklet!
91
87
 
92
88
  return (
89
+ // Though being in a worklet, you can use any component you like in here
93
90
  <View
94
91
  style={{
95
92
  justifyContent: "center",
@@ -131,20 +128,20 @@ export function ExampleList() {
131
128
 
132
129
  ## API
133
130
 
134
- XXXX
131
+ XXXX TODO
135
132
 
136
133
  ## Benchmark
137
134
 
138
135
  > Scrolling as fast as possible. Release build. iPhone 13 Pro.
139
136
 
140
- https://github.com/user-attachments/assets/10ebbccd-290a-44cb-9c70-4aa283134249
137
+ https://github.com/user-attachments/assets/aa7c2c41-b7ac-4d8b-9eb1-fd8b2feda4de
141
138
 
142
- | | react-native-list | Legend List |
143
- | --------- | ----------------- | ----------- |
144
- | FPS | 60 | 60 |
145
- | Memory | 86.88mb | 86.89mb |
146
- | Total CPU | 185.93% | 144.69% |
147
- | Can blank | no | yes |
139
+ | | Legend List | react-native-list |
140
+ | --------- | ----------- | ----------------- |
141
+ | FPS | 60 | 60 |
142
+ | Memory | 86.89mb | 86.88mb |
143
+ | Total CPU | 144.69% | 185.93% |
144
+ | Can blank | yes | no |
148
145
 
149
146
  Performance difference is best described as this:
150
147
 
@@ -155,9 +152,10 @@ Performance difference is best described as this:
155
152
 
156
153
  - For iOS when using dynamically sized items, try to use `iosConfig.estimatedItemSize` to roughly specify how many items will be visible in the view port. This can help a lot with performance.
157
154
  - When specifying sizes for items use `useLinearListLayout({})` inset configs. Avoid setting a width in the styles that exceed the actual available view port width.
155
+ - If you can, always provide item sizes upfront. With that the performance will be unbeatable.
158
156
  - In your item render function, when you have no item data yet it is tempting to return early with `null` or just an empty `<View />`. However, this is super bad for performance. There are two phases for native lists. First is view creation, where you're expected to create the view hierarchy for your item - just not with any data yet (so that any data could be bind to it). The second phase is actually binding data to the view. This will result in a simple "update props" operation on the native side instead of needing to create a new view hierarchy. Example:
159
157
 
160
- Bad:
158
+ ❌ **Bad**:
161
159
 
162
160
  ```jsx
163
161
  renderItemWorklet: ({ item }) => {
@@ -171,7 +169,7 @@ renderItemWorklet: ({ item }) => {
171
169
  )
172
170
  ```
173
171
 
174
- Good:
172
+ ✅ **Good**:
175
173
 
176
174
  ```jsx
177
175
  renderItemWorklet: ({ item }) => {
@@ -0,0 +1,4 @@
1
+ import type * as ReactModule from 'react';
2
+ declare function nativeLog(...args: unknown[]): void;
3
+ declare function reactRender(element: ReactModule.ReactElement, callback?: () => void): void;
4
+ export { nativeLog, reactRender };
@@ -0,0 +1,515 @@
1
+ /* eslint-disable @react-native/no-deep-imports, @typescript-eslint/no-unused-vars, no-dupe-keys, prettier/prettier */
2
+ // @ts-nocheck
3
+ function nativeLog(...args) {
4
+ global._log?.('[ReactFabricMirror] ' +
5
+ args
6
+ .map((a) => {
7
+ try {
8
+ return JSON.stringify(a);
9
+ }
10
+ catch (e) {
11
+ return '<failed to parse> ' + String(a);
12
+ }
13
+ })
14
+ .join(' '));
15
+ }
16
+ global.log = nativeLog;
17
+ const Reconciler = require('react-reconciler');
18
+ // FabricUIManager is basically a wrapper around global.nativeFabricUIManager
19
+ // but caches each function to avoid recreating a jsi::HostFunction on each call.
20
+ const { getFabricUIManager, } = require('react-native/Libraries/ReactNative/FabricUIManager');
21
+ const uiManager = getFabricUIManager();
22
+ const { create: createAttributePayload, diff: diffAttributePayloads, } = require('react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload');
23
+ const ReactNativeViewConfigRegistry = require('react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry');
24
+ global.rootHostContext = {};
25
+ global.childHostContext = {};
26
+ const { NoEventPriority, DefaultEventPriority, DiscreteEventPriority, ContinuousEventPriority, IdleEventPriority, } = require('react-reconciler/constants');
27
+ global.currentUpdatePriority = NoEventPriority;
28
+ global.rootInstance = {
29
+ containerTag: 3,
30
+ publicInstance: null,
31
+ };
32
+ const { getPublicInstance } = require('../shims/react-fiber-config-fabric.js');
33
+ //#region Event handling
34
+ const EventPluginUtilsModule = require('../../third_party/react/packages/react-native-renderer/src/legacy-events/EventPluginUtils');
35
+ const { setComponentTree } = EventPluginUtilsModule;
36
+ const { injectEventPluginOrder, injectEventPluginsByName, plugins: legacyPlugins, } = require('../../third_party/react/packages/react-native-renderer/src/legacy-events/EventPluginRegistry');
37
+ const ResponderEventPluginModule = require('../../third_party/react/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin');
38
+ const ReactNativeEventPluginOrderModule = require('../../third_party/react/packages/react-native-renderer/src/ReactNativeEventPluginOrder');
39
+ const ReactFabricGlobalResponderHandlerModule = require('../../third_party/react/packages/react-native-renderer/src/ReactFabricGlobalResponderHandler');
40
+ const SyntheticEventModule = require('../../third_party/react/packages/react-native-renderer/src/legacy-events/SyntheticEvent');
41
+ const accumulateIntoModule = require('../../third_party/react/packages/react-native-renderer/src/legacy-events/accumulateInto');
42
+ const forEachAccumulatedModule = require('../../third_party/react/packages/react-native-renderer/src/legacy-events/forEachAccumulated');
43
+ const { batchedUpdates, } = require('../../third_party/react/packages/react-native-renderer/src/legacy-events/ReactGenericBatching');
44
+ const { runEventsInBatch, } = require('../../third_party/react/packages/react-native-renderer/src/legacy-events/EventBatching');
45
+ const { HostComponent } = require('react-reconciler/src/ReactWorkTags');
46
+ const ResponderEventPlugin = ResponderEventPluginModule.default ?? ResponderEventPluginModule;
47
+ const ReactNativeEventPluginOrder = ReactNativeEventPluginOrderModule.default ?? ReactNativeEventPluginOrderModule;
48
+ const ReactFabricGlobalResponderHandler = ReactFabricGlobalResponderHandlerModule.default ??
49
+ ReactFabricGlobalResponderHandlerModule;
50
+ const SyntheticEvent = SyntheticEventModule.default ?? SyntheticEventModule;
51
+ const accumulateInto = accumulateIntoModule.default ?? accumulateIntoModule;
52
+ const forEachAccumulated = forEachAccumulatedModule.default ?? forEachAccumulatedModule;
53
+ const { customBubblingEventTypes, customDirectEventTypes } = ReactNativeViewConfigRegistry;
54
+ function getParent(inst) {
55
+ do {
56
+ inst = inst.return;
57
+ } while (inst && inst.tag !== HostComponent);
58
+ return inst || null;
59
+ }
60
+ function traverseTwoPhase(inst, fn, arg, skipBubbling) {
61
+ const path = [];
62
+ while (inst) {
63
+ path.push(inst);
64
+ inst = getParent(inst);
65
+ }
66
+ for (let i = path.length - 1; i >= 0; i--) {
67
+ fn(path[i], 'captured', arg);
68
+ }
69
+ if (skipBubbling) {
70
+ fn(path[0], 'bubbled', arg);
71
+ }
72
+ else {
73
+ for (let i = 0; i < path.length; i++) {
74
+ fn(path[i], 'bubbled', arg);
75
+ }
76
+ }
77
+ }
78
+ function getListener(inst, registrationName) {
79
+ const stateNode = inst.stateNode;
80
+ if (stateNode == null) {
81
+ return null;
82
+ }
83
+ const props = EventPluginUtilsModule.getFiberCurrentPropsFromNode(stateNode);
84
+ if (props == null) {
85
+ return null;
86
+ }
87
+ const listener = props[registrationName];
88
+ if (listener != null && typeof listener !== 'function') {
89
+ throw new Error(`Expected \`${registrationName}\` listener to be a function, got \`${typeof listener}\`.`);
90
+ }
91
+ return listener;
92
+ }
93
+ function listenerAtPhase(inst, event, propagationPhase) {
94
+ const registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
95
+ return getListener(inst, registrationName);
96
+ }
97
+ function accumulateDirectionalDispatches(inst, phase, event) {
98
+ const listener = listenerAtPhase(inst, event, phase);
99
+ if (listener) {
100
+ event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
101
+ event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
102
+ }
103
+ }
104
+ function accumulateTwoPhaseDispatchesSingle(event) {
105
+ if (event && event.dispatchConfig.phasedRegistrationNames) {
106
+ traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event, false);
107
+ }
108
+ }
109
+ function accumulateTwoPhaseDispatches(events) {
110
+ forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
111
+ }
112
+ function accumulateCapturePhaseDispatches(event) {
113
+ if (event && event.dispatchConfig.phasedRegistrationNames) {
114
+ traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event, true);
115
+ }
116
+ }
117
+ function accumulateDispatches(inst, _ignoredDirection, event) {
118
+ if (inst && event && event.dispatchConfig.registrationName) {
119
+ const registrationName = event.dispatchConfig.registrationName;
120
+ const listener = getListener(inst, registrationName);
121
+ if (listener) {
122
+ event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
123
+ event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
124
+ }
125
+ }
126
+ }
127
+ function accumulateDirectDispatchesSingle(event) {
128
+ if (event && event.dispatchConfig.registrationName) {
129
+ accumulateDispatches(event._targetInst, null, event);
130
+ }
131
+ }
132
+ function accumulateDirectDispatches(events) {
133
+ forEachAccumulated(events, accumulateDirectDispatchesSingle);
134
+ }
135
+ const ReactNativeBridgeEventPlugin = {
136
+ eventTypes: {},
137
+ extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
138
+ if (targetInst == null) {
139
+ return null;
140
+ }
141
+ const bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
142
+ const directDispatchConfig = customDirectEventTypes[topLevelType];
143
+ if (!bubbleDispatchConfig && !directDispatchConfig) {
144
+ throw new Error(`Unsupported top level event type "${topLevelType}" dispatched`);
145
+ }
146
+ const event = SyntheticEvent.getPooled(bubbleDispatchConfig || directDispatchConfig, targetInst, nativeEvent, nativeEventTarget);
147
+ if (bubbleDispatchConfig) {
148
+ const skipBubbling = event != null &&
149
+ event.dispatchConfig.phasedRegistrationNames != null &&
150
+ event.dispatchConfig.phasedRegistrationNames.skipBubbling;
151
+ if (skipBubbling) {
152
+ accumulateCapturePhaseDispatches(event);
153
+ }
154
+ else {
155
+ accumulateTwoPhaseDispatches(event);
156
+ }
157
+ }
158
+ else if (directDispatchConfig) {
159
+ accumulateDirectDispatches(event);
160
+ }
161
+ else {
162
+ return null;
163
+ }
164
+ return event;
165
+ },
166
+ };
167
+ function extractPluginEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
168
+ let events = null;
169
+ for (let i = 0; i < legacyPlugins.length; i++) {
170
+ const plugin = legacyPlugins[i];
171
+ if (plugin) {
172
+ const extractedEvents = plugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
173
+ if (extractedEvents) {
174
+ events = accumulateInto(events, extractedEvents);
175
+ }
176
+ }
177
+ }
178
+ return events;
179
+ }
180
+ function runExtractedPluginEventsInBatch(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
181
+ const events = extractPluginEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
182
+ runEventsInBatch(events);
183
+ }
184
+ function ensureLegacyEventPluginsInjected() {
185
+ try {
186
+ injectEventPluginOrder(ReactNativeEventPluginOrder);
187
+ }
188
+ catch (error) {
189
+ // The plugin order can only be injected once for a given registry instance.
190
+ if (!String(error).includes('Cannot inject event plugin ordering more than once')) {
191
+ throw error;
192
+ }
193
+ }
194
+ injectEventPluginsByName({
195
+ ResponderEventPlugin,
196
+ ReactNativeBridgeEventPlugin,
197
+ });
198
+ setComponentTree(
199
+ // Equivalent to ReactFabricComponentTree.getFiberCurrentPropsFromNode
200
+ (instance) => instance?.canonical?.currentProps ?? null,
201
+ // Equivalent to ReactFabricComponentTree.getInstanceFromNode
202
+ (node) => {
203
+ if (node?.canonical != null &&
204
+ node.canonical.internalInstanceHandle != null) {
205
+ return node.canonical.internalInstanceHandle;
206
+ }
207
+ return node ?? null;
208
+ },
209
+ // Equivalent to ReactFabricComponentTree.getNodeFromInstance
210
+ (fiber) => {
211
+ const publicInstance = getPublicInstance(fiber.stateNode);
212
+ if (publicInstance == null) {
213
+ throw new Error('Could not find host instance from fiber');
214
+ }
215
+ return publicInstance;
216
+ });
217
+ ResponderEventPlugin.injection.injectGlobalResponderHandler(ReactFabricGlobalResponderHandler);
218
+ }
219
+ ensureLegacyEventPluginsInjected();
220
+ function dispatchEvent(target, topLevelType, nativeEvent) {
221
+ const targetFiber = target;
222
+ let eventTarget = null;
223
+ if (targetFiber != null) {
224
+ const stateNode = targetFiber.stateNode;
225
+ if (stateNode != null) {
226
+ eventTarget = getPublicInstance(stateNode);
227
+ }
228
+ }
229
+ batchedUpdates(() => {
230
+ runExtractedPluginEventsInBatch(topLevelType, targetFiber, nativeEvent, eventTarget);
231
+ });
232
+ }
233
+ // This will be retrieved on the native side in JSI ... hm or we call and set it?
234
+ global.handleEvent = dispatchEvent;
235
+ //#endregion
236
+ // Counter for uniquely identifying views.
237
+ // % 10 === 1 means it is a rootTag.
238
+ // % 2 === 0 means it is a Fabric tag.
239
+ // This means that they never overlap.
240
+ global.nextReactTag = 200_000_000;
241
+ // ^ Why is this number so high?
242
+ // We share the ReactInstance (ie we don't create a seperate one)
243
+ // That means our surface shares the RCTComponentViewRegistry on iOS with the main React Native renderer.
244
+ // When creating new views its checking if the tag already exists, meaning we can't use the same tags that react is using. Bummer!\
245
+ // TODO: find a better solution for this
246
+ const HostConfig = {
247
+ now: performance.now,
248
+ getRootHostContext(rootContainerInstance) {
249
+ return global.rootHostContext;
250
+ },
251
+ getChildHostContext() {
252
+ return global.childHostContext;
253
+ },
254
+ // prepareForCommit(containerInfo) {},
255
+ // resetAfterCommit(containerInfo) {},
256
+ supportsPersistence: true,
257
+ createInstance: (type, props, rootContainerInstance, _currentHostContext, workInProgress) => {
258
+ const tag = global.nextReactTag;
259
+ global.nextReactTag += 2;
260
+ const viewConfig = ReactNativeViewConfigRegistry.get(type);
261
+ const updatePayload = createAttributePayload(props, viewConfig.validAttributes);
262
+ let node;
263
+ try {
264
+ nativeLog('[createInstance] calling createNode with type=', type, 'tag=', tag);
265
+ nativeLog('[createInstance] props=', updatePayload);
266
+ node = uiManager.createNode(tag, // reactTag
267
+ viewConfig.uiViewClassName, // viewName
268
+ rootContainerInstance.containerTag, // rootTag
269
+ updatePayload, // props
270
+ workInProgress // internalInstanceHandle
271
+ );
272
+ }
273
+ catch (e) {
274
+ nativeLog('[createInstance] ERROR in createNode:', e.message || String(e));
275
+ nativeLog('Stack:', new Error().stack);
276
+ throw e;
277
+ }
278
+ return {
279
+ node: node,
280
+ canonical: {
281
+ nativeTag: tag,
282
+ viewConfig, // TODO: is this needed? for what
283
+ currentProps: props, // funny, react is passing here props instead of updatePayload, is this okay?
284
+ internalInstanceHandle: workInProgress,
285
+ publicInstance: null,
286
+ publicRootInstance: rootContainerInstance.publicInstance,
287
+ },
288
+ };
289
+ },
290
+ finalizeInitialChildren(parentInstance, type, props, hostContext) {
291
+ nativeLog('[finalizeInitialChildren]');
292
+ return false;
293
+ },
294
+ cloneInstance(instance, type, oldProps, newProps, keepChildren, newChildSet) {
295
+ nativeLog('[cloneInstance] tag=', instance.canonical.nativeTag);
296
+ const viewConfig = instance.canonical.viewConfig;
297
+ const updatePayload = diffAttributePayloads(oldProps, newProps, viewConfig.validAttributes);
298
+ nativeLog('[cloneInstance] updatePayload=', updatePayload);
299
+ // TODO: If the event handlers have changed, we need to update the current props
300
+ // in the commit phase but there is no host config hook to do it yet.
301
+ // So instead we hack it by updating it in the render phase.
302
+ instance.canonical.currentProps = newProps;
303
+ const node = instance.node;
304
+ let clone;
305
+ if (keepChildren) {
306
+ if (updatePayload !== null) {
307
+ clone = uiManager.cloneNodeWithNewProps(node, updatePayload);
308
+ }
309
+ else {
310
+ // No changes
311
+ return instance;
312
+ }
313
+ }
314
+ else {
315
+ // If passChildrenWhenCloningPersistedNodes is enabled, children will be non-null
316
+ if (newChildSet != null) {
317
+ if (updatePayload !== null) {
318
+ clone = uiManager.cloneNodeWithNewChildrenAndProps(node, newChildSet, updatePayload);
319
+ }
320
+ else {
321
+ clone = uiManager.cloneNodeWithNewChildren(node, newChildSet);
322
+ }
323
+ }
324
+ else {
325
+ if (updatePayload !== null) {
326
+ clone = uiManager.cloneNodeWithNewChildrenAndProps(node, updatePayload);
327
+ }
328
+ else {
329
+ clone = uiManager.cloneNodeWithNewChildren(node);
330
+ }
331
+ }
332
+ }
333
+ return {
334
+ node: clone,
335
+ canonical: instance.canonical,
336
+ };
337
+ },
338
+ createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) {
339
+ const tag = global.nextReactTag;
340
+ global.nextReactTag += 2;
341
+ const node = uiManager.createNode(tag, // reactTag
342
+ 'RCTRawText', // viewName
343
+ rootContainerInstance.containerTag, // rootTag
344
+ { text: text }, // props
345
+ internalInstanceHandle // instance handle
346
+ );
347
+ return {
348
+ node: node,
349
+ };
350
+ },
351
+ createContainerChildSet() {
352
+ nativeLog('[createContainerChildSet]');
353
+ return uiManager.createChildSet();
354
+ },
355
+ appendChildToContainerChildSet(childSet, child) {
356
+ nativeLog('[appendChildToContainerChildSet]');
357
+ uiManager.appendChildToSet(childSet, child.node);
358
+ },
359
+ finalizeContainerChildren(container, newChildren) {
360
+ // Noop - children will be replaced in replaceContainerChildren
361
+ nativeLog('[finalizeContainerChildren]');
362
+ },
363
+ appendInitialChild(parentInstance, child) {
364
+ nativeLog('[appendInitialChild]');
365
+ uiManager.appendChild(parentInstance.node, child.node);
366
+ },
367
+ replaceContainerChildren(container, newChildren) {
368
+ nativeLog('[replaceContainerChildren]');
369
+ uiManager.completeRoot(container.containerTag, newChildren);
370
+ },
371
+ // TODO: hm, this could get problematic, to share event priorities between the fabric ui manager.
372
+ // Maybe we need to create a clone of the ui manager? the important thing is that all view managers are resolved when we do so.
373
+ // However that would concern me if commitHooks from reanimated would work.
374
+ setCurrentUpdatePriority(priority) {
375
+ global.currentUpdatePriority = priority;
376
+ },
377
+ getCurrentUpdatePriority() {
378
+ return global.currentUpdatePriority;
379
+ },
380
+ resolveUpdatePriority() {
381
+ if (global.currentUpdatePriority !== NoEventPriority) {
382
+ return global.currentUpdatePriority;
383
+ }
384
+ else {
385
+ return DefaultEventPriority;
386
+ }
387
+ // TODO:
388
+ /* const currentEventPriority = fabricGetCurrentEventPriority
389
+ ? fabricGetCurrentEventPriority()
390
+ : null;
391
+
392
+ if (currentEventPriority != null) {
393
+ switch (currentEventPriority) {
394
+ case FabricDiscretePriority:
395
+ return DiscreteEventPriority;
396
+ case FabricContinuousPriority:
397
+ return ContinuousEventPriority;
398
+ case FabricIdlePriority:
399
+ return IdleEventPriority;
400
+ case FabricDefaultPriority:
401
+ default:
402
+ return DefaultEventPriority;
403
+ }
404
+ }
405
+
406
+ return DefaultEventPriority; */
407
+ },
408
+ getPublicInstance(instance) {
409
+ return getPublicInstance(instance);
410
+ },
411
+ // getPublicTextInstance(textInstance, internalInstanceHandle) {
412
+ // if (textInstance.publicInstance == null) {
413
+ // textInstance.publicInstance = createPublicTextInstance(
414
+ // internalInstanceHandle
415
+ // )
416
+ // }
417
+ // return textInstance.publicInstance
418
+ // },
419
+ // getPublicInstanceFromInternalInstanceHandle(internalInstanceHandle) {
420
+ // const instance = internalInstanceHandle.stateNode
421
+ // // React resets all the fields in the fiber when the component is unmounted
422
+ // // to prevent memory leaks.
423
+ // if (instance == null) {
424
+ // return null
425
+ // }
426
+ // if (internalInstanceHandle.tag === HostText) {
427
+ // const textInstance = instance
428
+ // return this.getPublicTextInstance(textInstance, internalInstanceHandle)
429
+ // }
430
+ // const elementInstance = internalInstanceHandle.stateNode
431
+ // return this.getPublicInstance(elementInstance)
432
+ // },
433
+ prepareForCommit(containerInfo) {
434
+ return null;
435
+ },
436
+ resetAfterCommit(containerInfo) { },
437
+ trackSchedulerEvent() { },
438
+ resolveEventType() {
439
+ return null;
440
+ },
441
+ resolveEventTimeStamp() {
442
+ return -1.1;
443
+ },
444
+ shouldAttemptEagerTransition() {
445
+ return false;
446
+ },
447
+ shouldSetTextContent(type, props) {
448
+ return false;
449
+ },
450
+ // TODO: microtask scheduling, should work with worklets on the UI thread i believe! not sure though if even RN implements this?
451
+ supportsMicrotasks: false,
452
+ detachDeletedInstance(node) { },
453
+ beforeActiveInstanceBlur(internalInstanceHandle) {
454
+ // noop
455
+ },
456
+ afterActiveInstanceBlur() {
457
+ // noop
458
+ },
459
+ preparePortalMount(portalInstance) {
460
+ // noop
461
+ },
462
+ detachDeletedInstance(node) {
463
+ // noop
464
+ },
465
+ requestPostPaintCallback(callback) {
466
+ // noop
467
+ },
468
+ maySuspendCommit(type, props) {
469
+ return false;
470
+ },
471
+ maySuspendCommitOnUpdate(type, oldProps, newProps) {
472
+ return false;
473
+ },
474
+ maySuspendCommitInSyncRender(type, props) {
475
+ return false;
476
+ },
477
+ preloadInstance(instance, type, props) {
478
+ return true;
479
+ },
480
+ startSuspendingCommit() {
481
+ return null;
482
+ },
483
+ suspendInstance(state, instance, type, props) { },
484
+ suspendOnActiveViewTransition(state, container) { },
485
+ waitForCommitToBeReady(state, timeoutOffset) {
486
+ return null;
487
+ },
488
+ getSuspendedCommitReason(state, rootContainer) {
489
+ return null;
490
+ },
491
+ isPrimaryRenderer: false,
492
+ };
493
+ const Renderer = Reconciler(HostConfig);
494
+ global.React = require('react');
495
+ function reactRender(element, callback) {
496
+ if (!global.rootContainer) {
497
+ global.rootContainer = Renderer.createContainer(global.rootInstance, 0, // concurrentRoot ? 1 : 0
498
+ null, false, null, 'ui-renderer', function onUncaughtError(error, info) {
499
+ nativeLog('[Error][ReactFabricMirror] Uncaught error in React renderer: ', error, info);
500
+ }, function onCaughtError(error, info) {
501
+ nativeLog('[Error][ReactFabricMirror] Caught error in React renderer: ', error, info);
502
+ }, function onRecoverableError(error, info) {
503
+ nativeLog('[Error][ReactFabricMirror] Recoverable error in React renderer: ', error, info);
504
+ }, function nativeOnDefaultTransitionIndicator() {
505
+ // Native doesn't have a default indicator.
506
+ });
507
+ }
508
+ // updateContainerSync + flushSyncWork is making the renderer work immediately/blocking/…sync
509
+ Renderer.updateContainerSync(element, global.rootContainer, null, callback);
510
+ // Renderer.flushPassiveEffects();
511
+ Renderer.flushSyncWork();
512
+ nativeLog('[ReactFabricMirror] updateContainer finished');
513
+ }
514
+ nativeLog('[ReactFabricMirror] ReactFabricMirror initialized');
515
+ export { nativeLog, reactRender };
@@ -0,0 +1,2 @@
1
+ import { UiListModule } from './specs/UIListModule.nitro';
2
+ export declare const uiListModule: UiListModule;
@@ -0,0 +1,2 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ export const uiListModule = NitroModules.createHybridObject('UiListModule');
package/lib/index.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { uiListModule } from './UiListModule';
2
+ import { uiManagerHelper } from './renderer/fabric/UiManagerHelper';
3
+ import { List } from './views/List';
4
+ export { ViewHolder } from './specs/ViewHolder.nitro';
5
+ export { IOSWorkletsModuleProxyHolder } from './specs/IOSWorkletsModuleProxyHolder.nitro';
6
+ export type { NativeListItem } from './specs/UiListView.nitro';
7
+ export type { ListItemSize, ListKey, ListProps, ListRef, ListRenderer, } from './views/List';
8
+ export { List, uiListModule, uiManagerHelper };
package/lib/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import { scheduleOnUI } from 'react-native-worklets';
2
+ import { uiListModule } from './UiListModule';
3
+ import { uiManagerHelper } from './renderer/fabric/UiManagerHelper';
4
+ import { List } from './views/List';
5
+ import { Platform } from 'react-native';
6
+ import { getReactFabricRenderer } from './renderer/react/ReactFabricRenderer';
7
+ const boxed = uiListModule;
8
+ const nativeFabricUIManager = globalThis.nativeFabricUIManager;
9
+ function setup() {
10
+ // TODO: ask SWM if they can remove their JS thread checks, then we could just access this from the UI thread.
11
+ const iosWorkletsModuleHolder = Platform.OS === 'ios' ? uiListModule.iosGetWorkletsModule() : null;
12
+ scheduleOnUI(() => {
13
+ 'worklet';
14
+ globalThis.nativeFabricUIManager = nativeFabricUIManager;
15
+ boxed.setupExternalSurface(iosWorkletsModuleHolder);
16
+ // This will setup the react instance on the UI runtime:
17
+ getReactFabricRenderer();
18
+ });
19
+ }
20
+ setup();
21
+ export { List, uiListModule, uiManagerHelper };